Code Focused
Using Razor with Visual Basic
Build a Web site using MVC 3 and the Razor View Engine
In my local developer community, the popularity of the Microsoft Model-View-Controller Web development platform is clearly increasing, particularly since version 3 -- featuring the Razor view engine -- was released last January, and the ASP.NET MVC Tools Update was released in April.
Some examples:
- At the recent Lansing Day of .NET event, the MVC 3 presentation was the most popular of the day, with 70 percent of the attendees crowded into a standing-room-only session.
- A Microsoft Access consulting firm is developing its first Microsoft .NET Web application in MVC 3, attracted by the clean HTML views and crisp syntax of the Razor view engine.
- The internal IS group for a large multi-national firm has set MVC 3 as the standard for Web applications.
I believe these are a microcosm of the strong interest in MVC 3 Razor view engine throughout the full .NET Web dev community.
One of the first frustrations encountered by Visual Basic developers seeking to adopt MVC 3 is that the majority of the examples are presented in C#, and the language differences can make utilizing the examples difficult. This article illustrates a Visual Basic MVC 3 Razor view engine Web site with features that are typical for a complete application.
I demonstrate Create, Read, Update, Delete (CRUD) operations on a SQL Compact 3.5 database using LINQ-to-SQL. The database table is updated with separate views following the scaffolding generated by the Visual Studio templates. MvcMailer is adapted to work with Visual Basic to send e-mails, with the HTML message body prepared with the full power of the Razor view engine. Security is enforced based on the user's membership in security roles, both by restricting access to particular controller actions and by providing alternate content in HTML views based on security roles.
This article is not meant to be a comprehensive introduction to MVC 3 concepts, but rather an example of how to prepare a Visual Basic MVC 3 Razor view engine Web site with commonly requested features. The code download contains a fully functional Visual Basic MVC 3 Razor view engine Web site. Tips and tricks for Controllers and Views are shared.
The official MSDN Library documentation page for MVC 3 is here. MSDN code downloads can be found here.
Create an MVC 3 Razor Project with a Database
First, ensure you have the proper development environment for MVC 3. You'll need Visual Studio 2010 or Visual Web Developer 2010 Express. Install the April 2011 MVC 3 Tools Update. Get what you need here. Create a new solution and choose the ASP.NET MVC 3 Web Application, Internet Application (forms authentication) and Razor view engine with HTML5 semantic markup. Immediately run the application, click Log On and register as a new user to create the ASPNETDB.MDF database in the Web project App_Data folder.
This article uses the Microsoft-supplied sample SQL Server Compact Edition (SSCE) version 3.5 database Northwind.sdf, normally installed at C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Samples. For simplicity, LINQ-to-SQL will be used to access the database. A batch file is included in the code download to demonstrate how to generate the needed DataContext code file with the SQLMetal utility. This DataContext file must be generated and included in the solution with a successful build before any of the data classes can be seen when generating MVC views. As with any LINQ-to-SQL project, a reference to System.Data.Linq is needed in the project. SSCE 4.0 is not supported by LINQ-to-SQL.
Note: The Entity Framework (EF) 4.1 with the DbContext API and Code First feature may finally provide a viable alternative to LINQ-to-SQL, when the requirement to drop and recreate the database upon any schema change is resolved. See Scott Hanselman's blog post for more information.
CRUD and Details for Customers
The default MVC scaffolding assumes that the CRUD functions will be handled by different Web pages, driven by a data list page. Add a Customer menu tab by editing the _Layout.vbhtml file to include this line as a new element of the <nav> tag:
<li>@Html.ActionLink("Customer", "Index", "Customer")</li>
It's easiest to create the controller first, and then create the needed views by right-clicking the action name in the controller and choosing the Add View... option. Create a Customer controller with empty read/write actions. Right-click the Index view and create an Index view, strongly typed to the Customers table with the Razor (VBHTML) view engine and the List scaffold template. Similarly create strongly typed views for each of the remaining controller actions with their corresponding scaffold template -- for instance, Details view with Details scaffold and so on. The essential structure of a Visual Basic Razor view engine is shown in Listing 1 with a single field implemented.
By default, generated Views do not show an error summary. In order for users to clearly see any generated error messages, change @Html.ValidationSummary(True) to @Html.Validation-Summary(False) in the view.
Now add LINQ-to-SQL code to provide data to display the views, and to process the data received from the views upon submit. The controller template assumed that the table key would be an identity-seed integer, but in the Northwind customers table, the CustomerID field is a string. As a result, change all occurrences of "id as integer" to "id as string" in the Customer controller and add a CustomerID data field to the Create and Edit views after the </ legend> tag as follows:
<div class="editor-label">
@Html.LabelFor(Function(model) model.CustomerID)
</div>
<div class="editor-field">
@Html.EditorFor(Function(model) model.CustomerID)
@Html.ValidationMessageFor(Function(model) model.CustomerID)
</div>
In any typical controller, several actions have both an HttpGet version and an HttpPost version. The code for the Edit action illustrates the pattern to provide data on the HttpGet and process the data on the HttpPost, as shown in Listing 2. See the code download for the remaining action methods. Option Strict Off is required in order to use the late-binding dynamic object ViewBag. If you prefer to leave Option Strict On, the ViewData method can be used.
Each view needs a way to show errors from the controller. Place Razor view engine code similar to Listing 3 at the top of each view.
Tips and Tricks for Controllers
The model passed to the view should represent the central data record or records being processed by the view. Secondary display-only information, such as the address information linked by the key in the model, should be passed to the view by the Viewbag or ViewData objects.
Web forms developers may be comfortable using Http Session to store information that needs to persist between posts. MVC introduces TempData, which does use Session, but only until the next request. This can cause issues if an AJAX call is made between posts, as that call will clear TempData. If you need to persist data beyond the TempData lifespan, feel free to use Session -- but be sure to clear it when you're done.
The TryUpdateModel (entity) method fills any properties in the entity with matching properties from the form collection. More-complex views can be supported by using TryUpdateModel in an HttpPost method with multiple entities. For example, if a Create view permitted the user to enter both customer and order information, the HttpPost would contain:
TryUpdateModel(customer)
TryUpdateModel(order)
The Customer controller accesses the database directly, which makes unit testing the Controller more difficult. For better unit testing, consider modifying the controller to call database service methods defined by an interface so that the interface can be implemented by mock concrete methods during unit testing.
Using Views
Views are translated into standard HTML forms by the Razor view engine, so Web page rules and capabilities still apply. Any fields that need to appear in the HttpPost form collection must be contained within the form tag scope, even if just an input type hidden field. Views can and often do contain jQuery to provide UI automation, validation or AJAX calls to the controller for real-time validation. Try to keep the logic within the view as simple as possible for easier unit testing.
Views can be edited, saved and refreshed to see the changes while the application remains running. View Page Source and Firebug are still very useful tools for resolving issues with a view. Notice that Listing 3 shows that HTML code within an @Code block -- it must be preceded by the @ symbol.
The views produced by the scaffold are functional, but clearly not attractive. The @Html.EditorFor helper is the default for text entry fields, but doesn't permit CSS styling within the tag. If you wish to style the tag in the view, use the @Html.TextBoxFor helper instead. An example with styling is:
@Html.TextBoxFor(Function(model) model.Phone,
New With {.style =
"color:red; border-width:thick;"})
Correctly referencing site assets such as images and scripts can be difficult when development and production have different base URLs. The Url.Content construct resolves this issue. For example:
href="@Url.Content("~/Content/Site.css")"
Partial views are useful for implementing elements used in multiple views. Partial views share the same model as the view in which they appear. The new Html.Raw helper provides a simple way of displaying un-encoded HTML when the content is known to be safe.
The Html.DropDownListFor helper makes it easy to display a list of choices with a specific value selected, typically from an associated key in the model. To display a drop-down list of all company names from our Customers table with the "Split Rail Beer & Ale" company pre-selected, use the controller code shown in Listing 4.
Sending E-Mail
One of the common tasks Web applications need to perform is sending e-mail, often to notify customers or users of certain events.
The open source MvcMailer (found here) is very useful, as it allows the full power of the Razor view engine to be used to create the HTML body of an e-mail. While MvcMailer is designed for C# projects, it can be used in Visual Basic with a few tweaks, based on the May 8, 2011, version. MvcMailer supports redirection of e-mails to a folder and interface-based views to aid testing.
Install MvcMailer using the NuGet command "install-package MvcMailer." This adds a <mailSettings> section to the web.config with two <smtp> tags, to either specify the settings to send e-mails (default) or specify a pickup directory where e-mails are written rather than sent. Comment out the web.config method you don't want to use.
For a Visual Basic implementation, create a View folder named MailerBase to hold all e-mail views to be used with MvcMailer. Use the Session object -- rather than the Model or ViewBag objects -- to pass information to the MvcMailer view. To use the Send extension method, include an Imports statement for the Mvc.Mailer namespace in your controller.
To demonstrate, an e-mail link has been added to the Customers Index view grid. It displays a view with that customer's details and a text area to enter custom e-mail body text. This is posted to the controller, which uses MvcMailer and the CustomerEmail Razor view engine to format the e-mail body. It includes a default footer with the customer contact information from the database and requests any updates to the contact information.
Despite the loosely typed Session objects passed to the view, the following Razor view engine code can be used to allow the data to be accessed in the view, strongly typed with full IntelliSense (see Listing 5 for the full MvcMailer controller and view example):
@Code
Layout = Url.Content("~/Views/Shared/_EmailLayout.vbhtml")
Dim emailInfo = CType(Session("emailInfo"), MVC3RazorVB.CustomerEmail)
Dim customer = CType(Session("customer"), MVC3RazorVB.Customers)
End Code
Welcome @customer.ContactName to our web site!
Enforcing Role-Based Security
Any Web site that doesn't enforce security is a demo, not an application; understanding how to enforce security is essential. The user is required to log into the site; based on membership in certain roles, user access is potentially limited. Security can be enforced at the controller level, at the controller-method level or with conditional logic within the controller method and/or the view.
A common technique is to create multiple views based on access role -- for example, the same controller method may return the "Customer_Read" view for a departmental user, but the "Customer_Edit" view for an admin or clerical user. Alternately, Razor view engine script can be used to disable certain functions within a single view.
Read-only views can be quickly created from full Edit views by changing all occurrences of the EditorFor helper to the DisplayFor helper, removing any update buttons and disabling drop-downs by adding the disabled attribute with a statement similar to:
@Html.DropDownListFor(Function(model) model.CustomerID,
CType(ViewBag.IDs, List(Of SelectListItem)),
"Select One", New With {.disabled = "disabled"})
To demonstrate security, several users and roles were created with the Web administration tool, accessed by the "world hammer" icon at the top of the solution explorer panel. There are three logins defined for the site. User public has no access due to a lack of membership in any roles. User clerical has edit and details customer access via membership in the clerical and employee roles. User admin has full CRUD access due to membership in the admin and employee roles.
To ensure that only members of the employee role can access the Web site, create a ControllerBase.vb class, as shown here (make sure all controllers except the AccountController inherit from it; the AccountController needs to be an unsecured controller so that unauthenticated users can execute its methods to log in):
<Authorize(Roles:="employee")>
Public Class ControllerBase
Inherits System.Web.Mvc.Controller
Protected Sub New()
End Sub
End Class
The Customer Index() action displays a list of all customers, with the right-most column containing ActionLinks such as edit, e-mail, details and delete. The Razor view engine code to display these links only for the appropriate user is shown in Listing 6. Note also that the link to create a new customer is restricted to admin users only.
Limiting the display of ActionLinks isn't sufficient to secure the application, as actions can be performed directly via the URL, such as "/Customer/Delete/ALKFI." Each controller method needs its own <Authorize> attribute to limit execution to the appropriate roles. If a user tries to directly execute an unauthorized action via its URL, the user will be redirected back to the login page. For example:
' GET: /Customer/Create
<Authorize(Roles:="admin")>
Function Create() As ActionResult
Return View()
End Function
By default, the TryUpdateModel method tries to update all matching public properties between the formcollection and the model. This is a potential security issue, as it can allow unintended model properties to be updated by an altered form post. For this reason, it's recommended to use an overload of the TryUpdateModel that takes a whitelist (included properties) or blacklist (excluded properties) to ensure only the intended properties are updated. For example, an Edit action form post intended to update only the Contact Name and Contact Title should appear as follows:
Dim ContactNameTitle() As String = {"ContactName", "ContactTitle"}
TryUpdateModel(customer, includeProperties:=ContactNameTitle)
Get on the Razor Bus
This article and the code download are intended to provide a foundation in the use of ASP.NET MVC 3 Razor view engine for the Visual Basic developer. While I tried to illustrate the major points within the text, a careful review of the code download is strongly recommended in order to get the full benefit.
I find the MVC 3 Razor view engine Web development platform very elegant and enjoyable to work in. I'd like to see many more Visual Basic developers adopt MVC 3 Razor view engine as their default Web development technology, and I hope this article makes it more approachable for those hindered by the lack of documentation and examples. Finally, I'd like to thank Matt VanNuys of Dart Container Corp. for his assistance in explaining some of the finer points of the MVC 3 Razor view engine.