【转】ASP.NET MVC 3 Service Location, Part 5: IDependencyResolver
What's New Since Preview 1?
We shipped ASP.NET MVC 3 Beta today, and with it we’ve made some significant progress (and departures) from the MVC 3 Preview 1 build released in July as it pertains to service location.
We received significant feedback during the Preview 1 time frame about our use proposed use of the Common Service Locator. The majority opinion was that MVC should allow Common Service Locators to integrate with MVC, but strict dependence on it was not necessary (or even desirable, since it adds an extra binary dependency for redistribution and deployment).
We have introduced a new interface: System.Web.Mvc.IDependencyResolver. This interface is intended to simplify the requirements for service location/dependency resolution with MVC.
Disclaimer
This blog post talks about ASP.NET MVC 3 Beta, which is a pre-release version. Specific technical details may change before the final release of MVC 3. This release is designed to elicit feedback on features with enough time to make meaningful changes before MVC 3 ships, so please comment on this blog post or contact me if you have comments.
IDependencyResolver
For developers porting code from MVC 3 Preview 1 to MVC 3 Beta, this interface replaces IMvcServiceLocator.
To implement a dependency resolver for MVC, you will need to implement this interface:
public interface IDependencyResolver { object GetService(Type serviceType); IEnumerable<object> GetServices(Type serviceType); }
In Part 1 of the series, I talked about three ways that MVC consumes services with the service locator: singly registered services, multiply registered services, and creating arbitrary objects.
Resolving singly registered services and arbitrary object creation will call IDependencyResolver.GetService(), whereas resolving multiply registered services will be done by calling IDependencyResolver.GetServices(). We’ve added two activator services which provide alternatives for arbitrary object creation (see Part 10 for information on the Controller Activator, and Part 11 for information on the View Page Activator).
Aside from the simplicity of the interface, the major departure from Common Service Locator is that implementers of IDependencyResolver should always return null from GetService when it cannot find the service. Similar to the CSL, IDependencyResolver.GetServices should always return an empty collection if it cannot find any services. If your implementation of IDependencyResolver throws any exceptions (or returns null from GetServices instead of an empty collection), it will be surfaced to the user as a run-time error.
DependencyResolver
For developers porting code from MVC 3 Preview 1 to MVC 3 Beta, this class replaces MvcServiceLocator.
There is a registration point for dependency resolvers: the DependencyResolver static class.
public class DependencyResolver { public static IDependencyResolver Current { get; } public static void SetResolver(IDependencyResolver resolver); public static void SetResolver(object commonServiceLocator); public static void SetResolver(Func<Type, object> getService, Func<Type, IEnumerable<object>> getServices); }
There are three registration points you can use: one if you have an implementation of IDependencyResolver, one if you have an implement of Common Service Locator’s IServiceLocator, and one if you want to register an ad-hoc resolver based on functions with the correct signature.
The second overload takes an object instead of IServiceLocator, because ASP.NET MVC 3 doesn’t have a hard dependency on the CommonServiceLocator. It will use runtime reflection to determine if the object in question is really an implementation of IServiceLocator.
Regardless of which registration method you use, we will return an implementation of IDependencyResolver when you call DependencyResolver.Current. If you’ve registered something other than an implementation of IDependencyResolver, we wrap it up for you, so that all service location-aware code can just use the single interface.
There is a default dependency resolver which uses Activator.CreateInstance, which is suitable for users who do not plan to use dependency injection.
Extension methods for IDependencyResolver
You may have noticed that there are no generic versions of GetService or GetServices on IDependencyResolver. We provide extension methods for IDependencyResolver which provide these generic methods:
TService GetService<TService>(this IDependencyResolver resolver) { return (TService)resolver.GetService(typeof(TService)); } IEnumerable<TService> GetServices<TService>(this IDependencyResolver resolver) { return resolver.GetServices(typeof(TService)).Cast<TService>(); }
By using extension methods, we have simplified implementation of IDependencyResolver, while consumers of the interface can use either the early-bound/strongly-typed generic versions (and avoid the cast in their code) or the late-bound/weakly-typed versions (sometimes useful when you want to create an instance of a concrete type that you expect to implement a specific interface or derive from a specific abstract base class).
Differences between IDependencyResolver and Common Service Locator
The primary difference between IDependencyResolver and Common Service Locator centers around exceptions. Where Common Service Locator expects you to throw exceptions on single-service lookup failure, IDependencyResolver's GetService() method expects you to return null on single-service lookup failure.
Both interfaces expect you to return an empty collection (not null) for multi-service lookup when there are no registered services.
The documentation for IDependencyResolver can be found online at MSDN.
What’s Next?
The first new area of service location in MVC 3 Beta is model validation.