Reputation: 5203
I have been struggling to get a partial View working in Razor. The View engine cannot make sense of the code below but it is simple using the ASPX View engine. Can anyone show me how to get this to work with Razor? Note that I am just writing out a calendar so the <tr> tag happens at the end of every week. The first sign of a problem is that the Razor code will not format in the VS editor and it complains that the 'while' block is missing its closing brace. I have tried all kinds of combinations, even using a delegate. (I think the cause of the problem may be the conditional TR tag because it is highlighted as an error because it is not closed.)
Razor (doesn't work)
<table class="calendarGrid">
<tr class="calendarDayNames">
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
@{
var loopDate = gridStartDate;
}
@while (loopDate <= gridEndDate)
{
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
<tr class="calendarWeek">
}
<td class="calendarDay">
<span class="calendarDayNumber">@loopDate.Day</span>
@if (Model.AllCalendarDays.ContainsKey(loopDate.Date))
{
foreach (var ev in Model.AllCalendarDays[loopDate.Date])
{
<span class="calendarEvent">@ev.Venue</span>
}
}
</td>
@{
loopDate = loopDate.AddDays(1);
@if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
</tr>
}
}
}
ASPX (works)
<table class="calendarGrid">
<tr class="calendarDayNames">
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
<%
var loopDate = gridStartDate;
while (loopDate <= gridEndDate)
{
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
%>
<tr class="calendarWeek">
<%} %>
<td class="calendarDay">
<span class="calendarDayNumber">
<%: loopDate.Day %></span>
<% if (Model.AllCalendarDays.ContainsKey(loopDate.Date))
{
foreach (var ev in Model.AllCalendarDays[loopDate.Date])
{ %>
<span class="calendarEvent">
<%: ev.Venue %></span>
<% }
} %>
</td>
<% {
loopDate = loopDate.AddDays(1);
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{ %>
</tr>
<% }
}
} %>
</table>
Working solution in Razor based on @jgauffin's view model suggestion and @dommer's ugly raw html solution. Combined together they're almost aesthetically acceptable. :)
View model now has iterator
public IEnumerable<Tuple<DateTime, IList<CalendarEventDto>>> GridItems()
{
var loopDate = GridStartDate;
while (loopDate <= GridEndDate)
{
yield return new Tuple<DateTime, IList<CalendarEventDto>>(loopDate.Date, AllCalendarDays[loopDate.Date]);
loopDate = loopDate.AddDays(1);
}
}
Okay, the Tuple is lazy but I will probably create another model to hold more complex information about the date and events (IsPast/greyed, etc).
The pesky View
@foreach (var item in Model.GridItems())
{
if (item.Item1.DayOfWeek == DayOfWeek.Monday)
{
@Html.Raw("<tr class=\"calendarWeek\">");
}
@Html.Raw("<td class=\"calendarDay\">");
@Html.Raw(string.Format("<span class=\"calendarDayNumber\">{0}</span>", item.Item1.Day));
foreach (var ev in item.Item2)
{
@Html.Raw(string.Format("<span class=\"calendarEvent\">{0}</span>", Server.HtmlEncode(ev.Venue)));
}
@Html.Raw("</td>");
if (item.Item1.DayOfWeek == DayOfWeek.Sunday)
{
@Html.Raw("</tr>");
}
}
Note that when I reformat the View source in VS, it gets egregiously tabbed, with the if statement having about 10 tabs to the left of it, but there are no compilation warnings and it does what I want. Not nice, or easy though. I think the Razor devs should provide some support for explicit breakout and breakin to code and markup so that when the parser cannot parse it unambiguously, we can tell it what we intended.
@Andrew Nurse's solution Andrew 'works on the ASP.Net team building the Razor parser!'. His solution runs okay but still produces compiler warnings and is obviously confusing Visual Studio because the code cannot be reformatted without ending up in a big glob on a few lines:
<tbody>
@foreach (var calDay in Model.GridItems())
{
if (calDay.DayOfWeek == DayOfWeek.Monday)
{
@:<tr class="calendarWeek">
}
<td class="calendarDay">
<span class="calendarDayNumber">@calDay.Day</span>
@foreach (var ev in calDay.CalendarEvents)
{
<span class="calendarEvent">@ev.Venue</span>
}
</td>
if (calDay.DayOfWeek == DayOfWeek.Sunday)
{
@:</tr>
}
}
</tbody>
Upvotes: 3
Views: 2089
Reputation: 6294
The primary issues here were these lines:
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
<tr class="calendarWeek">
}
...
@if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
</tr>
}
The problem is that Razor uses the tags to detect the start and end of markup. So since you didn't close the "tr" tag inside the first if, it doesn't actually switch back to code, so it doesn't see the "}" as code. The solution is to use "@:", which lets you put a line of markup without regard for tags. So replacing those lines with this should work and be more concise than using Html.Raw:
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
@:<tr class="calendarWeek">
}
...
@if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
@:</tr>
}
Upvotes: 4
Reputation: 14951
MAJOR EDIT: Okay, what happens if you do this?
<table class="calendarGrid">
<tr class="calendarDayNames">
<th>Monday</th>
<th>Tuesday</th>
<th>Wednesday</th>
<th>Thursday</th>
<th>Friday</th>
<th>Saturday</th>
<th>Sunday</th>
</tr>
@{
var loopDate = gridStartDate;
while (loopDate <= gridEndDate)
{
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
@Html.Raw("<tr class=\"calendarWeek\">");
}
@Html.Raw("<td class=\"calendarDay\">");
@Html.Raw("<span class=\"calendarDayNumber\">" + loopDate.Day + "</span>");
if (Model.AllCalendarDays.ContainsKey(loopDate.Date))
{
foreach (var ev in Model.AllCalendarDays[loopDate.Date])
{
@Html.Raw("<span class=\"calendarEvent\">" + ev.Venue + "</span>");
}
}
@Html.Raw("</td>");
loopDate = loopDate.AddDays(1);
if (loopDate.DayOfWeek == DayOfWeek.Monday)
{
@Html.Raw("</tr>");
}
}
}
Upvotes: 1
Reputation: 101192
I would move all logic to the viewmodel which leaves the following code in your view:
@while (Model.MoveNext())
{
@Model.WeekHeader
<td class="calendarDay">
<span class="calendarDayNumber">@Model.DayNumber</span>
@foreach (var ev in Model.CurrentDayEvents)
{
<span class="calendarEvent">@ev.Venue</span>
}
</td>
@Model.WeekFooter
}
And the new model:
public class CalendarViewModel
{
private DateTime _currentDate;
public string WeekHeader
{
get
{
return _currentDate.DayOfWeek == DayOfWeek.Monday ? "<tr class="calendarWeek">" : "";
}
}
public string WeekFooter
{
get
{
return _currentDate.DayOfWeek == DayOfWeek.Monday ? "</tr>" : "";
}
}
public IEnumerable<DayEvent>
{
get
{
return AllCalendarDays.ContainsKey(loopDate.Date) ? AllCalendarDays[loopDate.Date] ? new List<DayEvent>();
}
}
public bool MoveNext()
{
if (_currentDate == DateTime.MinValue)
{
_currentDate = gridStartDate;
return true;
}
_currentDate = _currentDate.AddDays(1);
return _currentDate <= gridEndDate;
}
}
Upvotes: 1
Reputation: 42433
Have you tried adding <text>
tags around the contents of the blocks?
I think the Razor parse only works when it's obvious where the blocks end. It may be getting confused by the fact you have an if, a td
and then some more code, all inside the block.
There's more info on this here: http://weblogs.asp.net/scottgu/archive/2010/12/15/asp-net-mvc-3-razor-s-and-lt-text-gt-syntax.aspx
Upvotes: 0