Reorganize. net core practice -- filter [44]

preface

Briefly introduce filter

text

The type of filter, written in the Microsoft document:

Each filter type executes at different stages in the filter pipeline:

The authorization filter runs first to determine whether the user has been authorized for the request. If the request is not authorized, the authorization filter can short circuit the pipeline.

Resource filter: run after authorization.

OnResourceExecuting Run the code before the rest of the filter pipeline. For example, OnResourceExecuting Run the code before binding the model.

OnResourceExecuted Run the code after the rest of the pipeline is complete.

Action filter:

Run the code immediately before and after calling the action method.

You can change the parameters passed to the operation.

You can change the results returned from the operation.

Pages in Razor I won't support it.

The exception filter applies a global policy to unhandled exceptions before writing anything to the response body.

The result filter runs code before and immediately after the result of the action. Action methods run only if they execute successfully. They are useful for logic that must revolve around the execution of a view or formatter.

Interaction mode:

Generally, authorization filters and exception filters are used in every project (monomer or micro service).

Authorization filter is because we want to ensure that the resources accessed by users are accessible.

The exception filter generally handles some uncapped exceptions. Of course, for the sake of code simplicity, we generally do not try catch in the code.

If a piece of code is a known exception, for example, if you know that a parameter does not conform to the rules, you will actively throw a custom exception, and then handle it uniformly in the exception filter.

for instance:

If we want to process the error code.

Then it will be thrown directly in the code.

throw CustomException("errorCode");

Then in the exception filter:

if(Exception is CustomException customException)
{
   // Process the error code to get its error message
}

Of course, we can also use exception handling middleware:

public class CustomExceptionHandlerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly CustomExceptionHandlerOptions _options;

    public CustomExceptionHandlerMiddleware(RequestDelegate next, IOptions<CustomExceptionHandlerOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (System.Exception ex)
        {
            var logger = context.RequestServices.GetRequiredService<ILoggerFactory>()
                .CreateLogger<CustomExceptionHandlerMiddleware>();
            if (context.RequestAborted.IsCancellationRequested && (ex is TaskCanceledException || ex is OperationCanceledException))
            {
                _options.OnRequestAborted?.Invoke(context, logger);
            }
            else
            {
                _options.OnException?.Invoke(context, logger, ex);
            }
        }
    }
}

The benefits of exception handling middleware are also self-evident. If the company encapsulates its own exception middleware, each service will get a unified log with the same processing method, and the overall style of each service is the same.

Many of these developments must be familiar, and then the result filter is generally used in the gateway.

If we need to add a parameter, requestId, for tracking, to facilitate log query, we can do the following:

public class AddRequestIdResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddRequestIdResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddRequestIdResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
		if (context.Result is ObjectResult objectResult)
		{
			if (objectResult.Value is DtoBaseModel dtoModel)
			{
				dtoModel.RequestId = context.HttpContext.TraceIdentifier;
			}
		}else if (context.Result is EmptyResult emptyResult)
		{
			context.Result = new ObjectResult(new DtoBaseModel{RequestId = context.HttpContext.TraceIdentifier });
		}
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddRequestIdResultServiceFilter.OnResultExecuted");
    }
}

Of course, this is not the real requestId, but the log tracking id, but the TraceId.

Generally, a requestId is added to the gateway for log query. Generally, this requestId is only added to the gateway, because distributed tracking is generally done, that is, the traceId of the access chain behind the gateway will be the same. This will be summarized separately later.

The result filter is executed only when an action or action filter generates an action result. The result filter is not executed when:

The authorization filter or resource filter shorted the pipeline.

The exception filter handles exceptions by generating action results.

The Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting method can set Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel to true to short circuit the execution of operation results and subsequent result filters.

Set write response object in case of short circuit to avoid generating null response. If an exception is thrown in IResultFilter.OnResultExecuting, it will cause:

Block the execution of action results and subsequent filters.

The result is seen as failure rather than success.

There is one thing worth noting:

When Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted Method, the response may have been sent to the client. If the response has been sent to the client, it cannot be changed.

If the operation result execution has been short circuited by another filter setting, resultexecutedcontext.cancelled is set to true.

If the operation result or subsequent result filter throws an exception, ResultExecutedContext.Exception is set to a non NULL value.

Setting Exception to NULL effectively handles the Exception and prevents it from being thrown at a later stage of the pipeline. When handling exceptions in the result filter, there is no reliable way to write data to the response. If the header is flushed to the client when the operation result throws an Exception, there is no reliable mechanism for sending failure codes.

For IAsyncResultFilter, all subsequent result filters and operation results can be executed by calling await next on ResultExecutionDelegate. To short circuit, set to ResultExecutingContext.Cancel true without calling ResultExecutionDelegate:

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

Action filter

Implement the IActionFilter or IAsyncActionFilter interface.

Their execution revolves around the execution of operation methods.

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

ActionExecutingContext provides the following properties:

ActionArguments - used to read the input of the action method.

Controller - used to process controller instances.

Result - setting result shortens the execution of the action method and subsequent action filters.

Exception thrown in action method:

Prevents subsequent filters from running.

Unlike setting Result, the Result is considered a failure rather than a success.

example:

public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

This generally records the user's access, such as global registration and counting the number of action visits.

Resource filter

Resource filter:

Implement the IResourceFilter or IAsyncResourceFilter interface.
Execution will overwrite most of the filter pipeline.
Only authorization filters run before resource filters.
Resource filters are useful if you want to short most pipes. For example, if the cache hits, the cache filter can bypass the rest of the pipeline.

Resource filter example:

Short circuit resource filter shown earlier.

DisableFormValueModelBindingAttribute:

You can prevent model bindings from accessing form data.

Used to upload large files to prevent form data from being read into memory.

For example:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
{
	public void OnResourceExecuting(ResourceExecutingContext context)
	{
		var formValueProviderFactory = context.ValueProviderFactories
				.OfType<FormValueProviderFactory>()
				.FirstOrDefault();
		if (formValueProviderFactory != null)
		{
			context.ValueProviderFactories.Remove(formValueProviderFactory);
		}

		var jqueryFormValueProviderFactory = context.ValueProviderFactories
			.OfType<JQueryFormValueProviderFactory>()
			.FirstOrDefault();
		if (jqueryFormValueProviderFactory != null)
		{
			context.ValueProviderFactories.Remove(jqueryFormValueProviderFactory);
		}
	}

	public void OnResourceExecuted(ResourceExecutedContext context)
	{
	}
}

junction

The next section briefly arranges the distributed log chain.

Posted by PHPcadet on Sat, 06 Nov 2021 08:27:13 -0700