Friday, December 9, 2011

Learning MVC: User Data in a Partial View

After spending some time trying to apply a somewhat decent layout to the site, I decided to put some common information in the sidebar. This information would be the same on every page, but will be visible only to logged in users. Initially I rendered it as a section, but that's clearly inefficient as the section tag has to be added to each and every view. That's against the DRY principle of MVC - Do not Repeat Yourself. Much more obvious decision is to place the common part into the partial view, and render this view right in the master page layout. I had to overcome a few obstacles, however, before I achieved the desired result. Here's the outcome:

First step was to create a partial view, which I called "Stats.cshtml" and placed under "Shared" folder together with other common views. Here is the initial Stats.cshtml:

<div id="sidebar" role="complementary">
<div class="callout">
<h3>Stats:</h3>
</div>
<section class="tbl">
<table cellspacing="0">
<caption>Your Things To Do</caption>
<tr>
<th scope="col">
In Basket Items:
</th>
<th scope="col">

</th>
</tr>
</table>
</section>
</div>

Which can be rendered from the _layout.cshtml using several methods, such as Render or RenderPartial. More about which one to choose in references below. Let's say I use RenderPartial the following way:

<content>
<div id="main" role="main">
@RenderBody()
</div>

@if(Request.IsAuthenticated){
Html.RenderPartial("Stats");
}
</content>

This solves the bit where the sidebar is rendered properly, and only visible to the logged in users. Now to the second part - how to display user-specific information? Currently the layout knows nothing about it. One of the ways is to create a controller for the partial view. I created a controller "StatsController" and placed some pretty basic code in it:

public class StatsController : Controller
{
private modelGTDContainer db = new modelGTDContainer();
private InBasketRepository repository = new InBasketRepository();
//
// GET: /Stats/

[Authorize]
public PartialViewResult Stats(Users user)
{
var inbasket = repository.FindUserInBasketItems(user.UserID);
return PartialView(inbasket.ToList());
}
}

Note the use of PartialViewResult instead of the usual ViewResult. When I used a ViewResult I got a fairly nasty infinite loop. Now my RenderPartial is not so useful anymore, because it does not accept a controller as a parameter. So I have to change it to either Html.Action or Html.RenderAction. One of the overloads accepts a view name and a controller name, this is the one I need:

<content>
<div id="main" role="main">
@RenderBody()
</div>

@if(Request.IsAuthenticated){
Html.RenderAction("Stats", "Stats");
}
</content>

And that's about it. The last bit is to specify the model in the Stats.cshtml and to pass some output to the page - at least a simple label:

@model IEnumerable<GTD.Models.InBasket>

@if (Request.IsAuthenticated)
{
<div id="sidebar" role="complementary">
<div class="callout">
<h3>Stats:</h3>
</div>
<section class="tbl">
<table cellspacing="0">
<caption>Your Things To Do</caption>
<tr>
<th scope="col">
In Basket Items:
</th>
<th scope="col">
@Html.Label(Model.Count().ToString())

</th>
</tr>
</table>
</section>
</div>
}

And that's what the user will see:

References:

When to use Html.RenderPartial and Html.RenderAction in ASP.NET MVC Razor Views
ASP.NET MVC 3 _Layout.cshtml Controller
Passing data from View to Partial View
ASP.NET MVC3 Razor syntax help - I'm getting stuck in an infinite loop by . Also posted on my website

No comments: