0

GitHub and NuGet

Do you use connection strings to instantiate your adapter services or just always configure it through IoC? I think that connection strings could provide a way more flexibility, especially while being combined with IoC container. Here are my two helper classes which I defined to handle URI based connection string syntax:

clr://assembly-name/full-class-name?param1=arg1&param2=arg2 
ioc://assembly-name/full-class-name?param1=arg1&param2=arg2 

How would you evaluate this approach from an architectural standpoint?

I have this to support clr://:

namespace System
{
    public static class Activator<T>
    {
        public static T CreateInstance(string uri) =>
            CreateInstance(new Uri(uri));

        public static T CreateInstance(Uri uri) =>
            uri.Scheme != "clr"
            ? throw new NotSupportedException()
            : (T)Activator.CreateInstance(GetReturnType(uri), GetArguments(uri));

        static object[] GetArguments(Uri uri)
        {
            var arguments = uri.Query
                .TrimStart('?')
                .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
                .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}");
    }
}

So:

using (var reader = Activator<TextReader>.CreateInstance(
    "clr://mscorlib/System.IO.StringReader?s=Hello%20World"))
    Assert.AreEqual("Hello World", reader.ReadToEnd());

And the following is for ioc:// (works perfect with AutofacServiceProvider, etc.):

namespace System
{
    public static class ServiceLocator
    {
        public static T GetService<T>(this IServiceProvider provider, string uri) =>
            provider.GetService<T>(new Uri(uri));

        public static object GetService(this IServiceProvider provider, string uri) =>
            provider.GetService(new Uri(uri));

        public static T GetService<T>(this IServiceProvider provider, Uri uri) =>
            (T)provider.GetService(uri);

        public static object GetService(this IServiceProvider provider, Uri uri)
        {
            if (uri.Scheme != "ioc")
                throw new NotSupportedException("Schema not supported.");

            var factory = (Delegate)provider.GetService(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 query = GetQuery(uri);
            return GetParameters(uri)
                .Select(p => Convert.ChangeType(query[p.Name], p.ParameterType))
                .ToArray();
        }

        static Dictionary<string, string> GetQuery(Uri uri) => 
            uri.Query
                .TrimStart('?')
                .Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(p => p.Split('='))
                .ToDictionary(nv => nv[0], nv => Uri.UnescapeDataString(nv[1]));

        static ParameterInfo[] GetParameters(Uri uri)
        {
            var query = GetQuery(uri);
            return GetReturnType(uri)
                .GetConstructors()
                .First()
                .GetParameters()
                .Where(p => query.ContainsKey(p.Name))
                .ToArray();
        }

        static Type GetReturnType(Uri uri) =>
            Type.GetType($"{uri.Segments[1]}, {uri.Host}");
    }
}

Where missing parameters are supposed to be provided by IoC container.

  • Please tell me that you are sanitising which dlls can be loaded, and which namespaces can be accessed? As is, this is a Hacker's dream. A single url can instantiate any code desired, its not even checked for interface correctness. It doesn't mater that the created object will fail a type cast later, the fact is that the object is created and can spin a thread off in its own constructor to keep itself alive. – Kain0_0 Jan 6 at 0:47
  • @Kain0_0 My connection strings contain credentials and well protected. I could add a guard condition line !typeof(T).IsAssignableFrom(GetReturnType(uri)) ? throw new InvalidCastException() : to CreateInstance<T>(Uri uri), that would be no worse then any well known ADODB connection string... – Dmitry Nogin Jan 6 at 1:07
  • That is a step in the right direction. It would at least limit instantiatable objects to those who already implement the desired interface. I would go further and add in at least a black list, or even better a precise white list. EG: adding System.Threading.* to the blacklist is just common sense. You can make it reasonably fast by using an actual regex instead of a list search. Going further and writing a list of permitted instantiables would be better although I understand its not always feasible. – Kain0_0 Jan 6 at 7:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.