I would like Autofac
to instantiate components based on connection strings in the form of the URI:
autofac://assembly-name/full-class-name?param1=arg1¶m2=arg2
So the following test will work:
[TestClass]
public class Locator_Should
{
[TestMethod]
public void Resolve()
{
var builder = new ContainerBuilder();
builder.RegisterType<DiskFolder>();
var container = builder.Build();
var folder = container.Resolve<IFolder>(
"autofac://LocatorTest/LocatorTest.DiskFolder?path=\\Windows");
Assert.IsTrue(folder.Any());
}
}
Where:
public interface IFolder : IEnumerable<string>
{
}
public class DiskFolder : ReadOnlyCollection<string>, IFolder
{
public DiskFolder(string path)
: base(Directory.GetFiles(path))
{
}
}
The library code is:
public static class AutofacLocator
{
public static T Resolve<T>(this ILifetimeScope scope, string uri) =>
scope.Resolve<T>(new Uri(uri));
public static object Resolve(this ILifetimeScope scope, string uri) =>
scope.Resolve(new Uri(uri));
public static T Resolve<T>(this ILifetimeScope scope, Uri uri) =>
(T)scope.Resolve(uri);
public static object Resolve(this ILifetimeScope scope, Uri uri)
{
if (uri.Scheme != "autofac")
throw new NotSupportedException("Schema not supported.");
var factory = (Delegate)scope.Resolve(GetFactory(uri));
return factory.DynamicInvoke(GetArguments(uri));
}
static Type GetFactory(Uri uri)
{
Func<Type[], Type> getType = Expression.GetFuncType;
var types = GetParameters(uri)
.Select(p => p.ParameterType)
.Append(GetReturnType(uri));
return getType(types.ToArray());
}
static object[] GetArguments(Uri uri)
{
var arguments = uri.Query
.TrimStart('?')
.Split('&')
.Select(p => p.Split('='))
.ToDictionary(nv => nv[0], nv => Uri.UnescapeDataString(nv[1]));
return GetParameters(uri)
.Select(p => Convert.ChangeType(arguments[p.Name], p.ParameterType))
.ToArray();
}
static ParameterInfo[] GetParameters(Uri uri) =>
GetReturnType(uri)
.GetConstructors()
.First()
.GetParameters();
static Type GetReturnType(Uri uri) =>
Type.GetType($"{uri.Segments[1]}, {uri.Host}");
}