The Weekly Source Code 30 - VB.NET with XML Literals as a View Engine for ASP.NET MVC
I was literally in the middle of writing the post when I saw a message from Andrew Davey about how he had implemented the same idea! Of course, his is way better, so I got to code via subtraction. That means subtracting out the crap I had written in a few minutes and dig into his code.
There are no unique ideas, right? ;) Either way, it's fun when the same idea is being thought about simultaneously.
Here's the general concept. A few weeks back I was talking with Avner Aharoni, a Language Program Manager, and he had been kicking around the idea of VB9's XML Literals making friendlier Views within ASP.NET MVC.
I've blogged about VB9's rocking sweet XML support before. It lets you create XML like this. Note the lack of strings...the XML is there in the language and the compiler and intellisense are all aware of it.
Dim books = <bookstore xmlns="http://examples.books.com">
<book publicationdate=<%= publicationdate %> ISBN=<%= isbn %>>
<title>ASP.NET Book</title>
<price><%= price %></price>
<author>
<first-name><%= a.FirstName %></first-name>
<last-name><%= a.LastName %></last-name>
</author>
</book>
</bookstore>
Views in ASP.NET MVC
Starting with the Northwind ASP.NET MVC Sample Code for Preview 3 that Phil updated, let's look at the List View:
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
<h2><%=ViewData.CategoryName %></h2>
<ul>
<% foreach (Product product in ViewData.Model.Products.Model) { %>
<li id="prod<%= product.ProductID %>">
<%= product.ProductName %>
<span class="editlink"">
(<%= Html.ActionLink("Edit", new { Action="Edit", ID=product.ProductID })%>)
</span>
</li>
<% } %>
</ul>
<%= Html.ActionLink("Add New Product", new { Action="New" }) %>
</asp:Content>
This is straight from the ASPX page with inline C#.
ASP.NET MVC Views using VB9's XML Literal Support
Now, we thought it'd be cool/interesting/potentially-something if we could use the XML Literal support to get, as Andrew puts it "compiled, strongly typed, intellisense friendly views." Sure, we mostly get that with the ASPX pages, but perhaps this would be better/easier/something? Keep in mind here that we're playing.
Your opinions on if this is a good idea or something to move forward would be useful. Leave comments and I'll compile and give them directly on to the VB and MVC team(s)! Remember, it can look like however you think it should, so don't be constrained by my spike or Andrew's.
Here's why Andrew thinks it's cool, quoted from a post on the ALT.NET mailing list:
Some key features I've used.
- <%= From ... Select ... %> to create repeated elements
- VB's ternary "If" operator for conditional output
- X-linq to post process the HTML before sending it
- choose between indented and compressed XML output
- modules of functions as "controls" - it's so simple :)
Here's what my solution looks like in Visual Studio. See how the ListVB.aspx has no "+" sign. There's no code-behind .cs file there even though this is a C# project. The meat of the View is in another assembly (although you could conceivably do something freaky and get VB and C# to live in the same assembly (be sure to read the comments)).
Actually, the ListVB.aspx file is VB, not C# and refers not to to a code-behind, but another class in another DLL, specifically the VBViews assembly.
<%@ Page Language="VB" MasterPageFile="~/Views/Shared/Site.Master"
AutoEventWireup="true" Inherits="VBViews.ListVB" Title="Products" %>
<%@ Import Namespace="NorthwindModel" %>
<%@ Import Namespace="System.Collections.Generic" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h2><%=ViewData.Model.CategoryName%></h2>
<% = GetList() %>
</asp:Content>
Here's the Visual Basic code in the other assembly.
Imports System.Xml.Linq
Imports NorthwindDemo.Models
Partial Public Class ListVB
Inherits System.Web.Mvc.ViewPage
Function GetList() As XElement
Dim c As Category = CType(ViewData.Model, Category)
Return <ul><%= From product In c.Products _
Select _
<li id=<%= "prod" & product.ProductID %>>
<span class="editlink">
<a href=<%= "/Products/Edit/" & product.ProductID %>>
<%= product.ProductName %>
</a>
</span>
</li> %>
</ul>
End Function
End Class
This won't really be clear without some syntax highlighting to make the point, so here it is again, but this time as a screenshot. See now the VB code, XML and <% %> blocks are all together in same line? VB is just generating XElement's which in turn will turn into a string when "rendered" by the ASPX page.
Andrew's Take #2 on the VB9 XML Literals as ASP.NET MVC Views
Andrew Davey's NRest project is more than just VB9 Views. It's a REST web framework for ASP.NET using the Nemerle programming language (using the May CTP). You can browser or GET his code with SVN here: http://svn.assembla.com/svn/nrest/trunk/. It's also a nicely laid out solution that uses the Ninject IOC but I'll cover that later. Do check out Andrew's screencast about his NRest project.
His code is a mix of C#, Nemerle and VB. The Website, Tests and Services are in C# and the Ninject modules are in Nemerle, along with the meat of the main NRest project. I think he could have used more of System.MVC, specifically the View Engines, that he did, but I'm still grokking his intent.
He's got a hierarchy in VB with a MainPageBase, Page, in order to achieve a kind of Master Pages:
Public Class MainPageBase(Of TChrome As MainChrome, TContent)
Inherits Page(Of TChrome, TContent)
Public Overrides Function GetHtml() As XElement
Return _
<html>
<head>
<title><%= Chrome.Title %></title>
<link href="/styles/demo.css" type="text/css" rel="Stylesheet"/>
<%= GetHeadContents().Elements() %>
</head>
<body>
<h1><%= Chrome.Title %></h1>
<%= GetBodyContents().Elements() %>
</body>
</html>
End Function
Public Overridable Function GetHeadContents() As XElement
Return <_></_>
End Function
Public Overridable Function GetBodyContents() As XElement
Return <_></_>
End Function
End Class
So a Hello World page in VB would be very simple, just this:
Public Class CustomerPage
Inherits MainPageBase(Of CustomerPageData)
Public Overrides Function GetBodyContents() As XElement
Return _
<_>
<p>Hello <%= Content.FirstName & " " & Content.LastName %></p>
</_>
End Function
End Class
All of this is a work in progress, but it's really cool that we're all trying to push the envelope and not afraid to try crazy stuff in order to make things better. It'll be cool for me to read this post in a year and either say "ew" or "cool!" depending on what direction we all went.
Have you done anything cool or crazy with Views and ViewEngines, Dear Reader?
Related Posts
- Is rooting for Visual Basic like rooting for the Red Sox?
- XLINQ to XML support in VB9
- Mixing Languages in a Single Assembly in Visual Studio seamlessly with ILMerge and MSBuild
- NRest Screencast
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
With the aspx+inline C# approach, you don't duplicate the model, as under the covers it's all just iterators and Response.Write. That's better.
I think a nicer alternative would be something like the a href="http://www.codeplex.com/MVPXML/Thread/View.aspx?ThreadId=7193">Typed Templates</a> (if only I had some spare time to put it together for ASP.NET MVC...
The XLinq stuff will indeed eat more memory than a pure streaming model. However the benefit of post processing may be worth it. I need to look into using XStreamingElement as well, since this defers the evaluation of the XML. Also, take a look at using PLINQ to generate XElements in parallel...
I'll get some screencasts up soon that explain more about what I'm trying to achieve.
I've a question on how you're connecting the ListVB.aspx page with the VBViews.ListVB class using the Inherits attribute. I have a ASP.NET (3.5) web application with the UI layout and style and some other options driven by the url the site was called from. So what my site does on calling is selecting the correct record from database by the url and creating the context from that record. the context contains the site language and which set of information to display.
Also the UI will be choosen by the record the way that there exist one theme and masterpage for each record, named by the record's ID. So the masterpages are named _1.master, _2.master etc. The masterpage contains some logic in a class DefaultMaster which inherits from the ASP.NET MasterPage class and all of my _<ID>.master masterpages have to inherit from that class. But because of the partial class model my class in the .master files doesn't work - the compiler would try to generate one class from DefaultMaster, _1.master, _2.master etc. Therefore I have to create an empty class for each new masterpage inheriting from DefaultMaster, which means that I have to recompile the whole site and also restart the productional web application when adding a new customer.
What I want to do is to copy only the new .master file, theme folder and some other static files (in App_Data) to the server and set up the new records in the database without the need of touching either the bin folder or the web.config file. Is this possible with the Inherits attribute and how exactly to do this?
Thanks in advance,
Marco
But I really do not like the fact that there's now HTML (XML really) in a .vb file separate from the view. Maintenance nightmare I think. Why can't the XLinq code of the GetList() method be used in the .aspx view? That would be better I think?
We tried this when VS 2008 first came out, I agree it's cool. We used a VB class library instead of aspx. It was a trick to get a VB and C# in one solution, dont remember the exact issue but we had some.
We stopped because of the VB XML Performance, Huge XML Node Trees.
We then switched to precompiled aspx pages that never spin up the Page object or any web controls. It's a hack, but it's very fast. In the aspx page simply close out the generated render method with a } Then create a static type save render method that accepts a writer and the model. Having a static view method is pretty cool, you can also place multiple views in one aspx page if needed. It's always precompiled strong typed as you never use a string "MyView.aspx", instead ViewName.Render(writer, model).
There are some other variations on this, to get to context and also inject js, head info etc...
Comments are closed.
I think in almost every scenario, the "inline c#" (or VB) solution will have less "noise" than this solution.
I see the VB XML literal feature as a way to sprinkle in a touch of XML/HTML in an ocean of VB.
While inline C#/VB is a way to sprinkle in a touch of C# in an ocean of HTML.
Your views are likely to be dominated by HTML, so it makes sense to use an engine that does HTML well, while letting you sprinkle in a bit of code.