.Net Core Middleware - Getting Form Data from Request
Asked Answered
A

1

11

In a .NET Core Web Application I am using middleware (app.UseMyMiddleware) to add some logging on each request:

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler(MyMiddleware.GenericExceptionHandler);
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseMyMiddleware();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
        public static void UseMyMiddleware(this IApplicationBuilder app)
        {
            app.Use(async (context, next) =>
            {
                await Task.Run(() => HitDetails.StoreHitDetails(context));
                await next.Invoke();
            });
        }
        public static void StoreHitDetails(HttpContext context)
        {
            var config = (IConfiguration)context.RequestServices.GetService(typeof(IConfiguration));
            var settings = new Settings(config);
            var connectionString = config.GetConnectionString("Common");
            var features = context.Features.Get<IHttpRequestFeature>();
            var url = $"{features.Scheme}://{context.Request.Host.Value}{features.RawTarget}";

            var parameters = new
            {
                SYSTEM_CODE = settings.SystemName,
                REMOTE_HOST = context.Connection.RemoteIpAddress.ToString(),
                HTTP_REFERER = context.Request.Headers["Referer"].ToString(),
                HTTP_URL = url,
                LOCAL_ADDR = context.Connection.LocalIpAddress.ToString(),
                AUTH_USER = context.User.Identity.Name
            };

            using (IDbConnection db = new SqlConnection(connectionString))
            {
                db.Query("StoreHitDetails", parameters, commandType: CommandType.StoredProcedure);
            }
        }

This all works fine and I can grab most of what I need from the request but what I need next is the Form Data on a POST method.

context.Request.Form is an available option but when debugging I hover over it and see "The function evaluation requires all thread to run". If I try to use it the application just hangs.

What do I need to do to access Request.Form or is there an alternative property with POST data that I'm not seeing?

Archerfish answered 9/5, 2019 at 8:22 Comment(2)
That is probably because you are using Task.Run which will run your StoreHitDetails on a ThreadPool, and that's why you are seeing that message when you hover over Request.Form. Why don't you move all the logic in the StoreHitDetails to UseMyMiddleware? Or try using Request.Form after await Task.Run(() => HitDetails.StoreHitDetails(context)); is finished. I hope that helps.Favourite
Aha of course! Unfortunately I can't move the StoreHitDetails logic as it is reused elsewhere. This middleware doesn't have to be in a threadpool, it's just the only way I could get everything (bar Form) to work. Any idea how I write it without the async/await calls? Thanks,Archerfish
F
13

You can create a separate middleware rather than an inline one and then call the HitDetails.StoreHitDetails from there.

public class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task Invoke(HttpContext context)
    {
        HitDetails.StoreHitDetails(context);

        await _next(context);
    }
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class MiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

That way you can continue using app.UseMyMiddleware(); and you don't have to run it using Task.Run as you mentioned.

Or you can just try calling HitDetails.StoreHitDetails(context) without wrapping it in Task.Run

Edited

Check if your Request has a correct content type:

if (context.Request.HasFormContentType)
{
    IFormCollection form;
    form = context.Request.Form; // sync
    // Or
    form = await context.Request.ReadFormAsync(); // async

    string param1 = form["param1"];
    string param2 = form["param2"];
 }
Favourite answered 9/5, 2019 at 9:56 Comment(6)
Although this compiles correctly and doesn't specifically call await on HitDetails, the Form property is still thread locked for some reason. I even tried removing the async and await from the Task method but got the same results.Archerfish
Does it only happen during the debugging? Have you tried running it without the application being debugged?Favourite
It appears to not hang but all that's logged is __RequestVerificationToken and UserID properties. Am I looking in the wrong place for POST data?Archerfish
It could be something to do with the wrong Content-Type. I have edited the answer and added a check to see if the Request has the FormContentType i.e. application/x-www-form-urlencoded. Also, you could utilise the await httpContext.Request.ReadFormAsync(); method to read the form values asynchronously.Favourite
The reason is, that by design, the Form property will try to read the request body if it has not been read yet. This will fail if the request did not have the correct Content-TypeFavourite
Also, that's why you are still getting the error "the function evaluation requires all thread to run" because it's waiting for the Request body to be read. So, Task.Run could be a red-herring as I originally mentioned.Favourite

© 2022 - 2024 — McMap. All rights reserved.