C# 如何为某些端点编写自定义预检查中间件?
我有一个C# 如何为某些端点编写自定义预检查中间件?,c#,asp.net-core,C#,Asp.net Core,我有一个asp dotnetcoreweb服务,它公开了一些端点。对于某些端点,我想检查现有的记分卡是否可见。端点URL为: GET /api/v1/scorecard/{ScorecardId}/details GET /api/v1/scorecard/{ScorecardId}/rankings 这些端点只是示例,但它们的数量可能是十。每个端点都有自己的处理程序,如: public async Task<ScorecardDetails> Get(long scorecar
asp dotnetcore
web服务,它公开了一些端点。对于某些端点,我想检查现有的记分卡是否可见。端点URL为:
GET /api/v1/scorecard/{ScorecardId}/details
GET /api/v1/scorecard/{ScorecardId}/rankings
这些端点只是示例,但它们的数量可能是十。每个端点都有自己的处理程序,如:
public async Task<ScorecardDetails> Get(long scorecardId)
{}
public async Task<ScorecardRankings> Get(long scorecardId)
{}
公共异步任务获取(长记分卡ID)
{}
公共异步任务获取(长记分卡ID)
{}
在数据库中,有一个存储记分卡详细信息的表Scorecard
,其中有一列IsVisible
。对于在数据库中设置为IsVisible=False
的记分卡的所有scorecard
端点调用,我想返回404
。首先需要更改Get函数的返回类型,以便它们可以返回404
因此:
公共异步任务获取(长记分卡ID)
变为(伪代码):
公共异步任务获取(长记分卡ID){
如果(记分卡存在(记分卡ID)){
ScorecardDetails=GetDetails(记分卡ID);
返回Ok(详细信息);
}否则{
返回NotFound();
}
}
<代码> > p>我认为您应该考虑使用<代码> IActoFields<代码>和<代码> IasyCycActudio< <代码>。在这里,您有机会阅读已经绑定的模型中的参数,以便更好地验证它。当然,这种方式有其自身的复杂性,除非您接受我们在每个需要检查对象存在性的方法上修饰每个参数的方式。这样做相当不方便,但为了方便起见,您需要设计一个模型,允许您声明(设置或配置)目标端点,以及如何为存在性检查过程确定所需参数
这里我将介绍使用中间件的方法,就像您最初想要的一样。这听起来比使用动作过滤器更方便,但也有其自身的复杂性和不便。在中间件阶段,我们没有任何绑定到参数的数据,甚至没有任何可用的RouteData
。这意味着我们需要解析路径中的路由值(这里只有对象的id)。解析是一项复杂的工作,尤其是当我们需要加快它时。然而,我认为在这里使用Regex
是可以接受的(尽管框架代码似乎不喜欢使用Regex
来获得最佳性能)。框架代码对性能有更严格的要求,因为它是我们构建一切的平台。但在代码中,您可以在性能和易于实现之间进行权衡
首先,我们需要这样一个自定义中间件:
public class EnsureExistenceByIdFromRouteValuesMiddleware
{
readonly RequestDelegate _next;
readonly EnsureExistenceByIdFromRouteValuesOptions _options;
public EnsureExistenceByIdFromRouteValuesMiddleware(RequestDelegate next,
EnsureExistenceByIdFromRouteValuesOptions options)
{
_next = next;
_options = options;
}
public async Task Invoke(HttpContext context)
{
var serviceType = _options.ExistenceCheckingServiceType;
var routePatterns = _options.RoutePatterns;
if (serviceType != null && routePatterns != null && routePatterns.Count > 0)
{
var service = context.RequestServices.GetRequiredService(_options.ExistenceCheckingServiceType) as IExistenceCheckingService;
if (service != null)
{
var matchedRoute = routePatterns.Select(e => Regex.Match(context.Request.Path,
e ?? "",
RegexOptions.Compiled | RegexOptions.IgnoreCase,
TimeSpan.FromSeconds(3)))
.FirstOrDefault(e => e.Success);
var id = matchedRoute?.Groups?.Skip(1)?.FirstOrDefault()?.Value;
if (!string.IsNullOrEmpty(id))
{
var isExisted = await service.ExistsAsync(id);
if (!isExisted && !context.Response.HasStarted)
{
context.Response.StatusCode = 404;
if (!_options.LetMvcHandle404)
{
return;
}
}
}
}
}
await _next(context);
}
}
public interface IExistenceCheckingService
{
Task<bool> ExistsAsync(object id);
}
//this is a sample implementation (just for demo)
public class ExistenceCheckingService : IExistenceCheckingService
{
public Task<bool> ExistsAsync(object id)
{
//dummy implementation for testing, only id of 1 is existed.
return Task.FromResult(Equals(id, "1"));
}
}
关联的选项类:
public class EnsureExistenceByIdFromRouteValuesOptions
{
public IList<string> RoutePatterns { get; } = new List<string>();
public Type ExistenceCheckingServiceType { get; set; }
public bool LetMvcHandle404 { get; set; }
public EnsureExistenceByIdFromRouteValuesOptions AddRoutePattern(string pattern)
{
RoutePatterns.Add(pattern);
return this;
}
public EnsureExistenceByIdFromRouteValuesOptions ClearRoutePatterns()
{
RoutePatterns.Clear();
return this;
}
}
在启动时使用它。配置服务
:
services.EnsureExistenceByIdFromRouteValues();
在启动中使用它。配置:
public static class EnsureExistenceByIdFromRouteValuesExtensions
{
public static IServiceCollection EnsureExistenceByIdFromRouteValues(this IServiceCollection services)
{
//configure the MvcOptions to add the custom middleware
return services.Configure<MvcOptions>(o => {
o.Filters.Add(new EnsureExistenceByIdFromRouteValuesActionFilter());
}).AddScoped<IExistenceCheckingService, ExistenceCheckingService>();
}
public static IApplicationBuilder UseEnsureExistenceByIdFromRouteValuesMiddleware(this IApplicationBuilder app,
EnsureExistenceByIdFromRouteValuesOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
return app.UseMiddleware<EnsureExistenceByIdFromRouteValuesMiddleware>(options);
}
public static IApplicationBuilder UseEnsureExistenceByIdFromRouteValuesMiddleware(this IApplicationBuilder app,
Action<EnsureExistenceByIdFromRouteValuesOptions> configureOptions)
{
if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions));
var options = new EnsureExistenceByIdFromRouteValuesOptions();
configureOptions(options);
return app.UseEnsureExistenceByIdFromRouteValuesMiddleware(options);
}
//we use this filter for lately handling the 404 (set by the middleware)
class EnsureExistenceByIdFromRouteValuesActionFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context) {}
public void OnActionExecuting(ActionExecutingContext context)
{
if(context.HttpContext.Response.StatusCode == 404)
{
context.Result = new StatusCodeResult(404);
}
}
}
}
app.UseEnsureExistenceByIdFromRouteValuesMiddleware(o => {
//add your Regex patterns here
o.AddRoutePattern("/scorecard/(\\d+)/details/?$");
o.AddRoutePattern("/scorecard/(\\d+)/rankings/?$");
o.ExistenceCheckingServiceType = typeof(IExistenceCheckingService);
//setting this to true to not short-circuit right after the middleware
//the MVC middleware next will handle this (in the action filter)
//That way you will have a chance to use a custom view for 404
//(otherwise such as for Web APIs, we can let this be false as by default).
//o.LetMvcHandle404 = true;
});
注意:您需要了解正则表达式才能使用此函数。在上面的代码中,我只包含2个示例正则表达式(与问题中发布的示例路径相匹配)。正则表达式模式必须包含一个对象id的捕获组(示例模式中的(\\d+
)。这应该是第一个组(或者应该是唯一的组)。您的服务器得到的URL类似于“/api/v1/scorecard/{ScorecardId}/details”您的服务器端点是“/api/v1/scorecard”,然后您需要得到URL的其余部分以提取参数类似于“{ScorecardId}/details”。请参阅以下内容:不要担心完整的url如何映射到处理程序。
services.EnsureExistenceByIdFromRouteValues();
app.UseEnsureExistenceByIdFromRouteValuesMiddleware(o => {
//add your Regex patterns here
o.AddRoutePattern("/scorecard/(\\d+)/details/?$");
o.AddRoutePattern("/scorecard/(\\d+)/rankings/?$");
o.ExistenceCheckingServiceType = typeof(IExistenceCheckingService);
//setting this to true to not short-circuit right after the middleware
//the MVC middleware next will handle this (in the action filter)
//That way you will have a chance to use a custom view for 404
//(otherwise such as for Web APIs, we can let this be false as by default).
//o.LetMvcHandle404 = true;
});