代码改变世界

MVC如何分离Controller与View在不同的项目

2011-08-01 17:20  Kevin-wang  阅读(508)  评论(0编辑  收藏  举报

A. 提出问题

First of all, it is certainly a good idea to put your model into a separate project. As you've discovered, this is trivial.

Regarding Controllers and Views, I don't see any obvious advantage to separating them for most basic projects, although you may have a particular need to do so in a particular application.

If you do choose to do this, then you will need to tell the framework how to find your controllers. The basic way to do this is by supplying your own ControllerFactory. You can take a look at the source code for the DefaultControllerFactory to get an idea for how this is done. Subtyping this class and overriding the GetControllerType(string controllerName) method may be enough to accomplish what you're asking.

Once you've created your own custom ControllerFactory, you add the following line to Application_Start in global.asax to tell the framework where to find it:

ControllerBuilder.Current.SetControllerFactory(new MyControllerFactory()); 

Update: Read this post and the posts it links to for more info. See also Phil Haack's comment on that post about:

ControllerBuilder.Current.DefaultNamespaces.Add( 
    "ExternalAssembly.Controllers");

...which is not a complete solution, but possibly good enough for simple cases.

B. 第一种解决方案--指定其它控制器的命名空间

How to call controllers in external assemblies in an ASP.NET MVC application

If your ASP.NET MVC is growing large, it’s likely that you are partitioning your controllers in different namespaces, or maybe even in different assemblies, and it might happen that you have controllers with the same in different namespaces.

Phil Haack and Steve Sanderson wrote some great write-ups on how to partition an ASP.NET application down into Areas, a concept that exists in MonoRail but not in the core ASP.NET MVC framework. The two posts above allow grouping both the controllers and the views, so if you want a complete solution to the aforementioned problem make sure you read them.

What I want to point out here is that both the two approaches above are based on a hidden feature of the framework and the way the ControllerFactory looks for controller class. So it can be used in your own hand-made “area grouping” solution.

Let’s start directly with the solution: if you add to a route a datatoken named “namespaces” with a list of namespaces, the controller factory will look in these namespaces.

Route externalBlogRoute = new Route(
"blog/{controller}/{action}/{id}",
new MvcRouteHandler()
);

externalBlogRoute.DataTokens = new RouteValueDictionary(
new
{
namespaces = new[] { "ExternalAssembly.Controllers" }
});

routes.Add("BlogRoute", externalBlogRoute);

With the MapRoute helper method the things are easier since one of the many overloads allows you to specify directly an array of strings:

routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" },
new[] { "ExternalAssembly.Controllers" }
);

This is easier than having to remember the name of the datatoken key.

The default controller factory already takes into account the namespaces datatoken. In fact it follows this flow:

  1. Look in the namespaces provided inside the route
  2. if not found, then will look into all the namespaces

This is useful if you have many controllers with the same name in different namespaces: by specifying the namespace in the route definition, you are limiting where the framework will look for the controller.

As last point, if the namespace is in an external assembly, you have to add it as reference of the ASP.NET MVC web application project, otherwise it will not probe it.

B. 第二种解决方案——指定程序集和命名空间(自定义控制器工厂)

ASP.NET MVC - Developing Custom ControllerFactory

ASP.NET MVC Framework has been around for quite sometime now.It has now moved to BETA from it's initial CTP versions.I am a very big fan of this framework for it's simplicity and extensibility.For the next few posts I would like discuss about the various extensibility points of this framework.In this post I will note down my observations about implementing a custom controller factory.

When adding a controller to a ASP.NET MVC Web application we see that the Controller name must end with "Controller" and it is recommended that Controller classes to be put inside a subfolder named "Controllers" in the web application directory.Now think of a situation where I have many controller classes and for better maintainability I need to put them into separate assemblies.In addition to that I need to load the controller classes in a configurable manner based on controller name in the URI.

This can be done by implementing a custom controller factory to instantiate the right controller class based on config settings.To do this we need to implement the interface System.Web.Mvc.IControllerFactory .The two methods in this interface are

  1. System.Web.Mvc.IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) - To create the controller instance based on RequestContext and controller name
  2. void ReleaseController(System.Web.Mvc.IController controller) - Release the controller instance

Following is the sample code with very rudimentary implementation:

public class CustomControllerFactory : IControllerFactory
{

    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {

        string controllerType = string.Empty;
        IController controller = null;

        //Read Controller Class & Assembly Name from Web.Config
        controllerType = ConfigurationManager.AppSettings[controllerName];

        if (controllerType == null) throw new ConfigurationErrorsException("Assembly not configured for controller " + controllerName);

        //Create Controller Instance

        controller = Activator.CreateInstance(Type.GetType(controllerType)) as IController;
        return controller;

    }

    public void ReleaseController(IController controller)
    {
        //This is a sample implementation
        //If pooling is used write code to return the object to pool
        if (controller is IDisposable)
        {
            (controller as IDisposable).Dispose();

        }
        controller = null;
    }

}

Now the question comes how do we integrate this ControllerFactory with the application.This can be done by setting the Controller factory using System.Web.Mvc.ControllerBuilder in Application_Start event of Global.asax.cs

protected void Application_Start()
{
           RegisterRoutes(RouteTable.Routes);
           ControllerBuilder.Current.SetControllerFactory(typeof(SB.Web.Mvc.CustomControllerFactory));
  }

Lastly we need to add the config entries for the Controller classes and assemblies in Web.config as shown below:

<appSettings>
  <add key="Home" value="SB.Controllers.HomeController, SB.Controllers"/>
</appSettings>

We can enhance this further by adding features like pooling,instancing mode (Singleton) etc.

In my next post I will discuss about my observations about the extension points related to Views.