Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace some segments' values preserving others?

To continue with one of my previous questions (Unexpected route chosen while generating an outgoing url), please, consider the following route (no defaults, no constraints):

"{controller}/{month}-{year}/{action}/{user}"

Suppose while rendering some page via an incoming url matching this route (so the request context contains values for each of the route segments), I need to generate a url which changes only month and year, preserving the exact values of all other segments.

According to the rule, I mentioned in the linked question (i.e. The routing system will reuse values only for segment variables that occur earlier in the URL pattern than any parameters that are supplied.), if I specify the new month and year only through an anonymous object, I'll lose the value of the user segment (i.e. the route will not even match).

I'm aware of two ways to overcome this:

  1. Do not specify an anonymous object at all; but set the necessary segment values right in the context.RouteData.Values; after generating the desired url, return the original values back, for the rest page rendering to be executed with the original request segment values; this would look like this:

    public static MvcHtmlString GetDateUrl(this RequestContext context, 
                                           DateTime date)
    {
        UrlHelper url = new UrlHelper(context);
    
        object month = null, year = null;
        if (context.RouteData.Values.ContainsKey("month"))
        {
            month = context.RouteData.Values["month"];
            year = context.RouteData.Values["year"];
        }
    
        try
        {
            context.RouteData.Values["month"] = date.Month;
            context.RouteData.Values["year"] = date.Year;
            return MvcHtmlString.Create(
                url.Action((string)context.RouteData.Values["action"]));
        }
        finally
        {
            if (month == null)
            {
                if (context.RouteData.Values.ContainsKey("month"))
                {
                    context.RouteData.Values.Remove("month");
                    context.RouteData.Values.Remove("year");
                }
            }
            else
            {
                context.RouteData.Values["month"] = month;
                context.RouteData.Values["year"] = year;
            }
        }
    }
    
  2. Instead of an anonymous object, use a RouteValueDictionary, based on the current request context:

    public static MvcHtmlString GetDateUrl(this RequestContext context, 
                                           DateTime date)
    {
        UrlHelper url = new UrlHelper(context);
    
        var values = new RouteValueDictionary(context.RouteData.Values);
        values["month"] = date.Month;
        values["year"] = date.Year;
    
        string action = (string)context.RouteData.Values["action"];
    
        return MvcHtmlString.Create(url.Action(action, values));
    }
    

The second option seems to be more short-spoken, while the first does not create an extra dictionary, manipulating the request context.

Which way is better? Or maybe I am doing/understanding something way wrong?

Sanderson S., Freeman A. - Pro ASP.NET MVC 3 Framework (3rd edition) says not to rely on segment values reuse, but do specify all the values needed at the exact place and time.

I showed the way I found to do this task, but what is the correct (or simply a better) way to replace segments' values, preserving others, if the replaced segments occur earlyer in the route?

EDIT

Based on the answer by Jani Hyytiäinen. The route in question is only an example. The target mvc application contains different route, some may be more complicated then this one. So the approach I am searching for should be able to work with any of them. I'd say this extension method only knows, it has to change month and year, while preserve all other segments. The number of other segment is arbitrary. So hardcoding all of them is not a possible solution at all. Besides I have source of the current segment values except the request context itself.

like image 625
horgh Avatar asked Nov 03 '22 07:11

horgh


1 Answers

public static MvcHtmlString GetDateUrl(this RequestContext context, DateTime date)
{
    var values = context.RouteData.values;

    var data = new{
        controller = values["controller"]  ?? "Home";
        action = values["action"]  ?? "Index";
        month = date.Month;
        year = date.Year;
        user = context.HttpContext.User.Identity.Name;
    };

    string url = UrlHelper.RouteUrl(data);
    return MvcHtmlString.Create(url);
}

Why make it any more complicated? You could argue that you'd want to do some null checking on the HttpContext, but the day you have a RequestContext without HttpContext, a null check here is least of your problems. Either that or then you have a programmer doing something uberly funky and you DO want the application to blow in all it's glory right there and let them know they're doing something that don't work and they should stop right there before it's in the production.

You also might be temped to do additional checking for whatever else here, but in reality, this method's responsibility ends here. It's supposed to take in a date and return a route url as MvcHtmlString. Period.

From performance point, you could explicitly define a specific route name in here when generating the url, but for that to make any sense, you'd need to have quite a large route collection. Considering you'd be creating a hidden dependency between this method and the route configuration. The day someone changes the route names, the app blows up and it becomes hard to find why.

You might also want to use a RouteValueDictionary, but there's no reason. If I remember correctly, UrlHelper will internally use object anyway. Even if I remembered it wrong, it's well capable of converting it for you.

References: "do specify all the values needed at the exact place and time", Sanderson S., Freeman A. - Pro ASP.NET MVC 3 Framework

http://msdn.microsoft.com/en-us/library/dd505226(v=vs.108).aspx

like image 172
Jani Hyytiäinen Avatar answered Nov 08 '22 23:11

Jani Hyytiäinen