重新整理 .net core 实践篇——— endpoint[四十七]
前言
简单整理一些endpoint的一些东西,主要是介绍一个这个endpoint是什么。
正文
endpoint 从表面意思是端点的意思,也就是说比如客户端的某一个action 是一个点,那么服务端的action也是一个点,这个端点的意义更加具体,而不是服务端和客户端这么泛指。
比如说客户端的action请求用户信心,那么服务端的action就是GetUserInfo,那么endpoint 在这里是什么意思呢?是GetUserInfo的抽象,或者是GetUserInfo的描述符。
那么netcore 对endpoint的描述是什么呢?
派生 Microsoft.AspNetCore.Routing.RouteEndpoint
RouteEndpoint 是:
那么来看一下:
app.UseRouting();
代码为:
public static IApplicationBuilder UseRouting(this IApplicationBuilder builder)
{
if (builder == null)
throw new ArgumentNullException(nameof (builder));
EndpointRoutingApplicationBuilderExtensions.VerifyRoutingServicesAreRegistered(builder);
DefaultEndpointRouteBuilder endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder);
builder.Properties["__EndpointRouteBuilder"] = (object) endpointRouteBuilder;
return builder.UseMiddleware<EndpointRoutingMiddleware>((object) endpointRouteBuilder);
}
VerifyRoutingServicesAreRegistered 主要验证路由标记服务是否注入了:
private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app)
{
if (app.ApplicationServices.GetService(typeof (RoutingMarkerService)) == null)
throw new InvalidOperationException(Resources.FormatUnableToFindServices((object) "IServiceCollection", (object) "AddRouting", (object) "ConfigureServices(...)"));
}
然后可以查看一下DefaultEndpointRouteBuilder:
internal class DefaultEndpointRouteBuilder : IEndpointRouteBuilder
{
public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder)
{
IApplicationBuilder applicationBuilder1 = applicationBuilder;
if (applicationBuilder1 == null)
throw new ArgumentNullException(nameof (applicationBuilder));
this.ApplicationBuilder = applicationBuilder1;
this.DataSources = (ICollection<EndpointDataSource>) new List<EndpointDataSource>();
}
public IApplicationBuilder ApplicationBuilder { get; }
public IApplicationBuilder CreateApplicationBuilder()
{
return this.ApplicationBuilder.New();
}
public ICollection<EndpointDataSource> DataSources { get; }
public IServiceProvider ServiceProvider
{
get
{
return this.ApplicationBuilder.ApplicationServices;
}
}
}
我们知道builder 是用来构造某个东西的,从名字上来看是用来构造DefaultEndpointRoute。
建设者,一般分为两种,一种是内部就有构建方法,另一种是构建方法在材料之中或者操作机器之中。
这个怎么说呢?比如说一个构建者相当于一个工人,那么这个工人可能只带材料去完成一个小屋。也可能构建者本身没有带材料,那么可能材料之中包含了制作方法(比如方便面)或者机器中包含了制作方法比如榨汁机。
然后来看一下中间件EndpointRoutingMiddleware:
public EndpointRoutingMiddleware(
MatcherFactory matcherFactory,
ILogger<EndpointRoutingMiddleware> logger,
IEndpointRouteBuilder endpointRouteBuilder,
DiagnosticListener diagnosticListener,
RequestDelegate next)
{
if (endpointRouteBuilder == null)
throw new ArgumentNullException(nameof (endpointRouteBuilder));
MatcherFactory matcherFactory1 = matcherFactory;
if (matcherFactory1 == null)
throw new ArgumentNullException(nameof (matcherFactory));
this._matcherFactory = matcherFactory1;
ILogger<EndpointRoutingMiddleware> logger1 = logger;
if (logger1 == null)
throw new ArgumentNullException(nameof (logger));
this._logger = (ILogger) logger1;
DiagnosticListener diagnosticListener1 = diagnosticListener;
if (diagnosticListener1 == null)
throw new ArgumentNullException(nameof (diagnosticListener));
this._diagnosticListener = diagnosticListener1;
RequestDelegate requestDelegate = next;
if (requestDelegate == null)
throw new ArgumentNullException(nameof (next));
this._next = requestDelegate;
this._endpointDataSource = (EndpointDataSource) new CompositeEndpointDataSource((IEnumerable<EndpointDataSource>) endpointRouteBuilder.DataSources);
}
里面就做一些判断,是否服务注入了。然后值得关注的是几个新鲜事物了,比如MatcherFactory、DiagnosticListener、CompositeEndpointDataSource。
把这些都看一下吧。
internal abstract class MatcherFactory
{
public abstract Matcher CreateMatcher(EndpointDataSource dataSource);
}
CreateMatcher 通过某个端点资源,来创建一个Matcher。
这里猜测,是这样子的,一般来说客户端把某个路由都比作某个资源,也就是uri,这里抽象成EndpointDataSource,那么这个匹配器的作用是:
Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
也就是匹配出Endpoint。
那么再看一下DiagnosticListener,DiagnosticListener 源码就不看了,因为其实一个系统类,也就是system下面的类,比较复杂,直接看其描述就会。
DiagnosticListener 是一个 NotificationSource,这意味着返回的结果可用于记录通知,但它也有 Subscribe 方法,因此可以任意转发通知。 因此,其工作是将生成的作业从制造者转发到所有侦听器 (多转换) 。 通常情况下,不应 DiagnosticListener 使用,而是使用默认设置,以便通知尽可能公共。
是一个监听作用的,模式是订阅模式哈。https://docs.microsoft.com/zh-cn/dotnet/api/system.diagnostics.diagnosticlistener?view=net-6.0 有兴趣可以去看一下。
最后这个看一下CompositeEndpointDataSource,表面意思是综合性EndpointDataSource,继承自EndpointDataSource。
public CompositeEndpointDataSource(IEnumerable<EndpointDataSource> endpointDataSources) : this()
{
_dataSources = new List<EndpointDataSource>();
foreach (var dataSource in endpointDataSources)
{
_dataSources.Add(dataSource);
}
}
是用来管理endpointDataSources的,暂且不看其作用。
public Task Invoke(HttpContext httpContext)
{
Endpoint endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
EndpointRoutingMiddleware.Log.MatchSkipped(this._logger, endpoint);
return this._next(httpContext);
}
Task<Matcher> matcherTask = this.InitializeAsync();
if (!matcherTask.IsCompletedSuccessfully)
return AwaitMatcher(this, httpContext, matcherTask);
Task matchTask = matcherTask.Result.MatchAsync(httpContext);
return !matchTask.IsCompletedSuccessfully ? AwaitMatch(this, httpContext, matchTask) : this.SetRoutingAndContinue(httpContext);
async Task AwaitMatcher(
EndpointRoutingMiddleware middleware,
HttpContext httpContext,
Task<Matcher> matcherTask)
{
await (await matcherTask).MatchAsync(httpContext);
await middleware.SetRoutingAndContinue(httpContext);
}
async Task AwaitMatch(
EndpointRoutingMiddleware middleware,
HttpContext httpContext,
Task matchTask)
{
await matchTask;
await middleware.SetRoutingAndContinue(httpContext);
}
}
一段一段看:
Endpoint endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
EndpointRoutingMiddleware.Log.MatchSkipped(this._logger, endpoint);
return this._next(httpContext);
}
如果有endpoint,就直接运行下一个中间件。
Task<Matcher> matcherTask = this.InitializeAsync();
看InitializeAsync。
private Task<Matcher> InitializeAsync()
{
var initializationTask = _initializationTask;
if (initializationTask != null)
{
return initializationTask;
}
return InitializeCoreAsync();
}
接着看:InitializeCoreAsync
private Task<Matcher> InitializeCoreAsync()
{
var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
if (initializationTask != null)
{
// This thread lost the race, join the existing task.
return initializationTask;
}
// This thread won the race, do the initialization.
try
{
var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
// Now replace the initialization task with one created with the default execution context.
// This is important because capturing the execution context will leak memory in ASP.NET Core.
using (ExecutionContext.SuppressFlow())
{
_initializationTask = Task.FromResult(matcher);
}
// Complete the task, this will unblock any requests that came in while initializing.
initialization.SetResult(matcher);
return initialization.Task;
}
catch (Exception ex)
{
// Allow initialization to occur again. Since DataSources can change, it's possible
// for the developer to correct the data causing the failure.
_initializationTask = null;
// Complete the task, this will throw for any requests that came in while initializing.
initialization.SetException(ex);
return initialization.Task;
}
}
这里面作用就是创建matcher,值得注意的是这一句代码:var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
创建匹配器的资源对象是_endpointDataSource。
this._endpointDataSource = (EndpointDataSource) new CompositeEndpointDataSource((IEnumerable<EndpointDataSource>) endpointRouteBuilder.DataSources);
这个_endpointDataSource 并不是值某一个endpointDataSource,而是全部的endpointDataSource,这一点前面就有介绍CompositeEndpointDataSource。
然后看最后一段:
if (!matcherTask.IsCompletedSuccessfully)
return AwaitMatcher(this, httpContext, matcherTask);
Task matchTask = matcherTask.Result.MatchAsync(httpContext);
return !matchTask.IsCompletedSuccessfully ? AwaitMatch(this, httpContext, matchTask) : this.SetRoutingAndContinue(httpContext);
async Task AwaitMatcher(
EndpointRoutingMiddleware middleware,
HttpContext httpContext,
Task<Matcher> matcherTask)
{
await (await matcherTask).MatchAsync(httpContext);
await middleware.SetRoutingAndContinue(httpContext);
}
async Task AwaitMatch(
EndpointRoutingMiddleware middleware,
HttpContext httpContext,
Task matchTask)
{
await matchTask;
await middleware.SetRoutingAndContinue(httpContext);
}
这里面其实就表达一个意思哈,如果task完成了,然后就直接运行下一步,如果没有完成就await,总之是要执行MatchAsync然后再执行SetRoutingAndContinue。
internal abstract class Matcher
{
/// <summary>
/// Attempts to asynchronously select an <see cref="Endpoint"/> for the current request.
/// </summary>
/// <param name="httpContext">The <see cref="HttpContext"/> associated with the current request.</param>
/// <returns>A <see cref="Task"/> which represents the asynchronous completion of the operation.</returns>
public abstract Task MatchAsync(HttpContext httpContext);
}
MatchAsync 前面也提到过就是匹配出Endpoint的。
然后SetRoutingAndContinue:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task SetRoutingAndContinue(HttpContext httpContext)
{
// If there was no mutation of the endpoint then log failure
var endpoint = httpContext.GetEndpoint();
if (endpoint == null)
{
Log.MatchFailure(_logger);
}
else
{
// Raise an event if the route matched
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
{
// We're just going to send the HttpContext since it has all of the relevant information
_diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
}
Log.MatchSuccess(_logger, endpoint);
}
return _next(httpContext);
}
这里没有匹配到也没有终结哈,为什么这么做呢?因为匹配不到,下一个中间件可以做到如果没有endpoint,那么就指明做什么事情,不要一下子写死。
如何如果匹配成功了,那么发送一个事件,这个事件是DiagnosticsEndpointMatchedKey,然后打印匹配成功的一些log了。
private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
那么这里我们可以监听做一些事情,看项目需求了。
其实看到这里有两点疑问了Matcher的具体实现和EndpointDataSource 是怎么来的。
先看一下Matcher吧,这个肯定要在路由服务中去查看了。
public static IServiceCollection AddRouting(
this IServiceCollection services,
Action<RouteOptions> configureOptions)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (configureOptions == null)
{
throw new ArgumentNullException(nameof(configureOptions));
}
services.Configure(configureOptions);
services.AddRouting();
return services;
}
然后在AddRouting 中查看,这里面非常多,这里我直接放出这个的依赖注入:
services.TryAddSingleton<MatcherFactory, DfaMatcherFactory>();
那么这里进入看DfaMatcherFactory。
public override Matcher CreateMatcher(EndpointDataSource dataSource)
{
if (dataSource == null)
{
throw new ArgumentNullException(nameof(dataSource));
}
// Creates a tracking entry in DI to stop listening for change events
// when the services are disposed.
var lifetime = _services.GetRequiredService<DataSourceDependentMatcher.Lifetime>();
return new DataSourceDependentMatcher(dataSource, lifetime, () =>
{
return _services.GetRequiredService<DfaMatcherBuilder>();
});
}
查看DataSourceDependentMatcher.Lifetime:
public sealed class Lifetime : IDisposable
{
private readonly object _lock = new object();
private DataSourceDependentCache<Matcher>? _cache;
private bool _disposed;
public DataSourceDependentCache<Matcher>? Cache
{
get => _cache;
set
{
lock (_lock)
{
if (_disposed)
{
value?.Dispose();
}
_cache = value;
}
}
}
public void Dispose()
{
lock (_lock)
{
_cache?.Dispose();
_cache = null;
_disposed = true;
}
}
}
Lifetime 是生命周期的意思,里面实现的比较简单,单纯从功能上看似乎只是缓存,但是其之所以取这个名字是因为Dispose,在Lifetime结束的时候带走了DataSourceDependentCache? _cache。
为了让大家更清晰一写,DataSourceDependentCache,将其标红。
然后CreateMatcher 创建了DataSourceDependentMatcher。
return new DataSourceDependentMatcher(dataSource, lifetime, () =>
{
return _services.GetRequiredService<DfaMatcherBuilder>();
});
看一下DataSourceDependentMatcher:
public DataSourceDependentMatcher(
EndpointDataSource dataSource,
Lifetime lifetime,
Func<MatcherBuilder> matcherBuilderFactory)
{
_matcherBuilderFactory = matcherBuilderFactory;
_cache = new DataSourceDependentCache<Matcher>(dataSource, CreateMatcher);
_cache.EnsureInitialized();
// This will Dispose the cache when the lifetime is disposed, this allows
// the service provider to manage the lifetime of the cache.
lifetime.Cache = _cache;
}
这里创建了一个DataSourceDependentCache。
public DataSourceDependentCache(EndpointDataSource dataSource, Func<IReadOnlyList<Endpoint>, T> initialize)
{
if (dataSource == null)
{
throw new ArgumentNullException(nameof(dataSource));
}
if (initialize == null)
{
throw new ArgumentNullException(nameof(initialize));
}
_dataSource = dataSource;
_initializeCore = initialize;
_initializer = Initialize;
_initializerWithState = (state) => Initialize();
_lock = new object();
}
// Note that we don't lock here, and think about that in the context of a 'push'. So when data gets 'pushed'
// we start computing a new state, but we're still able to perform operations on the old state until we've
// processed the update.
public T Value => _value;
public T EnsureInitialized()
{
return LazyInitializer.EnsureInitialized<T>(ref _value, ref _initialized, ref _lock, _initializer);
}
private T Initialize()
{
lock (_lock)
{
var changeToken = _dataSource.GetChangeToken();
_value = _initializeCore(_dataSource.Endpoints);
// Don't resubscribe if we're already disposed.
if (_disposed)
{
return _value;
}
_disposable = changeToken.RegisterChangeCallback(_initializerWithState, null);
return _value;
}
}
这里Initialize可以看到调用了_initializeCore,这个_initializeCore就是传递进来的DataSourceDependentMatcher的CreateMatcher,如下:
private Matcher CreateMatcher(IReadOnlyList<Endpoint> endpoints)
{
var builder = _matcherBuilderFactory();
for (var i = 0; i < endpoints.Count; i++)
{
// By design we only look at RouteEndpoint here. It's possible to
// register other endpoint types, which are non-routable, and it's
// ok that we won't route to them.
if (endpoints[i] is RouteEndpoint endpoint && endpoint.Metadata.GetMetadata<ISuppressMatchingMetadata>()?.SuppressMatching != true)
{
builder.AddEndpoint(endpoint);
}
}
return builder.Build();
}
这里可以看到这里endpoint 必须带有ISuppressMatchingMetadata,否则将不会被匹配。
这个_matcherBuilderFactory 是:
也就是:
看一下builder:
public override Matcher Build()
{
#if DEBUG
var includeLabel = true;
#else
var includeLabel = false;
#endif
var root = BuildDfaTree(includeLabel);
// State count is the number of nodes plus an exit state
var stateCount = 1;
var maxSegmentCount = 0;
root.Visit((node) =>
{
stateCount++;
maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth);
});
_stateIndex = 0;
// The max segment count is the maximum path-node-depth +1. We need
// the +1 to capture any additional content after the 'last' segment.
maxSegmentCount++;
var states = new DfaState[stateCount];
var exitDestination = stateCount - 1;
AddNode(root, states, exitDestination);
// The root state only has a jump table.
states[exitDestination] = new DfaState(
Array.Empty<Candidate>(),
Array.Empty<IEndpointSelectorPolicy>(),
JumpTableBuilder.Build(exitDestination, exitDestination, null),
null);
return new DfaMatcher(_loggerFactory.CreateLogger<DfaMatcher>(), _selector, states, maxSegmentCount);
}
BuildDfaTree 这个是一个算法哈,这里就不介绍哈,是dfa 算法,这里理解为将endpoint创建为一颗数,有利于匹配就好。
最后返回了一个return new DfaMatcher(_loggerFactory.CreateLogger(), _selector, states, maxSegmentCount);
DfaMatcher 就是endpoint匹配器。
那么进去看DfaMatcher 匹配方法MatchAsync。
public sealed override Task MatchAsync(HttpContext httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException(nameof(httpContext));
}
// All of the logging we do here is at level debug, so we can get away with doing a single check.
var log = _logger.IsEnabled(LogLevel.Debug);
// The sequence of actions we take is optimized to avoid doing expensive work
// like creating substrings, creating route value dictionaries, and calling
// into policies like versioning.
var path = httpContext.Request.Path.Value!;
// First tokenize the path into series of segments.
Span<PathSegment> buffer = stackalloc PathSegment[_maxSegmentCount];
var count = FastPathTokenizer.Tokenize(path, buffer);
var segments = buffer.Slice(0, count);
// FindCandidateSet will process the DFA and return a candidate set. This does
// some preliminary matching of the URL (mostly the literal segments).
var (candidates, policies) = FindCandidateSet(httpContext, path, segments);
var candidateCount = candidates.Length;
if (candidateCount == 0)
{
if (log)
{
Logger.CandidatesNotFound(_logger, path);
}
return Task.CompletedTask;
}
if (log)
{
Logger.CandidatesFound(_logger, path, candidates);
}
var policyCount = policies.Length;
// This is a fast path for single candidate, 0 policies and default selector
if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector)
{
ref readonly var candidate = ref candidates[0];
// Just strict path matching (no route values)
if (candidate.Flags == Candidate.CandidateFlags.None)
{
httpContext.SetEndpoint(candidate.Endpoint);
// We're done
return Task.CompletedTask;
}
}
// At this point we have a candidate set, defined as a list of endpoints in
// priority order.
//
// We don't yet know that any candidate can be considered a match, because
// we haven't processed things like route constraints and complex segments.
//
// Now we'll iterate each endpoint to capture route values, process constraints,
// and process complex segments.
// `candidates` has all of our internal state that we use to process the
// set of endpoints before we call the EndpointSelector.
//
// `candidateSet` is the mutable state that we pass to the EndpointSelector.
var candidateState = new CandidateState[candidateCount];
for (var i = 0; i < candidateCount; i++)
{
// PERF: using ref here to avoid copying around big structs.
//
// Reminder!
// candidate: readonly data about the endpoint and how to match
// state: mutable storarge for our processing
ref readonly var candidate = ref candidates[i];
ref var state = ref candidateState[i];
state = new CandidateState(candidate.Endpoint, candidate.Score);
var flags = candidate.Flags;
// First process all of the parameters and defaults.
if ((flags & Candidate.CandidateFlags.HasSlots) != 0)
{
// The Slots array has the default values of the route values in it.
//
// We want to create a new array for the route values based on Slots
// as a prototype.
var prototype = candidate.Slots;
var slots = new KeyValuePair<string, object?>[prototype.Length];
if ((flags & Candidate.CandidateFlags.HasDefaults) != 0)
{
Array.Copy(prototype, 0, slots, 0, prototype.Length);
}
if ((flags & Candidate.CandidateFlags.HasCaptures) != 0)
{
ProcessCaptures(slots, candidate.Captures, path, segments);
}
if ((flags & Candidate.CandidateFlags.HasCatchAll) != 0)
{
ProcessCatchAll(slots, candidate.CatchAll, path, segments);
}
state.Values = RouteValueDictionary.FromArray(slots);
}
// Now that we have the route values, we need to process complex segments.
// Complex segments go through an old API that requires a fully-materialized
// route value dictionary.
var isMatch = true;
if ((flags & Candidate.CandidateFlags.HasComplexSegments) != 0)
{
state.Values ??= new RouteValueDictionary();
if (!ProcessComplexSegments(candidate.Endpoint, candidate.ComplexSegments, path, segments, state.Values))
{
CandidateSet.SetValidity(ref state, false);
isMatch = false;
}
}
if ((flags & Candidate.CandidateFlags.HasConstraints) != 0)
{
state.Values ??= new RouteValueDictionary();
if (!ProcessConstraints(candidate.Endpoint, candidate.Constraints, httpContext, state.Values))
{
CandidateSet.SetValidity(ref state, false);
isMatch = false;
}
}
if (log)
{
if (isMatch)
{
Logger.CandidateValid(_logger, path, candidate.Endpoint);
}
else
{
Logger.CandidateNotValid(_logger, path, candidate.Endpoint);
}
}
}
if (policyCount == 0 && _isDefaultEndpointSelector)
{
// Fast path that avoids allocating the candidate set.
//
// We can use this when there are no policies and we're using the default selector.
DefaultEndpointSelector.Select(httpContext, candidateState);
return Task.CompletedTask;
}
else if (policyCount == 0)
{
// Fast path that avoids a state machine.
//
// We can use this when there are no policies and a non-default selector.
return _selector.SelectAsync(httpContext, new CandidateSet(candidateState));
}
return SelectEndpointWithPoliciesAsync(httpContext, policies, new CandidateSet(candidateState));
}
这一段代码我看了一下,就是通过一些列的判断,来设置httpcontext 的 Endpoint,这个有兴趣可以看一下.
这里提一下上面这个_selector:
services.TryAddSingleton<EndpointSelector, DefaultEndpointSelector>();
看一下DefaultEndpointSelector:
internal static void Select(HttpContext httpContext, CandidateState[] candidateState)
{
// Fast path: We can specialize for trivial numbers of candidates since there can
// be no ambiguities
switch (candidateState.Length)
{
case 0:
{
// Do nothing
break;
}
case 1:
{
ref var state = ref candidateState[0];
if (CandidateSet.IsValidCandidate(ref state))
{
httpContext.SetEndpoint(state.Endpoint);
httpContext.Request.RouteValues = state.Values!;
}
break;
}
default:
{
// Slow path: There's more than one candidate (to say nothing of validity) so we
// have to process for ambiguities.
ProcessFinalCandidates(httpContext, candidateState);
break;
}
}
}
private static void ProcessFinalCandidates(
HttpContext httpContext,
CandidateState[] candidateState)
{
Endpoint? endpoint = null;
RouteValueDictionary? values = null;
int? foundScore = null;
for (var i = 0; i < candidateState.Length; i++)
{
ref var state = ref candidateState[i];
if (!CandidateSet.IsValidCandidate(ref state))
{
continue;
}
if (foundScore == null)
{
// This is the first match we've seen - speculatively assign it.
endpoint = state.Endpoint;
values = state.Values;
foundScore = state.Score;
}
else if (foundScore < state.Score)
{
// This candidate is lower priority than the one we've seen
// so far, we can stop.
//
// Don't worry about the 'null < state.Score' case, it returns false.
break;
}
else if (foundScore == state.Score)
{
// This is the second match we've found of the same score, so there
// must be an ambiguity.
//
// Don't worry about the 'null == state.Score' case, it returns false.
ReportAmbiguity(candidateState);
// Unreachable, ReportAmbiguity always throws.
throw new NotSupportedException();
}
}
if (endpoint != null)
{
httpContext.SetEndpoint(endpoint);
httpContext.Request.RouteValues = values!;
}
}
这里可以看一下Select。
如果候选项是0,那么跳过这个就没什么好说的。
如果候选项是1,那么就设置这个的endpoint。
如果匹配到多个,如果两个相同得分相同的,就会抛出异常,否则就是最后一个,也就是说我们不能设置完全相同的路由。
然后对于匹配策略而言呢,是:
感兴趣可以看一下。
HttpMethodMatcherPolicy 这个是匹配405的。
HostMatcherPolicy 这个是用来匹配host的,如果不符合匹配到的endpoint,会设置验证不通过。
结
下一节把app.UseEndpoints 介绍一下。
重新整理 .net core 实践篇——— endpoint[四十七]的更多相关文章
- 重新整理 .net core 实践篇——— filter[四十四]
前言 简单介绍一下filter 正文 filter 的种类,微软文档中写道: 每种筛选器类型都在筛选器管道中的不同阶段执行: 授权筛选器最先运行,用于确定是否已针对请求为用户授权. 如果请求未获授权, ...
- 重新整理 .net core 实践篇————配置应用[一]
前言 本来想整理到<<重新整理.net core 计1400篇>>里面去,但是后来一想,整理 .net core 实践篇 是偏于实践,故而分开. 因为是重新整理,那么就从配置开 ...
- 重新整理 .net core 实践篇————依赖注入应用[二]
前言 这里介绍一下.net core的依赖注入框架,其中其代码原理在我的另一个整理<<重新整理 1400篇>>中已经写了,故而专门整理应用这一块. 以下只是个人整理,如有问题, ...
- 重新整理 .net core 实践篇————依赖注入应用之援军[四]
前言 介绍第三方依赖注入框架Autofac,看看为我们解决什么问题. 下面介绍4个点: 命名注册 属性注册 aop 注入 子容器命名 正文 为什么我们需要使用第三方框架?第三方框架为我们做了什么?第三 ...
- 重新整理 .net core 实践篇—————日志系统之作用域[十七]
前言 前面介绍了服务与日志之间的配置,那么我们服务会遇到下面的场景会被遇到一些打log的问题. 前面我提及到我们的log,其实是在一个队列里面,而我们的请求是在并发的,多个用户同时发送请求这个时候我们 ...
- 重新整理 .net core 实践篇——— 权限中间件源码阅读[四十六]
前言 前面介绍了认证中间件,下面看一下授权中间件. 正文 app.UseAuthorization(); 授权中间件是这个,前面我们提及到认证中间件并不会让整个中间件停止. 认证中间件就两个作用,我们 ...
- 重新整理 .net core 实践篇————polly失败重试[三十四]
前言 简单整理一下polly 重试. 正文 在开发程序中一般都有一个重试帮助类,那么polly同样有这个功能. polly 组件包: polly 功能包 polly.Extensions.Http 专 ...
- 重新整理 .net core 实践篇————网关中的身份签名认证[三十七]
前言 简单整理一下网关中的jwt,jwt用于授权认证的,其实关于认证授权这块https://www.cnblogs.com/aoximin/p/12268520.html 这个链接的时候就已经写了,当 ...
- 重新整理 .net core 实践篇————跨域问题四十一]
前言 简单整理一下.net core 的跨域问题,这个以前也整理过比较详细的,故而在此简单整理一下. 正文 对跨域相对的就是同源,什么是同源呢? 协议相同(http/https) 主机(域名)相同 端 ...
随机推荐
- 2021.9.12考试总结[NOIP模拟51]
T1 茅山道术 仔细观察发现对于每个点只考虑它前面第一个与它颜色相同的点即可. 又仔细观察发现对一段区间染色后以这个区间内点为端点的区间不能染色. 于是对区间右端点而言,区间染色的贡献为遍历到区间左端 ...
- STM32单片机的学习方法(方法大体适用所有开发版入门)
1,一款实用的开发板. 这个是实验的基础,有时候软件仿真通过了,在板上并不一定能跑起来,而且有个开发板在手,什么东西都可以直观的看到,效果不是仿真能比的.但开发板不宜多,多了的话连自己都不知道该学哪个 ...
- 小白自制Linux开发板 九. 修改开机Logo
许久不见啊,今天我们继续来修改我们的系统. 通过前面的几篇文章我们已经能轻松驾驭我们的开发板了,但是现在都是追求个性化的时代,我们在开发板上打上了自己的Logo,那我们是否可以改变开机启动的Logo呢 ...
- linux中解压.tgz, .tar.gz ,zip ,gz, .tar文件
转载:https://blog.csdn.net/fu6543210/article/details/7984578 将.tgz文件解压在当前目录: tar zxvf MY_NAME.tgz 将.ta ...
- 议题解析与复现--《Java内存攻击技术漫谈》(一)
解析与复现议题 Java内存攻击技术漫谈 https://mp.weixin.qq.com/s/JIjBjULjFnKDjEhzVAtxhw allowAttachSelf绕过 在Java9及以后的版 ...
- DeWeb发展历程! 从2015年开始
有位朋友问: [高中]长兴(667499XX) 2021-01-15 15:52:11 deweb会长期做吗 我查了一下,发现deweb最早从2015开始,算起来已经做了5~6年了,目前已日臻成熟!
- clnt_create: RPC: Port mapper failure - Unable to receive: errno 113 (No route to host)
修改文件 /etc/sysconfig/nfs将#MOUNTD_PORT=892开启防火墙端口:firewalld-cmd --add-port=892/tcp
- 计算机网络漫谈之UDP和TCP
计算机网络漫谈之传输层 咱们讨论了如果需要确定一个计算机上的不同网络程序(比如QQ和浏览器),需要端口的标识,但是IP头部和帧的头部都没有端口的标识字段,需要新的协议.和前面IP协议的实现套路一样,我 ...
- robotframework-ride快捷方式打不开
我安装的是最新的RIDE2.0属于beta测试中,覆盖了3.8但仍不支持3.9 我的安装环境如下: 安装ride成功,启动ride的时候遇到了如下问题: 一:AttributeError: No at ...
- vscode输出窗口中文乱码
解决方法:开始->设置->时间和语言->其他日期.时间和区域设置->区域.更改位置->管理.更改系统区域设置->勾选->重启 完美解决!来源:https:// ...