Home > front end >  IHttpContextAccessor.HttpContext is null after execution falls out of the UseODataBatching middlewar
IHttpContextAccessor.HttpContext is null after execution falls out of the UseODataBatching middlewar

Time:03-07

I have a simple .NET6 OData API with the default configuration with Batch enabled. The API is configured with IdentityServer from the default VS template.

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();

The Problem

After making a $batch request, when execution falls out of the UseODataBatching middleware, the HttpContext property in the IHttpContextAccessor becomes null, and this triggers a NullReferenceException in the IdentityServer middleware thus returning 500 for all requests

Requests (from Postman):

{
    "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": {}
        }
    ]
}

When removing the IdentityServer middleware the batch request gets through and returns 200 for both requests with no problems.

And as a test i wrote a test middleware 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);
        }
    }

And IHttpContextAccessor.HttpContext is also null in here.

I saw this issue on the OData repo IHttpContextAccessor.HttpContext returns null when executing within an odata batch call, but i am using the ASP.NET Core version so i don't know the difference between the two in terms of implementation.

Is there a workaround for this that i can try? Thank you for your time.

CodePudding user response:

I ended up adding a middleware that repopulates the IHttpContextAccessor manually.

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);
    }
}

The middleware should be placed after the ODataBatching middleware.

  • Related