2015-06-25 13 views
7

Voglio raggiungere il controller bici con questi URL:C'è un modo per avere un RoutePrefix che inizia con un parametro opzionale?

/bikes  // (default path for US) 
/ca/bikes // (path for Canada) 

Un modo per ottenere che utilizza molteplici percorso Attributi per azione:

[Route("bikes")] 
[Route("{country}/bikes")] 
public ActionResult Index() 

per tenerlo asciutto preferisco usare un RoutePrefix, ma multipla percorso prefissi non sono consentiti:

[RoutePrefix("bikes")] 
[RoutePrefix("{country}/bikes")] // <-- Error: Duplicate 'RoutePrefix' attribute  
public class BikesController : BaseController 

    [Route("")] 
    public ActionResult Index() 

ho provato utilizzando solo questo percorso Prefisso:

[RoutePrefix("{country}/bikes")] 
public class BikesController : BaseController 

Risultato:/ca/​​bici opere,/bici 404s.

Ho provato a fare campagna opzionale:

[RoutePrefix("{country?}/bikes")] 
public class BikesController : BaseController 

stesso risultato:/ca/​​moto funziona,/Biciclette per 404s.

Ho provato dando paese un valore predefinito:

[RoutePrefix("{country=us}/bikes")] 
public class BikesController : BaseController 

stesso risultato:/CA/moto funziona,/bikes 404s.

C'è un altro modo per raggiungere il mio obiettivo utilizzando Attribute Routing? (E sì, so che posso fare questa roba registrando i percorsi in RouteConfig.cs, ma è quello che non sto cercando qui).

Sto usando Microsoft.AspNet.Mvc 5.2.2.

FYI: questi sono esempi semplificati - il codice vero e proprio ha un IRouteConstraint per le {country} valori, come:

[Route("{country:countrycode}/bikes")] 

risposta

3

Sono un po 'in ritardo per la festa, ma ho una soluzione funzionante per questo problema. Si prega di trovare il mio dettagliato post sul blog su questo tema here

scrivo giù riassunto qui sotto

È necessario creare 2 file come indicato di seguito



    using System; 
    using System.Collections.Generic; 
    using System.Collections.ObjectModel; 
    using System.Web.Http.Controllers; 
    using System.Web.Http.Routing; 

    namespace _3bTechTalk.MultipleRoutePrefixAttributes { 
    public class _3bTechTalkMultiplePrefixDirectRouteProvider: DefaultDirectRouteProvider { 
     protected override IReadOnlyList GetActionDirectRoutes(HttpActionDescriptor actionDescriptor, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) { 
     return CreateRouteEntries(GetRoutePrefixes(actionDescriptor.ControllerDescriptor), factories, new [] { 
     actionDescriptor 
     }, constraintResolver, true); 
     } 

     protected override IReadOnlyList GetControllerDirectRoutes(HttpControllerDescriptor controllerDescriptor, IReadOnlyList actionDescriptors, IReadOnlyList factories, IInlineConstraintResolver constraintResolver) { 
     return CreateRouteEntries(GetRoutePrefixes(controllerDescriptor), factories, actionDescriptors, constraintResolver, false); 
     } 

     private IEnumerable GetRoutePrefixes(HttpControllerDescriptor controllerDescriptor) { 
     Collection attributes = controllerDescriptor.GetCustomAttributes (false); 
     if (attributes == null) 
     return new string[] { 
     null 
     }; 

     var prefixes = new List (); 
     foreach(var attribute in attributes) { 
     if (attribute == null) 
     continue; 

     string prefix = attribute.Prefix; 
     if (prefix == null) 
     throw new InvalidOperationException("Prefix can not be null. Controller: " + controllerDescriptor.ControllerType.FullName); 
     if (prefix.EndsWith("/", StringComparison.Ordinal)) 
     throw new InvalidOperationException("Invalid prefix" + prefix + " in " + controllerDescriptor.ControllerName); 

     prefixes.Add(prefix); 
     } 

     if (prefixes.Count == 0) 
     prefixes.Add(null); 

     return prefixes; 
     } 


     private IReadOnlyList CreateRouteEntries(IEnumerable prefixes, IReadOnlyCollection factories, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) { 
     var entries = new List (); 

     foreach(var prefix in prefixes) { 
     foreach(IDirectRouteFactory factory in factories) { 
     RouteEntry entry = CreateRouteEntry(prefix, factory, actions, constraintResolver, targetIsAction); 
     entries.Add(entry); 
     } 
     } 

     return entries; 
     } 


     private static RouteEntry CreateRouteEntry(string prefix, IDirectRouteFactory factory, IReadOnlyCollection actions, IInlineConstraintResolver constraintResolver, bool targetIsAction) { 
     DirectRouteFactoryContext context = new DirectRouteFactoryContext(prefix, actions, constraintResolver, targetIsAction); 
     RouteEntry entry = factory.CreateRoute(context); 
     ValidateRouteEntry(entry); 

     return entry; 
     } 


     private static void ValidateRouteEntry(RouteEntry routeEntry) { 
     if (routeEntry == null) 
     throw new ArgumentNullException("routeEntry"); 

     var route = routeEntry.Route; 
     if (route.Handler != null) 
     throw new InvalidOperationException("Direct route handler is not supported"); 
     } 
    } 
    } 



    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Web; 
    using System.Web.Http; 

    namespace _3bTechTalk.MultipleRoutePrefixAttributes 
    { 
     [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] 
     public class _3bTechTalkRoutePrefix : RoutePrefixAttribute 
     { 
      public int Order { get; set; } 

      public _3bTechTalkRoutePrefix(string prefix) : this(prefix, 0) { } 

      public _3bTechTalkRoutePrefix(string prefix, int order) : base(prefix) 
      { 
       Order = order; 
      }   
     } 
    } 

Una volta fatto, aperto WebApiConfig.cs e aggiungi a seguito data linea


config.MapHttpAttributeRoutes(new _3bTechTalkMultiplePrefixDirectRouteProvider()); 

Questo è tutto, ora è possibile aggiungere più prefisso di percorso nel vostro controller. Esempio di sotto



    [_3bTechTalkRoutePrefix("api/Car", Order = 1)] 
    [_3bTechTalkRoutePrefix("{CountryCode}/api/Car", Order = 2)] 
    public class CarController: ApiController { 
    [Route("Get")] 
    public IHttpActionResult Get() { 
     return Ok(new { 
     Id = 1, Name = "Honda Accord" 
     }); 
    } 
    } 

ho caricato una soluzione di lavoro here

Felice Coding :)

+0

Sembra fantastico! Penso che potresti migliorare la tua risposta aggiungendo il codice dei due file. Dall'aiuto: "Cita sempre la parte più rilevante di un link importante, nel caso in cui il sito target sia irraggiungibile o sia permanentemente offline." –

+1

@FrankvanEykelen ho aggiornato la mia risposta –

-1

io non sono sicuro se questo è il modo migliore, ma vorrei creare un aree per il Canada. e potrebbe creare un BikeController anche in Canada.

enter image description here

prossimo farò azioni dal Canada per puntare ai controller di default

make sure you take care of namespaces or you'll see error multiple controllers match the route. 


    // GET: Canada/Bike 
    //[UseUsControllerAction] 
    public ActionResult Index() 
    { 
     return new Web.Controllers.BikeController().Index(); 
    } 

Dovrete ripetere somecode seguendo questo metodo. Per attenersi al principio DRY, creerei un attributo che conosce a quale controller o azione puntare.

+2

mia domanda dovrà sostenere decine di paesi, in modo da creare decine di aree non sarà molto secco! ;-) –

0

Hai ragione che non puoi avere più prefissi di percorso, il che significa che la risoluzione di questo particolare caso d'uso non sarà semplice. Il modo migliore in cui posso pensare di ottenere quello che vuoi con la minima quantità di modifiche al tuo progetto è quello di sottoclasse il tuo controller. Per esempio:

[RoutePrefix("bikes")] 
public class BikeController : Controller 
{ 
    ... 
} 

[RoutePrefix("{country}/bikes")] 
public class CountryBikeController : BikeController 
{ 
} 

È una sottoclasse di controllo erediterà tutte le azioni da BikeController, quindi non c'è bisogno di ridefinire qualsiasi cosa, di per sé.Tuttavia, quando si tratta di generare URL e ottenere loro di andare al posto giusto, potrete sia necessario essere esplicito con il nome del controller:

@Url.Action("Index", "CountryBike", new { country = "us" } 

Oppure, se si sta utilizzando percorsi con nome, è' ll deve ignorare le vostre azioni nel controller sottoclasse in modo da poter applicare i nuovi nomi di percorso:

[Route("", Name = "CountryBikeIndex")] 
public override ActionResult Index() 
{ 
    base.Index(); 
} 

Inoltre, tenere a mente, che quando si usa parametri prefissi di percorso, tutte le azioni in tale controller dovrebbe prendere il parametro :

public ActionResult Index(string country = "us") 
{ 
    ... 
+0

Sembrava promettente, ma non riuscivo a farlo funzionare.Ho creato un nuovo progetto MVC e aggiunto un 'BikesController: Controller' e' CountryBikesController: BikesController', aggiunto 'string country =" usa "', ma i percorsi non sono riconosciuti: '@ Html.ActionLink (" Canada Bikes " , "Indice", "Bikes", nuovo {country = "canada"}, null) '==>/Bikes? Country = canada | '@ Html.ActionLink (" Canada CountryBikes "," Indice "," CountryBikes ", new {country =" canada "}, null)' ==>/CountryBikes? Country = canada. Grazie per l'aiuto! Ora ho intenzione di provare https://github.com/Dresel/RouteLocalization –

+0

Hai dimenticato di attivare il routing degli attributi molto probabilmente. Vai in RouteConfig.cs e decommenta la riga per il routing degli attributi. –

+0

No, non è così: 'routes.MapMvcAttributeRoutes();' è nella posizione corretta. –

0

È possibile utilizzare le route degli attributi con due opzioni ordinate.

public partial class GlossaryController : Controller { 

    [Route("~/glossary", Order = 2)] 
    [Route("~/{countryCode}/glossary", Order = 1)] 
    public virtual ActionResult Index() 
    { 
     return View(); 
    } 
} 

Se hai intenzione di avere percorsi specifici regione per tutte le pagine è possibile aggiungere un percorso per il percorso di configurazione sopra il default. Funzionerà solo per viste/controller senza percorsi di attributo.

routes.MapRoute(
    name: "Region", 
    url: "{countryCode}/{controller}/{action}/{id}", 
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }, 
    constraints: new { countryCode = @"\w{2}" } 
); 
+0

La mia domanda riguarda RoutePrefixAttributes sul controller, non su RouteAttributes sulle azioni. RoutePrefixAttribute non ha una proprietà Order, quindi questo non risolve il problema di errore "Attributo RoutePrefix" "duplicato". –

+1

Sto lavorando per aggiungere regioni a un sito. Ho provato tutto ciò che hai provato e non c'è un modo per farlo con RoutePrefix. Tuttavia, questo ha funzionato per me, aggiungendo i due prefissi di route ordinate laddove necessario e utilizzando un route def globale per azioni che non utilizzano attr routing. – WarrenDodsworth

0

La soluzione migliore che ho incontrato è dettagliata dalla NightOwl888 in risposta alla seguente domanda: ASP.NET MVC 5 culture in route and url. Il codice qui sotto è la versione ridotta del suo post. Funziona per me in MVC5.

Decorare ciascun controller con un solo RoutePrefix, senza segmento di cultura. All'avvio dell'applicazione, il metodo personalizzato MapLocalizedMvcAttributeRoutes aggiunge una voce di percorso localizzata per ogni azione del controller.

public class RouteConfig 
{ 
    public static void RegisterRoutes(RouteCollection routes) 
    { 
     // Omitted for brevity 

     MapLocalizedMvcAttributeRoutes(routes, "{culture}/", new { culture = "[a-z]{2}-[A-Z]{2}" }); 
    } 

    static void MapLocalizedMvcAttributeRoutes(RouteCollection routes, string urlPrefix, object constraints) 
    { 
     var routeCollectionRouteType = Type.GetType("System.Web.Mvc.Routing.RouteCollectionRoute, System.Web.Mvc"); 
     var subRouteCollectionType = Type.GetType("System.Web.Mvc.Routing.SubRouteCollection, System.Web.Mvc"); 
     var linkGenerationRouteType = Type.GetType("System.Web.Mvc.Routing.LinkGenerationRoute, System.Web.Mvc"); 
     FieldInfo subRoutesInfo = routeCollectionRouteType.GetField("_subRoutes", BindingFlags.NonPublic | BindingFlags.Instance); 
     PropertyInfo entriesInfo = subRouteCollectionType.GetProperty("Entries"); 
     MethodInfo addMethodInfo = subRouteCollectionType.GetMethod("Add"); 

     var localizedRouteTable = new RouteCollection(); 
     var subRoutes = Activator.CreateInstance(subRouteCollectionType); 
     Func<Route, RouteBase> createLinkGenerationRoute = (Route route) => (RouteBase)Activator.CreateInstance(linkGenerationRouteType, route); 

     localizedRouteTable.MapMvcAttributeRoutes(); 

     foreach (var routeCollectionRoute in localizedRouteTable.Where(rb => rb.GetType().Equals(routeCollectionRouteType))) 
     { 
      // routeCollectionRoute._subRoutes.Entries 
      foreach (RouteEntry routeEntry in (IEnumerable)entriesInfo.GetValue(subRoutesInfo.GetValue(routeCollectionRoute))) 
      { 
       var localizedRoute = CreateLocalizedRoute(routeEntry.Route, urlPrefix, constraints); 
       var localizedRouteEntry = new RouteEntry(string.IsNullOrEmpty(routeEntry.Name) ? null : $"{routeEntry.Name}_Localized", localizedRoute); 
       // Add localized and default routes and subroute entries 
       addMethodInfo.Invoke(subRoutes, new[] { localizedRouteEntry }); 
       addMethodInfo.Invoke(subRoutes, new[] { routeEntry }); 
       routes.Add(createLinkGenerationRoute(localizedRoute)); 
       routes.Add(createLinkGenerationRoute(routeEntry.Route)); 
      } 
     } 
     var routeEntries = Activator.CreateInstance(routeCollectionRouteType, subRoutes); 
     routes.Add((RouteBase)routeEntries); 
    } 

    static Route CreateLocalizedRoute(Route route, string urlPrefix, object constraints) 
    { 
     var routeUrl = urlPrefix + route.Url; 
     var routeConstraints = new RouteValueDictionary(constraints); 
     // combine with any existing constraints 
     foreach (var constraint in route.Constraints) 
     { 
      routeConstraints.Add(constraint.Key, constraint.Value); 
     } 
     return new Route(routeUrl, route.Defaults, routeConstraints, route.DataTokens, route.RouteHandler); 
    } 
} 
Problemi correlati