DotNetCurry Logo

Business Application using HTML5, ASP.NET MVC, Web API and Knockout.js - Part 1

Posted by: Mahesh Sabnis , on 10/12/2015, in Category ASP.NET MVC
Views: 48323
Abstract: Create a business application using newer web technologies like HTML5, ASP.NET MVC, Web API, Knockout.js and jQuery

In this changing era of web development, a modern web developer has to use a mash up of various client-side and server-side technologies to meet user requirements like high-responsive UI, validations, post back free model and so on. This article shows how to create a business application using newer web technologies like HTML5, ASP.NET MVC, Knockout.js and so on.

 

Business applications have some critical needs that must be implemented, some of them are listed below:

  • Remote Database access
  • Security
  • Validations
  • Data display
  • Responsive UI across devices
  • And so on..

This article is divided in two parts. In the first part, we will see the server-side bits including Database design, Model design, and MVC and WEB API controllers. In part 2 of this article we will implement the client-side logic using jQuery and Knockout library.

Update: Part 2 is available here http://www.dotnetcurry.com/aspnet-mvc/1206/business-app-html5-aspnet-mvc-webapi-knockoutjs-part2

Case Study

There are several property owners who want to Sell or Rent their properties to local residents as well as visitors or investors. But it is challenging for them to put an advertisement in the newspaper, because of limited local circulation and also due to the financials involved. So we will make it easier for them by creating a web portal where they can display properties details and make it available to internet users, all across the globe.

The Application Architecture:

html5-app-architecture

The above application architecture has the following specifications:

- Data Store: In our case, it is a SQL Server database. There are two separate databases used - one for storing Users and Roles information and the other for Property Information. This arrangement is done to isolate the security information (e.g. UserName, Roles, etc.) from the application database.

- Data Access Layer: This is implemented using EntityFramework. This layer provide entities, using which read-write operations can be performed easily.

- Web API Service Layer: Since the data needs to be exposed to the web and to various clients, we need open standards for data communication. This is possible using JavaScript Object Notation (JSON). Exposing data in JSON format over Http for web application is implemented using WEB API which is available since ASP.MVC 4.0

- The UI Layer: This layer is implemented using HTML 5 for Data Display and accepting values. Knockout.js, library is used for creating View Model on the client side which is further used for data-binding with UI and handling read/write operations. The jQuery library is used to make an Ajax call to WEB API for reading and writing data.

As already mentioned earlier, in this part of the article we will design the Database, Model layer, MVC and WEB API Controllers for Role Management, Owner, Customer, Property, etc.

The Application Implementation

Step 1: Open the Free Visual Studio 2013 Community Edition and create a new ASP.NET MVC application. Make sure that, the .NET Framework 4.5 is selected. Name the application as ‘A2_HTML5_Biz_App_New’. Since we will be using Client-Side jQuery and Knockout Libraries in the project, we need to add these libraries using NuGet Package Manager. Right-Click on the project and select ManageNuget Packages option and search for jQuery and Knockout latest versions libraries and install them.

Step 2: Open Web.Config file which will contain the connection string with the name ‘DefaultConnection’ to connect to the database (Note: The Database name is <ApplicationName>-<yyyymmdd<uniqueno>>.mdf.)

Step 3: Now we need to create an application database, this will contain tables for Property Owners, Customers and Properties registered by Owners. In the App_Data folder add a new Sql Server database of name PropertyStore.mdf. In this database add the following tables:

CREATE TABLE [dbo].[OwnerInfo] (
    [OwnerId]          INT           IDENTITY (1, 1) NOT NULL,
    [OwnerName]        VARCHAR (100) NOT NULL,
    [Address]          VARCHAR (200) NOT NULL,
    [City]             VARCHAR (50)  NOT NULL,
    [Contact1]         VARCHAR (20)  NOT NULL,
    [Contact2]         VARCHAR (20)  NOT NULL,
    [Email]            VARCHAR (200) NOT NULL,
    [RegistrationDate] DATETIME      NOT NULL,
    PRIMARY KEY CLUSTERED ([OwnerId] ASC)
);

CREATE TABLE [dbo].[OwnerPropertyDescription] (
    [OwnerPropertyId]        INT           IDENTITY (1, 1) NOT NULL,
    [PropertyType]           VARCHAR (40)  NOT NULL,
    [OwnerId]                INT           NOT NULL,
    [Address]                VARCHAR (200) NOT NULL,
    [BedRoomNo]              INT           NOT NULL,
    [TotalRooms]             INT           NOT NULL,
    [PropertyBuildupArea]    INT           NOT NULL,
    [PropertyDescription]    VARCHAR (200) NOT NULL,
    [PropertySaleRentStatus] VARCHAR (20)  NOT NULL,
    [SaleOrRentCost]         INT           NOT NULL,
    [PropertyAge]            INT           NOT NULL,
    [Status]                 VARCHAR (20)  NOT NULL,
    [RegistrationDate]       DATETIME      NOT NULL,
    PRIMARY KEY CLUSTERED ([OwnerPropertyId] ASC),
    FOREIGN KEY ([OwnerId]) REFERENCES [dbo].[OwnerInfo] ([OwnerId])
);

CREATE TABLE [dbo].[CustomerInfo] (
    [CustomerId]       INT           IDENTITY (1, 1) NOT NULL,
    [CustomerName]     VARCHAR (100) NOT NULL,
    [Address]          VARCHAR (200) NOT NULL,
    [City]             VARCHAR (50)  NOT NULL,
    [Email]            VARCHAR (200) NOT NULL,
    [Contact1]         VARCHAR (20)  NOT NULL,
    [Contact2]         VARCHAR (20)  NOT NULL,
    [RegistrationDate] DATETIME      NOT NULL,
    PRIMARY KEY CLUSTERED ([CustomerId] ASC)
);

The columns in the customer table has the same role as of the owner.

Step 4: In the Models folder, add a new ADO.NET Entity Data Model and name it as ‘PropertyStoreEDMX’. This starts the wizard.  Select the default values (Generate from Database), select the PropertyStore database and all tables from it. After the completion of the wizard, the mapping will be as follows:

table-mapping

The relationship between OwnerInfo and OwnerPropertyDescription specifies that one owner can list one or more properties.

Managing Roles for the application

Step 5: In this step we will define roles for each user. Roles are defined as ‘Administrator’, ‘Owner’ and ‘Customer’. Since we are using ASP.NET MVC5, we get an access to the ‘IdentityRole’ class. This is used to manage roles. This class is available through the Microsoft.AspNet.Identity.EntityFramework namespace:

In the Controllers folder, add a new MVC Controller, name it as ‘RoleController’ and add the following code in it:

[Authorize(Roles="Administrator")]
public class RoleController : Controller
{
    ApplicationDbContext ctx;

    public RoleController()
    {
        ctx = new ApplicationDbContext(); 
    }

    // GET: RoleController
    public ActionResult Index()
    {
        var appRoles = ctx.Roles.ToList(); 
        return View(appRoles);
    }

    public ActionResult Create()
    {
        return View();
    }
     
    // Action method to create Roles
     [HttpPost]
    public ActionResult Create(FormCollection collection)
    {
        try
        {
            ctx.Roles.Add(new Microsoft.AspNet.Identity.EntityFramework.IdentityRole()
            {
                Name = collection["RoleName"]
            });
            ctx.SaveChanges();
            ViewBag.ResultMessage = "Role created successfully !";
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

  

    public ActionResult AssignUserToRole()
    {
        var list = ctx.Roles.OrderBy(role => role.Name).ToList().Select(role => new SelectListItem { Value = role.Name.ToString(), Text = role.Name }).ToList();
        ViewBag.Roles = list;
        return View();
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult UserAddToRole(string UserName, string RoleName)
    {
        ApplicationUser user = ctx.Users.Where(usr => usr.UserName.Equals(UserName, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault();
        
        // Display Roles in DropDown
        
        var list = ctx.Roles.OrderBy(role => role.Name).ToList().Select(role => new SelectListItem { Value = role.Name.ToString(), Text = role.Name }).ToList();
        ViewBag.Roles = list;

        if (user != null)
        {
            var account = new AccountController();
            account.UserManager.AddToRoleAsync(user.Id, RoleName);

            ViewBag.ResultMessage = "Role created successfully !";

            return View("AssignUserToRole");
        }
        else
        {
            ViewBag.ErrorMessage = "Sorry user is not available";
            return View("AssignUserToRole");
        }
        
    }
}

In the above code, the method ‘Create’ creates a new Role. Methods AssignUserToRole() and UserAddToRole() are used for Role and User Management.

From the Action methods of the RoleController, scaffold views for Index, Create and AssignUserToRole. These views will be as below:

Index.cshtml

@model IEnumerable<Microsoft.AspNet.Identity.EntityFramework.IdentityRole>
@{
    ViewBag.Title = "Index";
}

<link href="~/Content/MyStyles/MyCss.css" rel="stylesheet" />
<h2>Index</h2>
<table class="table table-bordered table-striped">
    <tr>
        <td class="t1">
            @Html.ActionLink("Create New Role", "Create")
        </td>
        <td class="t1">
            @Html.ActionLink("Manage User In Role", "AssignUserToRole")
        </td>
    </tr>
</table>
<br />
<br />

<table class="table table-bordered table-striped">
    @foreach (var role in Model)
    {
        <tr>
            <td class="t1">
                @role.Name
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { roleName = @role.Name })
            </td>
        </tr>
    }
</table>

Create.cshtml

@{
    ViewBag.Title = "Create";
}
<h2>Create Role</h2>
@Html.ActionLink("Available Roles", "Index")  @Html.ActionLink("Manage User In Role", "AssignUserToRole")
<hr />
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <div>
        Role Name:
    </div>
    <p>
        @Html.Editor("RoleName")
  
    </p>
    <input type="submit" value="Save" class="btn btn-success"/>
}

AssignUserToRole.cshtml

@{
    ViewBag.Title = "Create";
}
<h2>Create Role</h2>
@Html.ActionLink("Available Roles", "Index") | @Html.ActionLink("Manage User In Role", "AssignUserToRole")
<hr />
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
    @Html.ValidationSummary(true)

    <div>
        Role Name:
    </div>
    <p>
        @Html.Editor("RoleName")
    </p>
    <input type="submit" value="Save" class="btn btn-success"/>
}

Changing Implementation of the Account Controller

Since we want to directly ask the user to set his role (Owner/Customer), we need to make changes in the ‘Register’ action method of the account controller. Add the following code in the Register method:

[AllowAnonymous]
public ActionResult Register()
{
    ViewBag.Name = new SelectList(ctx.Roles.ToList().Skip(1), "Name", "Name");
    return View();
}      

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.Email };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            //Assign Role to user Here 
           await this.UserManager.AddToRoleAsync(user.Id, model.Name);
           
            //Ends Here

            await SignInAsync(user, isPersistent: false);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            AddErrors(result);
        }
    }

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

In the above code, the highlighted code in the first Register method will store the available roles in the ViewBag so that they will be available in the User Registration View. The Register method with HttpPost, will then add the registered user in the Role.

For displaying the roles, change the Register.cshtml by adding dropdown as below:

<div class="form-group">
    @Html.Label("Select Your User Type", new { @class = "col-md-2 control-label" })
    <div class="col-md-10">
        @Html.DropDownList("Name")
    </div>
</div>

 

Creating Roles

To create role, the user must be administrator, and to do this from the RoleController code, remove or comment [Authorize (Role=”Administrator”)]) line and run the application. After navigating to the Create action of the RoleController, you will get the following View:

create-role

Create Owner and Customer roles

Step 6: In the Controllers folder, add an ASP.NET Web API 2 Controller with Actions using EntityFramework and name it OwnerInfoAPIController.

The OwnerInfoAPIController

owner-apicontroller

This will generate Http action methods for CRUD operations for OwnerInfo. Change the PostOwnerInfo action method as below:

[ResponseType(typeof(OwnerInfo))]
public IHttpActionResult PostOwnerInfo(OwnerInfo ownerInfo)
{
    try
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
        ownerInfo.RegistrationDate = DateTime.Now;

        db.OwnerInfoes.Add(ownerInfo);
        db.SaveChanges();
    }
    catch (DbEntityValidationException  e)
    {
  
        throw;
    }

    return CreatedAtRoute("DefaultApi", new { id = ownerInfo.OwnerId }, ownerInfo);
}

This adds the Registration Date for the Owner as per the server DateTime.

Similarly add an API controller for OwnerPropertyDescription.

owner-property-desc

In this API controller, change the GetOwnerPropertyDescriptions() action method as below:

public List<OwnerPropertyDescription> GetOwnerPropertyDescriptions()
{

    //Logic for Getting the Current Login User and its Owner Is
    var user = this.User.Identity.Name;
    var owner = (from o in db.OwnerInfoes.ToList()
                 where o.Email == user
                 select new OwnerInfo()
                 {
                     OwnerId = o.OwnerId
                 }).First();
    //Ends Here

    var OwnPropeDesc = from owpd in db.OwnerPropertyDescriptions.ToList()
                       where owpd.OwnerId == owner.OwnerId
                       select new OwnerPropertyDescription()
                       {
                           OwnerPropertyId = owpd.OwnerPropertyId,
                           PropertyType = owpd.PropertyType,
                           OwnerId = owpd.OwnerId,
                           Address = owpd.Address,
                           BedRoomNo = owpd.BedRoomNo,
                           TotalRooms = owpd.TotalRooms,
                           PropertyBuildupArea = owpd.PropertyBuildupArea,
                           PropertyDescription = owpd.PropertyDescription,
                           PropertySaleRentStatus = owpd.PropertySaleRentStatus,
                           SaleOrRentCost = owpd.SaleOrRentCost,
                           PropertyAge = owpd.PropertyAge,
                           Status = owpd.Status,
                           RegistrationDate = owpd.RegistrationDate
                       };

    return OwnPropeDesc.ToList();
}

The above action method does the following:

  • Get the Identity Name (login name) of the current login user
  • Based upon the login name, it retrieves the OwnerId
  • Based upon the OwnerId, the Properties registered by the owner are retrieved

Change the PostOwnerPropertyDescription() method by adding the highlighted statement:

[ResponseType(typeof(OwnerPropertyDescription))]
public IHttpActionResult PostOwnerPropertyDescription(OwnerPropertyDescription ownerPropertyDescription)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    ownerPropertyDescription.RegistrationDate = DateTime.Now;
    db.OwnerPropertyDescriptions.Add(ownerPropertyDescription);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = ownerPropertyDescription.OwnerPropertyId }, ownerPropertyDescription);
}

Add the CustomerInfoAPI Web API controller as shown in following image:

customerinfo-api

Change the PostCustomerInfo action method as shown in the following code:

[ResponseType(typeof(CustomerInfo))]
public IHttpActionResult PostCustomerInfo(CustomerInfo customerInfo)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    customerInfo.RegistrationDate = DateTime.Now;
    db.CustomerInfoes.Add(customerInfo);
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = customerInfo.CustomerId }, customerInfo);
}

Step 7: For custom operations like retrieving the Owner and Customer information based upon the login mail, add a new empty API controller of the name CustomAPIController. In this API controller class, add the following action methods.

PropertyStoreEntities ctx;

public CustomAPIController()
{
    ctx = new PropertyStoreEntities(); 
}

// Action Method to get the Owner Information based upon the Login Email

[Route("Owner/Get")]
[ResponseType(typeof(OwnerInfo))]
public IHttpActionResult GetOwner()        {
    try
    {
        var user = this.User.Identity.Name;
        var Owner = (from o in ctx.OwnerInfoes.ToList()
                     where o.Email == user
                     select new OwnerInfo()
                     {
                         OwnerId = o.OwnerId,
                         OwnerName = o.OwnerName,
                         Address = o.Address,
                         City = o.City,
                         Email = o.Email,
                         Contact1 = o.Contact1,
                         Contact2 = o.Contact2
                     }).First();
         return Ok(Owner);           
}
    catch (Exception)
    {
          return Ok(new OwnerInfo());
    }
}

/// <summary>
/// Get the Customer Info based upon the login Email
/// </summary>
/// <returns></returns>
[Route("Customer/Get")]
[ResponseType(typeof(CustomerInfo))]
public IHttpActionResult GetCustomer()
{
    try
    {
        var user = this.User.Identity.Name;
        var Customer = (from c in ctx.CustomerInfoes.ToList()
                       where c.Email == user
                       select new CustomerInfo()
                       {
                            CustomerId = c.CustomerId,
                            CustomerName = c.CustomerName,
                            Address = c.Address,
                            City = c.City,
                            Contact1 = c.Contact1,
                            Contact2 = c.Contact2,
                            Email = c.Email
                       }).First();
return Ok(Customer);            }
    catch (Exception)
    {
         return Ok(new CustomerInfo());
    }
}

To search Rental and Saleable properties listed by owner, add a new Model class in the Models folder:

public class SearchProperty
{
    public int PropertyId { get; set; }
    public string OwnerName { get; set; }

    public string Contact1 { get; set; }

    public string Contact2 { get; set; }
    public string Email { get; set; }
    public string PropertyType { get; set; }
    public string Address { get; set; }
    public int BedRoomNo { get; set; }
    public int TotalRooms { get; set; }
    public int BuildupArea { get; set; }
    public string SaleRentStatus { get; set; }

    public string Status { get; set; }
}

The above model class provides scalar properties for Owner information and property information registered by him.

To search properties by owner, we need to add action method in the CustomAPIController class as below:

// In the below method the propertytype will be : Flat, Row House,      
//Bunglow filter will be : AND, OR searchtype will be : Sale, Rent
 
[Route("Property/{propertytype}/{filter}/{searchtype}")]
public List<SearchProperty> GetProperties(string propertytype, string searchtype,string filter)
{
    List<SearchProperty> Properties = new List<SearchProperty>();

    switch (filter)
    { 
    
        case "AND":
            Properties = (from p in ctx.OwnerPropertyDescriptions.ToList()
                          where 
                          p.Status == searchtype && p.PropertyType == propertytype && p.Status == "Available"
                          select new SearchProperty()
                          {
                              PropertyId = p.OwnerPropertyId,
                              OwnerName = ctx.OwnerInfoes.Find(p.OwnerId).OwnerName,
                              Address = p.Address,
                              Contact1 = ctx.OwnerInfoes.Find(p.OwnerId).Contact1,
                              Contact2 = ctx.OwnerInfoes.Find(p.OwnerId).Contact2,
                              Email = ctx.OwnerInfoes.Find(p.OwnerId).Email,
                              BedRoomNo = p.BedRoomNo,
                              BuildupArea = p.PropertyBuildupArea,
                              PropertyType = p.PropertyType,
                              SaleRentStatus = p.PropertySaleRentStatus,
                              TotalRooms = p.TotalRooms,
                              Status = p.Status
                          }).ToList();
            break;

        case "OR":
            Properties = (from p in ctx.OwnerPropertyDescriptions.ToList()
                          where (p.Status == searchtype || p.PropertyType == propertytype) && p.Status == "Available"
                          select new SearchProperty()
                          {
                              PropertyId = p.OwnerPropertyId,
                              OwnerName = ctx.OwnerInfoes.Find(p.OwnerId).OwnerName,
                              Address = p.Address,
                              Contact1 = ctx.OwnerInfoes.Find(p.OwnerId).Contact1,
                              Contact2 = ctx.OwnerInfoes.Find(p.OwnerId).Contact2,
                              Email = ctx.OwnerInfoes.Find(p.OwnerId).Email,
                              BedRoomNo = p.BedRoomNo,
                              BuildupArea = p.PropertyBuildupArea,
                              PropertyType = p.PropertyType,
                              SaleRentStatus = p.PropertySaleRentStatus,
                              TotalRooms = p.TotalRooms,
                              Status = p.Status
                          }).ToList();
            break;
    }
    return Properties;
}


In the CustomAPIController class, all action methods are applied with Attribute Route. Specially the GetProperties() method accepts 3 string type parameters of name – propertytype (accepts values like ‘Flat’, ‘Row House’, etc), the searchtype parameter (accepts value like ‘Sale’ or ‘Rent’), and the filter parameter (either ‘AND’ or ‘OR’). Based upon these values, the URL is generated for accessing the action method.

Step 8: In the Controllers folder, add MVC controllers as shown below:

OwnerController

[Authorize(Roles="Owner")]
public class OwnerController : Controller
{
// GET: Owner The Action Method Provides the Current Login Email Information 
    public ActionResult Index()
    {
        var data =  this.User.Identity.Name;
        ViewBag.CurrentUserEmail = data;
        return View();
    }
}

The above controller is applied with the ‘Authorize’ attribute, this means that it can be accessible only by the users who are registered as ‘Owners’. The Index action method sends the current Login Email (username) to the View using ViewBag.CurrentUserEmail.

OwnerPropertyDescriptionController

[Authorize(Roles="Owner")]
public class OwnerPropertyDescriptionController : Controller
{
    // GET: OwnerPropertyDescription
    public ActionResult Index()
    {
        var data = this.User.Identity.Name;

        ViewBag.CurrentUserEmail = data; 

        return View();
    }
}

The above controller has a similar behavior like the OwnerController.

CustomerController

[Authorize(Roles = "Customer")]
public class CustomerController : Controller
{
    // GET: Customer
    public ActionResult Index()
    {
        var user = this.User.Identity.Name;
        ViewBag.CurrentUserEmail = user;
        return View();
    }

    public ActionResult SearchProperties()
    {
        return View();
    }
}

The CustomerController is applied with the Authorize attribute for the users registered as ‘Customer’. The Index action method returns the current login Email (username) to View using ViewBag object.

And that completes Part 1 of this article which contained the server-side part. In part 2 of this article we will implement the client-side logic using jQuery and Knockout library.

Conclusion: In the process of developing a Business application, an important step is to decide upon the database design, use of Data Access Layer and business logic. In this part of the article we have the designed required Database structure, MVC and Web API Controllers as per need of our business.

Download the entire source code of this article (Github)

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+
Further Reading - Articles You May Like!
Author
Mahesh Sabnis is a DotNetCurry author and Microsoft MVP having over 17 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). Follow him on twitter @maheshdotnet


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!