0

我有一个简单的 .NET6 OData API,默认配置启用批处理。API 使用默认 VS 模板中的 IdentityServer 进行配置。

Program.cs

var builder = WebApplication.CreateBuilder(args);

if (builder.Configuration.GetSection("ConnectionStrings:Active").Value == "Postgres")
{
    var connectionString = builder.Configuration.GetConnectionString("Postgres");
    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseNpgsql(connectionString));
}
else
{
    var connectionString = builder.Configuration.GetConnectionString("SQLServer");
    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(connectionString));
}

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
      {
          options.SignIn.RequireConfirmedAccount = true;
          options.Password.RequiredLength = 8;
      })
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddIdentityServer(options =>
{
    options.UserInteraction.LoginUrl = "/login";
    options.UserInteraction.LogoutUrl = "/logout";
})
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddLocalization();

builder.Services.AddControllersWithViews(options =>
    {
        options.ModelBinderProviders.Insert(0, new CustomModelBinderProvider());
    })
.AddOData(opt =>
{
    var batchHandler = new DefaultODataBatchHandler();
    batchHandler.MessageQuotas.MaxNestingDepth = 2;
    batchHandler.MessageQuotas.MaxReceivedMessageSize = 100;
    batchHandler.MessageQuotas.MaxOperationsPerChangeset = 10;

    opt.AddRouteComponents("oapi",
        new OdataModelBuilder().GetEDM(),
       services => services.AddSingleton<ISearchBinder, ODataSearch>());

    opt.AddRouteComponents("oapi_b",
       new OdataModelBuilder().GetEDM(),
       batchHandler);

    opt.EnableQueryFeatures();
})
.AddDataAnnotationsLocalization(options =>
{
    options.DataAnnotationLocalizerProvider = (type, factory) =>
        factory.Create(typeof(SharedResources));
})
.AddRazorRuntimeCompilation();

builder.Services.AddRazorPages();

builder.Services.AddAutoMapper(System.Reflection.Assembly.GetExecutingAssembly());

builder.Services.AddHttpContextAccessor();

if (builder.Environment.IsDevelopment())
{
    builder.Services.AddScoped<DummySeedService>();
}

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
    app.UseWebAssemblyDebugging();
    app.UseODataRouteDebug();

    using (var scope = app.Services.CreateScope())
    {
        var seedService = scope.ServiceProvider.GetRequiredService<DummySeedService>();
        await seedService.Seed();
    }
}
else
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();

app.UseODataBatching();
app.UseRouting();

app.UseMiddleware<ODataTestMiddleware>();

var supportedCultures = new[] { "en-US", "ar-SY" };
var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportedCultures[0])
    .AddSupportedCultures(supportedCultures)
    .AddSupportedUICultures(supportedCultures);

app.UseRequestLocalization(localizationOptions);

app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();

app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");

app.Run();

问题

发出$batch请求后,当执行掉出UseODataBatching中间件时,中间件中的HttpContext属性IHttpContextAccessor变为null,这会触发中间件NullReferenceException中的a,IdentityServer从而500为所有请求返回

Requests(来自邮递员):

{
    "requests": [
        {
            "id": "{{$guid}}",
            "url": "Patients(1)",
            "method": "GET",
            "headers": {
                "content-type": "application/json"
            }
        },
        {
            "id": "{{$guid}}",
            "url": "Patients(2)",
            "method": "GET",
            "headers": {
                "content-type": "application/json"
            }
        }
    ]
}

Responses

{
    "responses": [
        {
            "id": "8a4dac81-2662-472b-bef9-273401a53cfb",
            "status": 500,
            "headers": {}
        },
        {
            "id": "933c6bbe-db67-4526-8199-2eedc176dc7b",
            "status": 500,
            "headers": {}
        }
    ]
}

删除IdentityServer中间件时,批处理请求通过并返回200两个请求都没有问题。

作为测试,我写了一个测试中间件ODataTestMiddleware

  public class ODataTestMiddleware
    {
        private readonly RequestDelegate requestDelegate;

        public ODataTestMiddleware(RequestDelegate requestDelegate)
        {
            this.requestDelegate = requestDelegate;
        }

        public async Task InvokeAsync(HttpContext httpContext, IHttpContextAccessor contextAccessor)
        {
            await requestDelegate(httpContext);
        }
    }

并且IHttpContextAccessor.HttpContext在这里也是空的。

我在 OData 存储库中看到了这个问题IHttpContextAccessor.HttpContext 在 odata 批处理调用中执行时返回 null,但我使用的是ASP.NET Core版本,所以我不知道两者在实现方面的区别。

有没有我可以尝试的解决方法?感谢您的时间。

4

1 回答 1

0

我最终添加了一个IHttpContextAccessor手动重新填充的中间件。

public class ODataHttpContextFixer
{
    private readonly RequestDelegate requestDelegate;

    public ODataHttpContextFixer(RequestDelegate requestDelegate)
    {
        this.requestDelegate = requestDelegate;
    }

    public async Task InvokeAsync(HttpContext httpContext, IHttpContextAccessor contextAccessor)
    {
        contextAccessor.HttpContext ??= httpContext;

        await requestDelegate(httpContext);
    }
}

中间件应该放在ODataBatching中间件之后。

于 2022-03-04T04:31:36.323 回答