Header Ads

ASP.NET MVC5: Multiple View Models in Single View


There is a common scenario which is being asked quite a lot in ASP.NET MVC platform.
So, I have decided to demonstrate my take on this particular scenario. The scenario is as follow i.e. create a single page that will display the data from two different view models. Now the question arise here that ASP.NET MVC platform only attaches single model to a single view, so, how can this be achieved? Let me demonstrate how this is done.

Today, I shall be demonstrating about how to utilize multiple view models into a single view in ASP.NET MVC5 platform. Following are some prerequisites before you proceed further in this tutorial:
  1. Knowledge of ASP.NET MVC5.
  2. Knowledge of HTML.
  3. Knowledge of JavaScript.
  4. Knowledge of Bootstrap.
  5. Knowledge of Jquery.
  6. Knowledge of C# Programming.
You can download the complete source code for this tutorial or you can follow the step by step discussion below. The sample code is being developed in Microsoft Visual Studio 2015 Enterprise.

Download Now!

Before we jump into technical working of this scenario, let's build a little conceptual understanding of how we will achieve this scenario in ASP.NET MVC5 platform. To do achieve this, we will proceed in our code as follow i.e.

1) We have two independent models "Register Model" & "ResultSet Model" i.e.
 
2) Since we know the constraint on ASP.NET MVC platform i.e. we can only attach a single model to a single view. So, here we will create a parent place holder kind of model i.e. "Common Model" and make our actual view models the child to this model as shown below i.e.


3) After combining our two separate models into a single model, we then can easily attach our common place holder model to our single view as shown below i.e.


Now, let's see, how can we code this.  

1) Create a new MVC web project and name it "MultiModelSingleView".  
2) Create a file "LoginObj.cs" under "Models" folder and replace following code in it i.e.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MultiModelSingleView.Models
{
    public class LoginObj
    {
        public int Id { get; set; }

        public string Username { get; set; }

        public string Password { get; set; }
    }
}
   
3) Now, create our view models files i.e. "AccountViewModel.cs", "ResultSetViewModel.cs" & "CommonViewModel.cs" under "Models" folder and replace following code in each file accordingly as shown below i.e. Replace the following code in "AccountViewModel.cs" file i.e.


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MultiModelSingleView.Models
{
    public class AccountViewModel
    {
        [Display(Name = "Id")]
        public int Id { get; set; }

        [Display(Name = "Enter Username")]
        public string Username { get; set; }

        [Display(Name = "Enter Password")]
        public string Password { get; set; }
    }
}

Replace the following code in "ResultSetViewModel.cs" file i.e


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MultiModelSingleView.Models
{
    public class ResultSetViewModel
    {
        [Display(Name = "Result")]
        public List<LoginObj> ResultSet { get; set; }
    }
}

Replace the following code in "CommonViewModel.cs" file i.e


using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace MultiModelSingleView.Models
{
    public class CommonViewModel
    {
        public AccountViewModel AccountVM { get; set; }
        public ResultSetViewModel ResultSetVM { get; set; }
    }
}

In all the above snippets, we have created our view models according to the conceptual understanding that we have developed.

4) Create a new controller file "AccountController.cs" under "Controllers" folder and replace the following code in it i.e.


//-----------------------------------------------------------------------
// <copyright file="HomeController.cs" company="None">
//     Copyright (c) Allow to distribute this code.
// </copyright>
// <author>Asma Khalid</author>
//-----------------------------------------------------------------------

namespace MultiModelSingleView.Controllers
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using System.Web;
    using System.Web.Mvc;
    using Microsoft.AspNet.Identity;
    using Microsoft.AspNet.Identity.Owin;
    using Microsoft.Owin.Security;
    using MultiModelSingleView.Models;
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;

    public class AccountController : Controller
    {
 
        #region Register method

        #region GET: /Account/Register

        //
        // GET: /Account/Register
        [AllowAnonymous]
        public ActionResult Register()
        {
            // Initialization.
            CommonViewModel model = new CommonViewModel();
            model.AccountVM = new AccountViewModel();
            model.ResultSetVM = new ResultSetViewModel();

            // Get Result
            model.ResultSetVM.ResultSet = this.LoadData();

            return View(model);
        }

        #endregion

        #region  POST: /Account/Register

        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(CommonViewModel model)
        {
            if (ModelState.IsValid)
            {
                // Inserting.
                this.StoreData(model.AccountVM.Username, model.AccountVM.Password);

                // Get Result
                model.ResultSetVM = new ResultSetViewModel();
                model.ResultSetVM.ResultSet = this.LoadData();
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        #endregion

        #endregion

        #region Helpers

        #region Load Data

        /// <summary>
        /// Load data method.
        /// </summary>
        /// <returns>Returns - Data</returns>
        private List<LoginObj> LoadData()
        {
            // Initialization.
            List<LoginObj> lst = new List<LoginObj>();

            try
            {
                // Initialization.
                string line = string.Empty;
                string srcFilePath = "content/files/login_list.txt";
                var rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
                var fullPath = Path.Combine(rootPath, srcFilePath);
                string filePath = new Uri(fullPath).LocalPath;
                StreamReader sr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read));

                // Read file.
                while ((line = sr.ReadLine()) != null)
                {
                    // Initialization.
                    LoginObj infoObj = new LoginObj();
                    string[] info = line.Split(',');

                    // Setting.
                    infoObj.Id = Convert.ToInt32(info[0].ToString());
                    infoObj.Username = info[1].ToString();
                    infoObj.Password = info[2].ToString();

                    // Adding.
                    lst.Add(infoObj);
                }

                // Closing.
                sr.Dispose();
                sr.Close();
            }
            catch (Exception ex)
            {
                // info.
                Console.Write(ex);
            }

            // info.
            return lst;
        }

        #endregion

        #region Store Data

        /// <summary>
        /// Store data method.
        /// </summary>
        private void StoreData(string username, string password)
        {
            // Initialization.
            LoginObj obj = new LoginObj();

            try
            {
                // Setting.
                int idVal = this.LoadData().Select(p => p).ToList().Count > 0
                            ? (this.LoadData().OrderByDescending(p => p.Id).Select(p => p.Id).FirstOrDefault()) + 1
                            : 1;

                // Initialization.
                string line = string.Empty;
                string srcFilePath = "content/files/login_list.txt";
                var rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
                var fullPath = Path.Combine(rootPath, srcFilePath);
                string filePath = new Uri(fullPath).LocalPath;
                StreamWriter sw = new StreamWriter(new FileStream(filePath, FileMode.Append, FileAccess.Write));

                // Write file.
                string content = idVal.ToString() + "," + username + "," + password;
                sw.WriteLine(content);

                // Closing.
                sw.Dispose();
                sw.Close();
            }
            catch (Exception ex)
            {
                // info.
                Console.Write(ex);
            }
        }

        #endregion

        #endregion
    }
}

Lets break down the above code method by method i.e.


        #region Load Data

        /// <summary>
        /// Load data method.
        /// </summary>
        /// <returns>Returns - Data</returns>
        private List<LoginObj> LoadData()
        {
            // Initialization.
            List<LoginObj> lst = new List<LoginObj>();

            try
            {
                // Initialization.
                string line = string.Empty;
                string srcFilePath = "content/files/login_list.txt";
                var rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
                var fullPath = Path.Combine(rootPath, srcFilePath);
                string filePath = new Uri(fullPath).LocalPath;
                StreamReader sr = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read));

                // Read file.
                while ((line = sr.ReadLine()) != null)
                {
                    // Initialization.
                    LoginObj infoObj = new LoginObj();
                    string[] info = line.Split(',');

                    // Setting.
                    infoObj.Id = Convert.ToInt32(info[0].ToString());
                    infoObj.Username = info[1].ToString();
                    infoObj.Password = info[2].ToString();

                    // Adding.
                    lst.Add(infoObj);
                }

                // Closing.
                sr.Dispose();
                sr.Close();
            }
            catch (Exception ex)
            {
                // info.
                Console.Write(ex);
            }

            // info.
            return lst;
        }

        #endregion

The above code, creates a "LoadData()" method, which will load the data from a file, if the file contains any data, initially, the file is empty, since we have not register any account.


        #region Store Data

        /// <summary>
        /// Store data method.
        /// </summary>
        private void StoreData(string username, string password)
        {
            // Initialization.
            LoginObj obj = new LoginObj();

            try
            {
                // Setting.
                int idVal = this.LoadData().Select(p => p).ToList().Count > 0
                            ? (this.LoadData().OrderByDescending(p => p.Id).Select(p => p.Id).FirstOrDefault()) + 1
                            : 1;

                // Initialization.
                string line = string.Empty;
                string srcFilePath = "content/files/login_list.txt";
                var rootPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
                var fullPath = Path.Combine(rootPath, srcFilePath);
                string filePath = new Uri(fullPath).LocalPath;
                StreamWriter sw = new StreamWriter(new FileStream(filePath, FileMode.Append, FileAccess.Write));

                // Write file.
                string content = idVal.ToString() + "," + username + "," + password;
                sw.WriteLine(content);

                // Closing.
                sw.Dispose();
                sw.Close();
            }
            catch (Exception ex)
            {
                // info.
                Console.Write(ex);
            }
        }

        #endregion

Now the above code, creates a "StoreData(...)" method, which will store the data into the file, as we register any new account.


#region GET: /Account/Register

        //
        // GET: /Account/Register
        [AllowAnonymous]
        public ActionResult Register()
        {
            // Initialization.
            CommonViewModel model = new CommonViewModel();
            model.AccountVM = new AccountViewModel();
            model.ResultSetVM = new ResultSetViewModel();

            // Get Result
            model.ResultSetVM.ResultSet = this.LoadData();

            return View(model);
        }

        #endregion

The above code, creates our view method "Register()" HTTP GET, which will simply loads our data to "ResultSetViewModel ". Notice here that now in our view instead of returning an individual model to the view, we are returning our common view model, since, we have attached that with our view.


#region  POST: /Account/Register

        //
        // POST: /Account/Register
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public ActionResult Register(CommonViewModel model)
        {
            if (ModelState.IsValid)
            {
                // Inserting.
                this.StoreData(model.AccountVM.Username, model.AccountVM.Password);

                // Get Result
                model.ResultSetVM = new ResultSetViewModel();
                model.ResultSetVM.ResultSet = this.LoadData();
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

        #endregion

The above code, creates our view method "Register()" HTTP POST, which will simply loads our data to "ResultSetViewModel " after storing the register account details received via account view model. Notice here again that now in our view instead of returning an individual model to the view, we are returning our common view model, since, we have attached that with our view.  

5) Now, create a new view file "Register.cshtml" under "Views\Account" folder and replace the following code in it i.e.


@model MultiModelSingleView.Models.CommonViewModel
@{
    ViewBag.Title = "Register";
}

<h2>@ViewBag.Title.</h2>

@using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.AccountVM.Username, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.AccountVM.Username, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.AccountVM.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.AccountVM.Password, new { @class = "form-control" })
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}

<h2>Result List</h2>

@if (Model.ResultSetVM.ResultSet != null)
{
    for (int i = 0; i < Model.ResultSetVM.ResultSet.Count; i++)
    {
        <div class="row">
            <div class="col-md-2">
                <p>@Model.ResultSetVM.ResultSet[i].Id</p>
            </div>
            <div class="col-md-2">
                <p>@Model.ResultSetVM.ResultSet[i].Username</p>
            </div>
            <div class="col-md-2">
                <p>@Model.ResultSetVM.ResultSet[i].Password</p>
            </div>
        </div>
    }
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

In the above code, we have created a single view, which is attached to our common model and display data from the result set view model and send register account request via account view model.

6) Create a our standard view layout file "_Layout.cshtml" under "Views\Shared" folder and replace the following code in it i.e.


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")

    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css" />
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <center>
                <p><strong>Copyright &copy; @DateTime.Now.Year - <a href="http://www.asmak9.com/">Asma's Blog</a>.</strong> All rights reserved.</p>
            </center>
        </footer>
    </div>

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")


    @RenderSection("scripts", required: false)
</body>
</html>

 7) Now, execute the project and register some accounts, you will be able to see output as below i.e.

Conclusion

In this article, you will learn about mapping a multiple view models to a single UI view in ASP.NET MVC5 platform. You will also learn about conceptual understanding behind this scenario along with how to achieve this scenario from coding perspective.

No comments