ASP.NET MVC Tip #5 – Create Shared Views

In this tip, I explain how to create shared views for controller actions. A shared view can be used with multiple controllers. In particular, I explain how to create shared views for displaying and editing database data.

Imagine that the HomeController controller exposes an action named Index(). When the Index() action is invoked, the ASP.NET MVC framework first attempts to retrieve the Index view from the following path:

\Views\Home\Index.aspx

If the Index.aspx view is not present in the Home folder, the ASP.NET MVC framework next attempts to retrieve the view from the Shared folder:

\Views\Shared\Index.aspx

If the Index.aspx view can’t be retrieved from either location, then the error message in Figure 1 is displayed.

Figure 1 – Error displayed when attempting to retrieve non-existent view

clip_image002

So, the ASP.NET MVC framework always attempts to retrieve a view from the Shared folder when the view can’t be retrieved from the Views subfolder that corresponds to the controller’s name. This means that you can create default views for action methods that are shared across multiple controllers.

In today’s tip, I show you how to create shared views for performing standard database operations that can be used with the DataController class that we created in yesterday’s Tip #4. If you haven’t read yesterday’s tip, read it now at:

http://stephenwalther.com/archive/2008/06/18/asp-net-mvc-tip-4-create-a-custom-data-controller-base-class.aspx

I modified the DataController for this tip so that its controller actions return the LINQ to SQL DataContext to the view. For example, the modified DataController.Index() method looks like this:

Public Function Index() As ActionResult
    ViewData("DataContext") = Me.DataContext
    ViewData("DataTable") = Me.Table
    Return View()
End Function

We need both the LINQ to SQL DataContext and LINQ to SQL Table to render the grid of records.

The Index.aspx view, placed in the Views\Shared folder, is contained in Listing 1.

Listing 1 – Views\Shared\Index.aspx

<%@ Page Title="Index" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="false" CodeBehind="Index.aspx.vb" Inherits="Tip5.SharedViews.Index" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
 
<%=Me.RenderDataTable()%>
<br />
<%= Html.ActionLink("Add Record", "Create") %>
 
</asp:Content>

The Index.aspx view in Listing 1 simply calls the RenderDataTable() method declared in the view’s code-behind file. The Index.aspx.vb code, the code-behind file for Index.aspx, is contained in Listing 2.

Listing 2 -- \Views\Shared\Index.aspx.vb

Namespace SharedViews
 
    Partial Public Class Index
        Inherits DataViewBase
 
 
        Protected Function RenderDataTable() As String
            Dim sb As New StringBuilder()
            sb.AppendLine("<table>")
 
            ' Create Header Row
            Dim columnNames As String() = Me.GetColumnNames()
            sb.AppendLine("<tr>")
            sb.Append("<th></th>")
            For Each columnName As String In columnNames
                sb.AppendFormat("<th>{0}</th>", columnName)
            Next
            sb.AppendLine("</tr>")
 
            ' Create Data Rows
            Dim row As Object
            For Each row In Me.DataTable
                Dim identityValue As Integer = Me.GetIdentityValue(row)
                sb.AppendLine("<tr>")
                sb.Append("<td><small>")
                sb.Append(Me.Html.ActionLink("[View]", "Details", New With {.Id = identityValue}))
                sb.Append("&nbsp;")
                sb.Append(Me.Html.ActionLink("[Edit]", "Edit", New With {.Id = identityValue}))
                sb.Append("&nbsp;")
                sb.Append(Me.Html.ActionLink("[Delete]", "Delete", New With {.Id = identityValue}))
                sb.Append("</small></td>")
                For Each columnName As String In columnNames
                    Dim value As String = row.GetType() _
                        .GetProperty(columnName) _
                        .GetValue(row, Nothing) _
                        .ToString()
                    sb.AppendFormat("<td>{0}</td>", HttpUtility.HtmlEncode(value))
                Next
                sb.AppendLine("</tr>")
            Next
 
            sb.AppendLine("</table>")
            Return sb.ToString()
        End Function
 
    End Class
End Namespace

The RenderDataTable() method generates an HTML table that contains all of the columns and all of the rows represented by the DataTable property. The DataTable is passed by the DataController.Index() controller action to the Index.aspx view.

When you request the Index.aspx view, you get the page displayed in Figure 2.

Figure 2 – The Index.aspx view

clip_image004

Notice that View, Edit, and Delete links appear next to each record. You can click these links to perform the corresponding actions. Notice, furthermore, that there is an Add Record link at the bottom of the page. Clicking this link opens the Create.aspx view in Figure 3.

Figure 3 – The Create.aspx View

clip_image006

The form generated by the Create.aspx view is not pretty, and it does not support validation, but otherwise it works. If you enter a new movie, you can submit the new movie to the database.

The Create form is generated with the code in Listing 3.

Listing 3 – Views\Shared\Create.aspx.vb

Namespace SharedViews
 
    Partial Public Class Create
        Inherits DataViewBase
 
 
 
        Protected Function RenderCreateForm() As String
            Dim sb As New StringBuilder()
            Dim columnNames = Me.GetColumnNames()
            Dim identityColumnName = Me.GetIdentityColumnName()
 
            sb.AppendFormat("<form method='post' action='{0}'>", Me.Url.Action("New"))
            sb.AppendLine("<table>")
            Dim columnName As String
            For Each columnName In columnNames
                If columnName <> identityColumnName Then
                    sb.AppendLine("<tr>")
                    sb.AppendFormat("<th><label for='{0}'>{0}:</label></td>", columnName)
                    sb.AppendFormat("<td><input name='{0}' type='text' /></td>", columnName)
                    sb.AppendLine("</tr>")
                End If
            Next
            sb.AppendLine("</table>")
            sb.AppendLine("<input type='submit' value='Add Record' />")
            sb.AppendLine("</form>")
 
            Return sb.ToString()
        End Function
 
    End Class
End Namespace

The code in Listing 3 uses the LINQ to SQL DataContext and LINQ to SQL Table to generate the form automatically. Both of these objects are passed by the DataController.Create() controller action.

The purpose of this tip was to give you a sense of what you can do with shared views. In this tip, we examined how you could use shared views to automatically generate display, edit, and insert forms for working with database data. You could follow a similar strategy to create standard views for other types of controller actions (for example, standard shopping cart views).

If you would like to experiment with the code discussed in this tip, click the following link (The download includes both VB.NET and C# versions of the code).

Download the Code

4 Comments

  • Great couple of articles there. I see a lot of overlap from the whole Dynamic Data area, but given that this is geared towards usability with the MVC bits today, it still adds some good value.

    Perhaps it would be good to open source these classes/pages so we could get the community to extend them. For example, dealing with a non-trivial object model, it would be nice to get an automatic drop-down list for foreign-key fields, not display the FK ids, and provide links to child collections. Plus, there are those that would inevitably pitch in to evolve the GUI.

    Either way, thanks for the blogs. They are very content rich!

  • Asp net mvc tip 5 create shared views.. Huh, really? :)

  • Asp net mvc tip 5 create shared views.. Bully :)

  • Asp net mvc tip 5 create shared views.. Awful :)

Comments have been disabled for this content.