3

I have a small problem with ASP.NET and MVC5 (I am beginning with MVC.NET). Until yesterday, everything worked great! But today, I don't know why, I have problems with "Custom routes Annotations".

I have a Controler "Prodouits" with this Action :

[Route("{culture}/Produits/{nom_groupe}/{nom}-{id}")]
public ActionResult Detail(string nom_groupe, string nom, string id)
{
    // ...   
    return View();
}

In my views, when I call "Url.Action(...)", the URL is good. But when I go on the page, RouteData seems to be bad (RouteData are not restored correctly).

Look at my RouteData : Keys[0] => "MS_DirectRouteMatches" Keys[1] => "controller"

Values[0] => //A list of only 1 RouteData with my 6 parameters into it...
Values[1] => Produits

If I delete my "Custom Route Annontation", everything works great but the URL is very sad...

Does someone have an idea about the problem and the solution ? Thanks all for help!

EDIT: More information about the problem. I have a "BaseController" for the language. I override the "BeginExecuteCore" method. There is the code :

protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
    string cultureName = RouteData.Values["culture"] as string;

    // Attempt to read the culture cookie from Request
    if (cultureName == null)
        cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? Request.UserLanguages[0] : null; // obtain it from HTTP header AcceptLanguages

    // 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.ToLowerInvariant(); // 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);
}

Thank you for help :-)

EDIT: Nobody has a solution? Thank you again!

user3865244
  • 61
  • 1
  • 7
  • Is your `RouteData` a `RouteCollectionRoute`? If so then this question might be useful: http://stackoverflow.com/q/22416561 – Dan Jacka Oct 03 '14 at 02:20

2 Answers2

11

For attribute routing the route values are stored in a nested route key named "MS_DirectRouteMatches" (as you already found out yourself).

Here's how to make RouteData work as expected when working with Attribute Routing:

protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
    // DEMO: now cultureNameTest will be null
    string cultureNameTest = RouteData.Values["culture"] as string; 

    var routeData = RouteData;
    if (routeData.Values.ContainsKey("MS_DirectRouteMatches"))
    {
        routeData = ((IEnumerable<System.Web.Routing.RouteData>)RouteData.Values["MS_DirectRouteMatches"]).First();
    }

    // Now cultureName will not be null
    cultureName = routeData.Values["culture"] as string; 

I found this solution here: https://stackoverflow.com/a/29083492/533460

Community
  • 1
  • 1
Frank van Eykelen
  • 1,926
  • 1
  • 23
  • 31
1

You want to ensure that the segment variable {culture} is defined when rendering the link on the page. I suspect that is why the call to generate the action link might be failing

The MVC routing system when generating an outgoing route url, always ensure's that it can establish a value for every required segment variable in a route URL for the route to be a match. If you cannot always guarantee this, one way might be to redesign your link with a default value for the {culture} segment variable like so:

[Route("{culture=en-us}/Produits/{nom_groupe}/{nom}-{id}")]

Another option you could try is to define 2 routes on the same action method like so:

        [Route("{culture}/Produits/{nom_groupe}/{nom}-{id}")]
        [Route("Produits/{nom_groupe}/{nom}-{id}")]
        public ActionResult Detail(string nom_groupe, string nom, string id, string culture = null)
        {
           if(string.IsNullOrWhitespace(culture))    {
              culture = //Resolve a value for culture here
            }
            // ...   
            return View();
        }

This way you are getting the best of both worlds where if you cannot provide a value for the {culture} segment variable then it will match against the second attribute route mapping and you can just resolve a value in the action method.

ptat
  • 89
  • 1
  • 3