What is the.net core Pipeline?
As can be seen from the diagram above, the.net core pipeline is a series of processes that request to arrive at the server and return the response result. If we simplify the diagram below, the.net core pipeline is actually part of the middleware.Microsoft Middleware Documentation
Why is the pipeline part of the middleware?As I understand it,.net core is a request pipeline for configuring services and applications through the Startup class, so in a narrow sense, this is the request pipeline, the middleware pipeline that we want to understand today.
The core architecture of.net core is characterized by a middleware system, which is a code snippet for processing requests and responses.Middleware links to each other to form a pipeline.Incoming requests are piped, where each middleware has the opportunity to process them before passing them to the next middleware.Outgoing responses are also piped in reverse order.
PS: Simply put, it is a large part of the middle between the beginning of the request and the end of the response.You can think of it as "car sales" (starting the process of buying a car to a pick-up truck, hopefully not crying on the cover of a Mercedes-Benz). Ha-ha...
Let's also see why we need to simplify that at runtime.net core preinjects some necessary services and dependencies, and the list of services for the default injection (ServiceCollection) is as follows:
In a nutshell, there is Kestrel processing the request, converting the received request content (string stream) into structured data (HttpContext) for use by subsequent middleware services.Yes, service.That is, the Kestrel service is also a middleware.
The MVC in the first image is also implemented as a middleware.
There are other related services that can look at the services in the screenshot above, and the type of life cycle labeled next to it is the three injection modes of.net core mentioned earlier.
So what is the process from the program entry?
From Main () --> WebHost --> UseStartup
///
/// Specify the startup type to be used by the web host.
///
/// The to configure.
/// The to be used.
/// The .
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
string name = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).ConfigureServices((Action<IServiceCollection>)delegate(IServiceCollection services) { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { ServiceCollectionServiceExtensions.AddSingleton(services, typeof(IStartup), startupType); } else { ServiceCollectionServiceExtensions.AddSingleton(services, typeof(IStartup), (Func<IServiceProvider, object>)delegate(IServiceProvider sp) { IHostingEnvironment requiredService = ServiceProviderServiceExtensions.GetRequiredService<IHostingEnvironment>(sp); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.get_EnvironmentName())); }); } });
}
The code above explains that the necessary services are pre-injected, and services in Startup are delegated.Specifically, you can continue to explore:
UseSetting: Add or replace a setting in the configuration.
///
/// Add or replace a setting in the configuration.
///
/// The key of the setting to add or replace.
/// The value of the setting to add or replace.
/// The .
public IWebHostBuilder UseSetting(string key, string value)
{
_config.set_Item(key, value); return this;
}
ConfigureServices: Adds a delegate for configuring additional services for the host or web application. This may be called multiple times.
///
/// Adds a delegate for configuring additional services for the host or web application. This may be called
/// multiple times.
///
/// A delegate for configuring the .
/// The .
public IWebHostBuilder ConfigureServices(Action configureServices)
{
if (configureServices == null) { throw new ArgumentNullException("configureServices"); } return ConfigureServices(delegate(WebHostBuilderContext _, IServiceCollection services) { configureServices(services); });
}
ConventionBasedStartup
public class ConventionBasedStartup : IStartup
{
private readonly StartupMethods _methods; public ConventionBasedStartup(StartupMethods methods) { _methods = methods; } public void Configure(IApplicationBuilder app) { try { _methods.ConfigureDelegate(app); } catch (Exception ex) { if (ex is TargetInvocationException) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } throw; } } public IServiceProvider ConfigureServices(IServiceCollection services) { try { return _methods.ConfigureServicesDelegate(services); } catch (Exception ex) { if (ex is TargetInvocationException) { ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } throw; } }
}
OK, as we have determined here, what we can control is to inject the services we need through Startup, that is, the Startup injected middleware can do everything, such as handling authentication, errors, static files, etc. and MVC is also implemented as middleware in.net core as mentioned above.
So how many middleware does.net core build into us?As follows:
The built-in middleware we often use is:
app.UseExceptionHandler(); //exception handling app.UseStaticFiles(); //Static File app.UseAuthentication(); //Auth Validation app.UseMvc(); //MVC
We know that you can configure the.net core pipeline in the Configure method of the startup class, and by calling the Use* method on IApplicationBuilder, you can add a middleware to the pipeline, and the order in which it is added determines the order in which requests are made to traverse them.Therefore, if the order in which built-in middleware is added above, incoming requests will first traverse the exception handler middleware, then the static file middleware, then the authentication middleware, and finally be handled by the MVC middleware.
The Use* method is actually just a shortcut provided to us by.net core to make it easier to build pipelines.Behind the scenes, they all end up using (directly or indirectly) these keywords: User and Run.Both add a middleware to the pipeline, but Run adds a terminal middleware, which is the last Middleware in the pipeline.
So with built-in, it should be customizable.How to customize your own middleware?For a few simple demo demos:
(1) Non-branching pipelines
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Middleware A app.Use(async (context, next) => { Console.WriteLine("A (before)"); await next(); Console.WriteLine("A (after)"); }); // Middleware B app.Use(async (context, next) => { Console.WriteLine("B (before)"); await next(); Console.WriteLine("B (after)"); }); // Middleware C (terminal) app.Run(async context => { Console.WriteLine("C"); await context.Response.WriteAsync("Hello world"); });
}
Print results:
A (before)
B (before)
C
B (after)
A (after)
That's what the pipe chart shows:
(2) With branched pipes, when a pipeline without branching is used, it is equivalent to a line going straight to the bottom and returning the response result.But in general, we all want pipelines to be more flexible.Creating branched pipelines requires using the Map extension as a convention to create pipeline branches.Map creates a request pipeline branch based on a match of a given request path and executes the branch if the request path starts with a given path.There are then two types of branched pipelines:
1. No linked branches, official demo:
public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 1"); }); } private static void HandleMapTest2(IApplicationBuilder app) { app.Run(async context => { await context.Response.WriteAsync("Map Test 2"); }); } public void Configure(IApplicationBuilder app) { app.Map("/map1", HandleMapTest1); app.Map("/map2", HandleMapTest2); app.Run(async context => { await context.Response.WriteAsync("Hello from non-Map delegate. <p>"); }); }
}
Result:
The above disconnected branches are easy to understand, that is, different paths run different branches.If there is a parameter match, MapWhen is used, and MapWhen creates a request pipeline branch based on the result of a given predicate.Any predicate of type Func<HttpContext, bool>can be used to map the request to a new branch of the pipeline.Predicates are used to detect the existence of the query string variable branch. 2. With connected (reconnected upper main) branches, it is necessary to use UseWhen, upper demo:
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) => { Console.WriteLine("A (before)"); await next(); Console.WriteLine("A (after)"); }); app.UseWhen( context => context.Request.Path.StartsWithSegments(new PathString("/foo")), a => a.Use(async (context, next) => { Console.WriteLine("B (before)"); await next(); Console.WriteLine("B (after)"); })); app.Run(async context => { Console.WriteLine("C"); await context.Response.WriteAsync("Hello world"); });
}
Like the code above, when the request does not start with'/foo', the result is:
A (before)
C
A (after)
When the request starts with'/foo', the result is:
A (before)
B (before)
C
B (after)
A (after)
As you can see, the idea behind the middleware pipeline is very simple, but very powerful.Most of the functions are implemented as middleware by.Net core (authentication, static files, caching, MVC, etc.).Of course, it's easy to write your own code! You can write your own middleware later, official documents.