Brad Wilson在他自己的博客写了有关ASP.NET MVC3依赖注入支持的一系列文章,但由于某些原因(你懂的),需要翻墙才能阅读。

为了方便自己也方便喜欢的同学,特意翻墙再拷贝回来发布,以下是这一系列文章的索引:

  1. Part 1: Introduction
  2. Part 2: Controllers
  3. Part 3: View Engines/View Pages
  4. Part 4: Filters
  5. Part 5: IDependencyResolver
  6. Part 6: Model Validation 
  7. Part 7: Model Metadata
  8. Part 8: Value Providers
  9. Part 9: Model Binders
  10. Part 10: Controller Activator
  11. Part 11: View Page Activator  
posted @ 2011-01-25 18:22 四眼蒙面侠 阅读(156) 评论(3) 编辑

View Page Activator

In ASP.NET MVC, it’s common for views to be compiled into classes. In MVC 1.0, we shipped the WebFormViewEngine as the default view engine, which allowed the user to write views using the familiar <% %> syntax from ASP.NET WebForms. The first time the view is rendered, the view file goes through a code generation step, which is compiled into a class. The WebFormViewclass is responsible for creating and executing the class that resulted from the view.

In ASP.NET MVC 3, we introduced a new Razor view engine. Like the WebForm view engine, the .cshtml and .vbhtml files from Razor go through a code generation step at run-time which compiles down into classes. The RazorView class is the Razor view engine counterpart to the WebFormView class.

We have introduced a new base class (BuildManagerCompiledView) and a new service (IViewPageActivator) which is responsible for instantiating instances of the view classes that were compiled from the view files. We have made the IViewPageActivator instance findable via the dependency resolver.

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.

New Base Class: BuildManagerCompiledView

When we introduced the new Razor view engine, it was clear that there was a lot of common code between the infrastructure classes of our two view engines; namely, the WebFormView and RazorView (which both implement the IView interface) had significant shared code that was centered around BuildManager.

In the ASP.NET runtime, BuildManager is the class that is responsible for converting view files (like .aspx and .ascx files in WebForms, and .cshtml and .vbhtml files in Razor) into the underlying classes using code generation. BuildManager uses build providers to do most of that transformation work (which is outside the scope of this blog post). The important takeaway is that we extracted a common base class for the two view classes: BuildManagerCompiledView. The logic which creates view page class instances was centralized into this class, and the new IViewPageActivator service was introduced to allow pluggability of the creation of the view page classes.

Developers which are creating view engines which use build providers and BuildManager to convert view files into classes can take advantage of this new base class for their implementation(s) of the IView interface.

Implementing IViewPageActivator

The new IViewPageActivator interface contains a single method for creating a view page instance:

public interface IViewPageActivator {
    object Create(ControllerContext controllerContext, Type type);
}

Given the controller context and the view page class type, implementers of this interface must create the view page instance.

Location: IViewPageActivator

This is a “singly registered” style service introduced in MVC 3. There is no static registration point for this service as its purpose is strictly to support dependency injection; as such, the only way to register an instance of this service is through the dependency resolver.

The logic in BuildManagerCompiledView consults the dependency resolver, calling GetSerivce(typeof(IViewPageActivator)) and using the provided service when present. If there is no IViewPageActivator present in the dependency resolver, we will then ask the dependency resolver to create the concrete view page type by calling GetService(viewPageType). If the dependency resolver also fails to create the concrete view page type, we finally fall back to the MVC 2 behavior of using Activator.CreateInstance to create the view page type.

What's Next?

This is the end of the road for MVC 3 Beta. We believe we are now reasonably feature complete for MVC 3 and dependency resolution. If there are specific areas where you wish the framework would better enable dependency resolution, please don't hesitate to discuss them on the MVC Forums.

Thanks for reading!

posted @ 2011-01-25 17:59 四眼蒙面侠 阅读(129) 评论(0) 编辑

Controller Activator

In MVC 1.0, we introduced IControllerFactory to allow better dependency injection of controller instances. We also provided theDefaultControllerFactory, which created controller instances with Activator.CreateInstance. Some of this is discussed in Part 2 of this series.

We realized that DefaultControllerFactory was actually doing two things: turning the controller name into the controller type, and then instantiating the instance of that type. In ASP.NET MVC 3, we’ve split out the action of instantiating the controller instance into a new service: IControllerActivator. We have made the IControllerActivator instance findable via the dependency resolver.

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.

Implementing IControllerActivator

The new IControllerActivator signature is identical to the DefaultControllerFactory.GetControllerInstance virtual method:

public interface IControllerActivator {
    IController Create(RequestContext requestContext, Type controllerType);
}

Developers who previously implemented IControllerFactory by deriving from DefaultControllerFactory just to override the GetControllerInstance method for dependency injection purposes should now implement IControllerActivator instead.

Location: IControllerActivator

This is a “singly registered” style service introduced in MVC 3. There is no static registration point for this service as its purpose is strictly to support dependency injection; as such, the only way to register an instance of this service is through the dependency resolver.

The logic in DefaultControllerFactory was updated to consult the dependency resolver, calling GetSerivce(typeof(IControllerActivator)) and using the provided service when present. If there is no IControllerActivator present in the dependency resolver, we will then ask the dependency resolver to create the concrete controller type by calling GetService(controllerType). If the dependency resolver also fails to create the concrete controller type, we finally fall back to the MVC 2 behavior of using Activator.CreateInstance to create the controller type.

What’s Next?

In the final post of this series, we will discuss the new View Page Activator service.

posted @ 2011-01-25 17:58 四眼蒙面侠 阅读(122) 评论(0) 编辑

Value Providers

ASP.NET MVC 2 introduced a new method to find value providers: the ValueProviderFactory class. Value providers are used by the model binding system in MVC to populate the values of model objects. MVC includes value providers for several common value sources (including query string, form, route data, uploaded files, and JSON postbacks); MVC Futures includes several mode (including cookies, server values, session values, and temp data values). In ASP.NET MVC 3, we have made ValueProviderFactory instances findable via the dependency resolver.

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.

Implementing ValueProviderFactory

Developers who implement this class must provide an implementation of GetValueProvider which, given a controller context, optionally returns an instance of a class which implements the IValueProvider interface. The developer may also choose to return null if there is no appropriate value provider or values.

For reference purposes, there are several implementations of ValueProviderFactory in the MVC and MVC Futures source code. For a simple example, see the pair of classes: QueryStringValueProviderFactory and QueryStringValueProvider.

Implementing IValueProvider

Developers who implement this interface provide implementations of two methods: ContainsPrefix and GetValue. The former method is used to determine if there are any values in the provider with the given prefix (so the model binding system knows when to stop recursively binding). The latter method is used to get the value for a specific key.

There are two implementations of IValueProvider (NameValueCollectionValueProvider and DictionaryValueProvider) will accept collections of values to be used for the value provider implementation. Most of the value providers in the MVC source code actually derive from one of these two base classes.

Location: ValueProviderFactory

This is a “multiply registered” style service introduced in MVC 2. The static registration point for this service is atValueProviderFactories.Factories for non-DI users.

The logic in ValueProviderFactoryCollection (which implements ValueProviderFactories.Factories) was updated to consult the dependency resolver, calling GetServices(typeof(ValueProviderFactory)) and adding any services found to the list of statically registered services. Value providers all collaborate to provide values for model binding, with a “first one to provide the value wins” strategy, so registration order is important. The factories found in the dependency resolver will always run in the order they are returned from the resolver (and before the statically registered value provider factories); similarly, the value providers will be consulted in the order they are returned from the factories.

What’s Next?

Next up we’ll talk about Model Binders.

posted @ 2011-01-25 17:57 四眼蒙面侠 阅读(79) 评论(0) 编辑

Model Binders

ASP.NET MVC 1.0 introduced the IModelBinder interface. Developers who implement this interface are responsible for creating models from values obtained from value providers. In ASP.NET MVC 3, we introduced a new interface (IModelBinderProvider) which allows developers to dynamically provide implementations of IModelBinder. We have made IModelBinderProvider instances findable via the dependency resolver.

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.

Implementing IModelBinderProvider

In prior versions of MVC, developers could only register static mappings from types to model binder instances (throughModelBinders.Binders). While this satisfied simple uses, it made complex and powerful model binders a little more challenging to write.

When we decided to open model binding up for dependency resolution, we knew that we were going to need to introduce a layer of indirection, since model binders are usually only valid for a limited number of types (unless you are the default model binder).

We solved both problems by introducing a new interface:

public interface IModelBinderProvider {
    IModelBinder GetBinder(Type modelType);
}

Developers who implement this interface can optionally return an implementation of IModelBinder for a given type (they should return null if they cannot create a binder for the given type).

The system continues to rely on a “default model binder” if the system cannot find any appropriate model binder for the given type. You can change the default model binder by setting ModelBinders.Binders.DefaultBinder. Out of the box, this is set to an instance of DefaultModelBinder.

There are no implementations of IModelBinderProvider in MVC. However, there is a similar system in MVC Futures from MVC 2 (in the ModelBinding folder), and there are several model binder providers that could be used to spark ideas about writing providers for MVC 3, including model binder providers for arrays, binary data, collections, complex objects, and dictionaries.

Location: IModelBinderProvider

This is a “multiply registered” style service introduced in MVC 3. The static registration point for this service is at ModelBinderProviders.BinderProviders for non-DI users.

The logic in ModelBinderProviderCollection (which implements ModelBinderProviders.BinderProviders) consults the dependency resolver, calling GetSerivces(typeof(IModelBinderProvider)) and adding any services found to the list of statically registered services. Model binder provides are run in-order, with a “first one to provide a model binder wins” strategy, so registration order is important. The providers found in the dependency resolver will always run in the order they are returned from the resolver (and before the static registered providers).

What’s Next?

Next up we’ll look at the new Controller Activator service.

posted @ 2011-01-25 17:57 四眼蒙面侠 阅读(127) 评论(0) 编辑

Model Metadata

ASP.NET MVC 2 introduced an extensible model metadata system where developers could implement a class which derived from ModelMetadataProvider to provide meta-information about the models in the system. In ASP.NET MVC 3, we have made the metadata provider findable via the dependency resolver.

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.

Writing a Metadata Provider

For more information about ModelMetadata and what would be involved in writing a metadata provider, please see my blog post ASP.NET MVC 2 Templates, Part 2: ModelMetadata.

Location: ModelMetadataProvider

This is a “singly registered” style service introduced in MVC 2. The static registration point for this service is atModelMetdataProviders.Current for non-DI users.

The model metadata provider inspects models, and returns meta-information about the models, include information like display names, formatting strings for display and editing, flags on whether a value is editable or not, etc.

The logic in ModelMetadataProviders was updated to attempt to find the implementation of ModelMetadataProvider first by calling DependencyResolver.GetService(typeof(ModelMetadataProvider)). If the service does not exist in the dependency resolver, then it falls back to the static registration point. If you register a model metadata provider in both the static registration point and in the dependency resolver, MVC will throw an exception (since the service is intended to be singly registered).

The default model metadata provider continues to be DataAnnotationsModelMetadataProvider.

What’s Next?

Next up in the series is Value Providers.

posted @ 2011-01-25 17:56 四眼蒙面侠 阅读(105) 评论(0) 编辑

Model Validators

ASP.NET MVC 2 introduced an extensible model validation system where developers could implement a class which derived from ModelValidatorProvider to influence the validation process on both the client-side and server-side. In ASP.NET MVC 3, we have made these validator providers findable via the dependency resolver.

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.

Writing a Validator Provider

For an example implementation of ModelValidatorProvider, see my blog post: Enterprise Library Validation example for ASP.NET MVC 2.

Location: ModelValidatorProvider

This is a “multiply registered” style service introduced in MVC 2. The static registration point for this service is atModelValidatorProviders.Providers for non-DI users.

Model validator providers inspect models, and return validators that ensure that the models are valid. They also participate in the client-side validation by returning client “validation hints” consumed by JavaScript.

The logic in ModelValidatorProviderCollection (which implements ModelValidatorProviders.Providers) was updated to consult the dependency resolver, calling GetServices(typeof(ModelValidatorProvider)) and adding any services found to the list of statically registered service. Validator providers all collaborate to potentially validate a model value, so the ordering of validator providers is not important. Every provider will get an opportunity to provide validators.

What’s Next?

Next up we’ll talk about Model Metadata.

posted @ 2011-01-25 17:48 四眼蒙面侠 阅读(90) 评论(0) 编辑

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.

posted @ 2011-01-25 17:47 四眼蒙面侠 阅读(196) 评论(0) 编辑

Important Update

We've made significant changes to the IoC support in ASP.NET MVC 3 Beta. Please read Part 5 for more information.

Filters and Filter Providers

We introduced the concept of filters in ASP.NET MVC 1.0. A filter can implement one of more of the following interfaces:IActionFilter, IResultFilter, IExceptionFilter, and IAuthorizationFilter.

In prior versions of MVC, the only way to apply filters was to make a filter attribute and apply that attribute to a controller or an action method. In MVC 3, we've introduced the ability to have filters which are defined outside the scope of attributes (and found via filter providers), as well as a facility for registering global filters.

Update: (31 July 2010) I've added the source code for UnityMvcServiceLocator to the end of part 2.

Disclaimer

This blog post talks about ASP.NET MVC 3 Preview 1, 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.

Location: Filter Providers

Filter providers are a new feature to MVC 3. They are a "multiply registered" style service, with a static registration point at FilterProviders.Providers. This collection provides a facade method (GetFilters) which aggregates the filters from all of the providers into a single list. Order of the providers is not important.

By default, there are three filter providers registered into the application:

  • A filter provider for global filters (GlobalFilters.Filters)
  • A filter provider for filter attributes (FilterAttributeFilterProvider)
  • A filter provider for controller instances (ControllerInstanceFilterProvider)

In addition, the GetFilters facade method will also retrieve all the IFilterProvider instances in the service locator by calling MvcServiceLocator.Current.GetAllInstances<IFilterProvider>().

Implementing a Filter Provider

The filter provider interface is defined as follows:

namespace System.Web.Mvc {
    using System.Collections.Generic;

    public interface IFilterProvider {
        IEnumerable<Filter> GetFilters(ControllerContext controllerContext,
                                       ActionDescriptor actionDescriptor);
    }
}

The Filter class is a metadata class that contains a reference to the implementation of one of more of the filter interfaces, plus the filter's order and scope. The class is defined as:

namespace System.Web.Mvc {
    public class Filter {
        public const int DefaultOrder = -1;

        public Filter(object instance, FilterScope scope, int? order = null);

        public object      Instance { get; }
        public int         Order    { get; }
        public FilterScope Scope    { get; }
    }
}

Filter Ordering

Filters are run in a pre-determined order; this behavior has existed in previous versions of MVC, but we have now added an additional scope (global):

namespace System.Web.Mvc {
    public enum FilterScope {
        First = 0,
        Global = 10,
        Controller = 20,
        Action = 30,
        Last = 100,
    }
}

When determining the run order of filters, it sorts them first by their order (lowest numbers first), then by their scope (also lowest numbers first). A sorted example:

  • Order -100, Scope Last
  • Order 0, Scope First
  • Order 0, Scope Global
  • Order 0, Scope Controller
  • Order 0, Scope Action
  • Order 0, Scope Last
  • Order 100, Scope First

It's important to note here that "Controller" scope means "filters applied at the controller level"; the controller itself is also a filter which always runs first (that is, its order is Int32.MinValue and Scope is First). The execution order of filters with the same order and scope is undefined.

When the system runs the filters, they are sometimes run from the front of the list to the back (forwards), and sometimes run from the back of the list to the front (backwards). Additionally, filters may be skipped in some situations.

IActionFilter.OnActionExecuting is run in forward order, and IActionFilter.OnActionExecuted is run in reverse order. If your OnActionExecuting was never called (because an earlier filter terminated the chain), then your OnActionExecuted will not be called either.

IResultFilter.OnResultExecuting and OnResultExecuted follows the exact same rules as IActionFilter.

IAuthorizationFilter.OnAuthorization always runs in forward order, and IExceptionFilter.OnError always runs in reverse order.

In earlier versions of MVC, IExceptionFilter.OnError ran in forward order; for MVC 3, we have reversed the order of this filter based on community feedback. Exception filters in MVC have a similar feel to exception handlers in .NET, which unwind from the inside out. While this reversal is technically a breaking change, we're confident that it now behaves with better predictability.

Implementing a Filter

A filter is a class which implements one or more of the filter interfaces listed above. For filters which are attributes, you can use either the FilterAttribute or the ActionFilterAttribute as a base class. For non-attribute filters, there is no default base class; simply implement the filter interfaces as appropriate.

In addition to supporting one or more of the filter interfaces, you may also choose to implement IMvcFilter:

namespace System.Web.Mvc {
    public interface IMvcFilter {
        bool AllowMultiple { get; }
        int Order { get; }
    }
}

This is a metadata interface which allows you to tell the MVC framework how to treat instances of this filter.

We have updated the FilterAttribute base class to automatically implement this interface for you. The IMvcFilter.Order property mirrors the value of FilterAttribute.Order, and the IMvcFilter.AllowMultiple property is derived automatically based on the [AttributeUsage(AllowMultiple=)] attribute property applied to your custom filter attribute.

You probably noticed above that the Filter object's constructor takes an optional order parameter, which is null by default. By passing null, you're asking the Filter class to look for the implementation of IMvcFilter on the filter object itself to determine its order.

The AllowMultiple property of IMvcFilter is used during sorting in order to eliminate duplicate filters. When AllowMultiple is true, that means that all instances of the exact same filter type are allowed; when AllowMultiple is false, that means that only the last instance of the exact same filter type is allowed, and all others are discarded. If your filter does not implement IMvcFilter, then the default value for AllowMultiple is true.

Previously, all these implementation details were hidden inside the action descriptor classes and filter attribute classes.

Registering a Global Filter

A global filter is a filter that is going to run for every single action on every single controller. You can register a global filter by using the GlobalFilters.Filter static registration endpoint. When you specify a filter instance here, you cannot provide the scope, as it is automatically set to Global for you:

public sealed class GlobalFilterCollection : IEnumerable<Filter>, IFilterProvider {
    public int Count { get; }

    public void Add(object filter, int? order = null);
    public void Clear();
    public bool Contains(object filter);
    public void Remove(object filter);
}

Although you add filter instances as objects to the collection, it actually returns an instance of the Filter metadata class when you enumerate the container.

At this point in time, there is no way to register a global filter with the service locator.

Adding Dependency Injection to Filters

A filter provider is responsible to returning instances of filter objects to the MVC framework, so it's responsible for ensuring that dependency injection is properly observed, whenever possible.

One place where dependency injection has been difficult in the past is inside the filter attributes themselves. Because the .NET framework runtime is actually responsible for creating these attribute instances, we cannot use a traditional dependency injection strategy.

The Unity container is capable of providing non-constructor injection on existing object instances. Here is an example filter provider that overrides the default behavior of the FilterAttributeFilterProvider to enable property setter injection on filter attributes:

UnityFilterAttributeFilterProvider.cs

using System.Collections.Generic;
using System.Web.Mvc;
using Microsoft.Practices.Unity;

public class UnityFilterAttributeFilterProvider : FilterAttributeFilterProvider {
    private IUnityContainer _container;

    public UnityFilterAttributeFilterProvider(IUnityContainer container) {
        _container = container;
    }

    protected override IEnumerable<FilterAttribute> GetControllerAttributes(
                ControllerContext controllerContext,
                ActionDescriptor actionDescriptor) {

        var attributes = base.GetControllerAttributes(controllerContext,
                                                      actionDescriptor);
        foreach (var attribute in attributes) {
            _container.BuildUp(attribute.GetType(), attribute);
        }

        return attributes;
    }

    protected override IEnumerable<FilterAttribute> GetActionAttributes(
                ControllerContext controllerContext,
                ActionDescriptor actionDescriptor) {

        var attributes = base.GetActionAttributes(controllerContext,
                                                  actionDescriptor);
        foreach (var attribute in attributes) {
            _container.BuildUp(attribute.GetType(), attribute);
        }

        return attributes;
    }
}

Global.asax.cs

protected void Application_Start() {
    // ...

    var oldProvider = FilterProviders.Providers.Single(
        f => f is FilterAttributeFilterProvider
    );
    FilterProviders.Providers.Remove(oldProvider);

    var container = new UnityContainer();
    var provider = new UnityFilterAttributeFilterProvider(container);
    FilterProviders.Providers.Add(provider);

    // ...
}

This code removes the old FilterAttributeFilterProvider implementation and replaces it with the one that can use Unity to do property setter injection on the filter attributes, so that you could write attributes like this:

InjectedFilterAttribute.cs

using System;
using System.Web.Mvc;
using Microsoft.Practices.Unity;

public class InjectedFilterAttribute : ActionFilterAttribute {

    [Dependency]
    public IMathService MathService { get; set; }

    public override void OnResultExecuted(ResultExecutedContext filterContext) {
        filterContext.HttpContext.Response.Write(
            String.Format("<p>The filter says 2 + 3 is {0}.</p>",
                          MathService.Add(2, 3))
        );
    }
}

Of course, you can also register the new filter provider in the service locator, instead of using the static registration endpoint:

Global.asax.cs

protected void Application_Start() {
    // ...

    var oldProvider = FilterProviders.Providers.Single(
        f => f is FilterAttributeFilterProvider
    );
    FilterProviders.Providers.Remove(oldProvider);

    var container = new UnityContainer();
    var provider = new UnityFilterAttributeFilterProvider(container);
    container.RegisterInstance<IFilterProvider>(provider, "attributes");
    MvcServiceLocator.SetCurrent(new UnityMvcServiceLocator(container));

    // ...
}

What's Next?

This is the end of the road for MVC 3 Preview 1. In our next preview, we will probably have enabled even more service location scenarios, which we will then be able to document here. In the meantime, if there are specific areas where you wish the framework would better enable service location and/or dependency injection, please don't hesitate to discuss them on the MVC Forums.

Thanks for reading!

posted @ 2011-01-25 17:46 四眼蒙面侠 阅读(128) 评论(0) 编辑

Important Update

We've made significant changes to the IoC support in ASP.NET MVC 3 Beta. Please read Part 5 for more information.

View Engine Registration and View Page Creation

ASP.NET MVC 1.0 introduced View Engines (implementing IViewEngine and IView), shipping with the single WebForms-based view engine. Views, master pages, and partial views in WebForms view engine need to derive from ViewPage,ViewMasterPage, and ViewUserControl (respectively). In MVC 3, we've introduced new dependency injection points for all of these classes (plus the new base class for Razor views, WebViewPage).

Update: (31 July 2010) I've added the source code for UnityMvcServiceLocator to the end of part 2.

Disclaimer

This blog post talks about ASP.NET MVC 3 Preview 1, 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.

Location: View Engines

This is a "multiply registered" style service introduced in MVC 1.0. A view engine is registered with theViewEngines.Engines static collection. Order is important with view engines, as the collection facade will ask each view engine in order to render the view; the first one that says it can render it will win.

With MVC 3, we have updated the ViewEngineCollection facade methods (FindView and FindPartialView) to look in both the static registered view engines as well as the view engines it finds in the service locator. To find those view engines, it calls MvcServiceLocator.Current.GetAllInstance<IViewEngine>().

The following is an example of registering a view engine, using Microsoft Unity as the dependency injection container:

MyViewEngine.cs

public class MyViewEngine : IViewEngine {

    // ... implementation of IViewEngine ...

}

Global.asax.cs

protected void Application_Start() {
    // ...

    var container = new UnityContainer();
    container.RegisterType<IViewEngine, MyViewEngine>("MyViewEngine");
    MvcServiceLocator.SetCurrent(new UnityMvcServiceLocator(container));

    // ...
}

Now when the system resolves views by call FindView and FindPartialView, it will use your view engine. Any view engines the system finds in the service locator will come before any view engines registered with the static registration function. If your container supports explicit ordering, the view engines will be used in the order returned by the service locator.

Location: View Pages

View engines are ultimately responsible for rendering their views. In the case of the WebForms and Razor view engines, this takes the form of classes which are automatically generated from the source of the view (for example, an .aspx file or a .cshtml file). These classes implicit inherit base classes that the view engine is dependent upon (ViewPage, ViewMasterPage, ViewUserControl, or WebViewPage).

Historically, these classes have not had access to dependency injection/service location functionality, because their creation was buried deep inside the implementation of the view engine. In MVC 3, we have updated the built-in view engines to attempt to create the view page classes via the service locator; if that fails, it will fall back to using Activator.CreateInstance, just like in previous versions of MVC.

Again using Unity as our example container, let's presume we have the following code:

InjectedViewPage.cs

using System.Web.Mvc;
using Microsoft.Practices.Unity;

public abstract class InjectedWebViewPage : WebViewPage {
    [Dependency]
    public IMathService MathService { get; set; }
}

InjectedView.cshtml

@inherits InjectedWebViewPage
@{
    LayoutPage = "../Shared/_Layout.cshtml";
    View.Title = "Home Page";
}
<p>The page says 4 + 8 is @MathService.Add(4, 8).</p>

This gives us a view page that has access to a service that's dynamically injected into the base class for the view page.

Note that we're using property setter injection here rather than constructor injection, as we'd shown in our previous samples. The reason is a fairly technical one that has to do with the way the ASP.NET page compilation system works when it's converting your markup into a class. If you're curious about the specific details, please contact me. The important takeaway is that supporting dependency injection here means using some form other than constructor injection. Our example uses Unity's [Dependency] attribute to get property setter injection; the specific type of injection and how you accomplish it will depend on which service locator you're using.

What's Next?

The next area of service location in MVC 3 that we'll cover is Filters.

posted @ 2011-01-25 17:41 四眼蒙面侠 阅读(112) 评论(0) 编辑

Important Update

We've made significant changes to the IoC support in ASP.NET MVC 3 Beta. Please read Part 5 for more information.

Controller Creation

The most common form of service location today in ASP.NET MVC is for controller creation. In MVC 1.0, we created an interface named IControllerFactory which is responsible for the location and creation of controllers. This interface was introduced with the explicit desire to support dependency injection of controllers.

Update: (31 July 2010) I've added the source code for UnityMvcServiceLocator to the end of this post.

Disclaimer

This blog post talks about ASP.NET MVC 3 Preview 1, 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.

Location: IControllerFactory

This is a "singly registered" style service introduced in MVC 1.0. The static registration point for this service is atControllerBuilder.Current.SetControllerFactory for non-DI users.

The logic in ControllerBuilder was updated to attempt to find IControllerFactory first by calling MvcServiceLocator.Current.GetInstance<IControllerFactory>(). If the service does not exist in the service locator, then it falls back to the static registration point. The default IControllerFactory remains DefaultControllerFactory.

The following is an example implementation of a controller factory, using Microsoft Unity as the dependency injection container:

using System;
using System.Web.Mvc;
using System.Web.Routing;
using Microsoft.Practices.Unity;

public class UnityControllerFactory : IControllerFactory {
    private IUnityContainer _container;
    private IControllerFactory _innerFactory;

    public UnityControllerFactory(IUnityContainer container)
        : this(container, new DefaultControllerFactory()) {
    }

    protected UnityControllerFactory(IUnityContainer container,
                                     IControllerFactory innerFactory) {
        _container = container;
        _innerFactory = innerFactory;
    }

    public IController CreateController(RequestContext requestContext,
                                        string controllerName) {
        try {
            return _container.Resolve<IController>(controllerName.ToLowerInvariant());
        }
        catch (Exception) {
            return _innerFactory.CreateController(requestContext, controllerName);
        }
    }

    public void ReleaseController(IController controller) {
        _container.Teardown(controller);
    }
}

It delegates creation of controllers to an inner controller factory when the container does not contain the controller in question (in this case, it uses the DefaultControllerFactory as its default inner controller factory). This allows us to register controllers by name, rather than matching the controller string to a type name:

CustomNamed.cs

public class CustomNamed : Controller {
    public ActionResult Index() {
        return View();
    }
}

Global.asax.cs

public class MvcApplication : HttpApplication {
    protected void Application_Start() {
        // ...

        var container = new UnityContainer();
        container.RegisterType<IController, CustomNamed>("admin");

        var factory = new UnityControllerFactory(container);
        ControllerBuilder.Current.SetControllerFactory(factory);

        // ...
    }
}

Because of our custom controller factory, now any time the URL has "admin" for its controller, we'll end up using an instance of the CustomNamed class. Because we have our own custom controller factory, we don't need to follow the default MVC conventions for controller classes.

In the example above, we used the existing static registration point for controller factories. We could also have written Application_Start() like this:

Global.asax.cs

public class MvcApplication : HttpApplication {
    protected void Application_Start() {
        // ...

        var container = new UnityContainer();
        var factory = new UnityControllerFactory(container);
        container.RegisterInstance<IControllerFactory>(factory);
        container.RegisterType<IController, CustomNamed>("admin");

        MvcServiceLocator.SetCurrent(new UnityMvcServiceLocator(container));

        // ...
    }
}

Now MVC is getting the controller factory from the service locator rather than the static registration point. Although we're still creating the factory by hand in the above example, we open up the possibility for letting the container create the controller factory for us, including getting dependency injection during creation for any services it might need to use.

Location: Controller instances

This is a new feature for MVC 3. The MVC framework (specifically, the DefaultControllerFactory class) has been updated to attempt to create all controller instances with the registered service locator. If the creation fails, then it will fall back to the pre-MVC 3 behavior of using Activator.CreateInstance.

Again using Unity as our example container, let's presume we have the following code:

IMathService.cs

public interface IMathService {
    int Add(int left, int right);
}

MathService.cs

public class MathService : IMathService {
    public int Add(int left, int right) {
        return left + right;
    }
}

HomeController.cs

using System.Web.Mvc;

public class HomeController : Controller {
    IMathService _mathService;

    public HomeController(IMathService mathService) {
        _mathService = mathService;
    }

    public ActionResult Index() {
        return View(_mathService.Add(3, 6));
    }
}

What we're created is a service interface, an implementation of that interface that we want to use at runtime, and a controller which consumes it.

The controller takes the dependency for IMathService on its constructor. It doesn't know what instance of IMathService it's going to get, and it doesn't really care. This kind of dependency injection is called "constructor injection", and it's a fairly common way to do DI.

Because we've cut the dependency between HomeController and MathService, we've made it easier to swap out which service we'll use, as well as making it easier to test HomeController now. During unit testing we can provide a mock of IMathService to the controller.

To complete the registration process so that everything gets wired up automatically at runtime, we'll create the Unity container and tell it "whenever anything asks for IMathService, give it an instance of MathService":

Global.asax.cs

protected void Application_Start() {
    // ...

    var container = new UnityContainer();
    container.RegisterType<IMathService, MathService>();
    MvcServiceLocator.SetCurrent(new UnityMvcServiceLocator(container));

    // ...
}

Like many DI frameworks, Unity doesn't require explicit configuration to make arbitrary objects. Constructor injection is automatically supported. We don't have a single line of configuration in the container which knows anything about HomeController. Unity will look at the request for building HomeController, see that its constructor requires an IMathService, and recursively determine how to build that. If, for instance, the MathService class itself required some service, Unity would continue to recursively resolve all the dependent services until it has satisfied them all.

What's Next?

The next area of service location in MVC 3 that we'll cover is View Engines and View Pages.

UnityMvcServiceLocator.cs

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Microsoft.Practices.Unity;

public class UnityMvcServiceLocator : IMvcServiceLocator {
    IUnityContainer _container;

    public UnityMvcServiceLocator(IUnityContainer container) {
        _container = container;
    }

    public IEnumerable<TService> GetAllInstances<TService>() {
        return _container.ResolveAll<TService>();
    }

    public IEnumerable<object> GetAllInstances(Type serviceType) {
        return _container.ResolveAll(serviceType);
    }

    public TService GetInstance<TService>() {
        return (TService)Resolve(typeof(TService));
    }

    public TService GetInstance<TService>(string key) {
        return (TService)Resolve(typeof(TService), key);
    }

    public object GetInstance(Type serviceType) {
        return Resolve(serviceType);
    }

    public object GetInstance(Type serviceType, string key) {
        return Resolve(serviceType, key);
    }

    public object GetService(Type serviceType) {
        return Resolve(serviceType);
    }

    public void Release(object instance) {
        _container.Teardown(instance);
    }

    private object Resolve(Type serviceType, string key = null) {
        try {
            return _container.Resolve(serviceType, key);
        }
        catch (Exception ex) {
            throw new ActivationException(ex.Message, ex);
        }
    }
}

posted @ 2011-01-25 17:36 四眼蒙面侠 阅读(115) 评论(0) 编辑

Important Update

We've made significant changes to the IoC support in ASP.NET MVC 3 Beta. Please read Part 5 for more information.

Introduction

One of the new features in ASP.NET MVC 3 is the ability to register a service locator that will be used by the framework. Prior versions of the MVC framework have offered opportunities for introducing concepts like service location and dependency injection (DI); in MVC 3, we have formalized the process and opened up several new opportunities for developers.

This first post in the series will discuss the general strategy for service location in MVC 3. Later posts will discuss specific ways to perform service location and DI with existing and new features.

Disclaimer

This blog post talks about ASP.NET MVC 3 Preview 1, 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.

General Strategy

The most important part of the strategy with service location is that it's going to be optional. This means that if you are not interested in working with a service locator, you won't be forced to. We will always offer a way to perform customization functions without requiring you to employ a service locator. We will also work to preserve backward compatibility as much as possible when introducing new service location capability into existing features of MVC.

When using the registered service locator, MVC will generally employ one of three strategies, depending on the kind of work it's trying to do.

1. Locating a singly registered service

There are several services which MVC uses today which have the ability to register a single instance of such a service. One example is the controller factory, which implements IControllerFactory. There is a single controller factory that's used for the entire application.

When MVC attempts to use a singly registered service, it will first ask the service locator whether it has an instance of that service available. If it does, then MVC will use that; if it does not, then it will fall back to the singleton registration that's available for non-service locator users.

The upside of this means that service locator users won't be required to fill their locator/container with all the existing default services used by MVC, because it will automatically fall back to these defaults when nothing exists in the locator. The potential downside of this means that it is theoretically possible to register a custom service in both places, but only the one in the locator will be used.

2. Locating a multiply registered service

There are several services which MVC uses today which have the ability to register multiple instances of such a service. One example of this is the view engine, which implements IViewEngine. Typically, MVC offers a registration point which acts like a list of such services, and also offers an API which acts as a facade and figures out the appropriate way to do the work. In the case of view engines, this facade (ViewEngines.Engines) takes the form of "go to each view engine on the list and ask it for a view until one of them can provide it". There are also multiply registered services where the facade uses all the services (ModelValidatorProviders.Providers) and aggregates all the responses together.

When MVC attempts to use a multiply registered service, it will continue to ask the collection facade to do the work. The collection facade will use both the statically registered instances of the service as well as any of the instances registered with the service locator, and combines them together in the way that is most appropriate for the facade. Where service order is important (as with view engines, for example), this usually means that the service locator instances will come before the statically registered instances.

Like the single registration strategy, the upside of this means that service locator users won't be required to fill their locator/container with the existing default service instances. The potential downside of this is that most containers don't offer a native ordering function for multi-registration of services, so where ordering is important, it may be necessary to use the non-service locator APIs. In practice, however, it probably won't be much of an issue, as most applications don't generally need to rely on ordering (for example, they will only primarily use a single view engine), especially since service locator services generally go before the existing pre-registered services.

3. Creating arbitrary objects

The final way that MVC may use a service locator is in the creation of arbitrary objects. (This is where we stray from the strictly service location functionality and use it more like a dependency injection container.) Where we've found it appropriate that an object we create may be in need of dependency injection of services, we will attempt to create that object through the service locator. One example is controller objects, which may want to take service dependencies to do their work.

When MVC attempts to create an arbitrary object in this way, it will ask the service locator to create the object on its behalf. If the service locator cannot fulfill this object creation, it will generally fall back to the existing behavior in prior versions of MVC 2; usually, this means calling Activator.CreateInstance.

IMvcServiceLocator and MvcServiceLocator

To enable service location in MVC, we've introduced a new interface (IMvcServiceLocator) and a new static singleton registration class (MvcServiceLocator). For Preview 1, we have also replicated the existing Common Service Locatorinterface (IServiceLocator) and exception class (ActivationException) in the System.Web.Mvc namespace.

public interface IServiceLocator : IServiceProvider {
    IEnumerable<TService> GetAllInstances<TService>();
    IEnumerable<object> GetAllInstances(Type serviceType);
    TService GetInstance<TService>();
    TService GetInstance<TService>(string key);
    object GetInstance(Type serviceType);
    object GetInstance(Type serviceType, string key);
}

public interface IMvcServiceLocator : IServiceLocator {
    void Release(object instance);
}

public static class MvcServiceLocator {
    public static IMvcServiceLocator Current { get; }
    public static IMvcServiceLocator Default { get; }
    public static void SetCurrent(IServiceLocator locator);
}

The registration point is MvcServiceLocator.SetCurrent(), and only requires the service locator to support IServiceLocator. However, you'll notice that MvcServiceLocator.Current always returns an implementation of IMvcServiceLocator. We will automatically create the implementation of IMvcServiceLocator.Release() if one does not exist, which will call Dispose on the object if it implements IDisposable.

It's also worth noting that there will always be a service locator available, even if you've never registered one. Our default service locator is not a full fledged service locator, as it only calls Activator.CreateInstance and offers no registration system. It is not meant to be a replacement for a real service locator or dependency injection container.

No Configuration

MVC's usage of service location is limited to retrieving services. MVC has no need to configure the existing service locator. As such, we have made no effort to hide the configuration/registration process inherent to existing service locators or DI containers.

We feel that most people will choose the container they want to use based on its registration and configuration APIs, so attempting to hide them would be counter productive.

Complex Dependency Injection Needs

A common question might be why we chose to use Common Service Locator, when many container offer the ability to have extremely complex dependency injection systems (for example, nested containers for lifetime management tied to request lifetime).

Whenever possible, we will continue to offer factory-style services to allow complex dependency injection needs. For example, we will continue to offer IControllerFactory as a service, even though most users will be able to just registered their service locator without need to provide a custom controller factory any more. If you do need complex lifetime management, though, you can still opt to provide a controller factory which gives the flexibility to open up those complex creation options.

Open Question: To Common Service Locator Or Not?

For Preview 1, we decided not to ship the actual Common Service Locator (CSL) DLL. This decision was driven primarily by schedule.

We are currently debating internally as to whether or not to take the dependency on CSL. The advantage would be that container users would be able to leverage the existing implementations of CSL that most dependency injection containers come with (although they would need to add the implementation of IMvcServiceLocator, if desired). The disadvantage is that it creates a runtime dependency on an additional DLL that is not likely to be on the developer's machine or on the server, since CSL is not part of the .NET Framework. This slightly complicates the bin deployment story for MVC by adding a new DLL to remember to deploy, which would need to be in place whether the user uses a service locator or not.

Additionally, there is the matter of CSL's lack of the Release method, which we added to IMvcServiceLocator, and the matter of the mandate that CSL implementations throw exceptions rather than returning null when you cannot find an implementation of a specific service, which could cause real runtime performance issues.

What are your thoughts? Should we include a reference to the CSL, or is replicating the interface good enough? Should we attempt to work out a way to support both, thereby making the CSL reference optional and only necessary when the developer uses the CSL?

What's Next?

The first area of service location in MVC 3 that we'll cover is Controllers.

posted @ 2011-01-25 17:34 四眼蒙面侠 阅读(137) 评论(0) 编辑