Using User Controls as Page Templates in Dynamic Data

Dynamic Data has the concept of Page Templates, which are pages that live under ~/DynamicData/PageTemplates and are used by default for all tables.  Recently a user on the forum asked whether they could use User Controls instead of Pages for those templates.

To me, this means potentially two distinct scenarios, and I tried to address both in this post:

  1. Using routing: in this scenario, you still want all your requests to go through the routing engine, but have the user control templates somehow get used.  The URLs here would still  look identical to what they are in a default Dynamic Data app, e.g.  /app/Products/List.aspx (or whatever you set them to be in your routes).
  2. No routing: in this scenario, you want the URL to go directly to a specific .aspx page, and then have that page do the right thing to use Dynamic Data through the User Control templates.

Creating the User Controls

First, let’s look at what will be the same for both cases.  Under ~/DynamicData/PageTemplates, I deleted all the aspx files (to prove that they’re not used), and instead created matching ascx files (i.e. User Controls).  Those user controls contain essentially the same things that were in the pages, minus all the ‘outer’ stuff (e.g. things that relate to the master page).

Making it work using routing

Now let’s look specifically at case #1 above, where we want to use routing.  First, we need to make a small change to the route to use a custom route handler:

 routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {
    Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
    Model = model,
    RouteHandler = new CustomDynamicDataRouteHandler() 
});

Note how we set RouteHandler to our own custom handler type.  Now let see what this type looks like:

 public class CustomDynamicDataRouteHandler : DynamicDataRouteHandler {
    public override IHttpHandler CreateHandler(DynamicDataRoute route, MetaTable table, string action) {
        // Always instantiate the same page.  The page itself has the logic to load the right user control
        return (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath("~/RoutedTestPage.aspx", typeof(Page));
    }
}

It’s basically a trivial handler which always instantiates the same page!  That may look strange, but the idea is that all the relevant information is carried by the route data, which that page can then make use of.  Now let’s see what we’re doing in this one page.  It’s itself pretty trivial, with just a bit of logic in its Page_Init:

 protected void Page_Init(object sender, EventArgs e) {
    // Get table and action from the route data
    MetaTable table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
    var requestContext = DynamicDataRouteHandler.GetRequestContext(Context);
    string action = requestContext.RouteData.GetRequiredString("action");

    // Load the proper user control for the table/action
    string ucVirtualPath = table.GetScaffoldPageVirtualPath(action);
    ph.Controls.Add(LoadControl(ucVirtualPath));
}

The first 3 lines show you what it takes to retrieve the MetaTable and action from the route date.  Then the next couple lines load the right user control for the situation.  GetScaffoldPageVirtualPath is a simple helper method which has logic to locate the proper ascx:

 public static string GetScaffoldPageVirtualPath(this MetaTable table, string viewName) {
    string pathPattern = "{0}PageTemplates/{1}.ascx";
    return String.Format(pathPattern, table.Model.DynamicDataFolderVirtualPath, viewName);
}

And that’s basically it for the routed case!

Making it work without routing

Now let’s look at the non-routed case.  Here, the routes are not involved, so the URL goes directly to a page.  That page is similar to the one above, but with some key differences.  Here is what it does in it Page_Init:

 protected void Page_Init(object sender, EventArgs e) {
    // Get table and action from query string
    string tableName = Request.QueryString["table"];
    string action = Request.QueryString["action"];
    
    // Get the MetaTable and set it in the dynamic data route handler
    MetaTable table = MetaModel.Default.GetTable(tableName);
    DynamicDataRouteHandler.SetRequestMetaTable(Context, table);

    // Load the proper user control for the table/action
    string ucVirtualPath = table.GetScaffoldPageVirtualPath(action);
    ph.Controls.Add(LoadControl(ucVirtualPath));
}

The key difference  is that it can’t rely on route data being available, so it needs to get the table and action information from  somewhere else.  You can come up with arbitrary logic for this, but the most obvious way is to just use the query string (e.g. ?table=Products&action=List).

Then  it does sort of the reverse of the case above, and sets the MetaTable into the route handler.  Even though routing is not used, Dynamic Data tries to get the MetaTable from DynamicDataRouteHandler, so having it there allows many things to just work.

And finally, it loads the user control using the exact same steps as above.  And that’s that!

The code is attached below.

DynamicDataWithUserControls.zip

Comments

  • Anonymous
    November 27, 2008
    Track back from DotNetLinks.net

  • Anonymous
    December 01, 2008
    This is super slick. I really appreciate it. As you probably know, your solution also works on URLs to load Edit ASCX control, such as... <asp:hyperlink id="HyperLink3" runat="server" navigateurl="~/NonRoutedTestPage01.aspx?table=Products&action=Edit&ProductID=2">Test page that doesn't use routing, to load a form for Edit.</asp:hyperlink> ...so that is really great. Thank you. -- Mark Kamoski

  • Anonymous
    February 18, 2009
    Please post corrections/new submissions to the Dynamic Data Forum . Put FAQ Submission/Correction in

  • Anonymous
    February 18, 2009
    Is it possible to use multiple routing handlers is a nested form (as in composite or decorator patterns) so one routing handler serves as the input for the other (in a similar way streams are used which helps in handling security, etc.)? If not, what is the bast way to achieve this?

  • Anonymous
    July 19, 2010
    That is exactly what I was looking for. Great :-).

  • Anonymous
    February 17, 2011
    Somehow after converting it to vb... i seem to be getting an error: You need to have a DynamicManager control on the page and register your data control with it in order to use a DynamicQueryStringParameter. Please advice.

  • Anonymous
    February 17, 2011
    For Vb, You need to move the DynamicManager.RegisterControl method, and Gridview ColumnGenerator from the Page_Init to Page_Load.

  • Anonymous
    February 17, 2011
    Thanks Lim. However the inline select and update commands are not working. On Select: - Columns all disappear On Update - Data is not updated. Please assist.

  • Anonymous
    May 16, 2012
    This is a brilliant article - ive been searching the web for a while before i found it.