如何改寫(xiě)WebApi部分默認(rèn)規(guī)則
為什么要改
最近公司在推廣SOA框架,第一次正經(jīng)接觸這種技術(shù)(之前也有但還是忽略掉吧),感覺(jué)挺好,就想自己也折騰一下,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的SOA框架
用過(guò)mvc進(jìn)行開(kāi)發(fā),印象之中WebApi和Mvc好像是一樣的,帶著這樣的預(yù)設(shè)開(kāi)始玩WebApi,然后被虐得找不到著北。
被虐的原因,是Mvc和WebApi在細(xì)節(jié)上差別還是有點(diǎn)大,例如:
- 在Mvc中,一個(gè)Controller中的所有公共方法一般情況下可以響應(yīng)POST方法,而WebApi中不行
 - 在Mvc中,一個(gè)Action方法中的參數(shù)即可來(lái)自Url,也可以來(lái)自Form,而WebApi中不是這樣,具體的規(guī)則好像是除非你在參數(shù)中加了[FromBody],否則這個(gè)參數(shù)永遠(yuǎn)也無(wú)法從Form中獲取
 
這是這兩種技術(shù)我知道的最大的差別,其他的沒(méi)發(fā)現(xiàn)或者說(shuō)是沒(méi)注意,也有可能這些差別是因?yàn)槲也粫?huì)用,畢竟接觸WebApi時(shí)間不長(zhǎng)。如果我有些地方說(shuō)錯(cuò)了,請(qǐng)指正。
就這兩個(gè)不同點(diǎn),我查了很多資料,也沒(méi)有辦法解決,第一個(gè)還好,加個(gè)特性就行了,第二個(gè)的話好像就算加了[FromBody]也還是不行,感覺(jué)就是一堆限制。接著,既然這么多讓我不爽的地方,那我就來(lái)改造它吧。
改造的目標(biāo),有以下幾個(gè):
- 不再限制控制器必須以Controller結(jié)尾,其實(shí)這個(gè)并不是必須,只是被限制著確實(shí)不太舒服
 - 所有方法可以響應(yīng)所有的請(qǐng)求方法,如果存在方法名相同的方法,那么才需要特性來(lái)區(qū)分
 - Action中的參數(shù)優(yōu)先從Url中獲取,再?gòu)腂ody中獲取,從Body中獲取的時(shí)候,優(yōu)先假設(shè)Body中的數(shù)據(jù)是表單參數(shù),若不是則將Body中的數(shù)據(jù)當(dāng)作json或xml數(shù)據(jù)進(jìn)行獲取
 
定下了目標(biāo)之后,感覺(jué)微軟為什么要這樣設(shè)計(jì)WebApi呢,或許它有它的道理。
目標(biāo)好定,做起來(lái)真是頭大,一開(kāi)始想?yún)⒖脊镜腟OA框架的實(shí)現(xiàn),但因?yàn)槲矣昧薕WIN技術(shù)來(lái)進(jìn)行宿主,而看了公司的框架好像不是用的這個(gè),總之就是看了半天沒(méi)看懂應(yīng)該從哪個(gè)地方開(kāi)始,反而是越看越糊,畢竟不是完全一樣的技術(shù),所以還是自己弄吧。
OK,廢話了這么多,進(jìn)入正題吧。首先來(lái)一個(gè)鏈接,沒(méi)了這個(gè)文章我就不可能改造成功:http://m.survivalescaperooms.com/beginor/archive/2012/03/22/2411496.html
OWIN宿主
其實(shí)這個(gè)網(wǎng)上很多,我主要是為了貼代碼,不然的話下面幾小節(jié)寫(xiě)不下去
- [assembly: OwinStartup(typeof(Startup))]//這句是在IIS宿主的時(shí)候使用的,作用是.Net會(huì)查找Startup類來(lái)啟動(dòng)整個(gè)服務(wù) 
 - namespace Xinchen.SOA.Server 
 - { 
 - public class Startup 
 - { 
 - public void Configuration(IAppBuilder appBuilder) 
 - { 
 - HttpConfiguration config = new HttpConfiguration(); 
 - config.Routes.MapHttPRoute( 
 - name: "DefaultApi", 
 - routeTemplate: "{controller}/{action}" 
 - ); 
 - config.Services.Add(typeof(ValueProviderFactory), new MyValueProviderFactory());//自定義參數(shù)查找,實(shí)現(xiàn)第三個(gè)目標(biāo) 
 - config.Services.Replace(typeof(IHttpControllerSelector), new ControllerSelector(config));//自定義控制器查找,實(shí)現(xiàn)第一個(gè)目標(biāo) 
 - config.Services.Replace(typeof(IHttpActionSelector), new HttpActionSelector());//自定義Action查找,實(shí)現(xiàn)第二個(gè)目標(biāo) 
 - appBuilder.UseWebApi(config); 
 - } 
 - } 
 - } 
 
省略了部分不太重要的代碼,Services.Add和Replace從字面就能明白是什么意思,但我沒(méi)有試過(guò)是否必須要像上面那樣寫(xiě)才行
對(duì)控制器的限制
- public class ControllerSelector : IHttpControllerSelector 
 - { 
 - HttpConfiguration _config; 
 - IDictionary<string, HttpControllerDescriptor> _desriptors = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase); 
 - public ControllerSelector(HttpConfiguration config) 
 - { 
 - _config = config; 
 - } 
 - void InitControllers() 
 - { 
 - if (_desriptors.Count <= 0) 
 - { 
 - lock (_desriptors) 
 - { 
 - if (_desriptors.Count <= 0) 
 - { 
 - var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(x => !x.GlobalAssemblyCache && !x.IsDynamic); 
 - var controllerTypes = new List<Type>(); 
 - foreach (var ass in assemblies) 
 - { 
 - controllerTypes.AddRange(ass.GetExportedTypes().Where(x => typeof(ApiController).IsAssignableFrom(x))); 
 - } 
 - var descriptors = new Dictionary<string, HttpControllerDescriptor>(); 
 - foreach (var controllerType in controllerTypes) 
 - { 
 - var descriptor = new HttpControllerDescriptor(_config, controllerType.Name, controllerType); 
 - _desriptors.Add(descriptor.ControllerName, descriptor); 
 - } 
 - } 
 - } 
 - } 
 - } 
 - public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() 
 - { 
 - InitControllers(); 
 - return _desriptors; 
 - } 
 - public System.Web.Http.Controllers.HttpControllerDescriptor SelectController(System.Net.Http.HttpRequestMessage request) 
 - { 
 - InitControllers(); 
 - var routeData = request.GetRouteData(); 
 - var controllerName = Convert.ToString(routeData.Values.Get("controller")); 
 - if (string.IsNullOrWhiteSpace(controllerName)) 
 - { 
 - throw new ArgumentException(string.Format("沒(méi)有在路由信息中找到controller")); 
 - } 
 - return _desriptors.Get(controllerName); 
 - } 
 - } 
 
這個(gè)其實(shí)比較簡(jiǎn)單,測(cè)試中WebApi好像沒(méi)調(diào)用GetControllerMapping方法,直接調(diào)用了SelectController方法,最后一個(gè)方法中有兩個(gè)Get方法調(diào)用,Get只是把從字典獲取值的TryGetValue功能給封裝了一下,InitControllers方法是從當(dāng)前所有的程序集中找繼承了ApiController的類,找到之后緩存起來(lái)。這段代碼整體比較簡(jiǎn)單。
對(duì)Action的限制
- public class HttpActionSelector : IHttpActionSelector 
 - { 
 - public ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor) 
 - { 
 - var methods = controllerDescriptor.ControllerType.GetMethods(); 
 - var result = new List<HttpActionDescriptor>(); 
 - foreach (var method in methods) 
 - { 
 - var descriptor = new ReflectedHttpActionDescriptor(controllerDescriptor, method); 
 - result.Add(descriptor); 
 - } 
 - return result.ToLookup(x => x.ActionName); 
 - } 
 - public HttpActionDescriptor SelectAction(HttpControllerContext controllerContext) 
 - { 
 - var actionDescriptor = new ReflectedHttpActionDescriptor(); 
 - var routeData = controllerContext.RouteData; 
 - object action = string.Empty; 
 - if (!routeData.Values.TryGetValue("action", out action)) 
 - { 
 - throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在路由中未找到action")); 
 - } 
 - string actionName = action.ToString().ToLower(); 
 - var methods = controllerContext.ControllerDescriptor.ControllerType.GetMethods().Where(x => x.Name.ToLower() == actionName); 
 - var count = methods.Count(); 
 - MethodInfo method = null; 
 - switch (count) 
 - { 
 - case 0: 
 - throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中未找到名為" + actionName + "的方法")); 
 - case 1: 
 - method = methods.FirstOrDefault(); 
 - break; 
 - default: 
 - var httpMethod = controllerContext.Request.Method; 
 - var filterdMethods = methods.Where(x => 
 - { 
 - var verb = x.GetCustomAttribute<AcceptVerbsAttribute>(); 
 - if (verb == null) 
 - { 
 - throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中找到多個(gè)名為" + actionName + "的方法,請(qǐng)考慮為這些方法加上AcceptVerbsAttribute特性")); 
 - } 
 - return verb.HttpMethods.Contains(httpMethod); 
 - }); 
 - if (filterdMethods.Count() > 1) 
 - { 
 - throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中找到多個(gè)名為" + actionName + "的方法,并且這些方法的AcceptVerbsAttribute都含有" + httpMethod.ToString() + ",發(fā)生重復(fù)")); 
 - } 
 - else if (filterdMethods.Count() <= 0) 
 - { 
 - throw new HttpResponseException(controllerContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.NotFound, "在控制器" + controllerContext.ControllerDescriptor.ControllerName + "中找到多個(gè)名為" + actionName + "的方法,但沒(méi)有方法被配置為可以響應(yīng)" + httpMethod.ToString() + "請(qǐng)求")); 
 - } 
 - method = filterdMethods.FirstOrDefault(); 
 - break; 
 - } 
 - return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method); 
 - } 
 - } 
 
GetActionMapping方法很簡(jiǎn)單,從控制器類型中找到所有的Action方法并返回 
SelectAction方法相對(duì)復(fù)雜,其實(shí)就是第二個(gè)目標(biāo)的邏輯,代碼看起來(lái)比較多其實(shí)并有很難的地方。 
對(duì)Action的參數(shù)的限制
這一塊比較難,我試了很久才成功,而且還有坑
- public class ActionValueBinder : DefaultActionValueBinder 
 - { 
 - protected override HttpParameterBinding GetParameterBinding(HttpParameterDescriptor parameter) 
 - { 
 - ParameterBindingAttribute parameterBinderAttribute = parameter.ParameterBinderAttribute; 
 - if (parameterBinderAttribute == null) 
 - { 
 - ParameterBindingRulesCollection parameterBindingRules = parameter.Configuratio