Search code examples
c#asp.net-coreasp.net-core-mvccustom-action-filter

Action filter that sets language globally in ASP.NET Core using C#


I'm trying to create an ActionFilter that will read the Accept-Language header and find if it matches any value in locale and if it does not match, use the a default value which is "en".

First of all, this is my handler:

using MediatR;
using Microsoft.EntityFrameworkCore;
using Subscription.Domain.Common;
using Subscription.Domain.Exceptions;
using Subscription.Infrastructure.Configuration;
using System.Net;

namespace Subscription.API.Application.Package.Queries.Get
{
    public class GetHandler : IRequestHandler<GetRequest, EntityResponseModel>
    {
        private readonly SubscriptionContext _context;
        private readonly IHttpContextAccessor _httpContext;

        public GetHandler(SubscriptionContext context, IHttpContextAccessor httpContext)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
            _httpContext = httpContext;
        }

        public async Task<EntityResponseModel> Handle(GetRequest request, CancellationToken cancellationToken)
        {
            var lang = _httpContext.HttpContext.Request.Headers["Accept-Language"].ToString();

            var packages = await _context.Packages.Where(x => !x.IsDeleted)
                .Select(x => new GetResponseModel()
                {
                    Id = x.Id,
                    BrandId = x.BrandId,
                    StartAt = x.StartAt,
                    IconUrl = x.IconUrl,
                    ImageUrl = x.ImageUrl,
                    CreatedAt = x.CreatedDate,
                    Title = x.PackageTranslations.FirstOrDefault(pt => pt.PackageId == x.Id && pt.Locale.Equals(lang)).Title,
                    Description = x.PackageTranslations.FirstOrDefault(pt => pt.PackageId == x.Id && pt.Locale.Equals(lang)).Description

                }).ToListAsync();

            if (!packages.Any())
            {
                throw new DomainException(HttpStatusCode.NotFound, "No record found.");
            }

            return new EntityResponseModel()
            {
                Data = packages
            };
        }
    }
}

And this is the controller:

using Microsoft.AspNetCore.Mvc;
using MediatR;
using Subscription.API.Application.Package.Queries.Get;
using Subscription.API.Application.Bundle.Queries.GetById;
using Subscription.API.Filters;

namespace Subscription.API.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PackagesController : ControllerBase
    {
        private readonly IMediator _mediator;

        public PackagesController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        [ValidateHeaders]
        public async Task<ActionResult> GetAll()
        {
            var response = await _mediator.Send(new GetRequest() {  });

            return Ok(response);
        }
    }
}

This the filter class:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;

namespace Subscription.API.Filters
{
    public class ValidateHeaders : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            var headers = context.HttpContext.Request.Headers;
            // need to do something here
        }
    }
}

So, basically what I need is to create a filter that:

  1. Check the Accept-Language header if it have a value that matches locale
  2. If it does not have a value that matches it, return string with the default value which is "en"

Any idea what I should do?


Solution

  • You can do this in 2 different ways;

    • Action Filter
    • Middleware

    1.Action Filter

    If you use this action filter, you have to write this attribute on each endpoint.

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Filters;
    
    namespace Subscription.API.Filters
    {
        public class ValidateHeaders : ActionFilterAttribute
        {
            public override void OnActionExecuting(ActionExecutingContext context)
            {
                var languageHeader = context.HttpContext.Request.Headers.AcceptLanguage;
    
                if (string.IsNullOrWhiteSpace(languageHeader))
                   context.HttpContext.Request.Headers.AcceptLanguage = "en";
            }
        }
    }
    

    2.Middleware

    If you write it as middleware, it will be enough to place this middleware in the first place in the request pipeline.

    public class ValidateHeadersMiddleware
    {
        private readonly RequestDelegate _next;
    
        public ValidateHeadersMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task Invoke(HttpContext context)
        {
            var languageHeader = context.Request.Headers.AcceptLanguage;
    
            if (string.IsNullOrWhiteSpace(languageHeader))
                context.Request.Headers.AcceptLanguage = "en";
    
            //Continue processing
            if (_next != null)
                await _next.Invoke(context);      
        }
    }
    

    This will effect all requests.

    public static class MiddlewareExtension
    {
        public static IApplicationBuilder UseHeaderValidation(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<ValidateHeadersMiddleware>();
        }
    }
    

    In the Configure method in Startup.cs or minimal api configuration in Program.cs, use this middleware;

    ...
      
    var app = builder.Build();
    
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHeaderValidation();
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    
    ...