在WebAPI中自动创建Controller


在MIS系统中,大部分的操作都是基本的CRUD,并且这样的Controller非常多。

为了复用代码,我们常常写一个泛型的基类。

    public class EntityController<T> : ApiController
    {
        public IQueryable<T> GetAll()
        {
            ...
        }

        public T Get(int id)
        {
            ...
        }

        public T Put(int id, Ink ink)
        {
            ...
        }

        public T Post(Ink ink)
        {
            ...
        }

        public void Delete(int id)
        {
            ...
        }
    }

当增加一种类型的时候,我们需要手动增加一个子类:

    public class InkController : EntityController<Ink>
    {
    }

    public class PenController : EntityController<Pen>
    {
    }

当实体模型比较多的时候仍然就存在繁琐和难以维护的问题。因此我们也需要一种自动创建的机制,

要实现自动创建Controller,首先得把现在这种静态创建Controller的方式改成动态创建的方式。在WebAPI中,我们可以通过替换IHttpControllerSelector来实现动态创建Controller。

首先我们实现自己的IHttpControllerSelector

    public class MyControllerSelector : DefaultHttpControllerSelector
    {
        private readonly HttpConfiguration _configuration;

        public MyControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            var controllerName = base.GetControllerName(request);
            return new HttpControllerDescriptor(_configuration, controllerName, typeof(Controllers.EntityController<Ink>));
        }
    }

然后在初始化函数中注册我们的ControllerSelector

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
            // Web API configuration and services
            
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new MyControllerSelector(GlobalConfiguration.Configuration));

            // Web API routes
            config.MapHttpAttributeRoutes();
            ……
}
}

这样一来,即使没有子类InkController,我们同样可以特化泛型控制器EntityController<Ink>实现同样的效果。

到这一步后,还存在一个问题:ControllerSelector只能根据HttpRequest获取ControllerName,并不知道其对应的Model,不知道该如何特化EntityController。解决这个问题的常见的方式就是维护一张Controller名称和实体类型的映射表:

    public class MyControllerSelector : DefaultHttpControllerSelector
    {
        static Dictionary<string, Type> _entityMap;
        static MyControllerSelector()
        {
            _entityMap = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);

            _entityMap["Ink"] = typeof(Ink);
            _entityMap["Pen"] = typeof(Pen);
        }

        private readonly HttpConfiguration _configuration;

        public MyControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            _configuration = configuration;
        }


        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            var controllerName = base.GetControllerName(request);

            Type entityType = null;
            if (!_entityMap.TryGetValue(controllerName.ToLower(), out entityType))
            {
                return base.SelectController(request);
            }

            return new HttpControllerDescriptor(_configuration, controllerName, typeof(Controllers.EntityController<>).MakeGenericType(entityType));
        }
    }

虽然这样做本身没有什么问题。这种手动维护Controller列表的方式仍然无法达到自动创建Controller的要求,因此我们还需要一种自动生成这种映射表的机制。这里我仍然是采用同前文一样的Attribute+反射的方式。

首先写一个ControllerAttribute,

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public class ControllerAttribute : Attribute
    {
        public string Name { get; private set; }

        public ControllerAttribute(string name)
        {
            this.Name = name;
        }
    }

然后,在数据模型中标记该Attribute

    [Controller("Pen")]
    public class Pen

 

    [Controller("Ink")]
    public class Ink

最后,根据反射建立Controller名称和类型的关联关系

    static Dictionary<string, Type> _entityMap;
    static MyControllerSelector()
    {
        var assembly = typeof(MyControllerSelector).Assembly;

        var entityTypes = from type in assembly.GetTypes()
                            let controllerAtt = type.GetCustomAttribute<ControllerAttribute>()
                            where controllerAtt != null
                            select new { Type = type, ControllerName = controllerAtt.Name };

        _entityMap = entityTypes.ToDictionary(i => i.ControllerName, i => i.Type, StringComparer.OrdinalIgnoreCase);
    }

这样基本上就可以用了。最后顺手做一下优化,减少的HttpControllerDescriptor创建操作,最终版本的ControllerSelector如下。

    public class MyControllerSelector : DefaultHttpControllerSelector
    {
        private Dictionary<string, HttpControllerDescriptor> _controllerMap;

        public MyControllerSelector(HttpConfiguration configuration)
            : base(configuration)
        {
            var entityTypes = from type in typeof(MyControllerSelector).Assembly.GetTypes()
                             let controllerAtt = type.GetCustomAttribute<ControllerAttribute>()
                             where controllerAtt != null
                             select new { Type = type, ControllerName = controllerAtt.Name };

            _controllerMap = entityTypes.ToDictionary(
                                i => i.ControllerName,
                                i => new HttpControllerDescriptor(configuration, i.ControllerName,
                                        typeof(Controllers.EntityController<>).MakeGenericType(i.Type)),
                                StringComparer.OrdinalIgnoreCase);
        }

        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            HttpControllerDescriptor controllerDescriptor = null;
            if (!_controllerMap.TryGetValue(base.GetControllerName(request), out controllerDescriptor))
            {
                return base.SelectController(request);
            }
            else
            {
                return controllerDescriptor;
            }
        }
    }


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM