.NET Backend

ASP.NET Core – Resolving proper implementation in runtime using Autofac

A few days ago I faced an interesting problem. In a nutshell, I had one interface implemented by three classes:

 


    public interface IService
    {
        string GetMessage();
    }

    public class DomainService : IService
    {
		public string GetMessage()
			=> "Hello from domain service!";
    }

    public class ExternalService : IService
    {
		public string GetMessage()
			=> "Hello from external service!";
    }

    public class MockService : IService
    {
		public string GetMessage()
			=> "Hello from mock service!";
    }

 

All dependencies were registered in ASP.NET Core container. Now we come to the tricky part. Each HTTP request coming to API had a custom header which contains information about implementation that should be used. So, using another word – it was a client app that decided on implementation. My goal was very simple – to make it work.

 

Register dependencies by convention

Before we move to the code it’s worth to mention one thing. Developers decided to follow very simple convention. Each type of service (so domain, external, mock) were put in separate folders like
in the below example:

 

 

That meant that each type was placed in a different namespace which ended with „Domain”, „External” and „Mock”. That was a good place to start.
First thing I did was introducing Autofac container since it has way more options for configuring entire assemblies/namespaces. Then I created an extension method which helped me register all three namespaces:

 


		internal static void RegisterServicesByConvention(this ContainerBuilder containerBuilder)
		{
			var serviceAssembly = typeof(MockService).GetTypeInfo().Assembly;
			containerBuilder
				.RegisterServicesByNamespace(serviceAssembly, "Domain")
				.RegisterServicesByNamespace(serviceAssembly, "External")
				.RegisterServicesByNamespace(serviceAssembly, "Mock");
		}

		private static ContainerBuilder RegisterServicesByNamespace(this ContainerBuilder containerBuilder, Assembly assembly, string name)
		{
			containerBuilder.RegisterAssemblyTypes(assembly)
				.Where(t => t.Namespace.EndsWith(name))
				.AsImplementedInterfaces().WithMetadata("Target", name);
			return containerBuilder;
		}

 

As you see the code is very simple and it does two things. First, it gets the assembly where MockService is placed so I had an access to register all needed dependencies. Then I informed Autofac that I wanted to register services from each folder (so the namespace) as implemented interfaces so in this example IService. Notice that each registration had its own metadata which informed what type of service it is. So, at that point, I solved an issue with registration. For example, I knew that DomainService was registered as IService and it has a „Target” metadata with value „Domain”.

 

Creating a service factory

I still聽needed to resolve proper implementation at runtime based on HTTP request. That’s why I created a ServiceFactory:

 


    public interface IServiceFactory
    {
        TService GetService<TService>() where TService : class;
    }

	internal class ServiceFactory : IServiceFactory
	{
		private readonly string _targetType;
		private readonly ICustomDependencyResolver _resolver;

		public ServiceFactory(string targetType, ICustomDependencyResolver resolver)
		{
			_targetType = targetType;
			_resolver = resolver;
		}

		public TService GetService<TService>() where TService : class
		{
			var services = _resolver.Resolve<IEnumerable<Meta<TService>>>();
			var service = services.FirstOrDefault(s => _targetType.Equals($"{s.Metadata["Target"]}", StringComparison.OrdinalIgnoreCase))?.Value;
			return service ?? throw new InvalidOperationException();
		}
	}

 

As you see it gets two parameters:

  • string targetType – it’s a text got from request’s custom header
  • ICustomDependencyResolver resolver – it’s a small wrapper for Autofac’s ILifetimeScope which provides only one method – Resolve:

 

    public interface ICustomDependencyResolver
    {
        TResolved Resolve<TResolved>();
    }

	internal class CustomDependencyResolver : ICustomDependencyResolver
	{
		private readonly ILifetimeScope _lifetimeScope;

		public CustomDependencyResolver(ILifetimeScope lifetimeScope)
		{
			_lifetimeScope = lifetimeScope;
		}

		public TResolved Resolve<TResolved>()
			=> _lifetimeScope.Resolve<TResolved>();
	}

 

I simply don’t like using ILifetimeScope directly in my code but keep in mind that this step is not mandatory to make the whole thing work. Moving back to the factory class, the first line in GetService method does something weird:

 


var services = _resolver.Resolve<IEnumerable<Meta<TService>>>();

 

The reason for that is because all three types of services had to be registered as IService interface. What would be resolved by Autofac if we would ask for IService? Random pick? That’s why in that situation we need to ask about all implementations so we have IEnumerable. The next part so Meta<T> is present in the code because each of our service registration came with Metadata and that’s how Autofac wraps it inside. So the above line simply informs that we want all implementations of given interface which its metadata. Simple is that. In the next line, we pick proper implementation based on text got from HTTP request:

 


var service = services.FirstOrDefault(s => _targetType.Equals($"{s.Metadata["Target"]}", StringComparison.OrdinalIgnoreCase))?.Value;

 

Notice that I inverted the condition so Equals method comes from a string, not the Metadata object. This allowed me to add StringComparison enum. In the last line, the service is returned or the exception is thrown if service was not found (that basically means that someone put text to custom header other than three allowed). We still miss one thing. How did I provide a target type from request to factory? Below you have an extension method which did the job:

 


		internal static void RegisterServiceFactory(this ContainerBuilder containerBuilder)
		   => containerBuilder.Register(ctx =>
		   {
			   var dataSource = ctx.Resolve<IHttpContextAccessor>().HttpContext.Request.Headers
				   .Single(h => h.Key == "implementation-type").Value.First();
			   var resolver = ctx.Resolve<ICustomDependencyResolver>();
			   return new ServiceFactory(dataSource, resolver);
		   })
			  .As<IServiceFactory>();

 

Now the whole mystery is revealed 馃檪聽 I used ASP.NET Core IHttpContextAccessor to get the request and then I took of the target type. Of course, this action is kicked off each time I want to resolve my factory somewhere in the code. Of course, to make it work I had to register everything in Startup class:

 


        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

			var containerBuilder = new ContainerBuilder();
			containerBuilder.RegisterType<CustomDependencyResolver>().As<ICustomDependencyResolver>();
            containerBuilder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>();
			containerBuilder.RegisterServicesByConvention();
			containerBuilder.RegisterServiceFactory();

			containerBuilder.Populate(services);
			var container = containerBuilder.Build();
            return container.Resolve<IServiceProvider>();
        }

 

And below you can see the example usage of the factory inside default ValuesController:

 


    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        private readonly IService _service;

        public ValuesController(IServiceFactory factory)
        {
            _service = factory.GetService<IService>();
        }

        [HttpGet]
        public string Get()
            => _service.GetMessage();
    }

 

Before we’ll move to testing let’s summarise the entire flow:

  1. 聽HTTP request comes to the API and proper action on MVC controller is picked.
  2. Since controller has ServiceFactory as a dependency it needs to be resolved by Autofac.
  3. To create an instance of the factory, target type is got from the custom header and new instance of the factory is provided to the controller.
  4. When Controller’s constructor calls GetService method,
    beneath it resolves all implementations for given interface with its metadata.
  5. Based on the target type, proper implementation is picked and returned to the controller

 

Testing

To check whether it’s working I used a Postman app. The result is presented on the below gif:

 

 

Honestly, that was very interesting thing to do! A little bit challenging but the result is very cool. I hope that this will be helpful for some of you. Of course in production code, you need to add some more error handling to prevent all kinds of unexpected behaviors.

  • Pingback: dotnetomaniak.pl()

  • Michal Dymel

    Another idea could be to inject all implementations of IService and add ImplementationType property to the interface (Domain, External or Mock – could be an enum). Then you could inject all IService implementations in the ValuesController and choose the one you want by querying the property by implementation-type header.

    • I agree, but I didn’t want to change the actual implementation (code structure) of these services. Autofac’s metadata gave this by wrapping the implementations in Meta objects. But your solution is cool as well 馃檪

  • Micha艂

    Autentycznie zaraz zniose jajo… Polska strona, sami Polacy i gadaj膮 z sob膮 po angielsku… ????

    • Micha艂,
      piszemy komentarze po angielsku nie dlatego 偶eby pokaza膰, 偶e umiemy tylko, aby osoby kt贸re przeczytaj膮 ten tekst mog艂y r贸wnie偶 zrozumie膰 dalsz膮 dyskusj臋 w ramach tego tematu. Wbrew pozorom sporo fajnych pomys艂贸w wpada w艂a艣nie przez inne osoby w kometarzach. Pisz膮c je po polsku ograniczamy t膮 wiedz臋 tylko dla w膮skiego grona odbiorc贸w.

  • Pingback: Diyala Arts()

  • Pingback: Best Corporate Event Management Companies in Hyderabad()