RenderAction with ASP.NET MVC 3 Sessionless Controllers
One of the new features of ASP.NET MVC 3 is a controller-level
attribute to control the availability of session state. In the RC the
attribute, which lives in the System.Web.SessionState
namespace, is [ControllerSessionState]
; for RTM ScottGu says it will be renamed simply [SessionState]
. The attribute accepts a SessionStateBehavior
argument, one of Default
, Disabled
, ReadOnly
or Required
. A question that came up during a Twitter discussion a few weeks back is how the different behaviors affect Html.RenderAction()
, so I decided to find out.
The Setup
I started with an empty MVC 3 project and the Razor view engine.
We’ll let a view model figure out what’s going on with our controller’s Session
:
public class SessionModel
{
public SessionModel(Controller controller, bool delaySession = false)
{
SessionID = delaySession ? "delayed" : GetSessionId(controller.Session);
Controller = controller.GetType().Name;
}
public string SessionID { get; private set; }
public string Controller { get; private set; }
private static string GetSessionId(HttpSessionStateBase session)
{
try
{
return session == null ? "null" : session.SessionID;
}
catch (Exception ex)
{
return "Error: " + ex.Message;
}
}
}
The model is rendered by two shared views. Index.cshtml
gives us some simple navigation and renders actions from our various test controllers:
@model SessionStateTest.Models.SessionModel
@{
View.Title = Model.Controller;
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Host: @Model.Controller (@Model.SessionID)</h2>
<ul>
<li>@Html.ActionLink("No Attribute", "Index", "Home")</li>
<li>@Html.ActionLink("Exception", "Index", "Exception")</li>
<li>@Html.ActionLink("Default", "Index", "DefaultSession")</li>
<li>@Html.ActionLink("Disabled", "Index", "DisabledSession")</li>
<li>@Html.ActionLink("ReadOnly", "Index", "ReadOnlySession")</li>
<li>@Html.ActionLink("Required", "Index", "RequiredSession")</li>
</ul>
@{
Html.RenderAction("Partial", "Home");
Html.RenderAction("Partial", "Exception");
Html.RenderAction("Partial", "DefaultSession");
Html.RenderAction("Partial", "DisabledSession");
Html.RenderAction("Partial", "ReadOnlySession");
Html.RenderAction("Partial", "RequiredSession");
}
Partial.cshtml
just dumps the model:
@model SessionStateTest.Models.SessionModel
<div>Partial: @Model.Controller (@Model.SessionID)</div>
Finally, we need a few test controllers which will all inherit from a simple HomeController
:
public class HomeController : Controller
{
public virtual ActionResult Index()
{
return View(new SessionModel(this));
}
public ActionResult Partial()
{
return View(new SessionModel(this));
}
}
[ControllerSessionState(SessionStateBehavior.Default)]
public class DefaultSessionController : HomeController { }
[ControllerSessionState(SessionStateBehavior.Disabled)]
public class DisabledSessionController : HomeController { }
[ControllerSessionState(SessionStateBehavior.ReadOnly)]
public class ReadOnlySessionController : HomeController { }
[ControllerSessionState(SessionStateBehavior.Required)]
public class RequiredSessionController : HomeController { }
And finally, a controller that uses the SessionModel
constructor’s optional delaySession
parameter. This parameter allows us to test RenderAction
‘s Session
behavior if the host controller doesn’t use Session
:
public class ExceptionController : HomeController
{
public override ActionResult Index()
{
return View(new SessionModel(this, true));
}
}
The Reveal
So what do we find? Well the short answer is that the host controller’s SessionStateBehavior
takes precedence. In the case of Home
, Default
, ReadOnly
, and Required
, we have access to Session
information in all rendered actions:[
](http://solutionizing.files.wordpress.com/2010/12/sessionless-renderaction-home.png)
If the host controller is marked with SessionStateBehavior.Disabled
, all the rendered actions see Session
as null
:
I see this is the key finding to remember: an action that depends on Session
, even if its controller is marked with SessionStateBehavior.Required
,
will be in for a nasty NullRef surprise if it’s rendered by controller
without. It would be nice if the framework either gave some sort of
warning about this, or if they used a Null Object pattern instead of
just letting Session
return null
.
Finally, things get really weird if a Session
-dependent action is rendered from a host controller that doesn’t reference Session
, even if SessionState
is enabled:
It’s pretty clear the issue has something to do with where RenderAction()
happens in the request lifecycle, but it’s unclear how to resolve it short of accessing Session
in the host controller.
So there we have it…a comprehensive testing of sessionless controllers and RenderAction
for the ASP.NET MVC 3 Release Candidate. Hopefully the inconsistencies
of the latter two cases will be resolved or at least documented before
RTM.