2015-11-01 10 views
7

Używam następującego artykułu Addition to ASP.NET MVC Localization - Using routing do obsługi tras wielokulturowych.Dlaczego otrzymuję wyjątek "Publiczna metoda działania nie została znaleziona na kontrolerze"?

Jeśli spojrzysz na sekcję "Rejestrowanie tras", zobaczysz, że aktualne trasy są aktualizowane (w metodzie "RegisterRoutes") segmentem "{culture}".

Różnica polega na tym, że chcę zachować obecne trasy i dodać duplikat dla każdego z segmentu "{culture}", więc dla trasy takiej jak "foo/bar" otrzymam duplikat "{culture}/foo/bar ".

Widać, że upewniam się również, że nowa trasa jest najważniejsza.

public static void MapMvcMultiCultureAttributes(this RouteCollection routes, bool inheritedRoutes = true, string defaultCulture = "en-US", string cultureCookieName = "culture") 
{ 
    routes.MapMvcAttributeRoutes(inheritedRoutes ? new InheritedRoutesProvider() : null); 

    var multiCultureRouteHandler = new MultiCultureMvcRouteHandler(defaultCulture, cultureCookieName); 

    var initialList = routes.ToList(); 
    routes.Clear(); 

    foreach (var routeBase in initialList) 
    { 
     var route = routeBase as Route; 
     if (route != null) 
     { 
      if (route.Url.StartsWith("{culture}")) 
      { 
       continue; 
      } 

      var cultureUrl = "{culture}"; 
      if (!String.IsNullOrWhiteSpace(route.Url)) 
      { 
       cultureUrl += "/" + route.Url; 
      } 

      var cultureRoute = routes.MapRoute(null, cultureUrl, null, new 
      { 
       culture = "^\\D{2,3}(-\\D{2,3})?$" 
      }); 

      cultureRoute.Defaults = route.Defaults; 
      cultureRoute.DataTokens = route.DataTokens; 

      foreach (var constraint in route.Constraints) 
      { 
       cultureRoute.Constraints.Add(constraint.Key, constraint.Value); 
      } 

      cultureRoute.RouteHandler = multiCultureRouteHandler; 
      route.RouteHandler = multiCultureRouteHandler; 
     } 

     routes.Add(routeBase); 
    } 
} 

W "InheritedRoutesProvider" wygląda następująco:

private class InheritedRoutesProvider : DefaultDirectRouteProvider 
{ 
    protected override IReadOnlyList<IDirectRouteFactory> GetActionRouteFactories(ActionDescriptor actionDescriptor) 
    { 
     return actionDescriptor.GetCustomAttributes(typeof(IDirectRouteFactory), true) 
      .Cast<IDirectRouteFactory>() 
      .ToArray(); 
    } 
} 

Mój kontroler wygląda następująco:

public class MyBaseController: Controller 
{ 
    [HttpGet] 
    [Route("bar")] 
    public virtual ActionResult MyAction(){ 
    { 
     return Content("Hello stranger!"); 
    } 
} 

[RoutePrefix("foo")] 
public class MyController: MyBaseController 
{ 
} 

Mój "RegisterRoutes" metoda wygląda następująco:

public static void RegisterRoutes(RouteCollection routes) 
{ 
    routes.MapMvcMultiCultureAttributes(); 
    routes.LowercaseUrls = true; 
} 

Teraz, jeśli to zrobię:

  • /foo/bar - DZIAŁA!
  • /pl/foo/bar - HttpException Sposób działania publiczne „MyAction” nie został odnaleziony na kontrolerze „MyController”
+0

Jaka jest zawartość metody "RegisterRoutes". –

+0

@RezaAghaei Zaktualizowano. – Dan

+0

Gdzie zadeklarowałeś domyślną trasę? –

Odpowiedz

1

mogę podać przykład w jaki sposób to zrobić. Ten przykład, którego używasz, jest dość stary.

  1. Wdrożenie w kontrolerach (użycie dziedziczenia) BeginExecuteCore sposób jak poniżej:

    protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state) 
    { 
        string cultureName = RouteData.Values["culture"] as string; 
    
        if (cultureName == null) 
         cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? 
           Request.UserLanguages[0] : // obtain it from HTTP header AcceptLanguages 
           null; 
    
        // Validate culture name 
        cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe 
    
        if (RouteData.Values["culture"] as string != cultureName) 
        { 
         // Force a valid culture in the URL 
         RouteData.Values["culture"] = cultureName.ToLower(); // lower case too 
    
         // Redirect user 
         Response.RedirectToRoute(RouteData.Values); 
        } 
    
        // Modify current thread's cultures    
        Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName); 
        Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture; 
    
        return base.BeginExecuteCore(callback, state); 
    } 
    
  2. Dodaj kilka trasy

     routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 
    
         routes.MapRoute(
          name: "Custom", 
          url: "{controller}/{action}/{culture}", 
          defaults: new { culture = CultureHelper.GetDefaultCulture(), controller = "Coordinate", action = "Index" } 
    
  3. Wdrożenie klasy pomocnika kultura

publicznych klasy statyczne CultureHelper {private static readonly _cultures list = new List { "listOfClutures" };

public static bool IsRighToLeft() 
    { 
     return System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft; 
    } 

    public static string GetImplementedCulture(string name) 
    { 
     if (string.IsNullOrEmpty(name)) 
      return GetDefaultCulture(); // return Default culture 
     if (!CultureInfo.GetCultures(CultureTypes.SpecificCultures).Any(c => c.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))) 
      return GetDefaultCulture(); // return Default culture if it is invalid 
     if (_cultures.Any(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase))) 
      return name; // accept it 

     var n = GetNeutralCulture(name); 
     foreach (var c in _cultures) 
      if (c.StartsWith(n)) 
       return c; 
     return GetDefaultCulture(); // return Default culture as no match found 
    } 

    public static string GetDefaultCulture() 
    { 
     return "en-GB"; // return Default culture, en-GB 
    } 
    public static string GetCurrentCulture() 
    { 
     return Thread.CurrentThread.CurrentCulture.Name; 
    } 
    public static string GetCurrentNeutralCulture() 
    { 
     return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name); 
    } 
    public static string GetNeutralCulture(string name) 
    { 
     if (!name.Contains("-")) return name; 

     return name.Split('-')[0]; // Read first part only. E.g. "en", "es" 
    } 

    public static List<KeyValuePair<string, string>> GetImplementedLanguageNames() 
    { 
     List<KeyValuePair<string, string>> languageNames = new List<KeyValuePair<string, string>>(); 

     foreach (string culture in _cultures) 
     { 
      languageNames.Add(new KeyValuePair<string, string>(culture, CultureInfo.GetCultureInfo(culture).EnglishName)); 
     } 

     languageNames.Sort((firstPair, nextPair) => 
     { 
      return firstPair.Value.CompareTo(nextPair.Value); 
     }); 

     string currCulture = GetCurrentCulture(); 
     languageNames.Remove(new KeyValuePair<string, string>(currCulture, CultureInfo.GetCultureInfo(currCulture).EnglishName)); 
     languageNames.Insert(0, new KeyValuePair<string, string>(currCulture, CultureInfo.GetCultureInfo(currCulture).EnglishName)); 

     return languageNames; 
    } 

    public static string GetDateTimeUsingCurrentCulture(DateTime dateToConvert) 
    { 
     CultureInfo ci = new CultureInfo(GetCurrentCulture()); 
     return dateToConvert.ToString(ci.DateTimeFormat.ShortDatePattern + ' ' + ci.DateTimeFormat.ShortTimePattern); 
    } 
} 
+0

Dziękuję za przykład, ale nie chcę przekierowywać na wypadek, gdyby bieżąca trasa była inna niż podana (lub pusta), a także chciałbym, aby segment {culture} był pierwszym segmentem i pracował z dowolnym rodzajem trasa (normalna trasa lub atrybut trasy). Dzięki temu użytkownik będzie mógł uzyskać dostęp do mojej trasy z określoną kulturą lub bez niej (bez przekierowania). – Dan

0

Nie sądzę, będziesz w stanie osiągnąć z atrybutem routingu gdyż RouteCollection przekazane RegisterRoutes i jej późniejsze RouteData jest zupełnie inny i wykorzystuje wewnętrzne klasy, że nie można dostać.

Doszedłem do stwierdzenia, że ​​RouteData oczekuje klucza o nazwie "MS_DirectRouteMatches", którego wartością jest kolekcja RouteData. Google MS_DirectRouteMatches jeśli chcesz dalej kopać.

Ostatecznie zakładam, że nie ma to być punkt rozszerzenia.

Osiągniesz większy sukces, jeśli pozostaniesz przy konwencjonalnym routingu. Zauważyłem, że twój kod działał całkiem dobrze w tym scenariuszu, ale spodziewam się, że już to wiesz. Przepraszam, nie mogłem dać ci lepszego rozwiązania.

0

Z tego, co widzę, nie korzysta z nadrzędnego ActionResult. Nie jestem pewien, dlaczego tak się dzieje. Można zastąpić ActionResult w klasie pochodnej, ale wydaje mi się, że coś jest nie tak w spadku ... wina w zarządzaniu klasami.

public class MyBaseController: Controller 
{ 
    [HttpGet] 
    [Route("bar")] 
    public virtual ActionResult MyAction(){ 
    { 
     return Content("Hello stranger!"); 
    } 
} 

Więc zakładając, że to nie będzie działać:

[RoutePrefix("foo")] 
public class MyController: MyBaseController 
{ 

    [HttpGet] 
    [Route("foo")] 
    public override ActionResult MyAction(){ 
    { 
     return Content("Hello stranger!"); 
    } 
} 

bym Proponuję ten sposób:

[RoutePrefix("foo")] 
public class MyController: MyBaseController 
{ 

    [HttpGet] 
    [Route("foo")] 
    public ActionResult MyAction(){ 
    { 
     return Content("Hello stranger!"); 
    } 
} 

Ten przynajmniej powie, czy dziedziczenie jest wadliwy i można przejrzeć Twój kod.

Innym sposobem obejścia tego problemu byłoby użycie jednego kontrolera z dwiema metodami dla różnych formatów.

Powiązane problemy