在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;
}
}
}