. NET cloud native architect training camp (template method & & builder) -- learning notes

Posted by fighnight on Sat, 08 Jan 2022 03:02:28 +0100

catalogue

  • Template method
  • Source code
  • builder

Template method

Define the skeleton of an algorithm in operation, and delay some steps to subclasses, so that subclasses can redefine some specific steps of an algorithm without changing the structure of an algorithm

Source code

https://github.com/dotnet/aspnetcore/

In the directory aspnetcore \ SRC \ MVC \ MVC There is a ControllerActionInvoker under core \ SRC \ infrastructure, which inherits from ResourceInvoker

internal class ControllerActionInvoker : ResourceInvoker, IActionInvoker

Some algorithm skeletons are defined in ResourceInvoker, and some methods are assembled in InvokeAsync method

public virtual Task InvokeAsync()
{
    ...
    task = InvokeFilterPipelineAsync();
    ...
    return ReleaseResourcesCore(scope).AsTask();
    ...
}

There are also some abstract methods that need to be implemented in the subclass ControllerActionInvoker

/// <summary>
/// In derived types, releases resources such as controller, model, or page instances created as
/// part of invoking the inner pipeline.
/// </summary>
protected abstract ValueTask ReleaseResources();

protected abstract Task InvokeInnerFilterAsync();

Here is an application of template method, which is implemented through abstract classes and subclasses

The subclass has no InvokeAsync method. It completes the encapsulation at the top level, calls multiple methods, and provides some intermediate joint methods

From the perspective of MapControllers method, controllerendpointrotebuilderextensions is called

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});

The controllerendpointrotebuilderextensions class will tell you what happened during the whole registration process

First, it receives an iendpoint routebuilder

public static ControllerActionEndpointConventionBuilder MapControllers(this IEndpointRouteBuilder endpoints)
{
    ...
    
    EnsureControllerServices(endpoints);

    return GetOrCreateDataSource(endpoints).DefaultBuilder;
}

Get all services in the insurance controller services

var marker = endpoints.ServiceProvider.GetService<MvcMarkerService>();

MvcMarkerService needs to register first, obtain DataSources, and then register

private static ControllerActionEndpointDataSource GetOrCreateDataSource(IEndpointRouteBuilder endpoints)
{
    var dataSource = endpoints.DataSources.OfType<ControllerActionEndpointDataSource>().FirstOrDefault();
    if (dataSource == null)
    {
        var orderProvider = endpoints.ServiceProvider.GetRequiredService<OrderedEndpointsSequenceProviderCache>();
        var factory = endpoints.ServiceProvider.GetRequiredService<ControllerActionEndpointDataSourceFactory>();
        dataSource = factory.Create(orderProvider.GetOrCreateOrderedEndpointsSequenceProvider(endpoints));
        endpoints.DataSources.Add(dataSource);
    }

    return dataSource;
}

Traverse actions in ControllerActionEndpointDataSource

for (var i = 0; i < actions.Count; i++)
{
    if (actions[i] is ControllerActionDescriptor action)
    {
        _endpointFactory.AddEndpoints(endpoints, routeNames, action, _routes, conventions, CreateInertEndpoints);

These actions come from the base class ActionEndpointDataSourceBase

public ActionEndpointDataSourceBase(IActionDescriptorCollectionProvider actions)
{
    _actions = actions;

    Conventions = new List<Action<EndpointBuilder>>();
}

actions is bound to RequestDelegate through CreateEndpoints

protected override List<Endpoint> CreateEndpoints(IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions)

There is an AddEndpoints method in CreateEndpoints

_endpointFactory.AddEndpoints(endpoints, routeNames, action, _routes, conventions, CreateInertEndpoints);

Convert an action to an endpoint in the AddEndpoints method

var builder = new InertEndpointBuilder()
{
    DisplayName = action.DisplayName,
    RequestDelegate = _requestDelegate,
};
AddActionDataToBuilder(
    builder,
    routeNames,
    action,
    routeName: null,
    dataTokens: null,
    suppressLinkGeneration: false,
    suppressPathMatching: false,
    conventions,
    Array.Empty<Action<EndpointBuilder>>());
endpoints.Add(builder.Build());

Take a look_ requestDelegate

_requestDelegate = CreateRequestDelegate();

This is the entry point to actually execute every web api request

private static RequestDelegate CreateRequestDelegate()
{
    // We don't want to close over the Invoker Factory in ActionEndpointFactory as
    // that creates cycles in DI. Since we're creating this delegate at startup time
    // we don't want to create all of the things we use at runtime until the action
    // actually matches.
    //
    // The request delegate is already a closure here because we close over
    // the action descriptor.
    IActionInvokerFactory? invokerFactory = null;

    return (context) =>
    {
        var endpoint = context.GetEndpoint()!;
        var dataTokens = endpoint.Metadata.GetMetadata<IDataTokensMetadata>();

        var routeData = new RouteData();
        routeData.PushState(router: null, context.Request.RouteValues, new RouteValueDictionary(dataTokens?.DataTokens));

        // Don't close over the ActionDescriptor, that's not valid for pages.
        var action = endpoint.Metadata.GetMetadata<ActionDescriptor>()!;
        var actionContext = new ActionContext(context, routeData, action);

        if (invokerFactory == null)
        {
            invokerFactory = context.RequestServices.GetRequiredService<IActionInvokerFactory>();
        }

        var invoker = invokerFactory.CreateInvoker(actionContext);
        return invoker!.InvokeAsync();
    };
}

First, get the endpoint from the context, then get the ActionDescriptor from the endpoint, and then encapsulate it into an ActionContext

Create a invoker through invokerFactory, and finally call InvokeAsync, so the whole execution process is a delegate. When MapControllers is executed, the delegate is linked to the whole execution of endpoint.

The endpoint of each route finally points to the same place and all points to the same Delegate, but the definition of action obtained by this Delegate from the endpoint Metadata includes controller, method and parameter

Finally, it is called in the form of invoker, so three methods, ResourceInvoker, PageActionInvoker and ControllerActionInvoker, are used to play the role of template method

builder

It decomposes a complex object into several simple objects, and then builds them step by step

It separates change from invariance, that is, the components of the product are invariable, but each part can be flexibly selected

The builder and template methods are somewhat similar. One belongs to behavioral design pattern and the other belongs to creative design pattern

The template method emphasizes the decomposition of behavior, and the builder pays more attention to the decomposition of creating objects

Both are based on an abstract class and provide abstract methods to specific class implementations. The code is similar and the meaning is different

Topics: .NET