I want to make the creation of the pack Uri for WPF resources more verbose so they are easier to read and less error-prone and I thought I implement it using the builder pattern.
The base class for each builder (there are four of them) is the PackUriBuilder
. It implements the UriKind
logic, checks the path and requries the derived class to implement the AbsolutePath
property. It also provides static methods to initialize each of the builders.
(I omitted null checks to make is shorter).
abstract class PackUriBuilder
{
private string _path;
private UriKind _uriKind;
protected PackUriBuilder(string path)
{
if (!path.StartsWith("/")) throw new ArgumentException(paramName: nameof(path), message: "Path must start with \"/\".");
_path = path;
_uriKind = UriKind.Absolute;
}
public string Scheme => "pack";
public string RelativePath => _path;
public abstract string AbsolutePath { get; }
public static implicit operator string (PackUriBuilder builder) => builder.ToString();
public static implicit operator Uri(PackUriBuilder builder)
{
switch (builder._uriKind)
{
case UriKind.Absolute:
return new Uri(builder.ToString(), UriKind.Absolute);
case UriKind.Relative:
return new Uri(builder.RelativePath, UriKind.Relative);
}
// todo: I know this needs a better message ;-)
throw new ArgumentOutOfRangeException("Invalid UriKind.");
}
public override string ToString()
{
switch (_uriKind)
{
case UriKind.Absolute:
return AbsolutePath;
case UriKind.Relative:
return RelativePath;
}
// todo: I know this needs a better message ;-)
throw new ArgumentOutOfRangeException("Invalid UriKind.");
}
public Uri ToUri() => (Uri)this;
public PackUriBuilder Relative()
{
_uriKind = UriKind.Relative;
return this;
}
public PackUriBuilder Absolute()
{
_uriKind = UriKind.Absolute;
return this;
}
public static LocalAssemblyResourceFilePackUriBuilder LocalAssemblyResourceFile(string path)
{
return new LocalAssemblyResourceFilePackUriBuilder(path);
}
public static ReferencedAssemblyResourceFilePackUriBuilder ReferencedAssemblyResourceFile(string path, string assemblyShortName)
{
return new ReferencedAssemblyResourceFilePackUriBuilder(path, assemblyShortName);
}
public static ContentFilePackUriBuilder ContentFile(string path)
{
return new ContentFilePackUriBuilder(path);
}
public static SiteOfOriginPackUriBuilder SiteOfOrigin(string path)
{
return new SiteOfOriginPackUriBuilder(path);
}
}
These are the four builders for each pack Uri type.
class LocalAssemblyResourceFilePackUriBuilder : PackUriBuilder
{
public LocalAssemblyResourceFilePackUriBuilder(string path)
: base(path) { }
public override string AbsolutePath
{
get
{
return $"{Scheme}://application:,,,{RelativePath}";
}
}
}
class ReferencedAssemblyResourceFilePackUriBuilder : PackUriBuilder
{
private string _assemblyShortName;
private string _version;
private string _publicKey;
public ReferencedAssemblyResourceFilePackUriBuilder(string path, string assemblyShortName)
: base(path)
{
_assemblyShortName = assemblyShortName;
}
public ReferencedAssemblyResourceFilePackUriBuilder Version(string version)
{
_version = version;
return this;
}
public ReferencedAssemblyResourceFilePackUriBuilder PublicKey(string publicKey)
{
_publicKey = publicKey;
return this;
}
public override string AbsolutePath
{
get
{
var version = string.IsNullOrEmpty(_version) ? string.Empty : $";{_version}";
var publicKey = string.IsNullOrEmpty(_publicKey) ? string.Empty : $";{_publicKey}";
return $"{Scheme}://application:,,,/{_assemblyShortName}{version}{publicKey};component{RelativePath}";
}
}
}
class ContentFilePackUriBuilder : PackUriBuilder
{
public ContentFilePackUriBuilder(string path)
: base(path) { }
public override string AbsolutePath
{
get
{
return $"{Scheme}://application:,,,{RelativePath}";
}
}
}
class SiteOfOriginPackUriBuilder : PackUriBuilder
{
public SiteOfOriginPackUriBuilder(string path)
: base(path) { }
public override string AbsolutePath
{
get
{
return $"{Scheme}://siteoforigin:,,,{RelativePath}";
}
}
}
Examples
// LINQPad requires this because it's not running in WPF context.
if (!UriParser.IsKnownScheme("pack")) { new System.Windows.Application(); }
PackUriBuilder.LocalAssemblyResourceFile("/test.bmp").ToString().Dump();
PackUriBuilder.ReferencedAssemblyResourceFile("/test.bmp", "MyAssembly").Version("v2.3.0.1").ToString().Dump();
PackUriBuilder.SiteOfOrigin("/test.bmp").Relative().ToString().Dump();
Output:
pack://application:,,,/test.bmp
pack://application:,,,/MyAssembly;v2.3.0.1;component/test.bmp
/test.bmp