Using ASP.NET Web Pages with the Razor View Engine
Explore Web Pages, a productivity-boosting feature for ASP.NET programmers
January 16, 2012
ASP.NET Web Pages with Razor is a framework that has many names. Sometimes referred to as ASP.NET Web Pages or Razor Pages, ASP.NET Web Pages is a new framework that brings together the Razor view engine and an ASP.NET website. It supports most of the constructs of the Razor view engine in the ASP.NET MVC framework (minus the MVC-specific offerings) and adds additional WebMatrix capabilities, all within the website project template format.
For the average Web Forms developer, ASP.NET Web Pages introduces quite a few shifts in development styles. The framework no longer relies on a code-behind file; rather, it mixes server-side code and client-side HTML all into one package. This architecture is déjà vu for ASP developers because they develop using these building blocks in a very similar fashion. Similarly, ASP.NET Web Pages do not use controls or components; instead they render the raw HTML or use helper methods (methods similar to the HTML helpers in MVC, but with a different naming convention and structure). This means that even ASP.NET Web Forms developers will have to get accommodated to some changes if they want to use the framework.
We'll start our exploration of ASP.NET Web Pages by quickly examining Razor constructs and syntax. After that, we will look at examples that demonstrate how to use some of Razor's features and examine layout and content pages, rendering techniques, and more Razor features.
Razor Syntax
ASP.NET Web Pages are constructed very similarly to an ASP.NET MVC view, using the new Razor syntax. A lot of the same constructs are supported; many of the features described in my article "Working with the ASP.NET MVC 3 Razor View Engine" also apply to ASP.NET Web Pages. Now let's take a quick look at some syntax examples.
Razor views support inline and code statements, as shown in the code sample in Figure 1.
Welcome @userName@{if (DateTime.Now.Month == 12) {@:, did you get all of your Christmas shopping done?}}Please specify your zip code: @(Html.TextBox("ZipCodeChange"))
The Razor view engine provides some intelligence about inline and code statements, so that it recognizes where the server-side code stops and starts. Razor is smart enough to know that the @SomeObject.Name statement is server-side code but the surrounding span tag is client-side markup; this is because inline code is interpreted strictly. Code blocks, on the other hand, are denoted by curly braces (multi-line code blocks) or parentheses (single-line code statements) and aren't interpretive where they end; they end at a specific designation (an ending curly brace or parenthesis). All server-side code is denoted by the "@" character. Razor also supports comments using an asterisk combination (@* *@) to denote comment blocks, escaping the "@" character to display it directly as client content using "@@", and using special keywords in combination with the "@" character.
Razor supports two types of common reusable components: functions and helpers, each designated with a special keyword. Functions are declared with the @functions keyword, and defined in a code block. Figure 2 shows the structure of a functions block. A function defines a method that returns an HTML string, a string, or whatever the method may need. HTML strings are handy for rendering markup directly to the browser later. To use the function, simply call it using its name.
@functions {public static IHtmlString GetFormattedAddress(string address1, string city, string state) {return new HtmlString(address1 + "
" + city + ", " + state);}}
Functions vary from the way they are defined in MVC; instead of being defined in the view, functions need to be in the app_code folder and must be static, as shown in Figure 2. Being in the app_code and being static makes functions a global feature.
Helpers are a little different; instead of acting like a traditional function by defining an access level and return type, helpers define a method structure without returning anything, rendering their contents directly. Inside the method can be any type of code statements -- if/else, while, and so on. Figure 3 shows a sample helper rendering HTML and server-side content directly to the browser.
@helper GetNameAsHtml(string first, string last) { @last, @first}
A helper can render content to the browser in one of three ways: by using the element to denote a multi-line output, by using the @: character combination to denote a single-line statement to render, or by defining HTML tags directly (such as a span or div tag), which are interpreted as client markup. It's interesting to note here how a server-side helper can mix in client-side content, and within that client-side content resides more server-side inline statements.
There are two types of helpers: local (to the page only) or global (available across the entire application). A global helper looks a lot like a local helper, except that the definition of the helper resides in the App_Code folder. Global functions and helpers assume the name of the file as the class it resides in; for instance, the file GlobalHelpers.cshtml defining a helper named GetNameAsHtml can be referenced via GlobalHelpers.GetNameAsHtml().
Razor Offerings
Razor would be of little help to a developer if an ASP.NET web page couldn't be broken up into sections (as user controls enabled developers to do) or by defining a single template container to render the entire site in (as master pages allow). Thankfully, ASP.NET Web Pages does not disappoint in this regard. ASP.NET Web Pages supports layout and content pages, similar to a Web Form with a master page. A layout page looks like the condensed sample in Figure 4. Note that the head and root body are deliberately omitted.
@RenderPage("~/Layout/_Header.cshtml") . . @Page.Title @RenderBody() @RenderSection("Footer", false)
Also notice the use of three methods, each of which serves a particular purpose:
@RenderPage -- Renders a *.cshtml or *.vbhtml page directly at the current placeholder. This is similar to a server-side include of ASP, or equivalent to defining a user control in ASP.NET Web Forms. The difference here is that the view does not have a direct API to work with the rendered pages content; there are other ways to work around this limitation (using PageData, for instance, which we'll discuss later).
@RenderSection -- Equivalent to a ContentPlaceholder control, a section defines a custom region whereby a view can render specific content at a specific location of the layout page. Sections may be required or optional.
@RenderBody -- Rather than a view simply assuming that it has a section defined for the main content, the RenderBody statement renders the body that is not handled by an explicit section. The main content of the view does not define a section to render in.
Note that in Razor, no such user control concept exists; user controls are essentially a simplified page that can be rendered within the context of a parent page. This would normally mean that these reusable pages could be served by the browser; however, a special designation (an underscore at the beginning of the filename) means the page is not servable to the client. In addition, the framework also handles generating clean URLs without file extensions and has similar routing features to those provided in ASP.NET MVC.
Layout, content pages, and reusable pages can communicate with each other using a PageData collection. Data stored via PageData is persisted during the request only. It works similarly to TempData or ViewData containers in MVC; data pushed in can be read using a dictionary interface.
Razor also provides special support and initialization of application-wide settings using two special pages: _AppStart and _PageStart. AppStart is a great way to establish any application-wide settings or perform application initialization. For instance, the application could initialize a dependency injection container here or set up other framework-specific components. Settings specific to the page are established via the _PageStart page. Here the page defines common page settings in a global file. Similar to the web.config, the _PageStart defines common settings for a particular page, such as the layout file or common PageData settings.
Both of these special pages run within the context of the executing page; _PageStart can also explicitly control when the page renders by executing an @RunPage method. This means part of the _PageStart code is run before execution of the page, and the rest is run after it, giving developers more control of the page process. This method is optional and implicitly called at the end if omitted.
Out with ASP.NET Controls, In with Helpers
If you're wondering how ASP.NET Web Pages uses the ASP.NET Web Forms architecture, there's a simple answer for you: It doesn't. Instead, the ASP.NET Web Pages architecture mirrors ASP.NET MVC with a set of HTML helpers to construct the UI, in addition to a new set of WebMatrix helpers for more advanced controls. Lacking a code-behind page or a controller, ASP.NET Web Pages defines code in line with the markup as the means of serving requests. ViewState is also another mechanism that didn't make it into the framework, meaning developers have to manage the request and response scenarios manually.
Let's take a look at building a form using the Html Helper extensions. The Html property of the page defines a series of extension methods to render TextBox, CheckBox, and more types of controls. A developer can choose to go this route or instead define each HTML element directly. The sample form in Figure 5 displays an input form to the user and manages the request/response process completely. If any errors have occurred, input is validated appropriately.
@{if (IsPost) {if (string.IsNullOrEmpty(Request["FirstName"])) {this.ModelState.AddError("FirstName", "First name is required");}if (string.IsNullOrEmpty(Request["LastName"])) {this.ModelState.AddError("LastName", "Last name is required");}if (!this.ModelState.IsValid) {this.ModelState.AddFormError("Some errors have occurred on the form.");}}}@Html.ValidationSummary("These are the errors:")[email protected]("Last Name")@Html.TextBox("LastName")@Html.ValidationMessage("LastName", "*")@Html.Label("How Did You Hear About Us?")@Html.DropDownList("HearAbout", new SelectListItem[]{new SelectListItem { Text = "Select", Value = "" },new SelectListItem { Text = "Internet", Value = "I" },new SelectListItem { Text = "Email", Value = "E" },new SelectListItem { Text = "Word of Mouth", Value = "W" }})
The form has helpers for each input control, in addition to the ValidationMessage helper, which is an MVC-style construct for displaying validation errors in the UI. The form listens for errors added to ModelState, which is a dictionary, matching the validator name to the state of its validation. When an error added to the dictionary matches the name of the validation message, the validation message is displayed, giving the user an asterisk to denote that the error happened. Additionally, all validation errors are displayed in the ValidationSummary.
Notice how the page checks that the page posts back, by using the IsPost property. Upon post, the form checks the posted data, looking for errors. An automatic validation process does not happen here; each error needs to be explicitly checked for and, if found, added to the model state.
Controls
Although controls no longer exist, their replacements exist in the form of either direct HTML tags, HTML Helper extension methods, or a web component such as the WebGrid control. The WebGrid control, from the System.Web.Helpers namespace and assembly, follows a chaining-like operation to set up the control and render to the browser. Most parameters are established in the constructor of the WebGrid control. At the end of the process is a GetHtml() method, the method that performs the rendering and accepts a variety of rendering options, such as applying various styles to the control.
The example in Figure 6 creates a grid control. Notice how the settings for binding data (e.g., the data to display) are defined in the constructor, whereas the Cascading Style Sheets (CSS) classes to use for rendering the styles in the UI are defined in the GetHtml() method.
@(new WebGrid(results, columnNames: new string[] { "UserId", "Email" }, defaultSort:"Email").GetHtml(rowStyle:"RowStyle", headerStyle:"HeaderStyle", tableStyle:"TableStyle"))
Data is supplied to the grid in the constructor as an object. The framework handles the binding in a similar manner as the GridView control would, matching fieldnames in the data to a specific column (defined by specifying the names of the columns).
Components
If you were to peruse the examples of Razor web pages on the www.asp.net website, you would notice a series of helpers used to perform database, I/O, or web tasks. Some of these helpers still use objects in the .NET framework, whereas others are web-specific. The ASP.NET Web Pages framework made some procedural changes to how you perform certain tasks, which differs from how you perform those tasks in ASP.NET Web Pages' counterparts.
For instance, to work with a database and query the results, we can use the new Database class. This class handles the core ADO.NET operations, such as connecting to a database, opening the database connection, and executing a parameterized query or command. The Database class requires the same input as an ADO.NET command or data adapter; however, it encapsulates all this into a fluent interface, as shown in Figure 7.
var results = Database.Open("StarterSite").Query("select * from UserProfile");if (IsPost) { Database.Open("StarterSite").Execute("insert into UserProfile values (@0)", email);}
Query and Execute are the two main operations; both support parameterization. Parameters are numeric-based; instead of supplying a name, you supply a numeric value starting at zero. Both direct queries and stored procedures are supported by these two methods.
Even though a Database class provides simple ADO.NET operations, Entity Framework can still handle the heavy-duty work of querying and modifying data. As ASP.NET Web Pages are still object-oriented, they can support instantiating the ObjectContext and reading/writing data. The code in Figure 8 shows an example of using Entity Framework with Web Pages. This code responds to a form post, then processes the data and inserts a new object into the database.
@{var oc = new WebPageSamples.Data.StarterSiteObjectContext();if (Request.Form["AddNewEmail"] != null){var email = Request.Form["NewEmail"];if (!string.IsNullOrEmpty(email)){oc.UserProfiles.AddObject(new WebPageSamples.Data.UserProfile{Email = email});oc.SaveChanges();}}var results = oc.UserProfiles.ToList();} @(new WebGrid(results, columnNames: new string[] { "UserId", "Email" }, defaultSort:"Email").GetHtml(rowStyle:"RowStyle", headerStyle:"HeaderStyle", tableStyle:"TableStyle"))
In addition, disk I/O is fully supported using the .NET Framework's File class. This is nothing specific to ASP.NET Web Pages but is the same File class we've had in any other project. Figure 9 shows an example of using the File class in ASP.NET Web Pages.
var entry = Request.Form["Entry"];File.AppendAllLines(Server.MapPath("data.txt"), new string[] { entry });var lines = File.ReadAllLines(path);foreach (var line in lines) { .. }
In ASP.NET Web Pages you have a variety of methods available to read, write, or append data, depending on your needs. However, you are not restricted to the File class to perform these operations; you can stick to the traditional way of using a StreamWriter class to output data to a file, as shown in Figure 10.
var entry = Request.Form["EntryWriter"];using (var writer = new StreamWriter(File.Open(path, FileMode.Append))){writer.WriteLine(entry);}
Order Matters
If you look at the sample code provided with this article (click the Downloads button at the top of the article) and some of the code samples in the figures, you'll notice the concept that views have to be linear. ASP.NET Web Pages has a different API than Web Forms', which means views are rendered in a top-to-bottom fashion. This means that you have to carefully place where your code needs to be and ensure that it is rendered in the exact spot on the page that you want.
With Web Forms, we used to have an API for the page, letting us change form values at any point in time in the page life cycle. Code didn't have to be in any specific order to get the page to render correctly. The approach I just described is a shift away from the Web Forms architecture, more toward an ASP or an ASP.NET MVC view style. So be aware that order matters.
Give ASP.NET Web Pages a Try
What you've seen here is a comprehensive overview of the ASP.NET Web Pages framework. ASP.NET Web Pages uses a combination of HTML helpers, WebMatrix components, the Razor view engine, influences from the MVC framework, and client-side HTML to generate the next-generation web page. A Razor web page consists of a single page formed by a combination of server-side code and client-side HTML, using the Razor conventions to denote the differences. Additionally, MVC-style HTML extensions build the UI and provide additional features such as validation, complete with model state support.
Web Pages can still leverage everything that the .NET Framework offers; with Web Pages you can use the new Database class (a WebMatrix offering), or you can use LINQ to SQL or Entity Framework for data management. WebMatrix also supports all .NET Framework capabilities out of the box, although it doesn't fully support everything with ASP.NET. I encourage you to give Web Pages a try and see if it improves your productivity.
About the Author
You May Also Like