2016-03-06 33 views
8

Sto tentando di ignorare la cultura della richiesta corrente. L'ho fatto funzionare in parte usando un custom ActionFilterAttribute.Sovrascrivere facoltativamente la cultura della richiesta tramite url/route in un'applicazione ASP.NET Core 1.0

public sealed class LanguageActionFilter : ActionFilterAttribute 
{ 
    private readonly ILogger logger; 
    private readonly IOptions<RequestLocalizationOptions> localizationOptions; 

    public LanguageActionFilter(ILoggerFactory loggerFactory, IOptions<RequestLocalizationOptions> 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); 
    } 
} 

Sul controller Io uso il LanguageActionFilter.

[ServiceFilter(typeof(LanguageActionFilter))] 
[Route("api/{culture}/[controller]")] 
public class ProductsController : Controller 
{ 
    ... 
} 

Questo funziona fino ad ora, ma ho due problemi con esso:

  1. che non mi piacciono dover dichiarare {culture} in ogni controller, come ho intenzione di bisogno su ogni percorso.
  2. Avendo una cultura predefinita non funziona con questo approccio, anche se lo dichiaro come [Route("api/{culture=en-US}/[controller]")] per ovvi motivi.

L'impostazione di un risultato di instradamento predefinito non funziona nessuno.

app.UseMvc(routes => 
{ 
    routes.MapRoute(
     name: "DefaultRoute", 
     template: "api/{culture=en-US}/{controller}" 
    ); 
}); 

Ho anche studiato in un'implementazione personalizzata IRequestCultureProvider e aggiungerlo al metodo UseRequestLocalization come

app.UseRequestLocalization(new RequestLocalizationOptions 
{ 
    RequestCultureProviders = new List<IRequestCultureProvider> 
    { 
     new UrlCultureProvider() 
    }, 
    SupportedCultures = new List<CultureInfo> 
    { 
     new CultureInfo("de-de"), 
     new CultureInfo("en-us"), 
     new CultureInfo("en-gb") 
    }, 
    SupportedUICultures = new List<CultureInfo> 
    { 
     new CultureInfo("de-de"), 
     new CultureInfo("en-us"), 
     new CultureInfo("en-gb") 
    } 
}, new RequestCulture("en-US")); 

ma poi non ho accesso alle vie di lì (suppongo causano i percorsi sono fatti più tardi nella pipeline). Ovviamente potrei anche provare ad analizzare l'url richiesto. E non so nemmeno se potrei cambiare il percorso in questo posto in modo che corrisponda al percorso sopra con la cultura in esso.

Il passaggio della coltura tramite parametro di query o la modifica dell'ordine dei parametri all'interno del percorso non è un'opzione.

Entrambi gli URL api/en-us/products come noi come api/products devono essere indirizzati allo stesso controller, in cui i primi non cambiano la cultura.

L'ordine in cui la coltura viene determinato deve essere

  1. Se definito URL, portarlo
  2. Se non definito in URL, controllare stringa di query e utilizzare tale
  3. Se non definito interrogare, controllare i cookie
  4. Se non definito nel cookie, utilizzare l'intestazione Accept-Language.

2-4 viene effettuato tramite UseRequestLocalization e ciò funziona. Inoltre, non mi piace l'approccio attuale che deve aggiungere due attributi a ciascun controller ({culture} in route e allo [ServiceFilter(typeof(LanguageActionFilter))]).

Edit: Mi piace anche a limitare il numero delle zone valide a quello impostato in SupportedCultures proprietà della RequestLocalizationOptions passato al UseRequestLocalization.

IOptions<RequestLocalizationOptions> localizationOptions nel LanguageActionFilter sopra non funziona in quanto restituisce una nuova istanza di RequestLocalizationOptions dove SupportedCultures è sempre null e non quello passato al.

FWIW è un progetto WebApi RESTful.

+0

Quindi, per quanto ho capito dalla tua domanda, stai cercando di far funzionare quel percorso di URL, sì? Che dire se definisci due percorsi? Uno con '{culture}' e uno senza di esso? – Dealdiane

+0

Non solo, mi piacerebbe evitare di usare 'LanguageActionFilter' per diversi motivi, uno è che devo ripeterlo su ogni controller e l'altro è che non ho accesso a' RequestLocalizationOptions'. Come si può vedere nell'esempio, ho provato a iniettarlo, ma ottengo sempre la versione predefinita 'RequestLocalizationOptions' con' SupportedCultures' come null, non quella che è stata passata al middleware di localizzazione della richiesta – Tseng

+0

Aggiunta di 'api/{controller}' e 'api/{culture = en-US}/{controller}' dentro 'app.UseMvc (routes => ...)' non funziona – Tseng

risposta

18

Aggiornamento ASP.Net 1.1 Nucleo

Un nuovo RouteDataRequestCultureProvider è in arrivo come parte del 1.1 release, il che significa che si spera non sarà necessario creare più il proprio provider di richiesta. Potresti comunque trovare le informazioni qui utili (come i bit di instradamento) o potresti essere interessato a creare il tuo provider di cultura di richiesta.


Puoi creare 2 percorsi che ti permetteranno di accedere ai tuoi endpoint con e senza un segmento di cultura nell'URL. Entrambe /api/en-EN/home e /api/home verranno indirizzate al controller di casa. (Quindi /api/blah/home non corrisponderà alla rotta con cultura e otterrà 404 poiché il controller blah non esiste)

Per questi percorsi di lavoro, quello che include il parametro cultura ha una preferenza maggiore e il parametro cultura include un regex:

app.UseMvc(routes => 
{ 
    routes.MapRoute(
     name: "apiCulture", 
     template: "api/{culture:regex(^[a-z]{{2}}-[A-Z]{{2}}$)}/{controller}/{action=Index}/{id?}"); 

    routes.MapRoute(
     name: "defaultApi", 
     template: "api/{controller}/{action=Index}/{id?}");     

}); 

Gli itinerari qui sopra funziona con il regolatore di stile MVC, ma se si sta costruendo un'interfaccia resto utilizzando lo stile api wb del controller, l'attributo di routing è il modo preferito in MVC 6.

  • Uno opzione è quella di utilizzare l'attributo di routing, ma usare una classe di base per tutti i controller API dove si può impostare i segmenti di base della url:

    [Route("api/{language:regex(^[[a-z]]{{2}}-[[A-Z]]{{2}}$)}/[controller]")] 
    [Route("api/[controller]")] 
    public class BaseApiController: Controller 
    { 
    } 
    
    public class ProductController : BaseApiController 
    { 
        //This will bind to /api/product/1 and /api/en-EN/product/1 
        [HttpGet("{id}")] 
        public IActionResult GetById(string id) 
        { 
         return new ObjectResult(new { foo = "bar" }); 
        } 
    } 
    
  • Un modo rapido per evitare la classe base senza bisogno di codice personalizzato troppo è attraverso la web api compatibility shim:

    • Aggiungere il pacchetto "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-rc1-final"
    • Aggiungi le convenzioni shim:

      services.AddMvc().AddWebApiConventions(); 
      
    • Assicurarsi che i controller ereditano da ApiController, che viene aggiunto dal pacchetto spessore
    • Definire i percorsi tra cui il parametro di cultura con te sovraccarico MapApiRoute:

      routes.MapWebApiRoute("apiLanguage", 
      "api/{language:regex(^[a-z]{{2}}-[A-Z]{{2}}$)}/{controller}/{id?}"); 
      
      routes.MapWebApiRoute("DefaultApi", 
      "api/{controller}/{id?}"); 
      
  • La migliore e più pulito opzione creerebbe e applicherà il tuo IApplicationModelConvention che si occuperà di aggiungere il prefisso cultura ai tuoi percorsi di attributo.Questo è fuori del campo di applicazione per questa domanda, ma mi hanno messo in atto l'idea di questo localization article

Quindi è necessario creare un nuovo IRequestCultureProvider che esaminerà la richiesta URL ed estrarre la cultura da lì (se previsto).

Dopo l'aggiornamento a ASP .Net Core 1.1 è possibile evitare l'analisi manuale dell'URL di richiesta ed estrarre il segmento di cultura.

Ho controllato il implementation of RouteDataRequestCultureProvider in ASP.Net 1.1 core, e utilizzare un metodo di estensione HttpContext GetRouteValue(string) per ottenere segmenti url all'interno del fornitore richiesta:

culture = httpContext.GetRouteValue(RouteDataStringKey)?.ToString(); 

Tuttavia ho il sospetto (non ho avuto un possibilità di provarlo ancora) che ciò funzionerebbe solo aggiungendo middleware as MVC filters. In questo modo il middleware viene eseguito dopo il middleware di routing, che è quello che aggiunge IRoutingFeature in HttpContext. Come un test rapido, aggiungendo la seguente middleware prima UseMvc ti porterà nessun dato percorso:

app.Use(async (context, next) => 
{ 
    //always null 
    var routeData = context.GetRouteData(); 
    await next(); 
}); 

Al fine di attuare il nuovo IRequestCultureProvider è sufficiente:

  • Ricerca per il parametro della cultura nel percorso dell'URL della richiesta.
  • Se non viene trovato alcun parametro, restituire null. (Se tutti i provider restituiscono null, verrà utilizzata la cultura predefinita)
  • Se viene trovato un parametro cultura, restituire un nuovo ProviderCultureResult con quella cultura.
  • localization middleware riporterà a quello predefinito se non è una delle colture supportate.

L'implementazione sarà simile:

public class UrlCultureProvider : IRequestCultureProvider 
{ 
    public Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext) 
    { 
     var url = httpContext.Request.Path; 

     //Quick and dirty parsing of language from url path, which looks like "/api/de-DE/home" 
     //This could be skipped after 1.1 if using the middleware as an MVC filter 
     //since you will be able to just call the method GetRouteValue("culture") 
     //on the HttpContext and retrieve the route value 
     var parts = httpContext.Request.Path.Value.Split('/'); 
     if (parts.Length < 3) 
     { 
      return Task.FromResult<ProviderCultureResult>(null); 
     } 
     var hasCulture = Regex.IsMatch(parts[2], @"^[a-z]{2}-[A-Z]{2}$"); 
     if (!hasCulture) 
     { 
      return Task.FromResult<ProviderCultureResult>(null); 
     } 

     var culture = parts[2]; 
     return Task.FromResult(new ProviderCultureResult(culture)); 
    } 
} 

consentono infine le funzionalità di localizzazione compreso il vostro nuovo provider come il primo nella lista dei provider supportati. Poiché vengono valutati in ordine e il primo che ottiene un risultato non nullo vince, il provider avrà la precedenza e il prossimo arriverà a the default ones (stringa di query, cookie e intestazione).

var localizationOptions = new RequestLocalizationOptions 
{ 
    SupportedCultures = new List<CultureInfo> 
    { 
     new CultureInfo("de-DE"), 
     new CultureInfo("en-US"), 
     new CultureInfo("en-GB") 
    }, 
    SupportedUICultures = new List<CultureInfo> 
    { 
     new CultureInfo("de-DE"), 
     new CultureInfo("en-US"), 
     new CultureInfo("en-GB") 
    } 
}; 
//Insert this at the beginning of the list since providers are evaluated in order until one returns a not null result 
localizationOptions.RequestCultureProviders.Insert(0, new UrlCultureProvider()); 

//Add request localization middleware 
app.UseRequestLocalization(localizationOptions, new RequestCulture("en-US")); 
+0

La lettura del parametro cultura da url funziona bene ma la richiesta di '/ api/en-EN/products' risulta in un 404. Eventuali indizi? – Tseng

+0

Dai un'occhiata alle modifiche, dal momento che lo stile dei controller Web api sta lavorando fuori dalla scatola tramite l'instradamento degli attributi, potresti dover utilizzare lo shim di compatibilità in modo da poter definire i percorsi utilizzando la tabella di routing –

+0

Dov'è RC2? O una domanda migliore: QUANDO ci sarà RC2 ??? – Vi100