Wednesday, October 22, 2014

Simulating DryIoc's RegisterDelegate on LightInject

DryIoc 2.0 has a way now to register runtime types where their instances are determined at the later time. If you are using older version of DryIoc you can use this solution.

void RegisterWcfServices()
{
    var binding = new System.ServiceModel.BasicHttpBinding();
    
    // get all interfaces
    foreach (var contractType in typeof(TheServiceContracts.IMemberService).Assembly.GetTypes())
    {                
        _container.RegisterDelegate(contractType,
            r =>
            {
                var svcAddress = "http://localhost:1337/" + contractType.Name.Substring(1) + ".svc";
                var endpointAddress = new System.ServiceModel.EndpointAddress(svcAddress);

                return ServiceModelHelper.CreateService(contractType, binding, endpointAddress);
                
            }, Reuse.InResolutionScope);

    }
}


To simulate that in LightInject, use RegisterFallback
void RegisterWcfServices()
{
    var binding = new System.ServiceModel.BasicHttpBinding();

    System.Reflection.Assembly serviceAssembly = typeof(TheServiceContracts.IMemberService).Assembly;

    // Assuming TheServiceContracts assembly has interfaces only, the following code would suffice
    _container.RegisterFallback((t, s) => t.Assembly == serviceAssembly, 
        factory =>
        {
            var svcAddress = "http://localhost:1337/" + factory.ServiceType.Name.Substring(1) + ".svc";
            var endpointAddress = new System.ServiceModel.EndpointAddress(svcAddress);

            return ServiceModelHelper.CreateService(factory.ServiceType, binding, endpointAddress);            
        }
     );            
}


However, LightInject's RegisterFallback hack is not quite the same as DryIoc's RegisterDelegate. With DryIoc.RegisterDelegate, the service types (e.g., contractType) are stored in lookups, so when the time come the service type needs to be resolved, the delegate that returns an object for the service type could be determined quickly


Another problem with RegisterFallback is it is called lastly after LightInject determines the type is not registered at all, that could lead to performance problem. There's a good optimization though, once the unregistered type matches a RegisterFallback's condition, RegisterFallback's factory delegate is cached to the unregistered type, making the RegisterFallback's condition not to be evaluated again anymore


Another potential performance problem, the code could be littered with lots of ifs if we want to target specific types, to wit:
void RegisterWcfServices()
{
    var binding = new System.ServiceModel.BasicHttpBinding();
    
    System.Reflection.Assembly serviceAssembly = typeof(TheServiceContracts.IMemberService).Assembly;

    _container.RegisterFallback((t, s) => 
            t.Assembly == serviceAssembly 
            || t == typeof(IThinkService), 
        factory =>
        {
            if (factory.ServiceType.Assembly == serviceAssembly)
            {
                var svcAddress = "http://localhost:1337/" + factory.ServiceType.Name.Substring(1) + ".svc";
                var endpointAddress = new System.ServiceModel.EndpointAddress(svcAddress);

                return ServiceModelHelper.CreateService(factory.ServiceType, binding, endpointAddress);            
            }
            else if (factory.ServiceType == typeof(IThinkService))
            {
                // some specialized object construction here

                // return the constructed object
            }
            else
                return null;
        }
     );            
}


That lots of ifs could be avoided by registering specific types on their own RegisterFallback, e.g.,
_container.RegisterFallback((t, s) => t.Assembly == serviceAssembly,
    factory =>
    {
        var svcAddress = "http://localhost:61930/" + factory.ServiceType.Name.Substring(1) + ".svc";
        var endpointAddress = new System.ServiceModel.EndpointAddress(svcAddress);

        object instance = ServiceModelHelper.CreateService(factory.ServiceType, binding, endpointAddress);

        return instance;
    });


_container.RegisterFallback((t, s) => t == typeof(string),
      factory =>
      {
          return "Hello world";
      });


However, multiple RegisterFallback could potentially cause a delay on app's cold start especially if there are too many of them. On the positive aspect, once the unregistered type matches a RegisterFallback condition, RegisterFallback's factory delegate is cached to the unregistered object, making the RegisterFallback's condition not to be evaluated again anymore, plus there's no more many ifs inside the factory delegate


Another approach is to use LightInject's RegisterInstance:
void RegisterWcfServices()
{
    var binding = new System.ServiceModel.BasicHttpBinding();
        
    foreach (var contractType in typeof(TheServiceContracts.IMemberService).Assembly.GetTypes())
    {
        var svcAddress = "http://localhost:1337/" + contractType.Name.Substring(1) + ".svc";
        var endpointAddress = new System.ServiceModel.EndpointAddress(svcAddress);

        object instance = ServiceModelHelper.CreateService(contractType, binding, endpointAddress);
        _container.RegisterInstance(contractType, instance);
    }
}

However, that could potentially cause a delay on app's cold start too as instances for the interfaces are eagerly determined during IoC registration


Adding RegisterDelegate functionality on LightInject is desirable



Happy Coding!

No comments:

Post a Comment