michasaucer
michasaucer

Reputation: 5248

Reading HttpContext.Request as object?

My base Request class looks like this:

public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}

public abstract class QueryBase<T> : UserContext, IRequest<T> // IRequest is MediatR interface
{
}

public abstract class UserContext
{
    public string ApplicationUserId { get; set; } // and other properties
}

I want to write a middleware to my .NET Core 3.1 WebApi that will grab JWT from request header amd read ApplicationUserId from it. I started to code something:

public class UserInformation
{
    private readonly RequestDelegate next;

    public UserInformation(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var jwt = context.Request.Headers["Authorization"];
        // read jwt here
        var userContext = (UserContext)context.Request.Body; // i know it wont work
        userContext.ApplicationUserId = //whats next? Any ideas?

        await this.next(context);
    }
}

But to be honest i have no idea how to start so here are my questions:

As you can see, every request will be packed with my UserContext class and so on. How to cast HttpContext.Request.Body to my request object and attach ApplicationUserId to it? Is it possible? I want to acces to user credentials from my JWT from headers and i want to have that information in every request in my API (pass it to controller, then to command etc).

If getting this information from middleware is not the best practice, what is?

EDIT: Mcontroller that using MediatR:

// base controller:
[ApiController]
[Route("[controller]")]
public abstract class BaseController : ControllerBase
{
    private IMediator mediator;

    protected IMediator Mediator => this.mediator ?? (this.mediator = HttpContext.RequestServices.GetService<IMediator>());
}

// action in ProjectControlle

[HttpGet]
[Authorize]
public async Task<ActionResult<ProjectsListModel>> GetAllProjects()
{
    return Ok(await base.Mediator.Send(new GetAllProjectsQuery()));
}

// query:
public class GetAllProjectsQuery : QueryBase<ProjectsListModel>
{
}

// handler:

public class GetAllProjectsQueryHandler : IRequestHandler<GetAllProjectsQuery, ProjectsListModel>
{
    private readonly IProjectRepository projectRepository;

    public GetAllProjectsQueryHandler(IProjectRepository projectRepository)
    {
        this.projectRepository = projectRepository;
    }

    public async Task<ProjectsListModel> Handle(GetAllProjectsQuery request, CancellationToken cancellationToken)
    {
        var projects = await this.projectRepository.GetAllProjectsWithTasksAsync();

        return new ProjectsListModel
        {
            List = projects
        };
    }
}

Upvotes: 0

Views: 2475

Answers (1)

weichch
weichch

Reputation: 10065

You might not need a middleware, but need a model binder:

See: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1

Also see: https://learn.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding?view=aspnetcore-3.1

public class UserContextModelBinder : IModelBinder
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IModelBinder _defaultModelBinder;

    public UserContextModelBinder(
        IHttpContextAccessor httpContextAccessor,
        IOptions<MvcOptions> mvcOptions,
        IHttpRequestStreamReaderFactory streamReaderFactory)
    {
        _httpContextAccessor = httpContextAccessor;
        _defaultModelBinder = new BodyModelBinder(mvcOptions.Value.InputFormatters, streamReaderFactory);
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (!typeof(UserContext).IsAssignableFrom(bindingContext.ModelType))
        {
            return;
        }

        await _defaultModelBinder.BindModelAsync(bindingContext);

        if (bindingContext.Result.IsModelSet && bindingContext.Result.Model is UserContext)
        {
            var model = (UserContext)bindingContext.Result.Model;
            var httpContext = _httpContextAccessor.HttpContext;

            // Read JWT
            var jwt = httpContext.Request.Headers["Authorization"];

            model.ApplicationUserId = jwt;

            bindingContext.Result = ModelBindingResult.Success(model);
        }
    }
}

Then add model binder to UserContext class:

[ModelBinder(typeof(UserContextModelBinder))]
public abstract class UserContext
{
    public string ApplicationUserId { get; set; }
}

Also add IHttpContextAccessor to services in Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.AddHttpContextAccessor();
}

Upvotes: 1

Related Questions