In this post, I will review the dependency injection features of the ASP.NET Web API. There are a few posts out there but I felt there are areas still not covered so I am exploring the dependency injection options. I do not try explaining what Dependency Injection (DI), Inversion of Control (IoC) or Service Location are. If you are not familiar with these you perhaps first need to get a grasp by reading numerous resources out there.
Until ASP.NET MVC 3 none of the Microsoft products had any IoC interoperability. In ASP.NET MVC 2 you had to resort to replacing default controller factory with your own and server controllers by using service location. In WCF you had to provide your own factories using code or config which were not great. But ASP.NET MVC 3 for the first time relinquish its hold on object creation and acknowledged the need for DI as more teams embraced it.
ASP.NET Web API beta has a built in support for dependency injection. Just be careful that there are breaking changes in the ASP.NET Web API source code and the model has changed (it has been simplified). I will shortly explain beta release and then move on to the source code which the final release will probably look more like it.
ASP.NET Web API beta release
[Remember, this stuff has changed] DI is based on setting a dependency resolver (technically a service locator) on the HttpConfiguration object. This object is on the GlobalConfiguration object as a static property (web hosting) or in case of self hosting on HttpSelfHostConfiguration.
So in case of web hosting you would use:
GlobalConfiguration.Configuration.ServiceResolver.SetResolver(myResolver);There are basically 3 options when it comes to registering a dependency resolver:
- An instance of an object implementing IDependencyResolver dependency which has two methods: GetService and GetServices
- An instance of an object which has these two public methods: GetInstance and GetAllInstances. No interface needed and Web API will use reflection to call methods.
- Passing two delegates that return an instance or instances for a particular type
ASP.NET Web API source code release
This has been simplified and unified so there is only one option: using IDependencyResolver.
There is one change in IDependencyResolver though: in addition to GetServeice and GetServices (which now is in IDependencyScope interface implemented by IDependencyResolver), there is a BeginScope method.
BeginScope according to documentation is:
Starts a resolution scope. Objects which are resolved in the given scope will belong to that scope, and when the scope is disposed, those objects are returned to the container. Implementers should return a new instance of IDependencyScope every time this method is called, unless the container does not have any concept of scope or resource release (in which case, it would be okay to return 'this', so long as the calls to are effectively NOOPs).
This is particularly important if you leave responsibility of lifetime management of your objects to your DI container.
Developing a simple Dependency Resolver for AutoFac
AutoFac is my DI framework of choice (I have used Windsor and Unity before). So here I will write a few lines of code to hook AutoFac into ASP.NET Web API.
All you need to do is to implement IDependencyResolver and then create an instance of your object and use SetResolver on the configuration (see above) instance.
Your Dependency Resolver is not supposed to throw exceptions but it can return null in case the type is not registered.
ASP.NET Web API also tries to use Dependency Resolver (and in case you have provided your own will use yours) to resolve its own dependencies. This could have placed a big burden on such a dependency resolver to have knowledge of all ASP.NET Web API dependencies but the implementation is as such that Web API will use the objects that you return for dependency resolution and if you return null, it will use the default.
So here is the code for the simple AutoFac dependency resolver:
public class AutoFacDependencyResolver : IDependencyResolver { private ContainerBuilder _builder = new ContainerBuilder(); private IContainer _container; public ContainerBuilder Builder { get { return _builder; } } public void Build() { _container = _builder.Build(); } public IDependencyScope BeginScope() { return this; } public object GetService(Type serviceType) { object resolved = null; if (_container.TryResolve(serviceType, out resolved)) return resolved; else return null; } public IEnumerable<object> GetServices(Type serviceType) { object resolved = false; Type all = typeof(IEnumerable<>).MakeGenericType(serviceType); if (_container.TryResolve(all, out resolved)) return (IEnumerable<object>)resolved; else return null; } public void Dispose() { if(_container!=null) _container.Dispose(); } }Things to note are:
- We expose the builder so the client can register the components.
- We expose Build() method so at the end of registration, Build() is called. Ideally we should hide the builder and expose it through AutoFactDependencyResolver methods so that build could not be directly called on the builder by the client.
- AutoFac inherently does not have a ResolveAll method but instead you may resolve IEnumerable<T> in which case works as ResolveAll.
- Please note that we had to use TryResolve so that no exception is thrown. Alternatively you may use a try/catch block and return null in the catch (but that could mask other exceptions)
OK, let's imagine we have a controller which is dependent on loggers:
public interface ILogger { void Log(string value); } public class TraceLogger : ILogger { public void Log(string value) { Trace.WriteLine(value); } } public class DebugLogger : ILogger { public void Log(string value) { Debug.WriteLine(value); } } public class DiTestController : ApiController { private ILogger _logger; private List<Ilogger> _loggers; public DiTestController(IEnumerable<Ilogger> loggers) { _loggers = loggers.ToList(); } public string Get() { _loggers.ForEach(x => x.Log("Get")); return "Dependency Injection"; } }
So the controller will use ForEach to call Log on all ILoggers passed to it. In order to register the Dependency Resolver, all we have to do is this:
Config object here is a reference to GlobalConfiguration.Configuration (in case of web hosting) or an instance of HttpSelfHostConfiguration (in case of self-hosting).
AutoFacDependencyResolver resolver = new AutoFacDependencyResolver(); resolver.Builder.RegisterType<TraceLogger>().As<ILogger>(); resolver.Builder.RegisterType<DebugLogger>().As<ILogger>(); resolver.Builder.RegisterType<DiTestController>(); resolver.Build(); config.DependencyResolver = resolver;
Config object here is a reference to GlobalConfiguration.Configuration (in case of web hosting) or an instance of HttpSelfHostConfiguration (in case of self-hosting).
No comments:
Post a Comment
Note: only a member of this blog may post a comment.