在.NET MVC框架中,经常会遇到需要扩展现有控制器功能的情况。例如,可能需要在主MVC应用程序之外的项目或程序集中添加额外的控制器动作。本文将介绍如何实现这种扩展,包括继承、路由、构建事件等关键步骤。
假设有两个项目:
目标是在ProjectA中的现有控制器上添加额外的控制器动作或功能。这些额外的动作不会在主应用程序中出现,而是来自不同的项目/程序集。这在实现特定客户端功能时非常有用,例如,某个客户端可能需要特定的行为,而这些行为在主应用程序中可能并不相关。
在ProjectA中,控制器定义可能如下所示:
namespace ProjectA.Controllers
{
public class FooController : ApplicationController
{
public ActionResult Index()
{
return Content("Foo");
}
}
}
而在ProjectB中,可能有一个类似的类:
namespace ProjectB.Controllers
{
public class CustomFooController : FooController
{
public ActionResult Bar()
{
return Content("Bar");
}
}
}
希望所有不同的客户端都能访问Foo,但可能只有特定的客户端才能访问Foo/Bar。
这个过程需要执行几个不同的步骤,以确保一切按预期工作,具体如下:
如果确实想要扩展现有控制器的功能,那么继承可能是一个好方法。从主应用程序中的控制器继承将允许利用任何现有的属性、重写的方法或可能已经存在的底层基控制器。
只需要在ProjectB中添加对ProjectA项目的引用,然后定位希望继承的适当控制器:
using ProjectA.Controllers;
namespace ProjectB.Controllers
{
public class CustomFooController : FooController
{
public ActionResult Bar()
{
return Content("Bar");
}
}
}
这样做在路由方面可能会有些棘手。当尝试在当前应用程序中创建这个新控制器时,它会尝试使用该应用程序中定义的现有路由,这可能导致命名冲突和歧义,如果不小心的话。
根据提供的代码,这意味着可以轻松访问/CustomFoo/Bar,但可能无法像希望的那样访问/Foo/Bar。幸运的是,属性路由可以帮助解决这个问题。
简单地为自定义控制器动作添加一个[Route]属性,指明希望如何访问它:
[Route("Foo/Bar")]
public ActionResult Bar()
{
return new Content("Bar");
}
这将让MVC知道在查看其他一些路由之前,将这个动作映射到Foo/Bar URL。
为了使属性路由按预期工作,需要确保在主应用程序的RouteConfig.cs文件中调用MapMvcAttributeRoutes()方法:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
}
注意:如果正在扩展MVC区域中的控制器,将需要在AreaRegistration.cs文件中的RegisterArea()方法内做同样的事情:
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.MapMvcAttributeRoutes();
}
在主应用程序中,还需要进行一个额外的更改,即优先考虑其自己的命名空间中的路由。MapRoute()方法支持一个重载,可以通过namespaces参数来处理这个问题。
将namespaces参数设置为指向希望优先考虑的命名空间,即主应用程序中的命名空间(例如"ProjectA.Controllers")。
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Foo", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "ProjectA.Controllers" }
);
到目前为止,从代码的角度来看,一切都应该已经就绪。剩下的唯一事情就是将ProjectB中的代码放入ProjectA中,以便可以运行它。
处理和配置整个过程有多种方法,但一个非常简单的方法可能是通过构建事件。构建事件允许在构建过程的各个阶段执行一些代码。
感兴趣的是定义一个Post-Build事件,将ProjectB.dll文件移动到ProjectA的bin目录中,可能如下所示:
xcopy /E /Y /S "$(ProjectName).dll" "$(SolutionDir)\ProjectA\Bin\"
这将在ProjectB > Properties > Build Events > Post-Build Event Command Line下定义,如下所示: