I want to make sure a session id gets added to all urls which inherit from my base controller so I don't have to do it manually. As far as I can tell everything comes back to RouteCollection.GetUrl. But I can't seem to find a way to override it's behavior
or intercept the output in any way. Any ideas?
Ok, maybe someone can clarify how Route Defaults work for me. I tried adding a default to the Route so it would pull an existing value from the query string for me. I assumed if it wasn't specified by the method call then it would return the default for
the missing item. Here's what I tried:
public class SessionIdHelper
{
public SessionIdHelper() { }
public override string ToString()
{
if (String.IsNullOrEmpty(HttpContext.Current.Request["sid"]))
return String.Empty;
All I get is http://localhost:<port>/Home.mvc/About
Why doesn't it use the default? If I break in the debugger and look at the RouteData from the context I can see a value for [sid]. But it doesn't write it out from ActionLink(). Any ideas?
The method you are trying to do it will not work. This is for static values... like
/[controller]/[action]
default controller => Home
default action => index
means that '/' will actually render /home/index, and those will always be the defaults. In your case they change per-user. What I suggest is a custom route handler.
You can do something like this:
public class SessionAppendingRouteHandler : IRouteHandler
{
public IHttpHandler GetHandler(RequestContext context)
{
SessionAppendingHttpHandler handler = new SessionAppendingHttpHandler();
handler.RequestContext = context;
return handler.
}
}
public class SessionAppendingHttpHandler : MvcHandler
{
public override ProcessRequest(RequestContext context)
{
//append your sid here
}
}
Now in your route definition, any route that you want a session id appended to, you'll do this:
RouteTable.Routes.Add( new Route
{
Url = "/[controller].mvc/[action]/",
Defaults = new { action = "index" },
RouteHandler = typeof(SessionAppendingRouteHandler)
});
And now your session id will be available on every request to that url. From here it is trivial to access this from your Controller.
Ok, here's my first run at it, for testing I'm just using DateTime.Ticks for a value. I'm able to append a value to the RouteData, but it doesn't seem to get used by anything. Is there a way to get the value to append itself to the Urls that are built inside
of methods like Html.ActionLink and RedirectToAction ? It's there, but it doesn't seem like the value is being used at all.
public class SessionAppendingRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext context)
{
SessionAppendingHttpHandler handler = new SessionAppendingHttpHandler();
handler.RequestContext = context;
return handler;
}
}
public class SessionAppendingHttpHandler : MvcHandler
{
protected override void ProcessRequest(IHttpContext httpContext)
{
RequestContext.RouteData.Values.Add("sid", DateTime.UtcNow.Ticks);
base.ProcessRequest(httpContext);
}
}
unfortunately that scenario isn't going to work for what I'm trying to do. "sid" isn't the actual sessionid. It would be much easier if I could just store it in a cookie and I wouldn't be going through all this. But what I'm trying to do is append an id
which will act as more of a sessionid for a browser instance. The users of this application like to keep multiple browser windows open at the same time and what I need to do is reset "sid" when their session state changes to prevent data corruption caused
by different browsers using separate browser instances in different areas of the application.
At the same time I need to keep track of the current "sid" and so I want it's value to be appended whenever a ActionLink is called in the page template. And so that's what I'm trying to do with this.
So does anybody know why the default value doesn't get appended to my urls? Even when I put in a route like this:
it doesn't append the default? Am I misunderstanding how defaults work? Could someone explain defaults to me and why a default doesn't end up in the url?
Okay, I misunderstood you then. But if you store the value in the cookie, that's pretty much the same as a session id, so I don't see the difference. In any case, the problem you wanted to solve was to have this appended to every request, which we have
done. Just change the code grabbing the session id to get it from somewhere else (like Request.Cookies) and you should be set.
The problem with that approach is that if I set the cookie value from one browser session then the value changes for all browser sessions. Which is why I need the value to be in the url.
In any case, I have misunderstood how default values work. Here's a quote from Scott Guthrie about default values: "The "Defaults" property on the Route class defines a dictionary of default values to use in the event that the incoming URL doesn't include
one of the parameter values specified." So defaults will be passed to an action method that expects that parameter when the value doesn't exist in the url.
What we need (if anyone is listening) is a way to hook into the url building process (RouteCollection.GetUrl) and manipulate the output. For now, I think I'll look into an extension method on HtmlHelper for methods that call GetUrl and use those in place
of the existing methods.
I'm starting to see where you are going. The Defaults (as I read it) are merely static defaults, whereas you want them to be more of a context-specific factory for defining user/request specific values.
As a follow up I wanted to post the solution I ended up with. Originally I wanted to be able to apply a fix to a single place, but as it turns out I had to comprimise with 3 places and I was able to do it without having to replace built-in framework functionality.
1) Because I my controllers were already using a base controller that inherits directly from Controller (Controller --> MembershipBaseController --> [Instance]Controller) I was able to override RedirectToAction() like this:
protected override void RedirectToAction(object values)
{
var newValues = values;
//ServerState is an instance of custom class I'm using for session state
if (ServerState != null)
{
ServerState.Save();
//here I'm able to 'append' a value for sid
newValues = new { values, sid = ServerState.Ticket };
}
//let the MVC framework handle things normally
base.RedirectToAction(newValues);
}
2, 3 & 4) I wrote my own extension functions for HtmlHelper (ActionLink & Form) and UrlHelper which wrap the methods from MVCToolkit.dll. The only part I haven't figured out is how to wrap the generic versions (ActionLink<> and Form<>) because they expect the expression to be a method call to a specific action and my actions won't have an explicit parameter for sid, so I'll have to think of something else - like parsing the result and inserting the value.
public static class SessionHelper
{
#region SessionActionpublic static String SessionAction(this UrlHelper helper, String actionName)
{
String ticket = GetSessionTicket(helper.ViewContext);
String url = helper.Action(new
{
Action = actionName,
sid = ticket
});
return url;
}
public static String SessionAction(this UrlHelper helper, String actionName, String controllerName)
{
String ticket = GetSessionTicket(helper.ViewContext);
String url = helper.Action(new
{
Controller = controllerName,
Action = actionName,
sid = ticket
});
return url;
}
public static String SessionAction(this UrlHelper helper, Object values)
{
String ticket = GetSessionTicket(helper.ViewContext);
String url = helper.Action(new
{
values,
sid = ticket
});
return url;
}
#endregion
#region SessionForm/// <summary>
/// Wraps HtmlHelper.FormExtensions to provide session state functionality.
/// </summary>public static FormExtensions.SimpleForm SessionForm(this HtmlHelper helper, String actionName, String controllerName)
{
return helper.SessionForm(actionName, controllerName, String.Empty, FormExtensions.FormMethod.post, null);
}
/// <summary>
/// Wraps HtmlHelper.FormExtensions to provide session state functionality.
/// </summary>public static FormExtensions.SimpleForm SessionForm(this HtmlHelper helper, String actionName, String controllerName,
Object htmlAttributes)
{
return helper.SessionForm(actionName, controllerName, String.Empty, FormExtensions.FormMethod.post, htmlAttributes);
}
/// <summary>
/// Wraps HtmlHelper.FormExtensions to provide session state functionality.
/// </summary>public static FormExtensions.SimpleForm SessionForm(this HtmlHelper helper, String actionName, String controllerName,
FormExtensions.FormMethod method)
{
return helper.SessionForm(actionName, controllerName, String.Empty, method, null);
}
/// <summary>
/// Wraps HtmlHelper.FormExtensions to provide session state functionality.
/// </summary>public static FormExtensions.SimpleForm SessionForm(this HtmlHelper helper, String actionName, String controllerName,
FormExtensions.FormMethod method, Object htmlAttributes)
{
return helper.SessionForm(actionName, controllerName, String.Empty, method, htmlAttributes);
}
/// <summary>
/// Wraps HtmlHelper.FormExtensions to provide session state functionality.
/// </summary>public static FormExtensions.SimpleForm SessionForm(this HtmlHelper helper, String actionName, String controllerName,
String queryString, FormExtensions.FormMethod method, Object htmlAttributes)
{
//get the session ticket from the url
String ticket = GetSessionTicket(helper.ViewContext);
//append the 'sid' parameter and set its value to the session ticketif (String.IsNullOrEmpty(queryString))
queryString = String.Format("sid={0}", ticket);
else
queryString = String.Format("{0}&sid={1}", queryString, ticket);
//forward modified values to FormExtensions.Form method and return the resultreturn helper.Form(actionName, controllerName, queryString, method, htmlAttributes);
}
#endregion
#region SessionActionLink/// <summary>
/// Wraps HtmlHelper.LinkExtensions to provide session state functionality
/// </summary>public static String SessionActionLink(this HtmlHelper helper, String linkText, String actionName, String controllerName)
{
String ticket = GetSessionTicket(helper.ViewContext);
String url = helper.ActionLink(linkText, new
{
Controller = controllerName,
Action = actionName,
sid = ticket
});
return url;
}
/// <summary>
/// Wraps HtmlHelper.LinkExtensions to provide session state functionality
/// </summary>public static String SessionActionLink(this HtmlHelper helper, String linkText, object values)
{
String ticket = GetSessionTicket(helper.ViewContext);
var newValues = new { values, sid = ticket };
String url = helper.ActionLink(linkText, newValues);
return url;
}
/// <summary>
/// Wraps HtmlHelper.LinkExtensions to provide session state functionality
/// </summary>public static String SessionActionLink(this HtmlHelper helper, String linkText, String actionName)
{
String ticket = GetSessionTicket(helper.ViewContext);
String url = helper.ActionLink(linkText, new
{
Action = actionName,
sid = ticket
});
return url;
}
#endregion/// <summary>
/// Retrieves, validates and returns session ticket ('sid') from request state
/// </summary>private static String GetSessionTicket(ViewContext context)
{
String ticket = (String)null;
if (context is SessionViewContext)
ticket = ((SessionViewContext)context).ServerCookie.Ticket;
return ticket;
}
}
I hope this helps anyone
out there who is interested.
developmenta...
Member
115 Points
65 Posts
Append value to all urls built by RouteCollection.GetUrl
Feb 07, 2008 06:31 PM|LINK
I want to make sure a session id gets added to all urls which inherit from my base controller so I don't have to do it manually. As far as I can tell everything comes back to RouteCollection.GetUrl. But I can't seem to find a way to override it's behavior or intercept the output in any way. Any ideas?
developmenta...
Member
115 Points
65 Posts
Route Defaults
Feb 07, 2008 08:07 PM|LINK
Ok, maybe someone can clarify how Route Defaults work for me. I tried adding a default to the Route so it would pull an existing value from the query string for me. I assumed if it wasn't specified by the method call then it would return the default for the missing item. Here's what I tried:
public class SessionIdHelper
{
public SessionIdHelper() { }
public override string ToString()
{
if (String.IsNullOrEmpty(HttpContext.Current.Request["sid"]))
return String.Empty;
return string.Format(HttpContext.Current.Request["sid"]);
}
}
//from the Application_Start method
RouteTable.Routes.Add(new Route
{
Url = "[controller].mvc/[action]/[sid]",
Defaults = new { action = "Index", sid = new SessionIdHelper() },
RouteHandler = typeof(MvcRouteHandler)
});
But when I call Html.ActionLink like this:
<%= Html.ActionLink("About Us", "About", "Home") %>
All I get is http://localhost:<port>/Home.mvc/About
Why doesn't it use the default? If I break in the debugger and look at the RouteData from the context I can see a value for [sid]. But it doesn't write it out from ActionLink(). Any ideas?
subdigital
Contributor
2105 Points
445 Posts
ASPInsiders
Re: Route Defaults
Feb 07, 2008 08:49 PM|LINK
The method you are trying to do it will not work. This is for static values... like
/[controller]/[action]
default controller => Home
default action => index
means that '/' will actually render /home/index, and those will always be the defaults. In your case they change per-user. What I suggest is a custom route handler.
You can do something like this:
public class SessionAppendingRouteHandler : IRouteHandler { public IHttpHandler GetHandler(RequestContext context) { SessionAppendingHttpHandler handler = new SessionAppendingHttpHandler(); handler.RequestContext = context; return handler. } } public class SessionAppendingHttpHandler : MvcHandler { public override ProcessRequest(RequestContext context) { //append your sid here } }Now in your route definition, any route that you want a session id appended to, you'll do this:
And now your session id will be available on every request to that url. From here it is trivial to access this from your Controller.
Hope this helps!
http://www.flux88.com
ASP.NET MVP
Certified ScrumMaster
ASPInsider
MCSD
developmenta...
Member
115 Points
65 Posts
Re: Route Defaults
Feb 08, 2008 12:04 PM|LINK
Ok, here's my first run at it, for testing I'm just using DateTime.Ticks for a value. I'm able to append a value to the RouteData, but it doesn't seem to get used by anything. Is there a way to get the value to append itself to the Urls that are built inside of methods like Html.ActionLink and RedirectToAction ? It's there, but it doesn't seem like the value is being used at all.
public class SessionAppendingRouteHandler : IRouteHandler { public IHttpHandler GetHttpHandler(RequestContext context) { SessionAppendingHttpHandler handler = new SessionAppendingHttpHandler(); handler.RequestContext = context; return handler; } } public class SessionAppendingHttpHandler : MvcHandler { protected override void ProcessRequest(IHttpContext httpContext) { RequestContext.RouteData.Values.Add("sid", DateTime.UtcNow.Ticks); base.ProcessRequest(httpContext); } }subdigital
Contributor
2105 Points
445 Posts
ASPInsiders
Re: Route Defaults
Feb 08, 2008 01:50 PM|LINK
I was thinking more along the lines of:
public class SessionAppendingHttpHandler : MvcHandler { protected override void ProcessRequest(IHttpContext httpContext) { if(httpContext.Request.QueryString["sid"] == null) { string url = httpContext.Request.Url.ToString(); url += url.Contains("?") ? "&" : "?"; url += "sid=" + httpContext.Response.Session.SessionID; httpContext.Response.Redirect(url); } else { base.ProcessRequest(httpContext); } } }http://www.flux88.com
ASP.NET MVP
Certified ScrumMaster
ASPInsider
MCSD
developmenta...
Member
115 Points
65 Posts
Re: Route Defaults
Feb 08, 2008 03:14 PM|LINK
unfortunately that scenario isn't going to work for what I'm trying to do. "sid" isn't the actual sessionid. It would be much easier if I could just store it in a cookie and I wouldn't be going through all this. But what I'm trying to do is append an id which will act as more of a sessionid for a browser instance. The users of this application like to keep multiple browser windows open at the same time and what I need to do is reset "sid" when their session state changes to prevent data corruption caused by different browsers using separate browser instances in different areas of the application.
At the same time I need to keep track of the current "sid" and so I want it's value to be appended whenever a ActionLink is called in the page template. And so that's what I'm trying to do with this.
So does anybody know why the default value doesn't get appended to my urls? Even when I put in a route like this:
RouteTable.Routes.Add(new Route
it doesn't append the default? Am I misunderstanding how defaults work? Could someone explain defaults to me and why a default doesn't end up in the url?{
Url = "[controller].mvc/[action]/[sid]",
Defaults = new { action = "Index", sid = "123456789" },
RouteHandler = typeof(MvcRouteHandler)
});
subdigital
Contributor
2105 Points
445 Posts
ASPInsiders
Re: Route Defaults
Feb 08, 2008 03:25 PM|LINK
Okay, I misunderstood you then. But if you store the value in the cookie, that's pretty much the same as a session id, so I don't see the difference. In any case, the problem you wanted to solve was to have this appended to every request, which we have done. Just change the code grabbing the session id to get it from somewhere else (like Request.Cookies) and you should be set.
http://www.flux88.com
ASP.NET MVP
Certified ScrumMaster
ASPInsider
MCSD
developmenta...
Member
115 Points
65 Posts
Re: Route Defaults
Feb 08, 2008 03:54 PM|LINK
The problem with that approach is that if I set the cookie value from one browser session then the value changes for all browser sessions. Which is why I need the value to be in the url.
In any case, I have misunderstood how default values work. Here's a quote from Scott Guthrie about default values: "The "Defaults" property on the Route class defines a dictionary of default values to use in the event that the incoming URL doesn't include one of the parameter values specified." So defaults will be passed to an action method that expects that parameter when the value doesn't exist in the url.
What we need (if anyone is listening) is a way to hook into the url building process (RouteCollection.GetUrl) and manipulate the output. For now, I think I'll look into an extension method on HtmlHelper for methods that call GetUrl and use those in place of the existing methods.
subdigital
Contributor
2105 Points
445 Posts
ASPInsiders
Re: Route Defaults
Feb 08, 2008 04:07 PM|LINK
I'm starting to see where you are going. The Defaults (as I read it) are merely static defaults, whereas you want them to be more of a context-specific factory for defining user/request specific values.
If you had a route like:
/[sid]/[controller]/[action]
/login => LoginController
then you might be able to have the login page construct a link like this:
Html.Link( "continue", new { Controller="Home", Action="Index", sid = GetSid() })
this GetSid() could be a code-behind method or an extension method.
Of course now you have to redirect people to /login if the sid is null, but you can utilize the routehandler from above for that.
http://www.flux88.com
ASP.NET MVP
Certified ScrumMaster
ASPInsider
MCSD
developmenta...
Member
115 Points
65 Posts
Re: Route Defaults
Feb 08, 2008 07:24 PM|LINK
As a follow up I wanted to post the solution I ended up with. Originally I wanted to be able to apply a fix to a single place, but as it turns out I had to comprimise with 3 places and I was able to do it without having to replace built-in framework functionality.
1) Because I my controllers were already using a base controller that inherits directly from Controller (Controller --> MembershipBaseController --> [Instance]Controller) I was able to override RedirectToAction() like this:
protected override void RedirectToAction(object values) { var newValues = values; //ServerState is an instance of custom class I'm using for session state if (ServerState != null) { ServerState.Save(); //here I'm able to 'append' a value for sid newValues = new { values, sid = ServerState.Ticket }; } //let the MVC framework handle things normally base.RedirectToAction(newValues); }2, 3 & 4) I wrote my own extension functions for HtmlHelper (ActionLink & Form) and UrlHelper which wrap the methods from MVCToolkit.dll. The only part I haven't figured out is how to wrap the generic versions (ActionLink<> and Form<>) because they expect the expression to be a method call to a specific action and my actions won't have an explicit parameter for sid, so I'll have to think of something else - like parsing the result and inserting the value.
I hope this helps anyone out there who is interested.