dotnet / dotnet/aspnetcore

Add FallBackToDefaultCulture option to disable default‐culture fallback in RequestLocalizationMiddleware

Open
#61,784 2 comments 0 reactions 0 assignees View on GitHub
area-middleware feature-localization
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 5h
Merged PRs (30d)
276

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

When no configured `IRequestCultureProvider` (route, header, cookie, query, etc.) can determine a supported culture, the built-in `RequestLocalizationMiddleware` silently falls back to the `DefaultRequestCulture`. In some API or multi-tenant scenarios, falling back unexpectedly hides mis-configured clients or unsupported locales. I’d like an option to instead have the middleware fail fast—returning a 406 Not Acceptable—when no supported culture was found.

### Describe the solution you'd like

1. **Add a new boolean property to** `RequestLocalizationOptions`:

```csharp
///
/// Gets or sets a value indicating whether to set the request culture to the
/// when no supported culture can be determined
/// by the configured s (after any parent culture fallback).
/// Defaults to true.
///
///
/// This setting only takes effect if none of the configured providers returns a supported culture
/// (and after parent culture fallback, if is true).
/// When true, the middleware will fall back to .
/// When false, the middleware will throw ,
/// terminating the pipeline.
///
///
/// If is true (default), and none of the
/// providers determines a supported culture, the request culture is set to the default
/// (e.g., "en-US"). If it is false, the middleware throws a
/// , resulting in a 406 response.
///
public bool FallBackToDefaultCulture { get; set; } = true;
```

2. **Introduce a new exception** `RequestCultureNotSupportedException`:

```csharp
///
/// Thrown only when no supported cultures could be determined
/// and is false.
///
///
/// This exception indicates that the middleware was unable to match any of the
/// incoming culture values to the
/// or , and default fallback
/// behavior has been disabled.
///
public class RequestCultureNotSupportedException : Exception
{
///
/// Initializes a new instance of the class.
///
public RequestCultureNotSupportedException()
: base(Resources.Exception_RequestCultureNotSupported)
{
}
}
```

3. **Modify** `RequestLocalizationMiddleware.Invoke(...)` **to throw when strict mode is on**:

```csharp
///
/// Invokes the logic of the middleware.
///
/// The .
/// A that completes when the middleware has completed processing.
public async Task Invoke(HttpContext context)
{
ArgumentNullException.ThrowIfNull(context);

RequestCulture? requestCulture = null;

IRequestCultureProvider? winningProvider = null;

if (_options.RequestCultureProviders != null)
{
foreach (var provider in _options.RequestCultureProviders)
{
var providerResultCulture = await provider.DetermineProviderCultureResult(context);
if (providerResultCulture == null)
{
continue;
}
var cultures = providerResultCulture.Cultures;
var uiCultures = providerResultCulture.UICultures;

CultureInfo? cultureInfo = null;
CultureInfo? uiCultureInfo = null;
if (_options.SupportedCultures != null)
{
cultureInfo = GetCultureInfo(
cultures,
_options.SupportedCultures,
_options.FallBackToParentCultures);

if (cultureInfo == null)
{
_logger.UnsupportedCultures(provider.GetType().Name, cultures);
}
}

if (_options.SupportedUICultures != null)
{
uiCultureInfo = GetCultureInfo(
uiCultures,
_options.SupportedUICultures,
_options.FallBackToParentUICultures);

if (uiCultureInfo == null)
{
_logger.UnsupportedUICultures(provider.GetType().Name, uiCultures);
}
}

if (cultureInfo == null && uiCultureInfo == null)
{
continue;
}

cultureInfo ??= _options.DefaultRequestCulture.Culture;
uiCultureInfo ??= _options.DefaultRequestCulture.UICulture;

var result = new RequestCulture(cultureInfo, uiCultureInfo);
requestCulture = result;
winningProvider = provider;
break;
}
}

// If we found a culture OR default-fallback is allowed, continue
if (_options.FallBackToDefaultCulture || requestCulture != null)
{
requestCulture ??= _options.DefaultRequestCulture;

context.Features.Set(new RequestCultureFeature(requestCulture, winningProvider));

SetCurrentThreadCulture(requestCulture);

if (_options.ApplyCurrentCultureToResponseHeaders)
{
var headers = context.Response.Headers;
headers.ContentLanguage = requestCulture.UICulture.Name;
}

await _next(context);
return;
}

// Strict mode: no culture and no fallback → fail fast
throw new RequestCultureNotSupportedException();
}
```

4. **Example**:

```csharp
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = { new CultureInfo("fr-FR") },
SupportedUICultures = { new CultureInfo("fr-FR") },
FallBackToDefaultCulture = false
};
app.UseRequestLocalization(options);
```

### Additional context

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.