自定义的ControllerFactory:接口实现,支持Area

开发 后端
作者编写了一个简单的ControllerFactory来替换掉默认的DefaultControllerFactory,这个ControllerFactory可以实现Area功能。

几个星期之前,有个朋友对我说,他的项目中需要将前后台区分开来,也就是类似分Area的功能。不过Area只在MVC 2中出现,因此现在想在1.0版本中先实现类似的功能了。他打算,根据Route中捕获的内容(如“area”),然后去找对应命名空间下的Controller。这样看来不难,似乎只要在Route上做点配置,而默认的DefaultControllerFactory已经对命名空间的查询提供支持了(可惜有线程安全的问题)。

不过他说,***发现似乎这块功能不是他想象的那样,因此希望我可以看看到底是什么问题。由于当时没有扩展ASP.NET MVC的需求,后来我事情一多就忘了,现在先说声抱歉。最近开始对ASP.NET MVC动手动脚了,发现这样一个Area的功能非常有用,而且巧合的是,我也打算把Area和命名空间对应起来。

只是我选择的路和那位兄弟不一样,我打算自己写一个简单的ControllerFactory来替换掉默认的DefaultControllerFactory。这么做的主要原因是:我不知道DefaultControllerFactory已经提供对命名空间的支持了,微软默默地实现了却没有对外公开过,我也是后来阅读代码时才发现的。同时又意识到线程安全的问题,于是就还是打算自己写了。

好在ASP.NET MVC从设计之初就提供了扩展的能力,每个组件粒度都很小,大部分组件都是可以独立拔插的(Controller类除外,如果你使用自己的IController实现,就会发现大部分功能,如各Filter都失效了)。而要实现一个Controller Factory,只要实现一个简单的IControllerFactory就可以了(我喜欢接口):

public interface IControllerFactory  
{  
    IController CreateController(RequestContext requestContext, string controllerName);  
    void ReleaseController(IController controller);  

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

于是构建一个AreaControllerFactory也大致只需要以下一些代码:

public class AreaControllerFactory : IControllerFactory  
{  
    public IController CreateController(RequestContext requestContext, string controllerName)  
    {  
        ...  
    }  
 
    public void ReleaseController(IController controller)  
    {  
        IDisposable disposable = controller as IDisposable;  
        if (disposable != null)  
        {  
            disposable.Dispose();  
        }  
    }  
}  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

然后按照惯例,还是一步步谈起。首先是构造函数,我们的策略是根据不同的Area加载不同命名空间下的Controller类型。方便起见,我选择“基础命名空间”和“扩展部分”两块,它们从构造函数中传入:

private Dictionary<stringstring> m_areaPartMapping = new Dictionary<stringstring>();  
 
public string NamespaceBase { getprivate set; }  
 
public AreaControllerFactory(string namespaceBase)  
    : this(namespaceBase, null)  
{ }  
 
public AreaControllerFactory(string namespaceBase, IDictionary<stringstring> areaPartMapping)  
{  
    this.NamespaceBase = namespaceBase.EndsWith(".") ? namespaceBase : namespaceBase + ".";  
 
    if (areaPartMapping != null)  
    {  
        foreach (var pair in areaPartMapping)  
        {  
            this.m_areaPartMapping.Add(pair.Key.ToLowerInvariant(), pair.Value);  
        }  
    }  
}  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

于是我们就可以这样使用:

var controllerFactory = new AreaControllerFactory("MyApp.Controllers");  
ControllerBuilder.Current.SetControllerFactory(controllerFactory);  
  • 1.
  • 2.

如果在需要的时候,还可以指定Area与特定命名空间“部分”的映射关系。因此,我们需要从Area来获取这个“Part”:

private string GetNamespacePart(string area)  
{  
    if (String.IsNullOrEmpty(area)) return "";  
 
    string part;  
    if (this.m_areaPartMapping.TryGetValue(area.ToLowerInvariant(), out part))  
    {  
        return part;  
    }  
 
    return area;  
}  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

这里我选择“配置”和“约定”相结合的方式。得到一个Area之后,我们会在映射表里进行查找Part,如果没有,则Area本身便是Part。根据Part和Controller名称,我们便可以获得Controller的类型:

private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim();  
private Dictionary<string, Type> m_controllerTypes = new Dictionary<string, Type>();  
 
private Type GetControllerType(string area, string controllerName)  
{  
    string part = this.GetNamespacePart(area);  
 
    string typeName = String.IsNullOrEmpty(part) ?  
        this.NamespaceBase + controllerName.ToLowerInvariant() + "Controller" :  
        this.NamespaceBase + part + "." + controllerName.ToLowerInvariant() + "Controller";  
 
    Type type;  
 
    this.m_rwLock.EnterReadLock();  
    try 
    {  
        if (this.m_controllerTypes.TryGetValue(typeName, out type))  
        {  
            return type;  
        }  
    }  
    finally 
    {  
        this.m_rwLock.ExitReadLock();  
    }  
 
    type = Type.GetType(typeName, falsetrue);  
 
    if (type != null)  
    {  
        this.m_rwLock.EnterWriteLock();  
        try 
        {  
            this.m_controllerTypes[typeName] = type;  
        }  
        finally 
        {  
            this.m_rwLock.ExitWriteLock();  
        }  
    }  
 
    return type;  
}  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.

由于我选择在应用程序中使用同一个AreaControllerFactory对象,因此线程安全是一定要有保证的。这里我们用到了读写锁,不过请注意,红色那句话并不保证对于每个相同的typeName只执行一次,也不保证相同的typeName对于m_controllerTypes字典只会进行一次写操作(所以我没有Add,而是使用了下标操作)。不过,由于这些“重复”不会造成问题,因此就没有去涉及太多这方面的考虑。

***,便是那CreateControlle方法:

public IController CreateController(RequestContext requestContext, string controllerName)  
{  
    Type controllerType;  
    object area;  
    if (requestContext.RouteData.Values.TryGetValue("area"out area))  
    {  
        controllerType = this.GetControllerType(area.ToString(), controllerName);  
    }  
    else 
    {  
        controllerType = this.GetControllerType(null, controllerName);  
    }  
 
    if (controllerType == null)  
    {  
        throw new HttpException(404,  
            String.Format(  
                "Controller of path {0} not found.",  
                requestContext.HttpContext.Request.Path));  
    }  
 
    try 
    {  
        return (IController)Activator.CreateInstance(controllerType);  
    }  
    catch (Exception ex)  
    {  
        string message = String.Format("Error creating controller {0}" + controllerType);  
        throw new InvalidOperationException(message, ex);  
    }  
}  
 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

似乎没有什么可谈的:我们从RouteData中获取出area对应的值,并且调用GetControllerType方法获得Controller的类型,并使用Activator.CreateInstance创建对象。在不合法的情况下,抛出合适的异常即可。

至此,AreaControllerFactory就完成了,很容易,不是吗?很显然,这个组件的功能非常有限,例如为什么所有的Controller一定要在同一个命名空间下?没错,它其实只是符合“我要求”的一个东西。但是,在项目中很多东西都是如此,我只实现我够用的功能。例如,我可能不会向对外公开的API那样,严格检查每个问题,抛出严谨的异常。我可能倾向于在项目中使用接口,而不是使用抽象类。因为是我的项目,我可以快速反馈,需要修改的时候就修改吧。

同样的,如果DefaultControllerFactory真在某些特别情况下有问题,或者支持的有些复杂。那么不如我们就自己动手吧。一次性投入,而且这样的小组件也花不了多少时间。

本文来自老赵点滴:《支持Area的ControllerFactory》

【编辑推荐】

  1. ASP.NET Routing之“解析URL”功能详解
  2. 为ASP.NET MVC应用添加自定义路由
  3. 学习ASP.NET MVC路由的使用方法
  4. 浅析ASP.NET中的URL Rewrite
  5. 浅谈ASP.NET MVC框架
责任编辑:yangsai 来源: 老赵点滴
相关推荐

2015-02-12 15:33:43

微信SDK

2015-02-12 15:38:26

微信SDK

2017-08-22 16:40:22

前端JavaScript接口

2022-05-18 07:44:13

自定义菜单前端

2009-09-07 22:00:15

LINQ自定义

2009-07-06 17:36:06

ResultSetJDBC Connec

2024-04-03 09:18:03

Redis数据结构接口防刷

2024-08-26 11:13:26

字典entry自定义

2009-07-06 16:20:50

JSP自定义标签

2013-01-09 17:22:38

Android开发Camera

2015-07-29 10:31:16

Java缓存算法

2009-09-03 13:34:03

.NET自定义控件

2009-06-17 16:00:03

Hibernate自定

2022-03-01 16:09:06

OpenHarmon鸿蒙单选组件

2023-01-03 07:40:27

自定义滑块组件

2022-04-01 15:59:22

SQLPostgreSQL审计

2023-10-24 13:48:50

自定义注解举值验证

2022-12-07 08:56:27

SpringMVC核心组件

2009-08-04 13:07:46

C#自定义快捷键

2022-02-17 07:10:39

Nest自定义注解
点赞
收藏

51CTO技术栈公众号