skip to Main Content

At my workplace, I’ve been tasked with refactoring some code in an ASP.NET Core Web API application. In the application, I have multiple request models where some models include properties like UserId, ChartId, or both. For instance, the AcceptEvent model includes all these properties, while other models might only include ChartId.

I have created a service class to retrieve user claims:

public class UserClaimsService : IUserClaimsService
{
    public long ProvinceId { get; }
    public long UserId { get; }
    public long LoginChart { get; }

    public UserClaimsService(IHttpContextAccessor httpContextAccessor)
    {
        ClaimsPrincipal? user = httpContextAccessor.HttpContext.User;
        if (user.Identity.IsAuthenticated && user != null)
        {
            UserId = long.Parse(user.Claims.First(a => a.Type == ClaimTypes.NameIdentifier).Value);
            ProvinceId = long.Parse(user.Claims.First(a => a.Type == "ProvinceId").Value);
            LoginChart = long.Parse(user.Claims.First(a => a.Type == "ChartId").Value);
        }
    }
}

public interface IUserClaimsService
{
    public long ProvinceId { get; }
    public long UserId { get;  }
    public long LoginChart { get; }
}

This service is registered with AddScoped. However, I am unsure how to dynamically set these optional properties (e.g., ProvinceId, LoginChart) in the request models consistently.

Is using a global filter a good approach for dynamically setting these properties in request models? How can I achieve this, and are there any performance considerations or best practices I should be aware of?

2

Answers


  1. It should be possible to write a custom binder for request model.

    You probably use some of built-in binders, for example [FromQuery] or [FromHeader] attributes. Your custom binder could read claims from current Identity and bind a value based on claim name. More on this in article.

    Login or Signup to reply.
  2. You could use global filters to dynamically set properties in request models. to do that you can follow these below steps:

    1)Create a custom action filter that injects the IUserClaimsService and sets the relevant properties in the request models.

    Filters/SetClaimsPropertiesFilter.cs:

    using Microsoft.AspNetCore.Mvc.Filters;
    using ClaimsApi.Services;
    using ClaimsApi.Models;
    
    
        public class SetClaimsPropertiesFilter : IActionFilter
        {
            private readonly IUserClaimsService _userClaimsService;
    
            public SetClaimsPropertiesFilter(IUserClaimsService userClaimsService)
            {
                _userClaimsService = userClaimsService;
            }
    
            public void OnActionExecuting(ActionExecutingContext context)
            {
                foreach (var arg in context.ActionArguments.Values)
                {
                    if (arg is IClaimUserId claimUserId)
                    {
                        claimUserId.UserId = _userClaimsService.UserId;
                    }
                    if (arg is IClaimChartId claimChartId)
                    {
                        claimChartId.ChartId = _userClaimsService.LoginChart;
                    }
                    
                }
            }
    
            public void OnActionExecuted(ActionExecutedContext context)
            {
               
            }
        }
    

    2)Create interfaces that your request models can implement to indicate which properties should be set.

    Models/IClaimUserId.cs:

     public interface IClaimUserId
        {
            long UserId { get; set; }
        }
    

    Models/IClaimChartId.cs:

     public interface IClaimChartId
        {
            long ChartId { get; set; }
        }
    

    3)Implement Interfaces in Request Models:

    Models/AcceptEventModel.cs:

    public class AcceptEventModel : IClaimUserId, IClaimChartId
        {
            public long UserId { get; set; }
            public long ChartId { get; set; }
           
        }
    

    Models/AnotherRequestModel.cs:

     public class AnotherRequestModel : IClaimChartId
        {
            public long ChartId { get; set; }
            
        }
    

    4)Program.cs:

    using ClaimsApi.Filters;
    using ClaimsApi.Services;
    using System.Security.Claims;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    
    builder.Services.AddControllers(config =>
    {
        config.Filters.Add<SetClaimsPropertiesFilter>();
    });
    
    // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
    
    builder.Services.AddScoped<IUserClaimsService, UserClaimsService>();
    builder.Services.AddHttpContextAccessor();
    builder.Services.AddScoped<SetClaimsPropertiesFilter>();
    
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    var app = builder.Build();
    
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Use(async (context, next) =>
    {
        var userClaimsService = context.RequestServices.GetService<IUserClaimsService>();
        // This is just for testing purposes; in real scenarios, authentication middleware will set user claims.
        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, "1"),
            new Claim("ProvinceId", "2"),
            new Claim("ChartId", "3")
        };
        var identity = new ClaimsIdentity(claims, "TestAuthType");
        context.User = new ClaimsPrincipal(identity);
        await next.Invoke();
    });
    
    
    app.Run();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search