Click here to Skip to main content
15,867,141 members
Articles / Web Development / ASP.NET

A Multipurpose Details Page Template in ASP.NET Dynamic Data

Rate me:
Please Sign up or sign in to vote.
4.85/5 (20 votes)
30 Dec 2009Public Domain16 min read 72.7K   1.8K   66   14
A single page template that can replace all of the Details, Edit, and Insert page templates.

Introduction

The Page Template and Scaffolding features provided by the ASP.NET Dynamic Data Framework can save us serious amounts of time and effort by potentially limiting general customization work to only four or five page templates, regardless of how many entities there are in our data models. The Dynamic Data Framework's default routing mode, known as separate-page mode, in a new Dynamic Data site is to have one page each for the List, Details (view a record read-only), Edit, and Insert actions. The other pre-packaged routing mode, or combined-page mode, uses a single page for List, Details, Edit, and Insert actions. The combined-page mode site model simply uses a grid and a details panel on the same page, while the former has a list, grid, or page from where we can navigate to the edit or insert pages. This article focuses on how to use a single, detail view oriented, Page Template for edit, view, and insert actions.

Routing

Separate-page Mode

Both routing modes mentioned above are included as part of the boilerplate code for Global.asax whenever you create a new Dynamic Data site in Visual Studio. The separate-page mode is active by default:

C#
// The following statement supports separate-page mode,
// where the List, Detail, Insert, and 
// Update tasks are performed by using separate pages.
// To enable this mode, uncomment the following 
// route definition, and comment out the route definitions
// in the combined-page mode section that follows.
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {
    Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
    Model = model
});
Listing 1 - Routing definition for Separate-page mode.

True to recently established customs, the comments are slightly confusing, as they instruct you to "uncomment the following route definition" when it is actually already uncommented. I suppose this may help if you, at some point, comment the route definition out and forget how things work at a later stage.

This routing definition allows four values for the action substitution parameter, and doesn't specify any view. This means that each action will be routed to its own view. For example, a request with a URL of Customers/List.aspx will be routed to the List.aspx page, which will display a list of records from the Customers table, while a URL of Customers/Insert.aspx will cause a request to be routed to the Insert.aspx page, to insert a new Customer record.

One of the nice things about ASP.NET routing is that it allows us to map 'neat' request URLs, i.e., without file extensions, to real views with file extensions, so we could define a route where the URL need only be Customers/List (no ".aspx") to still be routed to the List.aspx page. I have absolutely no idea why Microsoft has not done this in the Dynamic Data Framework, but will investigate in a future article.

Combined- page mode is easy to activate, but in this case, the comments are true; the code really has to be uncommented. I have done so for the sake of clarity, but for the purposes of this article, and our focus on the separate-page mode, it should actually remain commented out.

Combined-page Mode

C#
// The following statements support combined-page mode,
// where the List, Detail, Insert, and Update tasks are performed by using the same page.
// To enable this mode, uncomment the following routes and comment out the route definition
// in the separate-page mode section above. 
//routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") {
//    Action = PageAction.List,
//    ViewName = "ListDetails",
//    Model = model
//});

//routes.Add(new DynamicDataRoute("{table}/ListDetails.aspx") {
//    Action = PageAction.Details,
//    ViewName = "ListDetails",
//    Model = model
//});
Listing 2 - Combined-page mode routing definition

These route definitions cause all requests to be routed to the ListDetails.aspx page, which displays a list and edit panel at the same time on the same page. This mode is very clumsy, and without extensive customization of ListDetails.aspx, is only really suitable for those situations where you need a maintenance UI.

If we look at the four page templates used by the Separate-page mode, we can see that Details.aspx, Edit.aspx, and Insert.aspx are almost identical. Details.aspx, which displays read-only data, even has exactly the same ValidationSummary and DynamicValidator controls as Edit.aspx!

Validators

Figure 1 - Mysterious validators in a read-only page

It seems that even the Dynamic Data development team sometimes engages in some 'copy and paste coding'.

If our project doesn't require extensive customization to these page templates, why not combine them into one page? Even if maintaining only four pages means negligible work, concentrating all the layout and functionality into one page reduces chances of errors creeping in, such as the copy and paste issue I mentioned above. Without giving too much thought to a name for this new page template, let's call it DetailsEditInsert.aspx and move on.

Implementing our New Page Template

I assume that most readers of this article will know how to create a new Dynamic Data site, add a data model to it, and register the data model in Global.asax. If you would like an introduction to Dynamic Data, or a walkthrough of getting a new site up and running, please refer to the Microsoft Dynamic Data website, or do a search and find many great articles covering the very basics.

I'll begin by creating a new Dynamic Data website that uses a data model and the ubiquitous Northwind database. It's not at all very important that your project is identical to the one I use here, or in the example code. As long as you have some scaffolded tables in a working Dynamic Data site, you will be able to follow me. That is one of the beautiful things about the Dynamic Data Framework and its templating.

Adding the New Page

I've created a new solution called DynamicDataArticles, and added a new website called AllActions to it.

Let's create the New Page template in the Dynamic Data\PageTemplates folder:

New_Page_Template.png

Figure 2 - The New Page template

The Others

Now, let's take a closer look at the other detail pages: Details.aspx, Edit.aspx, and Insert.aspx. As I noted before, they are all strikingly similar. All of these share a basic structure and set of key controls:

  • A DynamicDataManager above an UpdatePanel that contains all the other dynamic data and standard controls. The DynamicDataManager may not be located inside an UpdatePanel.
  • A DetailsView and an EntityDataSource control. Our interest lies with the DetailsView.
  • The UpdatePanel and ScriptManageProxy controls used for AJAX functions.

A fairly informative starting point is to copy all the layout and code from Details.aspx into DetailsEditInsert.aspx. Of course, you can always just create a copy of Details.aspx and rename it. Let's tell Dynamic Data to route all detail type (non-list) requests to our new page. Carefully change the single, default route definition in Global.asax, as follows:

C#
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {
    Constraints = new RouteValueDictionary(
      new { action = "List|Details|Edit|Insert" }),
    Model = model
});
Listing 1 - Before first route changes
C#
routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {
    Constraints = new RouteValueDictionary(new { action = "Details|Edit|Insert" }),
    ViewName = "DetailsEditInsert",
    Model = model
});
Listing 2 - After first route changes

See how we've narrowed the actions allowed in this route definition down to only the three detail type actions. This is because we are now specifying a view name, meaning all actions for this definition will get routed to the DetailsEditInsert.aspx view, whereas before, each action was routed to its corresponding view. It's simply not appropriate to also route the List action to DetailsEditInsert.aspx, so we move that action out to add a new, separate route definition only for list actions:

C#
routes.Add(new DynamicDataRoute("{table}/List.aspx")
{            
    ViewName = "List",
    Action = PageAction.List,
    Model = model
});
Listing 3 - New route definition for the "List" action

Note how I no longer use a substitution parameter for the action, because there is only one possible action for this route definition. Because I don't use an action parameter, I need to tell Dynamic Data what my action is, hence the PageAction.List property.

The resource cannot be found.

Description: HTTP 404. The resource you are looking for (or one of its dependencies) 
could have been removed, had its name changed, or is temporarily unavailable. 
Please review the following URL and make sure that it is spelled correctly.

Requested URL: /AllActions/DynamicData/PageTemplates/List.aspx

This happens because Visual Studio loses track of its Start Page for this site, and although the requested resource (/PageTemplates/List.aspx) does in fact exist, in Dynamic Data, we have to be routed to that resource. We cannot navigate there directly, as the DynamicDataManager control on the page will not have enough metadata to properly initialize the page. We can easily fix this by setting Default.aspx as the Start Page for this web site in Visual Studio.

Let's view the site in the browser and see if our new page is receiving requests. If we browse to Default.aspx, we should see a list of tables from our data model. Hover over one of the table links, and note the URL for the link. It should be ~/{table}/List.aspx, and clicking it should, of course, execute the List action for that table and show us a grid with the contents of the table.

List_Example.png

Figure 4 - List view of the Orders table

Take a look at the last part of the URLs for the Edit, Details, and Insert new item links. They all specify the action corresponding to the link. However, if we click on any of these links, our modified route definitions will ensure that we always end up on the DetailsEditInsert.aspx page. Things get more interesting when we click the Insert new item link:

InsertAttempt.png

Figure 5 - First attempt to Insert

Two details stand out here. The really major one is that the form is already populated! The other detail is actually very minor, being the heading says "Entry from table Orders", when it should say, "Add new entry to table Orders". These are both clues that our triple-purpose DetailsEditInsert.aspx doesn't know that it's supposed to be doing an insert. We copied the functionality of the Details.aspx verbatim into our new page, so we can't blame it for being confused. Note the value of the OrderID field. As our new page is trying to display details for an Order, but has no primary key, it settles on the first record in the Orders table, which has an OrderID of 10248. You can confirm this by viewing the data in the Orders table using, e.g., Server Explorer. Let's take care of the major details first.

As much as I would have liked to have routing details available to DetailsEditInsert.aspx so that we can see which action routed to this page, I could find no way of achieving this. I settled on examining the request URL:

C#
protected void Page_Init(object sender, EventArgs e)
{
    dynamicDataManager.RegisterControl(commonDetailsView);
    switch (Request.Url.Segments[3])
    {
        case "Insert.aspx":
            commonDetailsView.AutoGenerateInsertButton = true;
            commonDetailsView.ChangeMode(DetailsViewMode.Insert);
            break;
    }
}
Listing 4 - Giving the multipurpose page a single purpose
An Aside on Object Naming

Two control names in the above code will most likely look strange to you. Whenever I create a new page template in Dynamic Data, or when I customise the default templates, I revise the object naming in these templates to reflect my own standards and conventions. I recommend you do this as well, as the nice thing with Dynamic Data is that you seldom need to do this. Instead of doing it for every project, do it once for each standard page template, and simply replace the default templates with your own every time you create a new project.

I have renamed DynamicDataManager1 to dynamicDataManager, DetailsView1 to commonDetailsView, and DetailsDataSource to detailsDataSource. The "1" suffix always strikes me as making code or mark-up look sloppy and unfinished. I have also renamed other controls not germane to this article, and the complete result of my naming revision can be seen in the DetailsEditInsert.aspx page and its code-behind in the sample project.

My method in the above code is not the most elegant, nor robust, but for most purposes, it is quite adequate. The URLs are supplied by the Dynamic Data framework, and should always have at least four segments, the last of which should always be Details.aspx, Edit.aspx, Insert.aspx, or List.aspx, so I feel comfortable hard-coding the segment index as well as the strings in non-production web sites.

Comparing the original Insert.aspx to our new page also reveals some other key differences. First, the ItemInserted and ItemCommand events of commonDetailsView are not handled. All that both of these do is to return to the list view; the ItemCommand handler does this when the Cancel link is clicked, and the ItemInserted handler does the same, on condition that no errors occurred during the insert. The latter is worth mentioning because Dynamic Data will just leave you staring at the Insert view if any exceptions occur during the insert operation, with no indication of why nothing is happening!

Second, detailsDataSource needs to allow inserts as well. By default, on our new page (and the Details.aspx page), only deletes are allowed.

XML
<asp:EntityDataSource ID="detailsDataSource" 
       runat="server" EnableDelete="true" 
       EnableInsert="true" EnableUpdate="true">
    <WhereParameters>
        <asp:DynamicQueryStringParameter />
    </WhereParameters>
</asp:EntityDataSource>
Listing 5 - Enabling Inserts on the data source

Readers are well advised to add a control to the Insert.aspx and Edit.aspx page templates for displaying an error message, and to modify the ItemInserted and ItemUpdated event handlers, respectively, to detect exceptions and display an error message. Of course, those of us using DetailsEditInsert.aspx need only modify a single page (with both event handlers).

After making the above code changes, i.e., adding the event handlers and enabling inserts, our insert action using DetailsEditInsert.aspx works as expected, inserting the new item and returning us to the List view. Let's move on to getting the Edit action working as well.

EditAttempt.png

Figure 7 - First attempt at editing

As we can see in the above screenshot of the DetailsEditInsert.aspx form we see after clicking the Edit link on any item in the List view, our multi-purpose page template doesn't know it is supposed to be in Edit mode. Its rendered UI looks exactly like that of the original Details.aspx page, complete with an Edit link that would normally take you to the Edit view. In this case, it simply reloads DetailsEditInsert.aspx, complete with an Edit link.

Let's repeat the process we followed a short while ago to get the Insert action working. First, we need to tell the multi-purpose page what its current purpose is:

C#
protected void Page_Init(object sender, EventArgs e)
{
    dynamicDataManager.RegisterControl(commonDetailsView);
    switch (Request.Url.Segments[3])
    {
        case "Insert.aspx":
            commonDetailsView.AutoGenerateInsertButton = true;
            commonDetailsView.ChangeMode(DetailsViewMode.Insert);
            break;
        case "Edit.aspx":
            commonDetailsView.AutoGenerateEditButton = true;
            commonDetailsView.ChangeMode(DetailsViewMode.Edit);
            break;
    }
}
Listing 6 - Adding the 'Edit Purpose' to our page

This is the most important change to allow us to execute the Edit action with our multi-purpose page. Then, we must also enable edits on the entityDataSource control, i.e., add the EnableUpdate="true" attribute to its declaration in the mark-up, and add a handler for the ItemUpdated event of commonDetailsView.

Last Finesses

I hope most of you couldn't help feeling a little uneasy about the extra Edit and Delete links on our multipurpose page, in any of the three modes we implemented for that page template.

ExtraLinks.png

Figure 8 - Unexplained links

It's also not apparent in the above image which mode the page is in, because the same heading, "Entry from table Region", always appears (only for all Region table actions, of course), regardless of the page's mode. Let's fix these two issues before considering our work here over and hitting the pub.

Why are those Edit and Delete links being generated by our multipurpose template? Looking at the mark-up for DetailsEditInsert.aspx, we can see that commonDetailsView has a single template control declared in its Fields collection, and this template control defines the two problem links. It's simple enough to just delete the whole Fields element from the mark-up, but why are these links defined in the first place? Remember, we based our multipurpose page on the Details.aspx page, so that page probably needed these links; ergo they are only needed when our new page is in Details mode.

Adding and removing this template control field when we set our mode seems like a bit much work, and it's work I wasn't really interested in when I devised this project. Some experimentation quickly revealed that we can order a DetailsView control to automatically generate these links just as we do with the Update and Insert links. Let's add a third case to our mode switch:

C#
switch (Request.Url.Segments[3])
{
    case "Insert.aspx":
        commonDetailsView.AutoGenerateInsertButton = true;
        commonDetailsView.ChangeMode(DetailsViewMode.Insert);
        break;
    case "Edit.aspx":
        commonDetailsView.AutoGenerateEditButton = true;
        commonDetailsView.ChangeMode(DetailsViewMode.Edit);
        break;
    case "Details.aspx":
        commonDetailsView.AutoGenerateEditButton = true;
        commonDetailsView.AutoGenerateDeleteButton = true;
        commonDetailsView.ChangeMode(DetailsViewMode.ReadOnly);
        break;
}
Listing 7 - Final version of the mode switch

Now, if we click on a Details link in the List view, we get nearly everything we need, plus the two extra, problem links:

OwnEditLink.png

Figure 9 - Our own Edit and Delete links

If we now simply remove the explicit links from the Fields collection of commonDetailsView, what do we have to lose? Only one thing really, and that is the JavaScript delete confirmation that is attached to the client Click event of the Delete link, as seen in the LinkButton marked up for the Delete link, in "OnClientClick":

XML
<asp:LinkButton ID="DeleteLinkButton" 
    runat="server" CommandName="Delete" CausesValidation="false" 
    OnClientClick='return confirm("Are you sure you want to delete this item?");'
    Text="Delete" />
Listing 9 - JavaScript delete confirmation

If you click the bottom-most Delete link, as seen in the image above, you will get no confirmation. The displayed record will be deleted, and you will be routed back to the List view without passing Begin. Now, once we delete the two 'problem' links (notice that now 'problem' is in quotes), how can we regain the fairly useful delete confirmation? Well, seeing as the Delete link is automatically generated, we can be fairly confident that its text will always be "Delete". Armed with this assumption, a quick and dirty jQuery solution is not far away.

JavaScript
<script src="http://localhost:28537/AllActions/Scripts/jquery-1.3.2.js" 
        type="text/javascript"></script>
<script type="text/javascript">
    jQuery(function($) {
        $(".detailstable a:contains('Delete')").click(function() {
            return confirm("Are you sure you want to delete this item?");
        });
    });
</script>
Listing 10 - jQuery delete confirmation

Notice how I've had to use an absolute URL for the jQuery script reference. This is because of the 'artificial' URLs used by Dynamic Data routing. If I had used a relative URL, such as Scripts/jQuery-1.3.2.js, when the browser requests a page with a URL ending in, e.g., Categories/List.aspx, it also effectively requests the non-existent resource Categories/Scripts/jQuery-1.3.2.js, and gets a 404 - Not found response. This caused me some consternation, as I could see that jQuery was loaded in Default.aspx, and could not figure out why it wasn't loaded in Categories/List.aspx. Using advanced debugging tools such as Firebug, this took all of about ten minutes to resolve.

In a nutshell, the jQuery function above executes when the document element is ready in the browser DOM. It looks for all "a" elements (links) that contain the text "Delete", that are descendents of any elements with a CSS class of "detailstable", and attaches an anonymous event handler to the "click" event of these link elements, which does exactly the same as the one defined above for the LinkButton. The mark-up for the DetailsEditInsert.aspx page tells us that commonDetailsView has the CSS class "detailstable", and because the Delete link is automatically generated for us, we should have some confidence that the link will always contain that text.

Readers not familiar with jQuery could do a lot worse than to download it, add it to a simple HTML only project, open up the jQuery API Browser, and spend a few hours learning the basics. This API and library is definitely one where fairly advanced skills quickly bootstrap themselves from very elementary basics. The only ASP.NET alternatives for implementing our delete confirmation that I can imagine seem not just clumsy, but positively handicapped, in comparison to the above two-liner, but then maybe an astute reader, with a better imagination than me, can offer a pure ASP.NET solution.

Epilogue

Let's take another look at what we've accomplished. We have added a new page template called DetailsEditInsert.aspx that performs all the functions of the Details.aspx, Edit.aspx, and Insert.aspx page templates provided "out of the box" by the Dynamic Data project templates. We have learned a little about Dynamic Data routing, and I hope some of that may rub off on our knowledge of routing in general (mine was nearly non-existent before embarking on this project, and we've used a minimal example of the power of JavaScript, harnessed through jQuery).

This is my first article, in what I see becoming quite a collection, on the shiny and juicy goodies available in ASP.NET Dynamic Data. As I publish future articles, I will revise sections in this one that need exploration in much greater detail to link to the new ones, and I will continue to revise the growing collection to try and maintain a useful web of articles for those interested in Dynamic Data. Please stay tuned, and assist me where possible with suggestions, correction, and criticism.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Founder Erisia Web Development
South Africa South Africa
I am a software developer in Johannesburg, South Africa. I specialise in C# and ASP.NET MVC, with SQL Server, with special fondness for MVC and jQuery. I have been in this business for about eighteen years, and am currently trying to master Angular 4 and .NET Core, and somehow find a way to strengthen my creative faculties.
- Follow me on Twitter at @bradykelly

Comments and Discussions

 
QuestionFantastic article Pin
schadha29-Apr-14 10:11
schadha29-Apr-14 10:11 
GeneralMy vote of 5 Pin
jesykastillo2-Nov-12 8:06
jesykastillo2-Nov-12 8:06 
GeneralExcellent Article Pin
Asif Bg26-Jan-11 1:35
Asif Bg26-Jan-11 1:35 
GeneralMy vote of 5 Pin
Kanasz Robert12-Nov-10 0:31
professionalKanasz Robert12-Nov-10 0:31 
GeneralMy vote of 5 Pin
Armando Airo'26-Aug-10 4:31
Armando Airo'26-Aug-10 4:31 
GeneralMy vote of 1 Pin
arvinder_aneja29-Jan-10 23:44
arvinder_aneja29-Jan-10 23:44 
GeneralRe: My vote of 1 Pin
Brady Kelly5-Feb-10 0:18
Brady Kelly5-Feb-10 0:18 
GeneralNice Article Pin
thatraja19-Jan-10 2:25
professionalthatraja19-Jan-10 2:25 
GeneralNice Article. Pin
ProtoBytes4-Jan-10 7:00
ProtoBytes4-Jan-10 7:00 
I like how you illustrated the code and application.


GeneralRe: Nice Article. Pin
Brady Kelly4-Jan-10 9:50
Brady Kelly4-Jan-10 9:50 
GeneralHi Companion Pin
Anil Srivastava3-Jan-10 19:06
Anil Srivastava3-Jan-10 19:06 
GeneralRe: Hi Companion Pin
Brady Kelly3-Jan-10 19:49
Brady Kelly3-Jan-10 19:49 
Generalexcellent technical writing ! Pin
BillWoodruff31-Dec-09 19:02
professionalBillWoodruff31-Dec-09 19:02 
GeneralRe: excellent technical writing ! Pin
Brady Kelly31-Dec-09 21:07
Brady Kelly31-Dec-09 21:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.