专业国外网站建设,网站搜索引擎优化的基本内容,简洁大气公司网站,洛阳恒凯做的网站有哪些《200行代码#xff0c;7个对象——让你了解ASP.NET Core框架的本质》让很多读者对ASP.NET Core管道有了真实的了解。在过去很长一段时间中#xff0c;有很多人私信给我#xff1a;能否按照相同的方式分析一下MVC框架的设计与实现原理#xff0c;希望这篇文章能够满足你们的… 《200行代码7个对象——让你了解ASP.NET Core框架的本质》让很多读者对ASP.NET Core管道有了真实的了解。在过去很长一段时间中有很多人私信给我能否按照相同的方式分析一下MVC框架的设计与实现原理希望这篇文章能够满足你们的需求源代码可以通过原文下载。01IActionResult我们在前面将定义在Controller类型中的Action方法简化成只返回Task或者Void的方法并让方法自身去完成包括对请求予以相应的所有请求处理任务但真实的MVC框架并非如此。真正的MVC框架中具有一个名为IActionResult的重要结构顾名思义IActionResult对象一般会作为Action方法的返回值针对请求的响应任务基本上会由这个对象来实现。作为Action方法执行结果旨在对请求做最终响应的IActionResult接口同样具有极为简单的定义。如下main的代码片段所示IActionResult对象针对请求的响应实现在它唯一的ExecuteResultAsync方法中针对待执行Action的ActionContext上下文是其唯一的输入参数。public interface IActionResult
{Task ExecuteResultAsync(ActionContext context);
}
针对不同的请求响应需求MVC框架为我们定义了一系列的IActionResult实现类型应用程序同样也可以根据需要定义自己的IActionResult类型。作为演示我们定义了如下这个ContentResult类型它将指定的字符串作为响应主体的内容具体的内容类型媒体内容或者MIME类型则可以灵活指定。public class ContentResult : IActionResult
{private readonly string _content;private readonly string _contentType;public ContentResult(string content, string contentType){_content content;_contentType contentType;}public Task ExecuteResultAsync(ActionContext context){var response context.HttpContext.Response;response.ContentType _contentType;return response.WriteAsync(_content);}
}
由于Action方法可能没有返回值为了使Action执行流程执行Action方法将返回值转化成IActionResult对象执行IActionResult对象显得明确而清晰我们定义了如下这个“什么都没做”的NullActionResult类型它利用静态只读属性Instance返回一个单例的NullActionResult对象。public sealed class NullActionResult : IActionResult
{private NullActionResult() { }public static NullActionResult Instance { get; } new NullActionResult();public Task ExecuteResultAsync(ActionContext context) Task.CompletedTask;
}02执行IActionResult对象接下来我们将Action方法返回类型的约束放宽除了Task和VoidAction方法的返回类型还可以是IActionResult、TaskIActionResult和ValueTaskIActionResult。基于这个新的约定我们需要对前面定义的ControllerActionInvoker的InvokeAsync方法作如下的修改。如代码片段所示在执行目标Action方法之后我们调用ToActionResultAsync方法将返回对象转换成一个TaskIActionResult对象最终针对请求的响应只需要直接执行这个IActionResult对象即可。public class ControllerActionInvoker : IActionInvoker
{public ActionContext ActionContext { get; }public ControllerActionInvoker(ActionContext actionContext) ActionContext actionContext;public async Task InvokeAsync(){var actionDescriptor (ControllerActionDescriptor)ActionContext.ActionDescriptor;var controllerType actionDescriptor.ControllerType;var requestServies ActionContext.HttpContext.RequestServices;var controllerInstance ActivatorUtilities.CreateInstance(requestServies, controllerType);if (controllerInstance is Controller controller){controller.ActionContext ActionContext;}var actionMethod actionDescriptor.Method;var result actionMethod.Invoke(controllerInstance, new object[0]);var actionResult await ToActionResultAsync(result);await actionResult.ExecuteResultAsync(ActionContext);}private async TaskIActionResult ToActionResultAsync(object result){if (result null){return NullActionResult.Instance;}if (result is TaskIActionResult taskOfActionResult){return await taskOfActionResult;}if (result is ValueTaskIActionResult valueTaskOfActionResult){return await valueTaskOfActionResult;}if (result is IActionResult actionResult){return actionResult;}if (result is Task task){await task;return NullActionResult.Instance;}throw new InvalidOperationException(Action methods return value is invalid.);}
}
我们接下来将前面定义的ContentResult引入到演示实例的FoobarController中。如下面的代码片段所示我们将Action方法FooAsync和Bar的返回类型分别替换成TaskIActionResult和IActionResult具体返回的都是一个ContentResult对象。两个ContentResult对象都将同一段HTML片段作为响应的主体内容但是FooAsync方法将内容类型设置成 “text/html” 而Bar方法则将其设置为 “text/plain” 。public class FoobarController : Controller
{private static readonly string _html
html
headtitleHello/title
/head
bodypHello World!/p
/body
/html;[HttpGet(/{foo})]public TaskIActionResult FooAsync(){return Task.FromResultIActionResult(new ContentResult(_html, text/html));}public IActionResult Bar() new ContentResult(_html, text/plain);
}
演示程序启动之后如果采用与前面一样的URL访问定义在FoobarController的两个Action方法我们会在浏览器上得到如下图所示的输出结果。由于FooAsync方法将内容类型设置为 “text/html” 所以浏览器会将返回的内容作为一个HTML文档进行解析但是Bar方法将内容类型设置为 “text/plain” 所以返回的内容会原封不动地输出到浏览器上。03IActionResult类型转化前面的内容对Task方法的返回类型做出了一系列的约束但是我们知道在真正的MVC框架中定义在Controller中的Action方法可以采用任意的类型。为了解决这个问题我们可以考虑Action方法返回的数据对象转换成一个IActionResult对象。我们将类型转换规则定义成通过IActionResultTypeMapper接口表示的服务针对IActionResult的类型转换体现在Convert方法上。值得一提的是Convert方法表示待转换的对象的value参数并不一定是Action方法的返回值而是具体数据对象。如果Action方法的返回值是一个TaskTResult或者ValueTaskTResult对象它们的Result属性返回的参数这个待转换的数据对象。public interface IActionResultTypeMapper
{IActionResult Convert(object value, Type returnType);
}
简单起见我们定义了如下这个ActionResultTypeMapper类型将作为模拟框架对IActionResultTypeMapper接口的默认实现。如代码片段所示Convert方法将返回个内容类型为“text/plain”的ContentResult对象原始对象字符串描述ToString方法的返回值将作为响应主题的内容。public class ActionResultTypeMapper : IActionResultTypeMapper
{public IActionResult Convert(object value, Type returnType) new ContentResult(value.ToString(), text/plain);
}
当我们将针对Action方法返回类型的限制去除之后我们的ControllerActionInvoker自然需要作进一步修改。Action方法可能会返回一个TaskTResult或者ValueTaskTResult对象泛型参数TResult可以是任意类型所以我们在ControllerActionInvoker类型定义了如下两个静态方法ConvertFromTaskAsyncTValue和ConvertFromValueTaskAsyncTValue将它们转换成TaskIActionResult对象如果返回的不是一个IActionResult对象作为参数的IActionResultTypeMapper对象将来进行类型转换。我们定义在两个静态只读字段_taskConvertMethod和_valueTaskConvertMethod来保存描述这两个泛型方法的MethodInfo对象。public class ControllerActionInvoker : IActionInvoker
{private static readonly MethodInfo _taskConvertMethod;private static readonly MethodInfo _valueTaskConvertMethod;static ControllerActionInvoker(){var bindingFlags BindingFlags.Instance | BindingFlags.NonPublic| BindingFlags.Static;_taskConvertMethod typeof(ControllerActionInvoker).GetMethod(nameof(ConvertFromTaskAsync), bindingFlags);_valueTaskConvertMethod typeof(ControllerActionInvoker).GetMethod(nameof(ConvertFromValueTaskAsync), bindingFlags);}private static async TaskIActionResult ConvertFromTaskAsyncTValue(TaskTValue returnValue, IActionResultTypeMapper mapper){var result await returnValue;return result is IActionResult actionResult? actionResult: mapper.Convert(result, typeof(TValue));}private static async TaskIActionResult ConvertFromValueTaskAsyncTValue(ValueTaskTValue returnValue, IActionResultTypeMapper mapper){var result await returnValue;return result is IActionResult actionResult? actionResult: mapper.Convert(result, typeof(TValue));}…
}
如下所示的是InvokeAsync方法针对Action的执行。在执行了目标Action方法并得到原始的返回值后我们调用了ToActionResultAsync方法将返回值转换成TaskIActionResult最终通过执行IActionResult对象进而完成所有的请求处理任务。如果返回类型为TaskTResult或者ValueTaskTResult我们会直接采用反射的方式调用ConvertFromTaskAsyncTValue或者ConvertFromValueTaskAsyncTValue方法更好的方式是采用表达式树的方式执行类型转换方法以获得更好的性能。public class ControllerActionInvoker : IActionInvoker
{ public async Task InvokeAsync(){var actionDescriptor (ControllerActionDescriptor)ActionContext.ActionDescriptor;var controllerType actionDescriptor.ControllerType;var requestServies ActionContext.HttpContext.RequestServices;var controllerInstance ActivatorUtilities.CreateInstance(requestServies, controllerType);if (controllerInstance is Controller controller){controller.ActionContext ActionContext;}var actionMethod actionDescriptor.Method;var returnValue actionMethod.Invoke(controllerInstance, new object[0]);var mapper requestServies.GetRequiredServiceIActionResultTypeMapper();var actionResult await ToActionResultAsync(returnValue, actionMethod.ReturnType, mapper);await actionResult.ExecuteResultAsync(ActionContext);}private TaskIActionResult ToActionResultAsync(object returnValue, Type returnType, IActionResultTypeMapper mapper){//Nullif (returnValue null || returnType typeof(Task) || returnType typeof(ValueTask)){return Task.FromResult IActionResult (NullActionResult.Instance);}//IActionResultif (returnValue is IActionResult actionResult){return Task.FromResult(actionResult);}//TaskTResultif (returnType.IsGenericType returnType.GetGenericTypeDefinition() typeof(Task)){var declaredType returnType.GenericTypeArguments.Single();var taskOfResult _taskConvertMethod.MakeGenericMethod(declaredType).Invoke(null, new object[] { returnValue, mapper });return (TaskIActionResult)taskOfResult;}//ValueTaskTResultif (returnType.IsGenericType returnType.GetGenericTypeDefinition() typeof(ValueTask)){var declaredType returnType.GenericTypeArguments.Single();var valueTaskOfResult _valueTaskConvertMethod.MakeGenericMethod(declaredType).Invoke(null, new object[] { returnValue, mapper });return (TaskIActionResult)valueTaskOfResult;}return Task.FromResult(mapper.Convert(returnValue, returnType));}
}
从上面的代码片段可以看出在进行针对IActionResult的类型转换过程中使用到的IActionResultTypeMapper对象是从针对当前请求的依赖注入容器中提取的所以我们在应用启动之前需要作针对性的服务注册。我们将针对IActionResultTypeMapper的服务注册添加到之前定义的AddMvcControllers扩展方法中。public static class ServiceCollectionExtensions
{public static IServiceCollection AddMvcControllers(this IServiceCollection services){return services.AddSingletonIActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider().AddSingletonIActionInvokerFactory, ActionInvokerFactory().AddSingletonIActionDescriptorProvider, ControllerActionDescriptorProvider().AddSingletonControllerActionEndpointDataSource, ControllerActionEndpointDataSource().AddSingletonIActionResultTypeMapper, ActionResultTypeMapper();}
}
为了验证模拟框架对Action方法的任意返回类型的支持我们将前面演示实例定义的FoobarController做了如下的修改。如代码片段所示我们在FoobarController类型中定义了四个Action方法它们返回的类型分别为TaskContentResult、ValueTaskContentResult、TaskString、ValueTaskStringContentResult对象的内容和直接返回的字符串都是一段相同的HTML。public class FoobarController : Controller
{private static readonly string _html
html
headtitleHello/title
/head
bodypHello World!/p
/body
/html;[HttpGet(/foo)]public TaskContentResult FooAsync() Task.FromResult(new ContentResult(_html, text/html));[HttpGet(/bar)]public ValueTaskContentResult BarAsync() new ValueTaskContentResult(new ContentResult(_html, text/html));[HttpGet(/baz)]public Taskstring BazAsync() Task.FromResult(_html);[HttpGet(/qux)]public ValueTaskstring QuxAsync() new ValueTaskstring(_html);
}
我们在上述四个Action方法上通过标注HttpGetAttribute特性将路由模板分别设置为“/foo”、“/bar”、“/baz”和“/qux”所以我们可以采用相应的URL来访问这四个Action方法。下图所示的是这个Action的响应内容在浏览器上的呈现。由于Action方法Baz和Qux返回的是一个字符串按照ActionResultTypeMapper类型提供的转换规则最终返回的将是以此字符串作为响应内容内容类型为 “text/plain” 的ContentResult对象。