By minimalist simulation framework allows you to understand the design and implementation of ASP.NET Core MVC framework [novella]: The requested response

g5" 200 lines of code, seven objects - so that you understand the nature of ASP.NET Core framework " makes a lot of readers have real knowledge of ASP.NET Core pipeline. For a long period of time, there are many private letter to me: Can you analyze the design and implementation of the principles of MVC framework in the same way, I hope this article can meet your needs. Before the commencement of the introduction of this chapter, the way to make a few ad: "ASP.NET Core 3 framework Secret" has started selling a limited time 50% off discount as well as the last three days , are interested can scan two-dimensional code on the right or from here to buy into the group .

We " [Part I]: Routing integration " will define the Action method Controller type is reduced to only return Task or method Void, and to make the method itself to complete, including to be appropriate for all requests processing tasks to the request, but true MVC framework is not the case. True MVC frame has a structure called IActionResult important, the name implies, generally as objects IActionResult Action return value, basically this object is realized by a response requested task.

Download the source code:

IActionResult execution of
IActionResult type conversion

A, IActionResult

The results are intended to perform the final response to the request made IActionResult Interface also has an extremely simple method as a definition of Action. Main code snippet shown below, IActionResult response to the request for the object in its implementation only ExecuteResultAsync method, to be performed for the Action ActionContext context is its only input parameter.

public  interface IActionResult
{
    Task ExecuteResultAsync(ActionContext context);
}

Respond to the needs for different request, MVC framework we defined a series of IActionResult implementation types, applications may also need to define their own IActionResult type. As a demonstration, we define the following ContentResult this type, it specifies the string as the contents of the response body, the specific content type (MIME type or media content) you have the flexibility to specify.

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

As the Action method may not return a value, in order to make the Action implementation process (the implementation of Action method => return value will be converted into IActionResult objects => execution IActionResult objects) seem clear and obvious, we define this as "doing nothing" in NullActionResult type, which uses static readonly attribute NullActionResult Instance Object returns a single embodiment.

Public  sealed  class NullActionResult: IActionResult
{
    private NullActionResult() { }
    public static NullActionResult Instance { get; } = new NullActionResult();
    public Task ExecuteResultAsync(ActionContext context) => Task.CompletedTask;
}

Second, the implementation IActionResult objects

Next we will Action method returns the type of constraint to relax, in addition to the return type of Task and Void, Action method can also be IActionResult, Task <IActionResult> and ValueTask <IActionResult>. Based on this new agreement, we need to InvokeAsync the method defined above ControllerActionInvoker make the following modifications. As shown in the code fragment, after execution of the target method Action, we call ToActionResultAsync method returns the object is converted into a Task <IActionResult> objects, for a final response to the request only direct objects to perform this 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 Task<IActionResult> ToActionResultAsync(object result)
    {
        if (result == null)
        {
            return NullActionResult.Instance;
        }

        if (result is Task<IActionResult> taskOfActionResult)
        {
            return  await taskOfActionResult;
        }

        if (result is ValueTask<IActionResult> 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 method's return value is invalid.");
    }
}

Next, we will define the front ContentResult introduced to the demo instance FoobarController in. The following code fragment, we set the Action Bar and methods FooAsync return type are replaced Task <IActionResult> and IActionResult, is a specific return ContentResult object. ContentResult two objects are the same HTML fragment as the main content of the response, but the method FooAsync content type is set to "text / html", while the Bar method will be set to "text / plain".

public class FoobarController : Controller
{
    private static readonly string _html =
@"<html>
<head>
    <title>Hello</title>
</head>
<body>
    <p>Hello World!</p>
</body>
</html>";

    [HttpGet("/{foo}")]
    public Task<IActionResult> FooAsync()
    {
        return Task.FromResult<IActionResult>(new ContentResult(_html, "text/html"));
    }

    public IActionResult Bar() => new ContentResult(_html, "text/plain");
}

After the presentation starts, as before if the URL access method defined in two Action FoobarController, we will get output as shown in the browser. Because FooAsync method sets the content type to "text / html", so the content browser will return as an HTML document is parsed, but the Bar method to set the content type to "text / plain", so the content will be returned intact no moving outputted to the browser. Source code from here to download.

5-2

Three, IActionResult type conversion

Content on the front of the return type of Task method of making a series of constraints, but we know the true MVC framework, defined in the Controller Action method can be any type of adoption. To solve this problem, we can consider the data object Action method returns an object into a IActionResult. We will type conversion rules defined as represented by IActionResultTypeMapper service interface for the type of conversion IActionResult reflected in the Convert method. It is worth mentioning that, the Convert method represents a parameter value to be converted is not necessarily subject method is the return value of Action, but specific data objects. Action If the return value is a Task <TResult> or ValueTask <TResult> object parameters thereof Result of this property returns the data object to be converted.

public interface IActionResultTypeMapper
{
    IActionResult Convert(object value, Type returnType);
}

For simplicity, we define the following types will achieve this ActionResultTypeMapper as the default simulation framework IActionResultTypeMapper interface. As shown in the code fragment, the Convert method returns a content type "text / plain" ContentResult the objects, a string describing the original object (the return value of the ToString method) as a response to the content of the theme.

public class ActionResultTypeMapper : IActionResultTypeMapper
{
    public IActionResult Convert(object value, Type returnType) => new ContentResult(value.ToString(), "text/plain");
}

When we will remove restrictions on the return type of Action method, our ControllerActionInvoker natural need to make further changes. Action method may return a Task <TResult> or ValueTask <TResult> objects (TResult generic parameter may be any type), we define the following two types of static method ControllerActionInvoker (ConvertFromTaskAsync <TValue> and ConvertFromValueTaskAsync <TValue>) converts them into Task <IActionResult> objects, if not return a IActionResult object type conversion as the object parameters IActionResultTypeMapper future. We define two static read-only field (_taskConvertMethod and _valueTaskConvertMethod) to save describe these two generic method MethodInfo object.

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 Task<IActionResult> ConvertFromTaskAsync<TValue>(Task<TValue> returnValue, IActionResultTypeMapper mapper)
    {
        var result = await returnValue;
        return result is IActionResult actionResult
            ? actionResult
            : mapper.Convert(result, typeof(TValue));
    }

    private static async Task<IActionResult> ConvertFromValueTaskAsync<TValue>( ValueTask<TValue> returnValue, IActionResultTypeMapper mapper)
    {
        var result = await returnValue;
        return result is IActionResult actionResult
            ? actionResult
            : mapper.Convert(result, typeof(TValue));
    }
}

Action is performed for InvokeAsync method shown below. Resulting in the original and return value after execution of the target method Action, ToActionResultAsync method we call the return value to the Task <IActionResult>, and thus the final completion of all the requests by executing the processing tasks IActionResult object. If the return type is Task <TResult> or ValueTask <TResult>, we will be directly reflected by way of calling ConvertFromTaskAsync <TValue> or ConvertFromValueTaskAsync <TValue> method (way better way is to use an expression tree type conversion process to obtain execution better performance).

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.GetRequiredService<IActionResultTypeMapper>();
        var actionResult = await ToActionResultAsync(
            returnValue, actionMethod.ReturnType, mapper);
        await actionResult.ExecuteResultAsync(ActionContext);
    }

    private Task<IActionResult> ToActionResultAsync(object returnValue, Type returnType, IActionResultTypeMapper mapper)
    {
        //Null
        if (returnValue == null || returnType == typeof(Task) || returnType == typeof(ValueTask))
        {
            return Task.FromResult<IActionResult>(NullActionResult.Instance);
        }

        //IActionResult
        if (returnValue is IActionResult actionResult)
        {
            return Task.FromResult(actionResult);
        }

        //Task<TResult>
        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
        {
            var declaredType = returnType.GenericTypeArguments.Single();
            var taskOfResult = _taskConvertMethod.MakeGenericMethod(declaredType).Invoke(null, new object[] { returnValue, mapper });
            return (Task<IActionResult>)taskOfResult;
        }

        //ValueTask<TResult>
        if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(ValueTask<>))
        {
            var declaredType = returnType.GenericTypeArguments.Single();
            var valueTaskOfResult = _valueTaskConvertMethod.MakeGenericMethod(declaredType).Invoke(null, new object[] { returnValue, mapper });
            return (Task<IActionResult>)valueTaskOfResult;
        }

        return Task.FromResult(mapper.Convert(returnValue, returnType));
    }
}

As it can be seen from the above code fragment, to use for performing the type of conversion process IActionResult IActionResultTypeMapper objects are extracted from the injection depending on the current request for a vessel, so we need to make before starting the application specific service registration. We will register Add to AddMvcControllers extension method previously defined for IActionResultTypeMapper service.

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddMvcControllers(this IServiceCollection services)
    {
        return services
            .AddSingleton<IActionDescriptorCollectionProvider, DefaultActionDescriptorCollectionProvider>()
            .AddSingleton<IActionInvokerFactory, ActionInvokerFactory>()
            .AddSingleton <IActionDescriptorProvider, ControllerActionDescriptorProvider>()
            .AddSingleton<ControllerActionEndpointDataSource, ControllerActionEndpointDataSource>()
            .AddSingleton<IActionResultTypeMapper, ActionResultTypeMapper>();
    }
}

In order to verify the simulation framework for any type of support is returned to Action method, we previously demonstrated instance defined FoobarController made the following modifications. As shown in the code fragment, we define four methods FoobarController Action type, respectively, they return type Task <ContentResult>, ValueTask <ContentResult>, Task <String>, ValueTask <String>, and the contents of the object directly ContentResult the returned string is the same section of the HTML.

public class FoobarController : Controller
{
    private static readonly string _html =
@"<html>
<head>
    <title>Hello</title>
</head>
<body>
    <p>Hello World!</p>
</body>
</html>";

    [HttpGet("/foo")]
    public Task<ContentResult> FooAsync()
    => Task.FromResult(new ContentResult(_html, "text/html"));

    [HttpGet("/bar")]
    public ValueTask<ContentResult> BarAsync()
    => new ValueTask<ContentResult>(new ContentResult(_html, "text/html"));

    [HttpGet("/baz")]
    public Task<string> BazAsync() => Task.FromResult(_html);

    [HttpGet("/qux")]
    public ValueTask<string> QuxAsync() => new ValueTask<string>(_html);
}

We will mark HttpGetAttribute properties routing templates are provided on the four Action method "/ foo", "/ bar ", "/ baz" and "/ qux", so we can use the corresponding URL to access the four Action method. Action of this response is presented on the browser shown in the figure. Since the method of Action and Baz Qux returns a string, in accordance with the conversion rules ActionResultTypeMapper provided type, this will eventually return a string response content, content type "text / plain" ContentResult the object. Source code from here to download.

5-3

By minimalist simulation framework allows you to understand the design and implementation of ASP.NET Core MVC framework [Part I]: routing integration
by minimalist simulation framework allows you to understand the design and implementation of [novellas] ASP.NET Core MVC framework: request response
by minimalist simulation framework allows you to understand the design and implementation of ASP.NET Core MVC framework [Part II]: parameter binding

Guess you like

Origin www.cnblogs.com/artech/p/12550074.html