Optionally override request culture via url/route in an ASP.NET Core 1.0 Application
<p>I am trying to override the culture of the current request. I got it working partly using a custom <code>ActionFilterAttribute</code>.</p>



<pre class="lang-cs prettyprint-override"><code>public sealed class LanguageActionFilter : ActionFilterAttribute
{
    private readonly ILogger logger;
    private readonly IOptions&lt;RequestLocalizationOptions&gt; localizationOptions;

    public LanguageActionFilter(ILoggerFactory loggerFactory, IOptions&lt;RequestLocalizationOptions&gt; options)
    {
        if (loggerFactory == null)
            throw new ArgumentNullException(nameof(loggerFactory));

        if (options == null)
            throw new ArgumentNullException(nameof(options));

        logger = loggerFactory.CreateLogger(nameof(LanguageActionFilter));
        localizationOptions = options;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        string culture = context.RouteData.Values["culture"]?.ToString();

        if (!string.IsNullOrWhiteSpace(culture))
        {
            logger.LogInformation($"Setting the culture from the URL: {culture}");

#if DNX46
            System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
            System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
#else
            CultureInfo.CurrentCulture = new CultureInfo(culture);
            CultureInfo.CurrentUICulture = new CultureInfo(culture);
#endif
        }

        base.OnActionExecuting(context);
    }
}
</code></pre>

<p>On the controller I use the <code>LanguageActionFilter</code>.</p>

<pre class="lang-cs prettyprint-override"><code>[ServiceFilter(typeof(LanguageActionFilter))]
[Route("api/{culture}/[controller]")]
public class ProductsController : Controller
{
    ...
}
</code></pre>

<p>This works so far, but I have two issues with it: </p>

<ol>
<li>I don't like having to declare <code>{culture}</code> on every controller, as I am going to need it on every route. </li>
<li>I having a default culture doesn't work with this approach, even if I declare it as <code>[Route("api/{culture=en-US}/[controller]")]</code> for obvious reasons. </li>
</ol>

<p>Setting a default route results is not working neither. </p>

<pre class="lang-cs prettyprint-override"><code>app.UseMvc( routes =&gt;
{
    routes.MapRoute(
        name: "DefaultRoute",
        template: "api/{culture=en-US}/{controller}"
    );
});
</code></pre>

<p>I also investigated in a custom <code>IRequestCultureProvider</code> implementation and add it to the <code>UseRequestLocalization</code> method like </p>

<pre class="lang-cs prettyprint-override"><code>app.UseRequestLocalization(new RequestLocalizationOptions
{
    RequestCultureProviders = new List&lt;IRequestCultureProvider&gt;
    {
        new UrlCultureProvider()
    },
    SupportedCultures = new List&lt;CultureInfo&gt;
    {
        new CultureInfo("de-de"),
        new CultureInfo("en-us"),
        new CultureInfo("en-gb")
    },
    SupportedUICultures = new List&lt;CultureInfo&gt;
    {
        new CultureInfo("de-de"),
        new CultureInfo("en-us"),
        new CultureInfo("en-gb")
    }
}, new RequestCulture("en-US"));
</code></pre>

<p>but then I don't have access to the routes there (I assume cause the routes are done later in the pipeline). Of course I could also try to parse the requested url. And I don't even know if I could change the route at this place so it would match the above route with the culture in it. </p>

<p>Passing the culture via query parameter or changing the order of the parameters inside the route is not an option. </p>

<p>Both urls <code>api/en-us/products</code> as we as <code>api/products</code> should route to the same controller, where the former don't change the culture. </p>

<p>The order in which the culture will be determined should be</p>

<ol>
<li>If defined in url, take it</li>
<li>If not defined in url, check query string and use that</li>
<li>If not defined in query, check cookies</li>
<li>If not defined in cookie, use <code>Accept-Language</code> header. </li>
</ol>

<p>2-4 is done via <code>UseRequestLocalization</code> and that works. Also I don't like the current approach having to add two attributes to each controller (<code>{culture}</code> in route and the <code>[ServiceFilter(typeof(LanguageActionFilter))]</code>).</p>

<p><strong>Edit:</strong>
I also like to limit the number of valid locales to the one set in <code>SupportedCultures</code> property of the <code>RequestLocalizationOptions</code> passed to the <code>UseRequestLocalization</code>.</p>

<p><code>IOptions&lt;RequestLocalizationOptions&gt; localizationOptions</code> in the <code>LanguageActionFilter</code> above doesn't work as it returns a new instance of <code>RequestLocalizationOptions</code> where <code>SupportedCultures</code> is always <code>null</code> and not the one passed to the.</p>

<p>FWIW it's an RESTful WebApi project.</p>