在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