.NET Core开发日志——Action
在叙述controller一文中,有一处未做解释,即createcontrollerfactory方法中controlleractiondescriptor参数是如何产生的。这是因为其与action的关联性更大,所以放在本文中继续描述。
回到mvcroutehandler或者mvcattributeroutehandler的方法中:
public task routeasync(routecontext context) { ... var candidates = _actionselector.selectcandidates(context); if (candidates == null || candidates.count == 0) { _logger.noactionsmatched(context.routedata.values); return task.completedtask; } var actiondescriptor = _actionselector.selectbestcandidate(context, candidates); if (actiondescriptor == null) { _logger.noactionsmatched(context.routedata.values); return task.completedtask; } context.handler = (c) => { var routedata = c.getroutedata(); var actioncontext = new actioncontext(context.httpcontext, routedata, actiondescriptor); if (_actioncontextaccessor != null) { _actioncontextaccessor.actioncontext = actioncontext; } var invoker = _actioninvokerfactory.createinvoker(actioncontext); if (invoker == null) { throw new invalidoperationexception( resources.formatactioninvokerfactory_couldnotcreateinvoker( actiondescriptor.displayname)); } return invoker.invokeasync(); }; ... }
不难发现作为源头的actioncontext中传入了actiondescriptor,而这个参数的值是在actionselector中被筛选出来的。
public ireadonlylist<actiondescriptor> selectcandidates(routecontext context) { ... var cache = current; // the cache works based on a string[] of the route values in a pre-calculated order. this code extracts // those values in the correct order. var keys = cache.routekeys; var values = new string[keys.length]; for (var i = 0; i < keys.length; i++) { context.routedata.values.trygetvalue(keys[i], out object value); if (value != null) { values[i] = value as string ?? convert.tostring(value); } } if (cache.ordinalentries.trygetvalue(values, out var matchingroutevalues) || cache.ordinalignorecaseentries.trygetvalue(values, out matchingroutevalues)) { debug.assert(matchingroutevalues != null); return matchingroutevalues; } _logger.noactionsmatched(context.routedata.values); return emptyactions; }
然后可供筛选的actiondescriptors集合又是来自actiondescriptorcollectionprovider类。
private cache current { get { var actions = _actiondescriptorcollectionprovider.actiondescriptors; var cache = volatile.read(ref _cache); if (cache != null && cache.version == actions.version) { return cache; } cache = new cache(actions); volatile.write(ref _cache, cache); return cache; } }
它的内部又再调用了controlleractiondescriptorprovider类的onprovidersexecuting方法。
public actiondescriptorcollection actiondescriptors { get { if (_collection == null) { updatecollection(); } return _collection; } } private void updatecollection() { var context = new actiondescriptorprovidercontext(); for (var i = 0; i < _actiondescriptorproviders.length; i++) { _actiondescriptorproviders[i].onprovidersexecuting(context); } for (var i = _actiondescriptorproviders.length - 1; i >= 0; i--) { _actiondescriptorproviders[i].onprovidersexecuted(context); } _collection = new actiondescriptorcollection( new readonlycollection<actiondescriptor>(context.results), interlocked.increment(ref _version)); }
调用链继续深入到defaultapplicationmodelprovider之中。
public void onprovidersexecuting(actiondescriptorprovidercontext context) { if (context == null) { throw new argumentnullexception(nameof(context)); } foreach (var descriptor in getdescriptors()) { context.results.add(descriptor); } } protected internal ienumerable<controlleractiondescriptor> getdescriptors() { var applicationmodel = buildmodel(); applicationmodelconventions.applyconventions(applicationmodel, _conventions); return controlleractiondescriptorbuilder.build(applicationmodel); } protected internal applicationmodel buildmodel() { var controllertypes = getcontrollertypes(); var context = new applicationmodelprovidercontext(controllertypes); for (var i = 0; i < _applicationmodelproviders.length; i++) { _applicationmodelproviders[i].onprovidersexecuting(context); } for (var i = _applicationmodelproviders.length - 1; i >= 0; i--) { _applicationmodelproviders[i].onprovidersexecuted(context); } return context.result; } private ienumerable<typeinfo> getcontrollertypes() { var feature = new controllerfeature(); _partmanager.populatefeature(feature); return feature.controllers; }
到了这里终于可以看到action的影子,虽然现在还只是actionmodel。
public virtual void onprovidersexecuting(applicationmodelprovidercontext context) { ... foreach (var controllertype in context.controllertypes) { var controllermodel = createcontrollermodel(controllertype); if (controllermodel == null) { continue; } context.result.controllers.add(controllermodel); controllermodel.application = context.result; ... foreach (var methodinfo in controllertype.astype().getmethods()) { var actionmodel = createactionmodel(controllertype, methodinfo); if (actionmodel == null) { continue; } actionmodel.controller = controllermodel; controllermodel.actions.add(actionmodel); foreach (var parameterinfo in actionmodel.actionmethod.getparameters()) { var parametermodel = createparametermodel(parameterinfo); if (parametermodel != null) { parametermodel.action = actionmodel; actionmodel.parameters.add(parametermodel); } } } } }
利用controlleractiondescriptorbuilder类的build方法,可以得到预期的controlleractiondescriptor。
public static ilist<controlleractiondescriptor> build(applicationmodel application) { var actions = new list<controlleractiondescriptor>(); var methodinfomap = new methodtoactionmap(); var routetemplateerrors = new list<string>(); var attributeroutingconfigurationerrors = new dictionary<methodinfo, string>(); foreach (var controller in application.controllers) { // only add properties which are explicitly marked to bind. // the attribute check is required for modelbinder attribute. var controllerpropertydescriptors = controller.controllerproperties .where(p => p.bindinginfo != null) .select(createparameterdescriptor) .tolist(); foreach (var action in controller.actions) { // controllers with multiple [route] attributes (or user defined implementation of // iroutetemplateprovider) will generate one action descriptor per iroutetemplateprovider // instance. // actions with multiple [http*] attributes or other (iroutetemplateprovider implementations // have already been identified as different actions during action discovery. var actiondescriptors = createactiondescriptors(application, controller, action); foreach (var actiondescriptor in actiondescriptors) { actiondescriptor.controllername = controller.controllername; actiondescriptor.controllertypeinfo = controller.controllertype; addapiexplorerinfo(actiondescriptor, application, controller, action); addroutevalues(actiondescriptor, controller, action); addproperties(actiondescriptor, action, controller, application); actiondescriptor.boundproperties = controllerpropertydescriptors; if (isattributeroutedaction(actiondescriptor)) { // replaces tokens like [controller]/[action] in the route template with the actual values // for this action. replaceattributeroutetokens(actiondescriptor, routetemplateerrors); } } methodinfomap.addtomethodinfo(action, actiondescriptors); actions.addrange(actiondescriptors); } } ... return actions; }
controlleractiondescriptor包含了足以构建controller与action的属性。
public string controllername { get; set; } public virtual string actionname { get; set; } public methodinfo methodinfo { get; set; } public typeinfo controllertypeinfo { get; set; } public ilist<parameterdescriptor> parameters { get; set; }
controller的构建已经介绍过了,现在该谈谈关于action的。
先找到创建controlleractioninvokercacheentry对象的controlleractioninvokercache类的getcachedresult方法。可以看到两个关键参数objectmethodexecutor与actionmethodexecutor的创建方式。
public (controlleractioninvokercacheentry cacheentry, ifiltermetadata[] filters) getcachedresult(controllercontext controllercontext) { var cache = currentcache; var actiondescriptor = controllercontext.actiondescriptor; ifiltermetadata[] filters; if (!cache.entries.trygetvalue(actiondescriptor, out var cacheentry)) { ... var objectmethodexecutor = objectmethodexecutor.create( actiondescriptor.methodinfo, actiondescriptor.controllertypeinfo, parameterdefaultvalues); ... var actionmethodexecutor = actionmethodexecutor.getexecutor(objectmethodexecutor); cacheentry = new controlleractioninvokercacheentry( filterfactoryresult.cacheablefilters, controllerfactory, controllerreleaser, propertybinderfactory, objectmethodexecutor, actionmethodexecutor); cacheentry = cache.entries.getoradd(actiondescriptor, cacheentry); } ... return (cacheentry, filters); }
再到controlleractioninvoker类的next方法中跟踪到state.actioninside环节:
case state.actioninside: { var task = invokeactionmethodasync(); if (task.status != taskstatus.rantocompletion) { next = state.actionend; return task; } goto case state.actionend; }
终于可以找到创建action的方法。
private async task invokeactionmethodasync() { var controllercontext = _controllercontext; var objectmethodexecutor = _cacheentry.objectmethodexecutor; var controller = _instance; var arguments = _arguments; var actionmethodexecutor = _cacheentry.actionmethodexecutor; var orderedarguments = preparearguments(arguments, objectmethodexecutor); var diagnosticsource = _diagnosticsource; var logger = _logger; iactionresult result = null; try { diagnosticsource.beforeactionmethod( controllercontext, arguments, controller); logger.actionmethodexecuting(controllercontext, orderedarguments); var stopwatch = valuestopwatch.startnew(); var actionresultvaluetask = actionmethodexecutor.execute(objectmethodexecutor, controller, orderedarguments); if (actionresultvaluetask.iscompletedsuccessfully) { result = actionresultvaluetask.result; } else { result = await actionresultvaluetask; } _result = result; logger.actionmethodexecuted(controllercontext, result, stopwatch.getelapsedtime()); } ... }
核心的代码是这一句actionmethodexecutor.execute(objectmethodexecutor, controller, orderedarguments)
。
actionmethodexecutor与objectmethodexecutor即是之前生成controlleractioninvokercacheentry对象时传入的两个参数,controller是在state.actionbegin环节通过_instance = _cacheentry.controllerfactory(controllercontext);
生成的。orderedarguments是action方法所需的参数。
至于更详细的创建过程,可以到actionmethodexecutor类与objectmethodexecutor类中探寻,主要是涉及反射相关的知识,这里就不做进一步解释了。
推荐阅读
-
.NET Core开发日志——Action
-
基于 DevOps 实践的 .NET Core 给开发者带来了哪些好处?
-
谈谈ASP.NET Core MVC设计中的Controller与Action设计规范
-
基于 DevOps 实践的 .NET Core 给开发者带来了哪些好处?
-
.net core 使用阿里云分布式日志
-
ASP.NET Core扩展库之Http日志的使用详解
-
分享一个集成.NET Core+Swagger+Consul+Polly+Ocelot+IdentityServer4+Exceptionless+Apollo的入门级微服务开发框架
-
.net core 2.1 Nlog.Web.AspNetCore Nlog日志
-
.net core 程序发布后报错误,如何配置输出详细的错误日志
-
MobileForm控件的使用方式-用.NET(C#)开发APP的学习日志