前言
寫這篇WebApi Controller分類一是把Contrller分類的解決方法說一說,再順便把遇到的問題和解決方法聊一聊。 說實話第一次在項目中使用WebApi感覺非常的不順手。
遇到的問題和解決方法
1、多個Get方法。
Get請求方式是用的最多,而且多處需要重載,很好解決,只需根據參數重載方法即可,如果比較懶的話連路由規則都不需要定義了,直接Get?a=XXX&b=XXX。
2、多個Post方法。
如果按照WebApi官方的規則,那么一個WebApi是不能包含多個Post請求的, 至今我也沒找到類似Get重載處理Post重載一樣的方法,后來我發現有人建議直接用Action
名稱區分(eg. InsertPerson InsertProduct),然后添加HttpPost屬性,在前台直接調用,那么我考慮如果這樣用的話是不是又違背Restful的設計思想呢?
3、當遇到Delete請求時調用Delete接口始終沒有調成功。
一開始我定義Delete接口讓前端調用(ajax發送delete請求),一直報405,后來查了一把,說是需要Web.config配置,又是開啟IIS DELETE和PUT請求,幾種方法全試了最后還是沒搞定,無奈之下我改用了POST請求, PUT請求壓根沒有用。 在群里詢問了一把原來他們是在cs中用HttpClient中調用。
4、Post多個參數。
官方建議是使用FromBody標簽,多個參數用Dto封裝。 自定義Post參數綁定類,繼承HttpParameterBinding。然后在Application_Start()中注冊:GlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, SimplePostVariableParameterBinding.HookupParameterBinding);
Controller分類
Controller分類主要是為了解決項目過大以后Controller只有一級編目,項目結構上會顯的非常凌亂,而且會遇到 ApiController文件同名無法解決的問題,提起Controller分類的話大家首先會想到Area, 但實際應用你會發現那真的是為MVC量身打造的,絲毫沒有提WebApi考慮,比如新建Area時文件夾會自動創建Controller、Model、 View, 這些都是不需要的。 那么解決方法就是自己根據項目業務邏輯需要創建Controller編目,最重要的就是自定義Controller Select讓WebApi框架找到你定義的文件結構和ApiController【后面會附帶代碼】。
1、這是我Demo里的Controller結構,包含一個一級編目和二級編目(怎么樣,看起來結構一下就清晰多了?)

2、我把用到的Url匯總成一張表格
| Controller名稱 | 命名空間 | url |
| AdviseController | MvcApplication4.Controllers.ContractUs | /apix/ContactUs/Advise |
| ProductController | MvcApplication4.Controllers.ContractUs | /apix/ContactUs/Product |
| FinancialController | MvcApplication4.Controllers.Products.Enterprise | /apixx/Products/Enterprise/Financial |
| OfficeController | MvcApplication4.Controllers.Products.Enterprise | /apixx/Products/Enterprise/Office |
| PuzController | MvcApplication4.Controllers.Products.Game | /apixx/Products/Game/Puz |
| RpzController | MvcApplication4.Controllers.Products.Game | /apixx/Products/Game/Rpg |
url里包含前綴apix和apixx,參考博客園一朋友的寫法,另外我發現,如果一級編目和二級編目里的前綴如果寫成一樣是行不通的,會報No Controller was selected to handle this request,所以你需要根據你的編目結構不同而指定不同的前綴,url后就是Area和Category規則。
3、創建完Controller編目結構后就是創建路由規則了(重要)
在這里指定你的接口前綴 apix,apixx
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//只有一級編目
config.Routes.MapHttpRoute(
name:"AreaApi",
routeTemplate:"apix/{area}/{controller}/{id}",
defaults:new {id = RouteParameter.Optional});
//包含二級編目
config.Routes.MapHttpRoute(
name: "AreaCategoryApi",
routeTemplate: "apix/{area}/{category}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });
4、指定完路由規則,如果讓WebApi認識你的接口呢? 自定義Controller Selector,繼承自DefaultHttpControllerSelector, 對了,創建完自定義Controller Selector后別忘了在Application_Start()中注冊,否則也是無效的:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector),new ClassifiedHttpControllerSelector(GlobalConfiguration.Configuration));
private const string AREA_ROUTE_VARIABLE_NAME = "area";
private const string CATEGORY_ROUTE_VARIABLE_NAME = "category";
private const string THE_FIX_CONTROLLER_FOLDER_NAME = "Controllers";
private readonly HttpConfiguration m_configuration;
private readonly Lazy<ConcurrentDictionary<string, Type>> m_apiControllerTypes;
public ClassifiedHttpControllerSelector(HttpConfiguration configuration)
: base(configuration)
{
m_configuration = configuration;
m_apiControllerTypes = new Lazy<ConcurrentDictionary<string, Type>>(GetAllControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
return GetApiController(request);
}
private static string GetRouteValueByName(HttpRequestMessage request, string strRouteName)
{
IHttpRouteData data = request.GetRouteData();
if (data.Values.ContainsKey(strRouteName))
{
return data.Values[strRouteName] as string;
}
return null;
}
private static ConcurrentDictionary<string, Type> GetAllControllerTypes()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
Dictionary<string, Type> types = assemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && t.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase) && typeof(IHttpController).IsAssignableFrom(t))).ToDictionary(t => t.FullName, t => t);
return new ConcurrentDictionary<string, Type>(types);
}
private HttpControllerDescriptor GetApiController(HttpRequestMessage request)
{
string strAreaName = GetRouteValueByName(request, AREA_ROUTE_VARIABLE_NAME);
string strCategoryName = GetRouteValueByName(request, CATEGORY_ROUTE_VARIABLE_NAME);
string strControllerName = GetControllerName(request);
Type type;
try
{
type = GetControllerType(strAreaName, strCategoryName, strControllerName);
}
catch (Exception)
{
return null;
}
return new HttpControllerDescriptor(m_configuration, strControllerName, type);
}
private Type GetControllerType(string areaName, string categoryName, string controllerName)
{
IEnumerable<KeyValuePair<string, Type>> query = m_apiControllerTypes.Value.AsEnumerable();
string strControllerSearchingName;
if (string.IsNullOrEmpty(areaName))
{
strControllerSearchingName = THE_FIX_CONTROLLER_FOLDER_NAME + "." + controllerName;
}
else
{
if (string.IsNullOrEmpty(categoryName))
{
strControllerSearchingName = THE_FIX_CONTROLLER_FOLDER_NAME + "." + areaName + "." + controllerName;
}
else
{
strControllerSearchingName = THE_FIX_CONTROLLER_FOLDER_NAME + "." + areaName + "." + categoryName + "." + controllerName;
}
}
return query.Where(x => x.Key.IndexOf(strControllerSearchingName, StringComparison.OrdinalIgnoreCase) != -1).Select(x => x.Value).Single();
}
總結
找到了解決方法后,接下來就是重構項目了,對於Delete和Put請求一直還是想解決,還請大家指點指點。 代碼下載 http://pan.baidu.com/s/1bnHQItx
參考資料
1、http://www.cnblogs.com/guogangj/archive/2013/03/11/2950084.html
2、http://blogs.infosupport.com/asp-net-mvc-4-rc-getting-webapi-and-areas-to-play-nicely/
