top button
Flag Notify
    Connect to us
      Site Registration

Site Registration

Customize View, Partial View And Layout Search Locations In ASP.NET MVC

+5 votes
399 views

By default ASP.NET MVC stores all the views associated to a controller inside a sub-folder of Views folder. On the same lines partial views and layout pages are stored inside Shared sub-folder under Views folder. Although this default arrangement works well in most of the cases, at times you may want to deviate from this convention based arrangement and store views, partial views and layouts in some different folder structure. Consider, for example, a huge application that wants to arrange partial views in several sub-folders inside Shared folder. Or some application may want to store views outside views folder. In such cases when you use view names in methods such as View(), Html.Partial() and PartialView() the system won't be able to find the required .cshtml files. Of course, you can use fully qualified paths in some cases but that makes your code rigid because any future change in the paths will require changes at multiple places.

Luckily, you can easily deal with the situation by creating a custom view engine. This article tells you how.

Let's assume that you wish to store partial views and layouts in the following custom folder structure:

image

Notice that partial views and layouts aren't stored directly inside Shared folder. They are stored inside PartialViews and Layouts sub-folders of Shared folder. Views are stored as per default convention. Let's assume that Index view uses Test.cshtml like this:

@Html.Partial("Test")

If you run the application under these settings, you will get this error in the browser:

image

As you can see, the system is unable to find Test.cshtml residing inside PartialViews sub-folder.

To deal with this situation you can create a custom view engine and then supply the custom search locations to the new view engine. Don't worry. Creating a custom view engine to accomplish this task is quite straightforward. Let's do that now.

Add a new class to your project and give it whatever name you wish to give to the new view engine. In my code I am calling my view engine as BinaryIntellectViewEngine. The complete class is shown below:

public class BinaryIntellectViewEngine : RazorViewEngine
{
    public BinaryIntellectViewEngine()
    {
        string[] locations = new string[] {  
            "~/Views/{1}/{0}.cshtml",  
            "~/Views/Shared/PartialViews/{0}.cshtml",  
            "~/Views/Shared/Layouts/{0}.cshtml"
        };

        this.ViewLocationFormats = locations;
        this.PartialViewLocationFormats = locations;
        this.MasterLocationFormats = locations;
    }
}

The BinaryIntellectViewEngine class inherits from RazorViewEngine base class. In our example we are using Razor view engine and hence the above code inherits from RazorViewEngine class. This inbuilt class comes from System.Web.Mvc namespace.

The magic that makes the custom locations work happens inside the constructor of the custom view engine class. Here, we create an array of strings. This array contains a list of all locations where the view engine should look for the views, partial views and layout pages. Notice the convention used in the paths. The placeholder {0} contains the view or payout page name whereas {1} contains the folder name (controller name).

The three base class properties namely ViewLocationFormats, PartialViewLocationFormats and MasterLocationFormats point to the list of search locations for views, partial views and layout pages respectively. In our example, all the three properties are set to the location string array.

This completes our custom view engine. The final step is to register this new view engine with the MVC framework. This is done inside Application_Start event in Global.asax.

void Application_Start(object sender, EventArgs e)
{
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    RouteConfig.RegisterRoutes(RouteTable.Routes);

    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new BinaryIntellectViewEngine());
}

The Application_Start event handler clears the existing registered view engines by calling Clear() method of ViewEngines.Engines collection. Then the new view engine is added using Add() method.

That's it! If you run the application now you should see the Test partial view rendered successfully.

image

posted Oct 25, 2016 by Shivaranjini

  Promote This Article
Facebook Share Button Twitter Share Button LinkedIn Share Button


Related Articles

ASP.NET model binding is quite powerful and flexible. It caters to most of the scenarios without much configuration from developers. However, at times you may need to intervene in order to achieve the desired model binding effect. One such situation is when you use multiple instance of a partial view on a view. This article shows one possible approach to deal with such situations.

Suppose that you have a web page as shown below:

image

As shown in the above figure the web page captures OrderID, CustomerID, ShippingAddress and BillingAddress from the end user. This information is stored in a model class - Order - that looks like this:

public class Order
{
    public int OrderID { get; set; }
    public int CustomerID { get; set; }

    public Address ShippingAddress { get; set; }
    public Address BillingAddress { get; set; }
}

public class Address
{
    public string Street1{get;set;}
    public string Street2{get;set;}
    public string Country{get;set;}
    public string PostalCode{get;set;}
}

The Order class consists of four public properties namely OrderID, CustomerID, ShippingAddress and BillingAddress. Notice that OrderID and CustomerID are integer properties whereas ShippingAddress and BillingAddress properties are of type Address. The Address class is also shown and consists of four string properties - Street1, Street2, Country and PostalCode.

Now let's assume that the whole page is rendered using two ASP.NET MVC Partial Pages. The OrderID and CustomerID is captured using _BasicDetails.cshtml as shown below:

@model Demo.Models.Order

<table>
    <tr>
        <td>@Html.LabelFor(m=>m.OrderID)</td>
        <td>@Html.TextBoxFor(m=>m.OrderID)</td>
    </tr>
    <tr>
        <td>@Html.LabelFor(m=>m.CustomerID)</td>
        <td>@Html.TextBoxFor(m=>m.CustomerID)</td>
    </tr>
</table>

Note that _BasicDetails partial page has its model set to Order class. The partial page then uses LabelFor() and TextBoxFor() helpers to display a label and textbox for the OrderID and CustomerID model properties respectively.

The address information is captured using _Address.cshtml as shown below:

@model Demo.Models.Address

<table>
    <tr>
        <td>@Html.LabelFor(m=>m.Street1)</td>
        <td>@Html.TextBoxFor(m=>m.Street1)</td>
    </tr>
    <tr>
        <td>@Html.LabelFor(m=>m.Street2)</td>
        <td>@Html.TextBoxFor(m=>m.Street2)</td>
    </tr>
    <tr>
        <td>@Html.LabelFor(m=>m.Country)</td>
        <td>@Html.TextBoxFor(m=>m.Country)</td>
    </tr>
    <tr>
        <td>@Html.LabelFor(m=>m.PostalCode)</td>
        <td>@Html.TextBoxFor(m=>m.PostalCode)</td>
    </tr>
</table>

The _Address partial page has Address class as its model and uses LabelFor() and TextBoxFor() helpers to display model properties.

The Index view that makes use of _BasicDetails and _Address partial pages to form the complete page is shown below:

@model Demo.Models.Order

...
@using(Html.BeginForm("ProcessForm","Home",FormMethod.Post))
{
  <h3>Basic Details</h3>
  @Html.Partial("_BasicDetails")

  <h3>Shipping Address</h3>
  @Html.Partial("_Address",Model.ShippingAddress)
        
  <h3>Billing Address</h3>
  @Html.Partial("_Address",Model.BillingAddress)
        
  <input type="submit" value="Submit" />
}
</body>
</html>

The Index view renders the _BasicDetails partial page using Partial() helper. Since the model for Index view is Order class, the same is available to the _BasicDetails partial page. Then two instances of _Address partial page are rendered on the page to capture ShippingAddress and BillingAddress respectively. Recollect that _Address has Address class as its model. So, Model.ShippingAddress and Model.BillingAddress are passed to the Partial() helper.

The above form submits to ProcessForm action method that looks like this:

public ActionResult ProcessForm(Order ord)
{
    //do something with Order object here
    return View("Index");
}

And the Index() action method looks like this:

public ActionResult Index()
{
    Order ord = new Order();
    ord.BillingAddress = new Address();
    ord.ShippingAddress = new Address();
    return View(ord);
}

Both of these methods are quite straightforward and need no explanation.

Now comes the important and tricky part. If you run the application at this stage, you will get the following HTML markup in the browser (unwanted markup has been removed for the sake of clarity):

<form action="/Home/ProcessForm" method="post">        
<h3>Basic Details</h3>
<table>
    <tr>
        <td><label for="OrderID">OrderID</label></td>
        <td><input id="OrderID" name="OrderID" type="text" /></td>
    </tr>
    <tr>
        <td><label for="CustomerID">CustomerID</label></td>
        <td><input id="CustomerID" name="CustomerID" type="text" /></td>
    </tr>
</table>
<h3>Shipping Address</h3>
<table>
    <tr>
        <td><label for="Street1">Street1</label></td>
        <td><input id="Street1" name="Street1" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="Street2">Street2</label></td>
        <td><input id="Street2" name="Street2" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="Country">Country</label></td>
        <td><input id="Country" name="Country" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="PostalCode">PostalCode</label></td>
        <td><input id="PostalCode" name="PostalCode" type="text" value="" /></td>
    </tr>
</table>
<h3>Billing Address</h3>
<table>
    <tr>
        <td><label for="Street1">Street1</label></td>
        <td><input id="Street1" name="Street1" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="Street2">Street2</label></td>
        <td><input id="Street2" name="Street2" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="Country">Country</label></td>
        <td><input id="Country" name="Country" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="PostalCode">PostalCode</label></td>
        <td><input id="PostalCode" name="PostalCode" type="text" value="" /></td>
    </tr>
</table>
<input type="submit" value="Submit" />
</form>

Notice the markup in bold letters. Can you see HTML elements with duplicate id and name attributes? That's because you are rendering two instance of the _Address partial page. The model binding framework requires that the HTML fields follow this naming convention for the model binding to work as expected:

<input id="ShippingAddress_Street1" 
       name="ShippingAddress.Street1" type="text" value="" />
<input id="BillingAddress_Street1" 
       name="BillingAddress.Street1" type="text" value="" />

As you can see from the above markup the id and name attributes must fully quality the model property being bound. In the absence of such a naming pattern the Order instance won't be bound as expected as confirmed by the following figure:

image

As shown above the ShippingAddress and BillingAddress properties are null whereas OrderID and CustomerID are captured successfully.

The above problem can be solved by using a variation of the Partial() helper while rendering the _Address partial page. The following code shows how this is done:

<h3>Basic Details</h3>
@Html.Partial("_BasicDetails")

<h3>Shipping Address</h3>
@Html.Partial("_Address", 
  new ViewDataDictionary() 
  { 
    TemplateInfo = new TemplateInfo() 
      { HtmlFieldPrefix = "ShippingAddress" } })

<h3>Billing Address</h3>
@Html.Partial("_Address", 
  new ViewDataDictionary() 
    { TemplateInfo = new TemplateInfo() 
      { HtmlFieldPrefix = "BillingAddress" } })

The variation of Partial() helper used above uses ViewDataDictionary parameter to specify TemplateInfo. The HtmlFieldPrefix property of the TemplateInfo is set to ShippingAddress for the first instance and to the BillingAddress for the second instance.

If you run the application now, you will find the following markup in the browser:

<form action="/Home/ProcessForm" method="post">
<h3>Basic Details</h3>
<table>
    <tr>
        <td><label for="OrderID">OrderID</label></td>
        <td><input id="OrderID" name="OrderID" type="text" value="0" /></td>
    </tr>
    <tr>
        <td><label for="CustomerID">CustomerID</label></td>
        <td><input id="CustomerID" name="CustomerID" type="text" value="0" /></td>
    </tr>
</table>
<h3>Shipping Address</h3>
<table>
    <tr>
        <td><label for="ShippingAddress_Street1">Street1</label></td>
        <td><input id="ShippingAddress_Street1" 
                   name="ShippingAddress.Street1" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="ShippingAddress_Street2">Street2</label></td>
        <td><input id="ShippingAddress_Street2" 
                   name="ShippingAddress.Street2" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="ShippingAddress_Country">Country</label></td>
        <td><input id="ShippingAddress_Country" 
                   name="ShippingAddress.Country" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="ShippingAddress_PostalCode">PostalCode</label></td>
        <td><input id="ShippingAddress_PostalCode" 
                   name="ShippingAddress.PostalCode" type="text" value="" /></td>
    </tr>
</table>
<h3>Billing Address</h3>
<table>
    <tr>
        <td><label for="BillingAddress_Street1">Street1</label></td>
        <td><input id="BillingAddress_Street1" 
                   name="BillingAddress.Street1" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="BillingAddress_Street2">Street2</label></td>
        <td><input id="BillingAddress_Street2" 
                   name="BillingAddress.Street2" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="BillingAddress_Country">Country</label></td>
        <td><input id="BillingAddress_Country" 
                   name="BillingAddress.Country" type="text" value="" /></td>
    </tr>
    <tr>
        <td><label for="BillingAddress_PostalCode">PostalCode</label></td>
        <td><input id="BillingAddress_PostalCode" 
                   name="BillingAddress.PostalCode" type="text" value="" /></td>
    </tr>
</table>
<input type="submit" value="Submit" />
</form>

As expected the id and name attributes are now fully qualified and hence the model binding will happen as expected as shown below:

image

The model binding now correctly captures ShippingAddress as well as BillingAddress information.

READ MORE

Most of the times ASP.NET MVC views are rendered as a result of user navigating to some action. For example, when a user navigates to /home/index in the browser (either through address bar or through a hyperlink), ASP.NET MVC executes the action method and usually returns a view to the browser. This means each view is rendered as a result of a full GET or POST request. At times, however, you may want to load views dynamically through Ajax. This way you can render contents of a view without full page refresh.

Consider the following page:

image

The above page consists of a table that lists customers from the Customers table of Northwind database. Each customer row has two buttons - Customer Details and Order Details. Clicking on the respective button should display customer details and order details from the database. Without Ajax you would have submitted the page back to the server and then returned a view with the corresponding details. Using Ajax you can display the details without causing any postback to the server. This is shown below:

image

As you can see the above figure shows order details for CustomerID ALFKI above the customers table. These details are fetched via Ajax request.

While displaying data through Ajax request you have two options:

  • Fetch raw data from the server and embed it in HTML markup on the client side
  • Fetch HTML markup with data embedded from the server

Although the choice of the approach depends on a situation, it can be said that the former approach is suitable to make calls to Web API or when HTML display is dynamically decided by the client script. The later approach is suitable when ASP.NET MVC strongly typed views (or partial views) are being used to render the UI. In this example we will be using the later approach.

To develop this example, create a new ASP.NET MVC application based on the Empty template. Then add ADO.NET Entity Data Model for Customers and Orders tables of Northwind database. The Customer and Order entities are shown below:

image

Next, add HomeController and write the Index() action method as shown below:

public ActionResult Index()
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        List<Customer> model = db.Customers.ToList();
        return View(model);
    }
}

The Index() action simply retrieves all the Customer entities from the Customers DbSet and passes them to the Index view.

Now, add another action method - GetView() - to the HomeController as shown below:

public ActionResult GetView(string customerID,string viewName)
{
    object model = null;
    if(viewName=="CustomerDetails")
    {
        using(NorthwindEntities db=new NorthwindEntities())
        {
            model = db.Customers.Find(customerID);
        }
    }
    if (viewName == "OrderDetails")
    {
        using (NorthwindEntities db = new NorthwindEntities())
        {
            model = db.Orders.Where(o => o.CustomerID == customerID)
                      .OrderBy(o => o.OrderID).ToList();
        }
    }
    return PartialView(viewName,model);
}

The GetView() action method accepts two parameters - customerID and viewName. These two parameters are passed through an Ajax request. Depending on the viewName parameter either CustomerDetails partial view is returned to the caller or OrderDetails partial view is returned. These two view need model in the form of a Customer object and a List of Order entities respectively. That's why model variable is declared as object. Once model variable is populated the partial view name and the model is passed to the PartialView() method. Here, we used partial views because the HTML output is to be inserted in an existing page through Ajax.

Next, add one view (Index.cshtml) and two partial views (CustomerDetails.cshtml and OrderDetails.cshtml) to the Home sub-folder of Views folder.

Add the following markup to the CustomerDetails.cshtml partial view:

@model MVCViewsThroughAjax.Models.Customer

<table border="1" cellpadding="10">
    <tr>
        <td>Customer ID :</td>
        <td>@Model.CustomerID</td>
    </tr>
    <tr>
        <td>Company Name :</td>
        <td>@Model.CompanyName</td>
    </tr>
    <tr>
        <td>Contact Name :</td>
        <td>@Model.ContactName</td>
    </tr>
    <tr>
        <td>Country :</td>
        <td>@Model.Country</td>
    </tr>
</table>

The above markup is quite straightforward. The CustomerDetails partial view simply displays CustomerID, CompanyName, ContactName and Country of a Customer in a table.

Now add the following markup to the OrderDetails.cshtml partial page:

@model List<MVCViewsThroughAjax.Models.Order>

<table border="1" cellpadding="10">
    <tr>
        <th>Order ID</th>
        <th>Order Date</th>
        <th>Shipping Date</th>
        <th>Shipped To</th>
    </tr>
    @foreach(var item in Model)
    { 
        <tr>
            <td>@item.OrderID</td>
            <td>@item.OrderDate</td>
            <td>@item.ShippedDate</td>
            <td>@item.ShipCountry</td>
        </tr>
    }
</table>

The above markup iterates through the List of Order entities and renders a table with four columns - OrderID, OrderDate, ShippedDate and ShipCountry.

Now, add the following markup to the Index view:

@model List<MVCViewsThroughAjax.Models.Customer>

...
<html>
<head>
...
</head>
<body>
    <div id="viewPlaceHolder"></div>
    <br /><br />
    <table border="1" cellpadding="10">
        <tr>
            <th>Customer ID</th>
            <th>Company Name</th>
            <th colspan="2">Actions</th>
        </tr>
        @foreach(var item in Model)
        {
            <tr>
                <td>@item.CustomerID</td>
                <td>@item.CompanyName</td>
                <td><input type="button" class="customerDetails" 
                           value="Customer Details" /></td>
                <td><input type="button" class="orderDetails" 
                           value="Order Details" /></td>
            </tr>
        }
    </table>
</body>
</html>

The Index view receives a List of Customer entities as its model and renders a table with CustomerID, CompanyName and two buttons - Customer Details and Order Details.

Now comes the important part - making Ajax calls to display customer details and order details. Noticed the <div> at the beginning of the body section? The viewPlaceHolder is where the output of CustomerDetails.cshtml and OrderDetails.cshtml will be loaded. To do so we will use load() method of jQuery. Here is how that can be done:

$(document).ready(function () {

    $(".customerDetails").click(function (evt) {
        var cell=$(evt.target).closest("tr").children().first();
        var custID=cell.text();
        $("#viewPlaceHolder").load("/home/getview", 
            { customerID: custID, viewName: "CustomerDetails" });
    });

    $(".orderDetails").click(function (evt) {
        var cell = $(evt.target).closest("tr").children().first();
        var custID = cell.text();
        $("#viewPlaceHolder").load("/home/getview", 
           { customerID: custID, viewName: "OrderDetails" });
    });
});

Recollect that Customer Details and Order Details buttons have assigned CSS class of customerDetails and orderDetails respectively. The above jQuery code uses class selector to wire click event handlers to the respective buttons. Inside the click event handler of Customer Details button, the code retrieves the CustomerID from the table row. This is done using closest(), children() and first() methods. The CustomerID is stored in custID variable. Then load() method is called on viewPlaceHolder <div>. The first parameter of the load() method is the URL that will be requested through an Ajax request. The second parameter is a JavaScript object that supplies the data needed by the requested URL. In our example, GetView() action method needs two parameters - customerID and viewName. Hence the object has customerID and viewName properties. The customerID property is set to custID variable and viewName is set to CustomerDetails.

The click event handler of Order Details is similar but loads OrderDetails partial view.

That's it! You can now run the application and try clicking on both the buttons. The following figure shows customer details loaded successfully.

image

Notice that through out the application run the URL shown in the browser address bar remains unchanged indicating that Ajax requests are being made to display customer details and order details.

In the above example Ajax requests were made to /home/getview action. A user can also enter this URL in the browser's address bar producing undesirable results. As a precaution you can check the customerID and viewName parameters inside the GetView() action method (not shown in the above code). If these parameters are empty or contain invalid values you can throw an exception.

READ MORE

Developers new to ASP.NET MVC often ask these questions:

  • How do I pass multiple models to a view?
  • How do I pass anonymous objects to a view?

This article provides a solution to both of these questions. Before we discuss the solution, let's quickly understand these questions and possible solutions in brief.

How do I pass multiple models to a view?

Consider a case where you have two model classes - Customer and Order. ASP.NET MVC allows you to pass only ONE model to a view. You typically do that by passing the model as a View() method parameter. This means a view can be bound with one and only one model. So, in the preceding example a view can either display Customer data or Order data. What if a view requires to display both? You can tackle such a situation as follows:

  • Create a new view model class, say CustomerOrder, that has two properties. One for holding Customer data and other for holding Order data. 
    public class CustomerOrder
    {
      public List<Customer> Customers{get; set;}
      public List<Order> Orders{get; set;}
    }
  • Pass one of the model as the View() method parameter and other(s) through ViewData or ViewBag.
  • Use ExpandoObject to create a dynamic model.

The first two approaches are quite straightforward and hence I won't discuss them here. Out of these three the first solution (custom view model class) is a recommended approach to deal with the situation. However, it calls for creating of a new view model class (POCO). The second one does the job but the view can't be strongly typed because models passed through ViewData or ViewBag are not part of the Model property of the view. The third approach is the topic of this article and is discussed in detail in the later part of this article.

 How do I pass anonymous objects to a view?

Sometimes you need to write LINQ to Entities queries that return data filled in an anonymous type. Consider the following example:

var query = from c in db.Customers
            join o in db.Orders
            on c.CustomerID equals o.CustomerID
            orderby c.CustomerID ascending
            select new { 
                c.CustomerID,
                c.CompanyName,
                o.OrderID,
                o.OrderDate 
            };

Here, you are not selecting Customer objects (select c). You are creating an anonymous type that has four properties - CustomerID, CompanyName, OrderID and OrderDate. Although this LINQ query works without any problem, the problem is you can't pass anonymous types to an ASP.NET MVC view. If you try to do so, you will see a runtime error. Of course, just like previous case you can create a new view model (POCO with these four properties) and project your data in it. You can then pass the collection to the View() method. As before this is a recommended solution but again you need create an additional class. If you wish to avoid doing that for some reason, here also you can resort to ExpandoObject. You can store the realized data in an ExpandoObject and then pass it to the view.

ExpandoObject to the rescue

The ExpandoObject class resides in System.Dynamic namespace and represents an object whose members can be dynamically added and removed at run time. In our specific cases an ExpandoObject can act as a container for multiple pieces of data that you wish to pass as a model to a view. So, in a way ExpandoObject is a "dynamic" alternative to a manually created view model.

Storing data in an ExpandoObject is straightforward.

dynamic model = new ExpandoObject();
model.Customers = <some_data_here>;
model.Orders = <some_data_here>;

The above code creates an instance of ExpandoObject class. Notice that the instance is stored in a variable of type dynamic. The code then assigns Customers and Orders properties of the ExpandoObject to some data. You should be familiar with this usage because ViewBag uses the same "dynamic" mechanism to store values.

Ok. Now that you know what an ExpandoObject is and how to use it, let's put it to use in our specific situations.

Let's assume that you have two model classes - Customers and Orders - as a part of Entity Framework data model. You wish to pass both of them to a view. So, you can write something like this in an action method:

public ActionResult Index()
{
    NorthwindEntities db=new NorthwindEntities();

    dynamic model = new ExpandoObject();
    model.Customers = db.Customers.ToList();
    model.Orders = db.Orders.ToList();
    return View(model);
}

Notice the code marked in bold letters. The code creates an ExpandoObject and assigns two properties on it - Customers and Orders. These properties store a generic List of the respective model objects. The ExpandoObject is then passed to the View() method.

You can use  the ExpandoObject in the view as shown below:

@model dynamic

...
<h1>List of Customers</h1>
<div>
  @foreach(var item in Model.Customers)
  {
    <h2>@item.CustomerID</h2>
  }
</div>

Notice that the view is marked to use a dynamic model using the @model dynamic line. This line is optional. If you don't write any @model then Razor defaults to dynamic. The foreach loop then iterates through Model.Customers property and displays all the CustomerIDs. Note that since Model points to an ExpandoObject, you won't get any IntelliSense help for the property names. You will need to key them in yourself. On the same lines you can display Model.Orders data.

Store anonymous objects in an ExpandoObject

In the preceding example you stored List of Customer and Order objects respectively in the model ExpandoObject. Now let's see how to store an anonymous object in an ExpandoObject.

var addtionalInfo = new { 
                    CustomersNote="This is a customer note",
                    OrdersNote="This is an order note" };
IDictionary<string, object> 
expandoAddInfo = new ExpandoObject();
foreach (PropertyDescriptor property 
         in 
         TypeDescriptor.GetProperties(addtionalInfo.GetType()))
{
    expandoAddInfo.Add(property.Name, 
           property.GetValue(addtionalInfo));
}
model.AdditionalInfo = expandoAddInfo;

The code creates an anonymous object with two properties - CustomersNote and OrdersNote - and stores it in additionalInfo variable. Then an ExpandoObject is created. This time, however, it is captured as an IDictionary<string, object>. Then a foreach loop iterates through all the properties of additionalInfo object. This is done using PropertyDescriptor and TypeDescriptor classes from System.ComponentModel namespace. These classes allow you to reflect upon the properties of any object (additionalInfo in this case). All the properties from the anonymous object are transferred to the ExpandoObject by adding them to the expandoAddInfo variable. Finally, AdditionalInfo property of model (which itself is an ExpandoObject) is set to expandoAddInfo ExpandoObject.

You can pass the model object to the view exactly as before. Once received in the view you can access the AdditionalInfo property as shown below:

<h1>Additional Information</h1>
<h2>@Model.AdditionalInfo.CustomersNote</h2>
<h2>@Model.AdditionalInfo.OrdersNote</h2>

What if you wish to convert a collection of anonymous objects to an ExpandoObject? The following code shows how this can be done:

var query = from c in db.Customers
            join o in db.Orders
            on c.CustomerID equals o.CustomerID
            orderby c.CustomerID ascending
            select new { 
                c.CustomerID,
                c.CompanyName,
                o.OrderID,
                o.OrderDate 
            };

List<ExpandoObject> joinData=new List<ExpandoObject>();

foreach(var item in query)
{
    IDictionary<string, object> itemExpando = new ExpandoObject();
    foreach (PropertyDescriptor property 
             in 
             TypeDescriptor.GetProperties(item.GetType()))
    {
        itemExpando.Add(property.Name, property.GetValue(item));
    }
    joinData.Add(itemExpando as ExpandoObject);
}

model.JoinData = joinData;

The above code is similar to the previous case but deals with a List of ExpandoObject (joinData variable). Each ExpandoObject (itemExpando variable) holds data belonging to a single anonymous object. The joinData List is then assigned to the JoinData property of the model ExpandoObject.

You can access JoinData inside a view as shown below:

<h1>Customer Orders Join Data</h1>
<div>
    @foreach (var item in Model.JoinData)
    {
        <h2>@item.CustomerID (@item.OrderID on @item.OrderDate)</h2>
    }
</div>

A note of caution about using ExpandoObject as shown above - Although ExpandoObject solves our problem, one should carefully evaluate whether there is any flaw in the model creation process. You should give a thought to creating POCOs for holding the multiple pieces instead of using an ExpandoObject. Since ExpandoObject instances are dynamic Visual Studio IntelliSense won't help much. POCO containers, on the other hand, give all the benefits of strongly typed view.

That's it for today! Keep coding!!

READ MORE

A common way to perform list, insert, update and delete operations in ASP.NET MVC is to create four separate views. The List view forms the launching view where records are displayed and you can choose to Edit, Delete or Insert a record. However, in some cases you may want to perform all these operations in a single view itself. This task can be accomplished using full page postback or using Ajax. This article discusses the former technique.

Consider the following figure that shows one such arrangement:

image

The above figure shows a list of records from Customers table of Northwind database. You can Insert a new customer by clicking on Insert button. You can select a row for editing by clicking on the Select button. The selected customer is shown below the main table for editing. Similarly you can also delete a customer by clicking on the Delete button.

Model and View Model

Let's see how the above application can be built. Begin by creating a new empty ASP.NET MVCproject in Visual Studio. Then add an ADO.NET Entity Data Model for the Customers table. The Customer entity class is shown below:

image

Then add a new POCO to the Models folder and name it CustomersViewModel. As you will see later, this view model class will be passed from the HomeController to the Index view. The CustomersViewModel class is shown below:

public class CustomersViewModel
{
    public List<Customer> Customers { get; set; }
    public Customer SelectedCustomer { get; set; }
    public string DisplayMode { get; set; }
}

The CustomersViewModel class consists of three properties. The Customers property holds a List of Customer that are to be displayed on the view. The SelectedCustomer property points to a Customer that is selected by the user. If no Customer is selected this property is null. The DisplayMode property indicates the mode of the Customer details area. Possible values are ReadOnly (after selection), ReadWrite (during edit) and WriteOnly (during insert). For the sake of simplicity DisplayMode is created as a string property, you can easily make it to accept an enumeration.

Home controller and its action methods

Then add HomeController in the Controllers folder. The HomeController will contain the following action methods:

  • Index()
  • Select()
  • New()
  • Insert()
  • Edit()
  • Update()
  • Delete()
  • Cancel()

The method names are self-explanatory. All the actions except Index() are called as a result of POST operation. Let's discuss them briefly one by one.

public ActionResult Index()
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                m => m.CustomerID).Take(5).ToList(); 
        model.SelectedCustomer = null;
        return View(model);
    }
}

The Index() action fetches a list of customers and fills it in the Customers view model property. The SelectedCustomer is set to null because there is no selected customer in the beginning. Note that for the sake of simplicity the above code fetches only 5 customers. You can, of course, fetch all if you so wish.

[HttpPost]
public ActionResult New()
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                       m => m.CustomerID).Take(5).ToList();
        model.SelectedCustomer = null;
        model.DisplayMode = "WriteOnly";
        return View("Index", model);
    }
}

The New() action is called when a user hits the Insert button at the top of the page. It fills the Customers list as before. SelectedCustomer is set to null because a new record is to be added. The DisplayMode is set to WriteOnly because we will be accepting new customer details. The following figure shows how the insert area looks like:

image

[HttpPost]
public ActionResult Insert(Customer obj)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        db.Customers.Add(obj);
        db.SaveChanges();

        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                         m => m.CustomerID).Take(5).ToList();
        model.SelectedCustomer = db.Customers.Find(obj.CustomerID);
        model.DisplayMode = "ReadOnly";
        return View("Index", model);
    }
}

The Insert() action is called when a user fills new customer details and clicks on the Save button (see above figure). It receives a Customer object as its parameter. Inside, the Insert() action adds that new Customer to the database. It also sets the currently selected customer to the newly added customer by setting the SelectedCustomer property. The DisplayMode is set to ReadOnly so that the record is displayed in read-only manner.

[HttpPost]
public ActionResult Select(string id)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                    m => m.CustomerID).Take(5).ToList();
        model.SelectedCustomer = db.Customers.Find(id);
        model.DisplayMode = "ReadOnly";
        return View("Index",model);
    }
}

The Select() action method is called when the Select button from a customer table row is clicked. It receives CustomerID as its parameter. Inside, it fills Customers list as before. This time SelectedCustomer property is set to the Customer whose CustomerID is passed. The DisplayMode property is set to ReadOnly to indicate that the details of the selected customer should be displayed in a read-only table below the main customer listing (see below).

image

[HttpPost]
public ActionResult Edit(string id)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                        m => m.CustomerID).Take(5).ToList();
        model.SelectedCustomer = db.Customers.Find(id);
        model.DisplayMode = "ReadWrite";
        return View("Index", model);
    }
}

The Edit() action is called when a user clicks on the Edit button once a Customer is selected. Inside, it sets the SelectedCustomer property to the Customer whose CustomerID is passed to the method. DisplayMode property is set to ReadOnly to display that record in editable table as shown below:

image

[HttpPost]
public ActionResult Update(Customer obj)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        Customer existing = db.Customers.Find(obj.CustomerID);
        existing.CompanyName = obj.CompanyName;
        existing.ContactName = obj.ContactName;
        existing.Country = obj.Country;
        db.SaveChanges();
                
        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                      m => m.CustomerID).Take(5).ToList();

        model.SelectedCustomer = existing;
        model.DisplayMode = "ReadOnly";
        return View("Index", model);
    }
}

The Update() action is called when a user modifies an existing Customer data and clicks on the Save button (see above figure). Inside, the code updates an existing Customer and saves the changes back to the database. Then Customers, SelectedCustomer and DisplayMode properties of the view model are set.

[HttpPost]
public ActionResult Delete(string id)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        Customer existing = db.Customers.Find(id);
        db.Customers.Remove(existing);
        db.SaveChanges();

        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                          m => m.CustomerID).Take(5).ToList();

        model.SelectedCustomer = null;
        model.DisplayMode = "";
        return View("Index", model);
    }
}

The Delete() action is called when Delete button in any of the customer row is clicked. It receives CustomerID as its parameter. Inside, it removes the specified Customer and saves the changes back to the database. The SelectedCustomer is set to null because post deletion that customer no longer exists in the database. For the same reason, DisplayMode is set to an empty string.

[HttpPost]
public ActionResult Cancel(string id)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        CustomersViewModel model = new CustomersViewModel();
        model.Customers = db.Customers.OrderBy(
                          m => m.CustomerID).Take(5).ToList();
        model.SelectedCustomer = db.Customers.Find(id);
        model.DisplayMode = "ReadOnly";
        return View("Index", model);
    }
}

The Cancel() action is called when Cancel button from the Edit area is clicked. It receives CustomerID as its parameter. It changes the DisplayMode from ReadWrite to ReadOnly so that the SelectedCustomer is displayed in read-only fashion.

Notice that all the above action methods return Index view and CustomerViewModel object.

Index view

Now, let's see what goes inside the Index view.

@model MasterDetailsDemo.Models.CustomersViewModel

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
    <style>
        .SelectedCustomer
        {
            background-color:gray;
            font-weight:bold;
        }
    </style>
</head>
<body>
    <h1>List of Customers</h1>

    <form method="post">
        <input type="submit" 
         value="Insert" formaction="/home/new" />
        <br /><br />
        <table border="1" cellpadding="10">
            <tr>
                <th>CustomerID</th>
                <th>CompanyName</th>
                <th colspan="2">Actions</th>
            </tr>
            @foreach (var item in Model.Customers)
            {
                if (Model.SelectedCustomer != null)
                {
                    if (item.CustomerID == 
                        Model.SelectedCustomer.CustomerID)
                    {
                        @:<tr class="SelectedCustomer">
                    }
                    else
                    {
                        @:<tr>
                    }
                }
                else
                {
                    @:<tr>
                }
                <td>@item.CustomerID</td>
                <td>@item.CompanyName</td>
                <td><input type="submit" 
                     formaction="/home/select/@item.CustomerID" 
                     value="Select" /></td>
                <td><input type="submit" 
                     formaction="/home/delete/@item.CustomerID" 
                     value="Delete" /></td>
                @:</tr>
            }
        </table>
    </form>
    <br /><br />
    @{
        if(Model.SelectedCustomer!=null)
        {
            if (Model.DisplayMode == "ReadOnly")
            {
                Html.RenderPartial
                ("ShowCustomer",Model.SelectedCustomer);
            }
            if (Model.DisplayMode == "ReadWrite")
            {
                Html.RenderPartial
                ("EditCustomer",Model.SelectedCustomer);
            }
        }
        if (Model.DisplayMode == "WriteOnly")
        {
            Html.RenderPartial("InsertCustomer",
            new MasterDetailsDemo.Models.Customer());
        }
    }
</body>
</html>

The Index view is divided into two logical parts. The top part displays a list of customers in a table. Notice that a CSS class SelectedCustomer is applied to the row that contains the selected CustomerID. The bottom part displays a Partial Page based on the value of DisplayMode. This way either show, insert or edit areas are displayed. Notice that there are three partial pages involved:

  • ShowCustomer.cshtml
  • EditCustomer.cshtml
  • InsertCustomer.cshtml

These three partial pages render the read-only, read-write and write-only displays respectively. All of them take Customer object as their model. Let's see each of these partial pages one by one.

ShowCustomer partial page

The following code shows ShowCustomer.cshtml partial page.

@model MasterDetailsDemo.Models.Customer

@using(Html.BeginForm("Edit","Home",FormMethod.Post))
{ 
<table border="1" cellpadding="10">
    <tr>
        <td>Customer ID :</td>
        <td>@Model.CustomerID</td>
    </tr>
    <tr>
        <td>Company Name :</td>
        <td>@Model.CompanyName</td>
    </tr>
    <tr>
        <td>Contact Name :</td>
        <td>@Model.ContactName</td>
    </tr>
    <tr>
        <td>Country :</td>
        <td>@Model.Country</td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="submit" value="Edit" 
                   formaction="/home/edit/@Model.CustomerID" />
            <input type="submit" value="Cancel" 
                   formaction="/home/index" />
        </td>
    </tr>
</table>
}

Notice that the Edit and Cancel buttons submit to /home/edit and /home/index respectively. The other markup from the partial page is quite straightforward and displays CustomerID, CompanyName, ContactName and Country columns for a selected customer.

EditCustomer partial page

The following code shows what goes inside EditCustomer.cshtml:

@model MasterDetailsDemo.Models.Customer

@using (Html.BeginForm("Update", "Home", FormMethod.Post))
{
    <table border="1" cellpadding="10">
        <tr>
            <td>Customer ID :</td>
            <td>@Html.TextBoxFor(m => m.CustomerID, 
                         new { @readonly = "readonly" })</td>
        </tr>
        <tr>
            <td>Company Name :</td>
            <td>@Html.TextBoxFor(m => m.CompanyName)</td>
        </tr>
        <tr>
            <td>Contact Name :</td>
            <td>@Html.TextBoxFor(m => m.ContactName)</td>
        </tr>
        <tr>
            <td>Country :</td>
            <td>@Html.TextBoxFor(m => m.Country)</td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save" 
                       formaction="/home/update" />
                <input type="submit" value="Cancel" 
                       formaction="/home/cancel/@Model.CustomerID" />
            </td>
        </tr>
    </table>
}

Note that Save button and Cancel button submit to /home/update and /home/cancel respectively.

InsertCustomer partial page

Finally, here is the markup of InsertCustomer.cshtml:

@model MasterDetailsDemo.Models.Customer

@using (Html.BeginForm("Insert", "Home", FormMethod.Post))
{
    <table border="1" cellpadding="10">
        <tr>
            <td>Customer ID :</td>
            <td>@Html.TextBoxFor(m => m.CustomerID)</td>
        </tr>
        <tr>
            <td>Company Name :</td>
            <td>@Html.TextBoxFor(m => m.CompanyName)</td>
        </tr>
        <tr>
            <td>Contact Name :</td>
            <td>@Html.TextBoxFor(m => m.ContactName)</td>
        </tr>
        <tr>
            <td>Country :</td>
            <td>@Html.TextBoxFor(m => m.Country)</td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save" 
                       formaction="/home/insert" />
                <input type="submit" value="Cancel" 
                       formaction="/home/index" />
            </td>
        </tr>
    </table>
}

The Save and Cancel button submit to /home/insert and /home/index respectively.

That's it! All the parts of the application are in place. Run the application and test whether all the operations work as expected.

READ MORE
...