Dan Maharry

Writing about web development since 1997

ASP.NET 4.0 Part 15, Data Enhancements

Welcome to part 15 of my tour through ASP.NET 4.0. In this episode, we're going to look at how webforms developers can make more use of both Dynamic Data 4.0 and Entity Framework 4.0 thanks to several new features baked into ASP.NET 4.0.

For those of you unfamiliar with these two products, Entity Framework is the 'enterprise-level' data modeller (O/RM) technology, aimed at larger applications where LINQ2SQL was aimed at small ones. Dynamic Data meanwhile is a rapid development tool used to create a basic scaffolding for a website based on the schema of the database that supports. Almost a CSS-like construct based on the type of data in a field rather than the type,class or id of an HTML element.

It's odd how both Dynamic Data (DynData) and the Entity Framework (EF)  were both released in .NET 3.5 SP1 almost as a tester to garner public reaction to Microsoft's efforts thus far with both projects. And it's fair to say that mistakes / omissions were made in those v1 releases which are now being corrected. EF was vilified in comparison to other, more mature OR/M frameworks for many reasons including

  • Short list of shortcomings that have been fixed in EF 4
  • Unit testing shortfalls. EF objects couldn't be generated as plain old CLR objects (POCOs) - they had to be classes derived from EntityObject which meant they would hit the db when tests were run against them.
  • EF didn't support many to many relationships

DynData, which sat on top of EF, suffered from both EF's shortcomings and some of its own including.

  • DynData 3.5 required you to start a new project and coudn't easily be integrated into a webforms or MVC application
  • If you used EF as the data model, DynData couldn't tell which field in a database table was the primary key and would model it as an editable field. You had to go into the model and annotate it separately.
  • Scaffolding templates for only basic field types and none for entities (types defined across several table joins)

Needless to say, both DynData 4.0 and EF 4.0 address these issues and many more. It's now fair to say that both technologies are well beyond where their first versions perceptibly fell short.

EnableDynamicData

One of the goals of ASP.NET 4.0 was to make DynData's scaffolding available to both Webforms and MVC. As noted earlier, DynData v1 was a project of its own that couldn't be easily retrofitted into an existing website, be it using Webforms or MVC. To remedy this, ASP.NET 4.0 now includes a new method called EnableDynamicData that you can use on any page in a website or web application.

Consider the scenario where you've a FormView or GridView allowing you to update, add to or delete records from a database. Typically, the controls auto-generate columns containing textboxes and maybe a checkbox. Users can add in invalid text, dates, values outside desired ranges etc. So you disable auto-generation and template the columns yourself with a calendar control for dates, say, and validation controls. And then you do the same thing for various other data-bound controls based on the same tables of data. Rather than setting all columns and rows yourself on each control, the EnableDynamicData method tells ASP.NET to generate those customized input and validation controls itself using the DynData scaffolding generated against the database and annotations you may give it as a (central) template.

Let's take an example.

  • Open VS2010 and create a new, empty web site.
  • If you’re using SQL Express, add an App_Data folder and add in the AdventureWorksLT database. You’ll find it for download on codeplex.
  • Add add an Entity Data Model to your site. Generate it from the tables in the newly added database.
  • Add a new page to your site and place on it an EntityDataSource and a GridView. The EntityDataSource should retrieve the EntitySet for the Products table. The GridView should use the EntityDataSource and both should have Update and Deletes enabled.

Your page code should look like this.

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Demo</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:GridView runat="server" DataSourceID="entityDs" ID="gvwProducts">
      <Columns>
        <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
      </Columns>
    </asp:GridView>
    <asp:EntityDataSource runat="server" ID="entityDs" 
         ConnectionString="name=AdventureWorksLT2008_DataEntities" 
         DefaultContainerName="AdventureWorksLT2008_DataEntities" 
         EnableDelete="True" EnableFlattening="False" 
         EnableUpdate="True" EntitySetName="Products"
         EntityTypeFilter="Product"> </asp:EntityDataSource>   
    </div>
  </form>
</body>
</html>

If you run this page and try to update any of the currency-based fields for an item now listed in the grid (eg. StandardCost or ListPrice) with a non-currency value such as the string ‘abc’, you’ll get the following Yellow Screen Of Death message.

Error while setting property 'StandardCost': 'Cannot convert the value of parameter 'StandardCost' to the type 'System.Decimal'.'.

Of course, if we were to manually generate the columns in the GridView, we could add in validation to ensure that only valid currency values are sent to the database. But let’s not and enable the dynamic data framework on the GridView and see what happens. It’s a single line of code added to your page’s codebehind.

public partial class _Default : System.Web.UI.Page
{
  protected void Page_Init(object sender, EventArgs e)
  {
    gvwProducts.EnableDynamicData(typeof(AdventureWorksLT_DataModel.Product));
  }
}

If you run the page again and try to make the same page, you’ll see that some validation is performed pre-save and so clicking Update on the grid doesn’t produce the previously seen yellow screen of death.

Simple Validation

Admittedly, it’s not earth-shatteringly useful as the invalid value is simply highlighted by an asterisk to its side with the actual error message that StandardCost is a required value hidden in a tooltip, but by adding a ValidationSummary control to the page, things become much clearer. And we’re leveraging the Dynamic Data framework to generate validation for us, which we can always amp up by adding instructions centrally to the entity model such as an appropriate error message. For example,

namespace AdventureWorksLT_DataModel
{
  [MetadataType(typeof(ProductMetadata))]
  public partial class Product
  {  }

  public class ProductMetadata
  {
    [RegularExpression(@"^[0-9]{1,}\.[0-9]{2}$", 
       ErrorMessage="Value should be greater than 0.00")]
    public decimal StandardCost { get; set; }
  }
}

Run the page again and you’ll see that a RegularExpressionValidator is now added to the RequiredFieldValidator already there.

There are loads of new features in Dynamic Data 4.0 that we can now leverage in WebForms and MVC including. 

  • Primary keys are now identified as primary keys and are not shown as an editable field
  • New field templates for URLs and email addresses.
  • Many-to-many relationships are now handled as a field template

Entity templates are another new feature in Dynamic Data 4.0. Whereas field templates deal with single field types, entity templates deal with objects as a whole, be they contained in a single table or spanned across many. Let's say we've a website in which the information for an auction item is restricted based on whether you're an admin, the seller or a buyer. An entity template means you can control what each role sees at the object level rather than reiterating it across several pages where that object is bound to controls.

Entity and field templates are available to both MVC and Webforms.

QueryExtender

If there's one problem with LINQ-based DataSource controls (for example, LinqDataSource and EntityDataSource) in .NET 3.5 it's that filtering on the data isn't supported declaratively by the data source. You have to take control of the query being sent to the database, add the filtering yourself and then passing the query back to the data source, usually by handling the OnQueryCreated event.

protected void dsProducts_QueryCreated(object sender, QueryCreatedEventArgs e)
{
  if (!String.IsNullOrWhiteSpace(txtFilterName.Text))
  {
    e.Query = from p in e.Query.Cast<AdventureWorksLT_DataModel.Product>()
              where p.Name.StartsWith(txtFilterName.Text.Trim())
              select p;
  }
}

In .NET 4.0 however, there is now a solution - the new QueryExtender control. Let's take an example and reuse the page we've got going. It already pulls down the contents of the Products table from the AdventureWorks table. Let's add a simple textbox which let's you set the start of the product's name to search for.

<body>
  <form id="form1" runat="server">
  <div>
    Name starts with:
    <asp:TextBox ID="txtFilterName" runat="server">Sport</asp:TextBox>
    <asp:Button ID="btnGo" runat="server" Text="Go" />
    <br />
    <asp:GridView runat="server" DataSourceID="entityDs" ID="gvwProducts">
...

To include the filtering in the page, we add the QueryExtender to the page as follows.

<asp:QueryExtender runat="server" ID="qeFilter" TargetControlID="entityDs">
  <asp:SearchExpression DataFields="Name" SearchType="StartsWith">
    <asp:ControlParameter ControlID="txtFilterName" PropertyName="Text" />
  </asp:SearchExpression>
</asp:QueryExtender>

Et voila. As the page loads, the QueryExtender informs the EntityDataSource and filters the data it presents us based on the contents of the text box.

Summary

There's a heck of a lot of new and interesting stuff going on in and around the world of data in .NET. Besides the advancements to the Dynamic Data and Entity Frameworks, there's also a new version of ADO Data Services (now known as WCF data services), the introduction of data storage in the cloud with SQL Azure Services and client-side data binding with AJAX Data Templates.

In our look at one small corner of it all, we saw how the EnableDynamicData method allows us to leverage the Dynamic Data Framework to act as a CSS-like form presentation and validation rules template for all our templated, data bound controls - a level of integration between Dynamic Data and Webforms/MVC previously unavailable.

We also saw how we can leverage the new QueryExtender control against LinqDataSource and EntityDataSource controls to act as dynamic filters for the data they retrieve.

Notes From Guathon Birmingham

On March 26, Scott Guthrie came and talked to about 200 developers in the centre of Birmingham. For six hours. Here's the abridged and annotated version of what he said about ASP.NET

Talk 1 : ASP.NET Development

The focus for ASP.NET 4.0 is to improve the end-to-end development scenarios, and to provide a productivity boost for all development within VS2010.

Improvements in VS2010

  • Intellisense in VS2010 now takes three hints for filtering items: the start of the class or member name, a substring of the name, and its 'initials' based on how it's spelled using pascal/camel casing. For example, type HttpCac, Cache or HCP and you'll get HttpCachePolicy in the intellisense list.
  • When you select a variable all instance of that variable are highlighted in the code editor window.
  • You can make box \ vertical selections in the code editor window by holding down the alt key
  • Snippet support has been added for most asp.net and http elements. Perhaps the most used of these will be "runat tab tab" to add ="server". However, there is no snippet support yet in the XAML editors.
  • When editing javascript, variable type inference informs what intellisense lists for that object. This feature works across multiple javascript files by adding comment at the top of the file referencing the other page
/// <reference path="ScriptFile1.js" />
  • Ctrl+, gives you a live search across your projects. It's essentially a Find In All Files dialog on amphetamines. Make a search and then double click on a search result to go straight to it. However there is no filter to show only class names, methods, properties etc.
  • Right click on method name : Call hierarchy window shows the call tree for a given method.
  • Right click on method name : View sequence diagram to produce visio style diagram showing call stack diagrams.
  • Generate dependency graph.
  • Pinning support for watched variables.
  • Intellitrace allows you to step backwards from a breakpoint.. Tools - Options - Intellitrace - Enable  (very cool). It will also do parallel execution analysis.
    • Intellitrace dumps can be saved to disk, sent over to another machine, load the dump and investigated further on that machine from exactly the same point.
    • Intellitrace is not in Pro - it's in premium or ultimate.
    • Intellitrace requires .NET 4.0 (need to check with ScottGu)
  • MSDN subscribers, websitespark, bizspark and dreamspark subscribers get VS2010 on the day. MSDN subscribers get a free year's upgrade to the next level SKU.
  • VS2010 has multiple monitor support.
  • VS2010 includes new test impact analysis tools to help developers figure out which tests they should be using to test the changes they are making. (MSDN docs here and Chanel 9 vid here)

When using Intellitrace, does edit and continue work after you step backwards?
The answer depends on the IntelliTrace collection level. If the user has set the collection level to “IntelliTrace events and call information”, then edit and continue is disabled altogether. If the collection level is set to “IntelliTrace events only”, edit & continue is supported.

Does intellitrace require us to use .NET 4.0 or will it work with earlier versions of .net?
IntelliTrace works on CLR 2.0 and above.

Can you create a sequence diagram in VS, save it and then use that diagram as a basis for code generation in another project?
We do not have code generation out of the box from sequence diagrams.  It certainly would be possible to write an extension that would do that though.

Multi-Targetting

  • .net4 includes a new CLR and version string.
  • VS2010 supports multi-targeting for .NET 2.0, 3.0, 3.5, and 4.0.
  • A new AppPool type is added to IIS for .net 4.0 websites when .net 4.0 is installed.
  • VS2010 will install side by side with VS2008. However VS2008 does not read VS2010 project files. Meanwhile VS2010 will auto-convert VS2008 project files to VS2010 so you'll need to maintain separate solution and project files for teams working with both versions of visual studio
  • When you set the target framework in a project properties screen in VS2010, you can select all four .net frameworks plus "Load Other Frameworks". Intellisense in VS2010 is informed by the shipped reference assemblies for the targeted framework rather than the version of .net that visual studio is built against. So when a new set of reference assemblies is released, you can 'Load Other Assemblies' to include those reference assemblies in your list of frameworks to target. (For example, reference assemblies already exist for Windows Phone & Silverlight. Theoretically you could also create reference assemblies for Mono.)

VS2010 installs safely side-by-side with vs2008 on the machine, but does vs2008 install safely side-by-side with vs2010 already on the machine?
Yes – this works fine.

Will the reference assemblies for Silverlight 4 be included with its installer or will they be a separate download? Ditto for RTM Win Phone 7.
Yes – they are included as part of the SL4 tools and Windows Phone 7 tools.  No separate download required.

Webforms 4.0

Does the ScriptManager have some sort of internal list of scripts already on the CDN so that when EnableCDN is set to true, it only seeks out jQuery or MS AJAX from the CDNs rather than a developer’s own JS files? Or is there some way to upload your own scripts to a corner of the CDN?
Yes – I believe it does.  It also, though, allows you to add other scripts to the list and/or use alternative CDNs if you want.

Deployment

  • Config transform files - config transform files only work with web app projects and not website projects.
  • One click Publish mechanism - to file system, via ftp, or using WebDeploy (which also sucks out the sql for the database schema and includes them in the deployment package).
  • There will be a web deployment package for vs2010 for upgrading projects from vs08 or vs05, but new projects should use the new WebDeploy set built into vs2010. WDP2010 may not be released on April 12 with vs2010 but probably by the end of April.
  • New Package Publish Web step in project properties dialogs. (This is where you can pull the SQL schema and stuff).
    • Build Deployment Package option which pulls in all files, ACLs and SQL into one zip.
    • Zip can be published to remote server and populated remotely.
    • Zip can be imported into IIS. (RHS Actions - Deploy - Import Application)
    • WebDeploy can be scripted with Powershell.
    • WebDeploy can be used with asp.net 3.5
    • By default it will copy only the files that have changed.
    • WebDeploy is designed to work with shared hosting.

Final bits

End of Session One - 12 noon

Outside, the weather has turned and mild, grey, overcast Birmingham has turned into mild, grey, wet Birmingham with additional chances of heavier downpours as we go into the afternoon. Campaigners from world vision are apparent by the people avoiding them and those running the taste test for Magnums equally apparent for the opposite reason.

The stay at home mums outside the Odeon are wondering why 200 geeky people keep running in and out of the building.

Pre-session two questions

How do you see the relationship between WPF and Silverlight evolve? 
WPF has definitely enhanced in 4.0 as part of enhancing VS2010 with WPF. Our goal now is to keep the same programming models in sync between the two. Silverlight and WPF are both governed by the same team so there is a fair amount of cross-pollination between the two.

Is the QueryExtender control built using the dynamic LINQ library shipped earlier?
Yes. 

Will there be a ReMIX 10?
Possibly not, but if so then in September. Certainly in the future.

Session Two - ASP.MET MVC 2

There was heavy focus on emphasizing the new features of MVC 2 in this session, which was mostly demonstration. However, it began with a brief introduction to MVC for those who had not come across it before.

  • Webforms and MVC are complementary templating engines for ASP.NET. Web sites / apps can be 100% one or the other or hybrid.
  • MVC allows complete and full control over what is rendered to the browser. It is also geared towards better unit testing.
  • MVC 2 is a separate download for VS2008 and ASP.NET 3.5. It is built-in to VS2010 for ASP.NET 4.0
  • Like webforms, file - new project gives empty and working web app projects.
  • MVC uses a convention-based structure works for MVC.
  • MVC has three main components - Actions, Controllers,Views and Models.

Can you overload an action method with multiple signatures? 
Yes, although it’s probably easier to use default parameter values for this and more common to overload action methods by HTTP verb

// get /events/details?eventid = 3 
// if not specified, eventid given default value of 0. 
public string Details(int eventid = 0)  
  • MVC promotes separation of concerns. Which makes things\code easier to replace and easier to test.
  • Controllers pass data from a Model to a View template which then puts that data into HTML as required.
  • Model = objects that represent the data and corresponding validation and domain logic.

Scott builds a new database. Uses the Entity Framework to create the Model for the database. (File –> New -> EF Data Model).

  • New <%: variable %> syntax. HTML encodes the string value in the variable. Good for protecting against injection attacks.
  • MVC 2 pages now inherit from ViewPage<dynamic> which allows for late binding against the data type. Replacing dynamic with an actual type name will early bind it and allow intellisense against the model object in VS2010.
  • AddView wizard generates a default template to work with against a table or entity in the database. Uses the new <%: syntax.

What if your view contains more than one model?
You create a ViewModel class which encapsulates all the data items to add in and then inherit your view from ViewPage<viewmodelclass>

  • The view does not have to be strongly typed. Just inherit it from ViewPage<dynamic>

Strongly-typed html helpers

In MVC 1 = Html.TextBox("FirstName") would go find the type from the database. In MVC2  = Html.TextBoxFor(m=>m.FirstName) and it will pick up the type from the class. And pick up any renames of class properties which it wouldn't in MVC 1.

  • There are built-in snippet for these html helper methods.
  • Basically suffix MVC1 helper method name with For to get strongly typed version.
  • Scott demos the CRUD view templates based on the model.
  • Scott demos overloading the create action method by verbs. GET to show form, POST to add item to db. Note that [AcceptVerbs] decorator attribute in MVC1 still there but now also [HttpPost] [HttpDelete] etc as shortcut for [AcceptVerbs] also in MVC 2.
  • No support for HTTP OPTIONS verb.
  • New HtmlHelpers - EditorFor, DisplayFor. Both pick up the type of the variable and displays the appropriate editor or display type. Both work really well for whole classes - they'll inspect all properties and put in appropriate things for all of its properties.
Html.EditorFor(model=>model); 
  • If you have complex relationships between tables (foreign keys), editorFor doesn't automatically generate dropdowns for those foreign keys. But all is customizable. You can add data annotations to your model class or custom template annotations.

Data annotations

Add a Metadata class to Models directory

[MetadataType(typeof(EventMetadata))] 
public partial class Event 
{ 
   class EventMetadata 
   { 
      [ScaffoldColumn(false)] 
      public int EventId {get; set; } 

      [DataType(DataType.MultilineText)] 
      public string Description { get; set; } 
   } 
} 
  • Custom template file in Views/abc/EditorTemplates
  • Scaffold the template then adjust exactly as you want. Name it after the model you want to be templated. Good practice? Use EditorFor without a custom template to begin with and then use custom templates to lock it down before release.
  • Note that data annotations are picked up by editor template (not ignored)
Html.EditorFor(model=>model, "nameOfEditorTempalteToUse"); 

Validation In MVC

“Skinny controllers with fat models”: validation logic into model or service layer - not inside action methods.

  • Add custom validation stuff in metadata class
class EventMetadata 
{ 
   [Required(ErrorMessage="You msut specify a name")] 
   public string EventName {get; set; } 
} 
  • You can plug any other validation libraries into MVC 2 should you wish to do so. (separation of concerns). Or build your own. All descriptors derive from ValidationAttribute.
  • To include client-side validation as well as server-side, include MicrsoftAjax.js and MicrosoftMVCValidation.js and add <% Html.EnableClientValidation(); %> to your view.

ActionRendering

  • Html.Action and Html.RenderAction.
  • Allows you to encapsulate bits of common UI  with backend processing (a la user controls in webforms).
  • Create a view - set it as a partial view. Set [ChildActionOnly] on the action method. Then on another view add a call to Html.Action(method, controller);

What’s the difference between this and Html.Partial?
Partial doesn't do the data modelling stuff.

Areas

  • Allows to encapsulate Controllers, Models and Views.
  • Essentially another layer - {area}\{controller}\{action}. 
  • You can split out areas across multiple projects but the tooling isn't entirely there yet. 
  • Avoid having an area with the same name as a controller not in an area.

Can you scope a Shared folder within an Area folder?
Yes

Asynchronous Support In MVC 2

To perform asynchronous operations without blocking the working thread waiting for the response from a long operation.

Unit Testing

  • TDD support is enhanced in VS10.
  • You can generate entity template so they don't derive from EntityObject (and thus no dependence on a database)
  • Ctrl+Alt+Space toggles Intellisense into “Consume First” mode which stops it aggressively replacing classes and methods you haven't created yet. Indeed it will even add in references to phantom classes.
  • Right click on phantom class to generate class and methods. Then let test fail, then make test pass.
  • Code coverage features included

End of session

A massive thanks to Scott for talking and braving the rain, and to Dave and Phil who managed to put on this Guathon and the one in Glasgow at such relatively short notice. It worked a treat.

ASP.NET 4.0 Part 12, Routing Helper Functions

Welcome to part 12 of my tour through ASP.NET 4.0. In the last episode, we saw how improvements in ASP.NET 4.0 have made it much simpler to get routing for webforms set up and working. In this one, we’ll look in more detail at the new routing-friendly classes, properties and methods you can start to use once routing is set up.

Defining Routes

Let’s start by defining a couple of routes and using them as examples throughout this post.

routes.MapPageRoute("Users", "user-{username}", "~/users.aspx");
routes.MapPageRoute("Forums", "forum-{forumname}", "~/forum.aspx");
routes.MapPageRoute("Threads", "forum-{forumname}/thread-{threadid}", "~/thread.aspx");

We'll also create another one with default values for each of the route parameters, using another of MapPageRoute's overloads.

routes.MapPageRoute("Posts", "post/{forumname}/{threadid}/{postid}", 
  "~/thread.aspx", false,
  new RouteValueDictionary { 
    { "forumname", "Fishing" }, { "threadid", "12" }, { "postid", "1" } 
  });

Should we now visit ~/post/, thread.aspx will load and be passed in "Fishing", "12" and "1" as the values for the forumname, threadid and postid route parameters respectively. Should we visit ~/post/art, the default values for the threadid and postid parameters will passed in along with "art" as the forumname.

MapPageRoute has a couple of other tricks up its sleeve as well. An overload with five parameters allows you to define a basic validation for legal values for each of the route parameters. For example, let's say we wanted forumname to contain only lower-case letters. Then we would define the route like this.

routes.MapPageRoute("Posts", "post/{forumname}/{threadid}/{postid}", 
  "~/thread.aspx", false,
  new RouteValueDictionary { 
    { "forumname", "Fishing" }, { "threadid", "12" }, { "postid", "1" } 
  },
  new RouteValueDictionary { { "forumname", "^[a-z]*$" } }
);

Don't forget as well that MapPageRoute is just a quick route to registering a route that is handled by the built-in PageRouteHandler class. If you want to handle the request using your own handler, you can make the equivalent call to routes.Add.

routes.Add("Posts",
  new Route("post/{forumname}/{threadid}/{postid}",
    new RouteValueDictionary { 
      { "forumname", "Fishing" }, { "threadid", "12" }, { "postid", "1" } },
    new RouteValueDictionary { { "forumname", "^[a-z]*$" } }, null,
    new PageRouteHandler("~/thread.aspx", false)));

Hyperlinks

With our routes defined, we can now set up hyperlinks between routes. All we need are the name of the route and the values of any route parameters. If we need to do it inline in the code (perhaps because it's part of a databinding operation) we can use the new <%$RouteUrl %> syntax. For example.

Go to <a runat="server" 
  href='<%$RouteUrl:RouteName=Users, UserName=Dan  %>'>dan's page</a><br />
Go to <a runat="server" 
  href='<%$RouteUrl:RouteName=Forums, ForumName=Coding  %>'>coding forum</a><br />
Go to <a runat="server" 
  href='<%$RouteUrl:RouteName=Posts %>'>the default post</a><br />

If we need to set the hyperlink in code-behind, it's easiest to use GetRouteUrl(), a new method on System.Web.UI.Control and therefore the Page class. (This actually calls RouteCollection.GetVirtualPath() to get the required URL if you'd prefer to use that instead).

GetRouteUrl() has four overloads, broadly taking the following form

GetRouteUrl([string routeName], routeParameters);

Where specifying the routeName is optional and the routeParameters can be added as either an object of anonymous type or as a RouteValueDictionary. For example,

// Named route, parameters in anonymous type object
hypUsers.NavigateUrl = Page.GetRouteUrl("users", new {username = "Dan" });

// Named route, parameters in RouteValueDictionary
hypForums.NavigateUrl = Page.GetRouteUrl("forums", 
  new RouteValueDictionary { { "forumname", "Fishing" } );

// Named route, no parameters set, so defaults used
hypPosts.NavigateUrl = Page.GetRouteUrl("posts", new {} );

// No route named
hypNoName.NavigateUrl = Page.GetRouteUrl(new { forumname = "Gaming" });

In the last example here, we've not named a route to use a basis for a URL, but we have specified a route parameter called forumname, which appears in three of our declared routes. Which route will it use as a basis to generate the URL? Simply, the first route defined that contains the named parameter. In this case, that's "forums".

Redirection

If you need to redirect a user's browser to a given address, you'll need to call one of the three new redirect methods we mentioned briefly in part 10: either Response.RedirectToRoute() or Response.RedirectToRoutePermanent(). Both have the same five overloads with the only difference that RedirectToRoute() sends an HTTP 302 temporary redirect to the browser and RedirectToRoutePermanent() sends a HTTP 301.

// No Route Named, Parameters In Anonymous Type object
Response.RedirectToRoute( new { username = "Dan" });

// No Route Named, Parameters In RouteValueDictionary
Response.RedirectToRoute( 
  new RouteValueDictionary { { "forumname", "Fishing" } ););

// Only route named, so default parameter values used
Response.RedirectToRoute("posts"); 

// Named route, parameters in anonymous type object
Response.RedirectToRoute("users", new {username = "Dan" });

// Named route, parameters in RouteValueDictionary
Response.RedirectToRoute("forums", 
  new RouteValueDictionary { { "forumname", "Fishing" } );

Note that both these methods are equivalent to calling Response.Redirect(url, stopProcessing) with stopProcessing set to false. Again, more on that here in part 10.

RouteParameters

Last but not least, DataSource users will be interested to know that Microsoft have added a new RouteParameter object to ASP.NET 4.0 which you can use in exactly the same way as, for instance, a QueryStringParameter or a ControlParameter. If you use the Configure DataSource wizard you'll see it added to the bottom of the Source list when you need to add a WHERE clause to the data source.

1_RouteParameter

If you do select Route from the Source list, you'll need to set the name of the route parameter you want to use and also give a default value for the parameter if it isn't available for some reason.

2_RouteParameter

These are the only differences between using a RouteParameter and any other parameter object you use with your DataSources. If you prefer to write the code yourself, the net result of the screesnshots above is the following markup.

<asp:SqlDataSource ID="SqlDataSource1" runat="server" 
  ConnectionString="<%$ ConnectionStrings:UsersDB %>" 
  SelectCommand="SELECT [UserId], [Username] FROM [Users] WHERE ([Username] = @Username)">
  <SelectParameters>
    <asp:RouteParameter DefaultValue="Dan" Name="Username" RouteKey="username" 
         Type="String" />
  </SelectParameters>
</asp:SqlDataSource>

So then, in this article, we've looked at the new methods and objects you'll likely use most often when working with webforms and routing. Until the next time, happy coding.

ASP.NET 4.0 Part 11, Configuring Routing Is Easier

Welcome to part 11 of my tour through ASP.NET 4.0. In this episode, we're going to take a look at how routing for webforms has been made easier to set up in ASP.NET 4.0. This will be followed up in Part 12 with a look at the new route-aware classes, methods and properties in more detail.

Routing was originally introduced in ASP.NET 3.5 SP1 as part of the foundations to enable the ASP.NET MVC template engine to work. It could also be used against the webforms engine but a certain amount of rolling-your-own-foundation coding was required to enable it properly.

In ASP.NET 4.0, that initial legwork is no longer necessary as routing for webforms is now supported as standard in System.Web, the majority of the configuration for routing is pre-set in system-wide config files, and some extra goodness has been added into various classes for ease of use as well. All in all, if you haven't had occasion to try routing with webforms before, try it with ASP.NET 4.0. It's really simple.

Why?

There are a metric / boatload / of articles introducing routing and how it works. In a nutshell, routing, like URL rewriting, is a response to two general criticisms of websites.

  1. We humans prefer it if a page's URL is readable. For example, http://myforums.info/forum-Music is easier to read than http://myforums.info/forum.aspx?id=2. It's easier to remember too
  2. Search engines prefer human-readable URLs as well. In particular, they like URLs with no querystring parameters at the end. And, the more search engine friendly a site's URLs are, the better indexed they may be by a search engine and the higher they may appear in search rankings. (Obviously, URLs aren't the only thing indexing robots look at when they come to your site - Descriptions and Keywords are up there with a host of other things too - but they certainly help.)

So then, the major consequence of routing as far as webforms is concerned are URLs which are more readable for humans and search robots alike with no reduction of information to the page itself.

How?

Routes are initialized as named, regular expressions (using a subset of the standard .NET RegEx syntax) and given an order in which they will be matched against the currently requested URL. For example,

  1. user-{username}
  2. forum-{forumname}
  3. forum-{forumname}/thread-{threadid}

Let's say a request comes to the server for http://myforums.info/forum-Music.

  • If the request is for a page that actually exists on the site, no routing actually occurs and that page is loaded and runs as normal.
  • If the page doesn't exist, the requested URL is matched against each initialized route in the order you've specified. In this example, user-{username} doesn't match the requested URL, so the next route is tried.
  • When a route matching the requested URL is found, the request is routed to the page that deals with that route. Any named sections of the route are then made available to the handling page within the RouteData collection. For example, http://myforums.info/forum-Music matches forum-{forumname}, so control for the request would be passed to the page for that route along with a RouteData collection containing one KeyValuePair where forumname is the Key and Music is its value.
  • If no route is found to match the incoming request, it is passed to the standard ASP.NET 404 page or a custom page if you've defined one.

Routing Is Neither URL Rewriting nor Redirection

Hopefully, the above description should differentiate routing from URL rewriting and page redirection. However, just in case it didn't...

  • Routing is not Page Redirection
    Redirection can only work if the incoming URL is for a page that exist. It works by sending a response to the browser saying that the page has moved with the browser then issuing a second request for the URL it is being redirected to. Routed URLs on the other hand do not necessarily refer to a page that actually exists - they will often seem to refer to directories for example - and are dealt with in one request. Browsers will not issue a second request for the actual page which handles the route.
  • Routing is not URL Rewriting
    URL rewriting occurs very early on in the asp.net request pipeline, literally as request processing is beginning. When the rewritten URL is resolved, IIS then sends it down the pipeline to find a handler to deal with it. It has no control over what handler deals with the request. Routing, on the other hand, occurs further down the pipeline, once the request has been authorised, at which point the routing module dispatches the request to a handler based on which route the request matches and the handler specified by you to deal with the route. (IIS team member Ruslan Yarushev goes into much more detail on the differences between the two in this article.)

Setting Up Webforms Routing In ASP.NET 3.5

Briefly, here are the steps needed to enable routing for webforms in ASP.NET 3.5.

  1. All routing classes and namespaces are found in a new DLL, System.Web.Routing.dll to be included as a reference in your website.
  2. The UrlRoutingModule needs to be added to the list of modules enabled for the site in web.config (as described here in MSDN)
  3. A handler class inheriting IRouteHandler needs to be created that understands how to resolve the route into the page or handler that will actually process the request and generate the RouteData collection. Both Phil Haack and Chris Cavanagh put forward basic implementations of this handler, discussing potential security issues.
  4. Register the routes for the site in the Application_Start handler of global.asax using your new route handler class. For example,
protected void Application_Start(object sender, EventArgs e)
{
 RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
 // Route existing files (default will shortcut routing if physical file exists)
 RouteTable.Routes.RouteExistingFiles = true;

 // Add StopRoutingHandler for .axd and .asmx requests
 routes.Add(new Route("{resource}.axd/{*pathInfo}", 
              new StopRoutingHandler()));
 routes.Add(new Route("{service}.asmx/{*pathInfo}", 
              new StopRoutingHandler()));

 // Add existing routes
 routes.Add("Users", new Route("user-{username}", 
              new WebFormRouteHandler("~/users.aspx", true)));
 routes.Add("Forums", new Route("forum-{forumname}", 
              new WebFormRouteHandler("~/forum.aspx", true)));
 routes.Add("Threads", new Route("forum-{forumname}/thread-{threadid}", 
              new WebFormRouteHandler("~/thread.aspx", true)));

 // Add an unnamed handler for all unknown requests (now created in the PageRoute table)
 //routes.Add(new Route("{value}", 
                new WebFormRouteHandler("~/pages/default.aspx")));
}
  1. Write your pages as you would normally using the RouteData collection in place of the Querystring class. You'll need to make RouteData available by passing a RequestContext object to the object that needs access to the RouteData. Again, Phil Haack suggests one way of doing this (via an IRoutableObject interface) here.
  2. Finally, you have to configure IIS to have ASP.NET deal with requests to your routes, thus passing them on to your RouteHandler classes. If you're using IIS6 and your routes are extension-less (i.e. missing .aspx, .asmx , .ashx etc file extensions in their composition), you'll need to configure wildcard maps on a per-directory level according to where your routes point else you'll end up serving all your static files with ASP.NET as well. Full instructions for this come courtesy of Steve Sanderson (Part 1, Part 2).

Setting Up Webforms Routing In ASP.NET 4.0

By comparison, the procedure is far more straightforward in ASP.NET 4.0, thanks in large part to Microsoft being able to open up and edit System.Web and the classes inside it. Let's compare

  1. System.Web.Routing has been folded into System.Web.dll in .NET 4.0 so there is no need for an additional reference to it in your solution.
  2. The UrlRoutingModule is also already set up in the machine-wide web.config file which means that it works straight off the bat in IIS 6. For IIS 7 users, however, you'll need to enable it with the following addition to web.config.
<?xml version="1.0"?>
<configuration>
...
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      ...
    </modules>
  </system.webServer>
</configuration>
  1. ASP.NET 4.0 includes an implementation of IRouteHandler for you to use - it's called PageRouteHandler. There's no reason why you can't build your own as before, but it's no longer necessary.
  2. You still need to register routes for your application but if you're using the built-in PageRouteHandler class, you can use the MapPageRoute helper method to make route registration clearer.
protected void Application_Start(object sender, EventArgs e)
{
  RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
  // Route existing files (default will shortcut routing if physical file exists)
  RouteTable.Routes.RouteExistingFiles = true;

  // Add StopRoutingHandler for .axd and .asmx requests
  routes.Add(new Route("{resource}.axd/{*pathInfo}", 
              new StopRoutingHandler()));
  routes.Add(new Route("{service}.asmx/{*pathInfo}", 
              new StopRoutingHandler()));

  // Add existing routes with MapPageRoute helper method
  routes.MapPageRoute("Users", "user-{username}", "~/users.aspx");
  routes.MapPageRoute("Forums", "forum-{forumname}", "~/forum.aspx", true);

  // MapPageRoute is equivalent to adding a Route with the PageRouteHandler like so
  routes.Add("Threads", new Route("forum-{forumname}/thread-{threadid}", 
    new PageRouteHandler("~/thread.aspx", true)));
}
  1. Any pages you write now automatically have access to the RouteData collection as a property of the Page class itself, as well as several other route-aware additions we'll review in the next section.
protected void Page_Load(object sender, EventArgs e)
{
  lblHello.Text = 
    RouteData.Values["username"] == null ?
      "Hello Mr A. Nonymous" : 
      String.Format("Hello {0}", RouteData.Values["username"].ToString());
}
  1. Regrettably, while integration into IIS7 Integrated Mode remains quite simple, you still need to configure IIS6 \ IIS7 in Classic mode to associate the routes used in your site with ASP.NET as you did in ASP.NET 3.5. That's a failing of IIS6 rather than ASP.NET.

As you can see, setting up routing for an ASP.NET 4.0 webforms site is somewhat simpler than for the previous version as Microsoft has taken the opportunity to refactor routing into the core ASP.NET framework. In the next episode, we'll look at several of the new route-aware features for use once in ASP.NET 4.0 sites we've set routing up. Until then, happy coding!