【问题标题】:Why am I receiving the exception "A public action method was not found on controller"?为什么我会收到异常“在控制器上找不到公共操作方法”?
【发布时间】:2016-02-01 18:38:21
【问题描述】:

我正在使用下面的文章Addition to ASP.NET MVC Localization - Using routing来支持多文化路线。

如果您查看“注册路线”部分,您会看到当前路线已更新(在“RegisterRoutes”方法中)使用“{culture}”部分。

不同之处在于我想保留当前路线并为每个带有“{culture}”段的路线添加一个副本,因此对于像“foo/bar”这样的路线我会得到一个重复的“{culture}/ foo/bar"。

你可以看到我也在确保新路线排在第一位。

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);
    }
}

“InheritedRoutesProvider”如下所示:

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

我的控制器如下所示:

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

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

我的“RegisterRoutes”方法如下所示:

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

现在,如果我这样做:

  • /foo/bar - 工作!
  • /en-US/foo/bar - HttpException 在控制器“MyController”上找不到公共操作方法“MyAction”

【问题讨论】:

  • 你的RegisterRoutes方法内容是什么。
  • @RezaAghaei 已更新。
  • 你在哪里声明了默认路由?
  • 没有默认路由,我只依赖属性(我不想要带有默认值的经典控制器/动作路由)。
  • 糟糕,没有看到那个控制器。

标签: c# asp.net-mvc exception routing


【解决方案1】:

我可以举一个例子来说明我会怎么做。你使用的那个例子已经很老了。

  1. 在您的控制器(使用继承)中实现 BeginExecuteCore 方法,如下所示:

    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. 添加一些路线

            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    
            routes.MapRoute(
                name: "Custom",
                url: "{controller}/{action}/{culture}",
                defaults: new { culture = CultureHelper.GetDefaultCulture(), controller = "Coordinate", action = "Index" }
    
  3. 实现一个文化辅助类

公共静态类 CultureHelper { 私有静态只读列表 _cultures = 新列表 { “文化清单” };

    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);
    }
}

【讨论】:

  • 感谢您的示例,但我不想重定向以防当前路线与指定路线不同(或为空),并且我也希望 {culture} 段是第一个分段并使用任何类型的路线(正常路线或路线属性)。因此,用户将能够使用指定的文化或不指定的文化访问我的路线(没有重定向)。
【解决方案2】:

我认为您无法通过属性路由来实现这一点,因为传递给 RegisterRoutes 的 RouteCollection 及其后续 RouteData 完全不同,并且使用您无法访问的内部类。

据我所知,RouteData 需要一个名为“MS_DirectRouteMatches”的键,其值是 RouteData 的集合。如果您想进一步挖掘,请 Google MS_DirectRouteMatches

最终,我认为这不是一个扩展点。

如果您坚持传统路线,您将获得更大的成功。我发现您的代码在这种情况下运行良好,但我希望您已经知道这一点。抱歉,我无法为您提供更好的解决方案。

【讨论】:

    【解决方案3】:

    据我所知,它没有使用父 ActionResult。我不确定为什么会这样。您可以在派生类中覆盖 ActionResult,但在我看来,继承中存在一些问题。您管理类的方式有问题。

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

    所以假设这不起作用:

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

    我建议这样做:

    [RoutePrefix("foo")]
    public class MyController: MyBaseController
    {
    
        [HttpGet]
        [Route("foo")]
        public  ActionResult MyAction(){
        {
            return Content("Hello stranger!");
        }
    }
    

    这至少会告诉你继承是否有缺陷,你可以检查你的代码。

    解决此问题的另一种方法是将一个控制器与两种方法一起用于不同的格式。

    【讨论】:

      猜你喜欢
      • 2013-06-01
      • 2013-05-15
      • 2021-02-28
      • 1970-01-01
      • 2012-05-31
      • 1970-01-01
      相关资源
      最近更新 更多