AJAX CRUD in Grid using JQuery DataTables in ASP.NET MVC 5

Udemy

Introduction

I frequently come across questions on online forums like StackOverflow in which the questioners are able to create CRUD operations for their entity which is a normal form post, but they struggle implementing the CRUD via ajax so that page does not reloads completely for better User Experience.

In this post, I will be addressing this problem and we will learn that how can implement CRUD using JQuery DataTables in one page without any full page reloads.

Background

I had been writing in last few posts about the usage of JQuery DataTables and it can be leveraged to build a GridView like functionality in asp.net mvc, we saw how we can install it using NuGet Package Manager and implement a simple gird using the plugin which provides us the essential features like Sorting, Searching and Pagination, then we saw how we can implement the paging, filtering and Ordering, then we proceeded to see how advanced search can be added for more better user experience.

If someone wants to have refresher or interested to read the previous posts related, they can be found here:

In this post we will learn how we can add create, update and delete operations support in the grid that we already implemented using JQuery DataTables. The end result will look something like:


 Database Creation using Script

First of all we will run the script for database and tables creation that would be essential for this post. Following is the script for that:

CREATE DATABASE [AdvancedSearchGridExampleMVC]
GO
CREATE TABLE [dbo].[FacilitySites] ([FacilitySiteID] UNIQUEIDENTIFIER NOT NULL,
                                    [FacilityName]   NVARCHAR (MAX)   NULL,
                                    [IsActive]       BIT              NOT NULL,
                                    [CreatedBy]      UNIQUEIDENTIFIER NOT NULL,
                                    [CreatedAt]      DATETIME         NOT NULL,
                                    [ModifiedBy]     UNIQUEIDENTIFIER NULL,
                                    [ModifiedAt]     DATETIME         NULL,
                                    [IsDeleted]      BIT              NOT NULL
                                   );
GO

CREATE TABLE [dbo].[Assets] (
                             [AssetID]                   UNIQUEIDENTIFIER NOT NULL,
                             [Barcode]                   NVARCHAR (MAX)   NULL,
                             [SerialNumber]              NVARCHAR (MAX)   NULL,
                             [PMGuide]                   NVARCHAR (MAX)   NULL,
                             [AstID]                     NVARCHAR (MAX)   NOT NULL,
                             [ChildAsset]                NVARCHAR (MAX)   NULL,
                             [GeneralAssetDescription]   NVARCHAR (MAX)   NULL,
                             [SecondaryAssetDescription] NVARCHAR (MAX)   NULL,
                             [Quantity]                  INT              NOT NULL,
                             [Manufacturer]              NVARCHAR (MAX)   NULL,
                             [ModelNumber]               NVARCHAR (MAX)   NULL,
                             [Building]                  NVARCHAR (MAX)   NULL,
                             [Floor]                     NVARCHAR (MAX)   NULL,
                             [Corridor]                  NVARCHAR (MAX)   NULL,
                             [RoomNo]                    NVARCHAR (MAX)   NULL,
                             [MERNo]                     NVARCHAR (MAX)   NULL,
                             [EquipSystem]               NVARCHAR (MAX)   NULL,
                             [Comments]                  NVARCHAR (MAX)   NULL,
                             [Issued]                    BIT              NOT NULL,
                             [FacilitySiteID]            UNIQUEIDENTIFIER NOT NULL
                            );

GO

CREATE NONCLUSTERED INDEX [IX_FacilitySiteID]
   ON [dbo].[Assets]([FacilitySiteID] ASC);

GO
ALTER TABLE [dbo].[Assets]
    ADD CONSTRAINT [PK_dbo.Assets] PRIMARY KEY CLUSTERED ([AssetID] ASC);

GO

ALTER TABLE [dbo].[Assets]
   ADD CONSTRAINT [FK_dbo.Assets_dbo.FacilitySites_FacilitySiteID] FOREIGN KEY ([FacilitySiteID]) _
REFERENCES [dbo].[FacilitySites] ([FacilitySiteID]) ON DELETE CASCADE;

GO


You can find the script in attached source code in file name dbScript.sql which also contains sample data to get started quickly, running it will create the DB and will add some sample data in the tables as well.

Create/Insert Operation

For the insertion part, first of all we need to create a partial view in Views >> Asset by right clicking the Asset folder and navigate to Add >> MVC 5 Partial Page (Razor) like the below screen shot:


Open the container view in which asset rows are being rendered which _AssetsPartial.cshtml located in Views >> Asset directory and add the html for the Add Asset button which will open up a popup for inserting a new asset row in database:

<button type="button" class="btn btn-default btn-md" data-toggle="modal" data-url="@Url.Action("Create","Asset")" id="btnCreateAsset">
  <span class="glyphicon glyphicon-new-window" aria-hidden="true"></span> Add Asset
</button>

Now, open the Index View of Asset which is  Views >> Asset >> index.csthtml and in scripts section add the following jQuery code for the button click event which we just created above, it will call the create get action and will display the partial view in bootstrap modal to user, the code for which is:

$("#btnCreateAsset").on("click", function () {

       var url = $(this).data("url");

       $.get(url, function (data) {
               $('#createAssetContainer').html(data);

               $('#createAssetModal').modal('show');
       });

});

We also need to add bootstrap modal with container div in which we will be loading create partial view of Asset, so we will add the following html at the end of Index.cshtml view:

<div class="modal fade" id="createAssetModal" tabindex="-1" role="dialog" aria-labelledby="CreateAssetModal" aria-hidden="true" data-backdrop="static">
    <div id="createAssetContainer">
    </div>
</div>

Implementing Add GET Action 

Add action method for Asset Creation get request in the Asset controller file located at Controllers >> AssetController.cs which would be called by the above jQuery code, inside the action method we are populating the ViewModel and passing the instance back to PartialView as normally we do in mvc based application:

public ActionResult Create()
{
    var model = new AssetViewModel();
    model.FacilitySitesSelectList = GetFacilitiySitesSelectList();
    return View("_CreatePartial", model);
}

Add Partial View Creation:

Add a new Partial view in project in Asset Views for Create form, for that right click the Asset folder under Views in the Solution Explorer

Enter the name of partial view to be _CreatePartial in textbox or whatever name you think would be better in your case.



Implementing Add Partial View

Now we will write the partial view that we created in previous step _CreatePatial, add the following code in that View:

@model GridAjaxCRUDMVC.Models.AssetViewModel
@{
    Layout = null;
}

<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
            <h4 class="modal-title">Add Asset</h4>
        </div>
        @using (Ajax.BeginForm("Create", "Asset", null, new AjaxOptions { HttpMethod = "Post", OnSuccess = "CreateAssetSuccess" }, new {  @class = "form-horizontal", role = "form" }))
        {
            <div class="modal-body">
                <div class="form-horizontal">
                    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                    @Html.HiddenFor(model => model.AssetID, new { Value = Guid.NewGuid() })
                    <div class="form-group">
                        @Html.LabelFor(model => model.Barcode, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Barcode, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Barcode, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.SerialNumber, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.SerialNumber, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.SerialNumber, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.FacilitySiteID, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.DropDownListFor(model => model.FacilitySiteID, Model.FacilitySitesSelectList, "Select One", new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.FacilitySiteID, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.PMGuide, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.PMGuide, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.PMGuide, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.AstID, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.AstID, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.AstID, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.ChildAsset, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.ChildAsset, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.ChildAsset, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.GeneralAssetDescription, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.GeneralAssetDescription, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.GeneralAssetDescription, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.SecondaryAssetDescription, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.SecondaryAssetDescription, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.SecondaryAssetDescription, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Quantity, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Quantity, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Manufacturer, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Manufacturer, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Manufacturer, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.ModelNumber, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.ModelNumber, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.ModelNumber, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Building, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Building, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Building, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Floor, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Floor, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Floor, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Corridor, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Corridor, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Corridor, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.RoomNo, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.RoomNo, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.RoomNo, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.MERNo, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.MERNo, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.MERNo, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.EquipSystem, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.EquipSystem, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.EquipSystem, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Comments, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            @Html.EditorFor(model => model.Comments, new { htmlAttributes = new { @class = "form-control" } })
                            @Html.ValidationMessageFor(model => model.Comments, "", new { @class = "text-danger" })
                        </div>
                    </div>

                    <div class="form-group">
                        @Html.LabelFor(model => model.Issued, htmlAttributes: new { @class = "control-label col-md-2" })
                        <div class="col-md-10">
                            <div class="checkbox">
                                @Html.EditorFor(model => model.Issued)
                                @Html.ValidationMessageFor(model => model.Issued, "", new { @class = "text-danger" })
                            </div>
                        </div>
                    </div>


                </div>
            </div>
            <div class="modal-footer">
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                        <input type="submit" class="btn btn-primary" value="Save" />
                    </div>
                </div>
            </div>
        }
    </div>
</div>

Implementing Add/Create Post Action 

The important thing to note in the view code is the Ajax.BeginForm helper portion as the class Ajax should be enough to understand that it will post the model back to controller action via Ajax, which means that whole will not refresh i.e. no full postback will happen in terms on asp.net web forms.

The post action is simply mapping ViewModel object to the Model and then saving it in the repository:
[HttpPost]
public async Task<ActionResult> Create(AssetViewModel assetVM)
{
   if (!ModelState.IsValid)
        return View("_CreatePartial", assetVM);

   Asset asset = MaptoModel(assetVM);

   DbContext.Assets.Add(asset);
   var task = DbContext.SaveChangesAsync();
   await task;

   if (task.Exception != null)
   {
       ModelState.AddModelError("", "Unable to add the Asset");
       return View("_CreatePartial", assetVM);
   }

   return Content("success");
}

We are trying to save the newly entered asset in the database and tracking if it is saved successfully or not and if it saves successfully we are returning a string message “success” back to client side and in the success call back of Ajax Form we will be checking if operation was successful or not to do UI changes according to that.

Implementing Ajax Callback Function

We can see that in BeginForm helper method parameters we are specifying JavaScript function to be called when the Ajax Form successfully returns back from server i.e. OnSuccess = "CreateAssetSuccess"

Now let’s go to the index.cshtml view of Asset and define the success callback implementation:

function CreateAssetSuccess(data) {

    if (data != "success") {
       $('#createAssetContainer').html(data);
        return;
    }
    $('#createAssetModal').modal('hide');
    $('#createAssetContainer').html("");
    assetListVM.refresh();

}

What we are doing here is if the operation is not successful we are updating the client side html to notify the user about the failure of creation and if the insert goes successful we are closing the modal popup and refreshing the Grid to display the up to date information.

Edit/Update Operation:

Until now we should be able to run the application and successfully add new assets in the database via Ajax using the bootstrap modal which we created above, now let’s move to the update part of the Asset.
We will be adding another column in the datatable columns collection which will contain hyper link that will navigate to Edit View, but as we are using Ajax and bootstrap modal, we will be doing updates as well same way, so no redirect will be involved. Let’s get started.
Open the Index.csthml file and add a new column in the columns collection in jQuery datatable initialization, after updating the columns array our code would look like:

"columns": [
              { "title": "Bar Code", "data": "BarCode", "searchable": true },
              { "title": "Manufacturer", "data": "Manufacturer", "searchable": true },
              { "title": "Model", "data": "ModelNumber", "searchable": true },
              { "title": "Building", "data": "Building", "searchable": true },
              { "title": "Room No", "data": "RoomNo" },
              { "title": "Quantity", "data": "Quantity" },
              {
                  "title": "Actions",
                  "data": "AssetID",
                  "searchable": false,
                  "sortable": false,
                  "render": function (data, type, full, meta) {
                      return '<a href="@Url.Action("Edit","Asset")?id=' + data + '" class="editAsset">Edit</a>';
                     }
              }
           ]

We are setting the header of new column to display Actions as title and we would need to disable the searching and sorting for this column, as it is for edit operation and it does not makes sense to enable sorting and searching on this column. Next we are defining the render method of the column and we are generating anchor link html which could call the Edit action of Asset controller and will pass the current asset id to pull the information of it and display in the Edit View.

Defining Edit/Update GET Action:

After doing the datatable js changes, now we need to create a get action method which will pull the asset record from the database and will display it for editing to the user in a popup.
Let’s implement the Edit get action of it:

public ActionResult Edit(Guid id)
{
    var asset = DbContext.Assets.FirstOrDefault(x => x.AssetID == id);

    AssetViewModel assetViewModel = MapToViewModel(asset);

    if (Request.IsAjaxRequest())
        return PartialView("_EditPartial",assetViewModel);

    return View(assetViewModel);
}


The action simply retrieves the row from the database and after converting it to ViewModel passes it to back to partial view to rendered or returned back to the client side for processing, as no post backs would happen it will generate the html and will send the html back in response of ajax call which client side will handle and decide where to put that html.

Handling Action Links Events on Client Side using JQuery:

From the column render function you can see that there is class applied on anchor link called EditAsset, it is defined because jQuery event handler will be applied to the anchor link and Ajax call will be sent to server, let’s define the event handler for that in Index View:

$('#assets-data-table').on("click", ".editAsset", function (event) {

        event.preventDefault();

        var url = $(this).attr("href");

        $.get(url, function (data) {
            $('#editAssetContainer').html(data);

            $('#editAssetModal').modal('show');
        });

 });

Addition of Bootstrap Modal

Now add the bootstrap modal html in the Index View which will be placeholder for loading the Edit View, define it just after create  bootstrap modal html:

<div class="modal fade" id="editAssetModal" tabindex="-1" role="dialog" aria-labelledby="EditAssetModal" aria-hidden="true" data-backdrop="static">
    <div id="editAssetContainer">
    </div>
</div>

Edit/Update Partial View Creation:

Create another new partial view following the same steps which we did for adding _CreatePartial.cshtml which was for Create Asset, So add a new Partial View in the Asset folder in Views with name _EditPartial.cshtml and add the following code in it:

@model TA_UM.ViewModels.AssetViewModel
    @{ 
        Layout = null;
    }
<div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
                <h4 class="modal-title">Edit Asset</h4>
            </div>
@using (Ajax.BeginForm("Edit", "Asset", null, new AjaxOptions { HttpMethod="Post", OnSuccess = "UpdateAssetSuccess" }, new { @class = "form-horizontal", role = "form" }))
            {
                <div class="modal-body">
                    <div class="form-horizontal">
                        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
                        @Html.HiddenFor(model => model.AssetID)

                        <div class="form-group">
                            @Html.LabelFor(model => model.Barcode, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Barcode, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Barcode, "", new { @class = "text-danger" })
                            </div>
                        </div>
<div class="form-group">
                            @Html.LabelFor(model => model.SerialNumber, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.SerialNumber, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.SerialNumber, "", new { @class = "text-danger" })
                            </div>
                        </div>
<div class="form-group">
                            @Html.LabelFor(model => model.FacilitySiteID, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.DropDownListFor(model => model.FacilitySiteID,Model.FacilitySitesSelectList, "Select One", new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.FacilitySiteID, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.PMGuide, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.PMGuide, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.PMGuide, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.AstID, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.AstID, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.AstID, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.ChildAsset, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.ChildAsset, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.ChildAsset, "", new { @class = "text-danger" })
                            </div>
                        </div>
<div class="form-group">
                            @Html.LabelFor(model => model.GeneralAssetDescription, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.GeneralAssetDescription, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.GeneralAssetDescription, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.SecondaryAssetDescription, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.SecondaryAssetDescription, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.SecondaryAssetDescription, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Quantity, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Quantity, "", new { @class = "text-danger" })
                            </div>
                        </div>
<div class="form-group">
                            @Html.LabelFor(model => model.Manufacturer, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Manufacturer, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Manufacturer, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.ModelNumber, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.ModelNumber, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.ModelNumber, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Building, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Building, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Building, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Floor, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Floor, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Floor, "", new { @class = "text-danger" })
                            </div>
                        </div>
<div class="form-group">
                            @Html.LabelFor(model => model.Corridor, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Corridor, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Corridor, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.RoomNo, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.RoomNo, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.RoomNo, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.MERNo, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.MERNo, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.MERNo, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.EquipSystem, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.EquipSystem, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.EquipSystem, "", new { @class = "text-danger" })
                            </div>
                        </div>
<div class="form-group">
                            @Html.LabelFor(model => model.Comments, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                @Html.EditorFor(model => model.Comments, new { htmlAttributes = new { @class = "form-control" } })
                                @Html.ValidationMessageFor(model => model.Comments, "", new { @class = "text-danger" })
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Issued, htmlAttributes: new { @class = "control-label col-md-2" })
                            <div class="col-md-10">
                                <div class="checkbox">
                                    @Html.EditorFor(model => model.Issued)
                                    @Html.ValidationMessageFor(model => model.Issued, "", new { @class = "text-danger" })
                                </div>
                            </div>
                        </div>


                    </div>
                </div>
<div class="modal-footer">
                        <div class="form-group">
                            <div class="col-md-offset-2 col-md-10">
                                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                                <input type="submit" class="btn btn-primary" value="Save changes"/>
                            </div>
                        </div>
                    </div>
            }
        </div>
    </div>

The Edit View is also pretty same as we had for Insert except it would be posting to different action which would be responsible for handling the updates of a particular Asset row.

Implementing Edit/Update Post Action:

 Now let’s implement the post action of Edit:

        [HttpPost]
        public async Task<ActionResult> Edit(AssetViewModel assetVM)
        {

            assetVM.FacilitySitesSelectList = GetFacilitiySitesSelectList(assetVM.FacilitySiteID);
            if (!ModelState.IsValid)
            {
                Response.StatusCode = (int)HttpStatusCode.BadRequest;
                return View(Request.IsAjaxRequest() ? "_EditPartial" : "Edit", assetVM);
            }

            Asset asset = MaptoModel(assetVM);

            DbContext.Assets.Attach(asset);
            DbContext.Entry(asset).State = EntityState.Modified;
            var task = DbContext.SaveChangesAsync();
            await task;

            if (task.Exception != null)
            {
                ModelState.AddModelError("", "Unable to update the Asset");
                Response.StatusCode = (int)HttpStatusCode.BadRequest;
                return View(Request.IsAjaxRequest() ? "_EditPartial" : "Edit", assetVM);
            }

            if(Request.IsAjaxRequest())
            {
                return Content("success");
            }

            return RedirectToAction("Index");

        }


Handling Update Ajax Success CallBack

In Index.cshtml implement the OnSuccess callback function which will be called when the asset gets updated successfully, in the call back function modal would be closed and the form will be cleared so that if user opens for editing again the fresh html would be fetched and updated information will be displayed in View and of course datatable will also be refreshed to display the latest updates:

        /**** Edit Asset Ajax Form CallBack ********/

        function UpdateAssetSuccess(data) {

            if (data != "success") {
                $('#editAssetContainer').html(data);
                return;
            }
            $('#editAssetModal').modal('hide');
            $('#editAssetContainer').html("");
            assetListVM.refresh();

        }

The same approach will be followed for the details and delete action, let’s see the delete portion, first of all  open the Index view and let’s add the details and delete action hyperlinks in the render function where we defined the edit link above:

"render": function (data, type, full, meta) {
             return '<a href="@Url.Action("Edit","Asset")?id=' + data + '" class="editAsset">Edit</a> | <a href="@Url.Action("Details","Asset")?id=' + data + '">Details</a> | <a href="@Url.Action("Delete","Asset")?id=' + data + '">Delete</a>';
          }


Now the action column will contain three hyperlinks for Edit, Details and Delete of Asset.

Retrieve and Delete Operation:

At this stage we should be able to see the insert and update functionality working correctly, now we will move to the deletion part to see how we implement the Deletion part for Assets. For doing that, we will need to add class to the hyperlinks which we are generated in the render function for the column in which links will be appearing, let’s do that first, we need to define the render property for the last column in columns array of the DataTables initialization code, and we will define the how the column value should be rendered:

"render": function (data, type, full, meta) {
            return '<a href="@Url.Action("Edit","Asset")?id=' + data + '" class="editAsset">Edit</a> | <a href="@Url.Action("Details","Asset")?id=' + data + '" class="detailsAsset">Details</a> | <a href="@Url.Action("Delete","Asset")?id=' + data + '" class="deleteAsset">Delete</a>';
          }

We have added the detailsAsset and deleteAsset classes to the respective anchor tags so that we can bind the events using jQuery and do some logic to display details or delete particular asset.

After doing the above step, now we will be writing the events to handle the click of these two hyperlinks. We will have to write the following code to achieve it :

$('#assets-data-table').on("click", ".detailsAsset", function (event) {

                event.preventDefault();

                var url = $(this).attr("href");

                $.get(url, function (data) {
                    $('#detailsAssetContainer').html(data);

                    $('#detailsAssetModal').modal('show');
                });

Now as the handler for details tag is placed, let’s move to the view part and write the needed razor code in the respective view. So, Create a new partial view named _detailsPartial which will be responsible for displaying the details of the particular asset selected. For that again Right click the Asset folder in Views directory in Solution Explorer and do the same steps as previously and create the partial with the name mentioned above and add the following code in it.
@model GridAdvancedSearchMVC.Models.AssetViewModel

<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
            <h4 class="modal-title">Asset Details</h4>
            <hr/>
        </div>

        <div class="modal-body">
            <dl class="dl-horizontal">
                <dt>
                    @Html.DisplayNameFor(model => model.Barcode)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Barcode)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.SerialNumber)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.SerialNumber)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.FacilitySite)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.FacilitySite)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.PMGuide)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.PMGuide)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.AstID)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.AstID)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.ChildAsset)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.ChildAsset)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.GeneralAssetDescription)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.GeneralAssetDescription)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.SecondaryAssetDescription)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.SecondaryAssetDescription)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Quantity)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Quantity)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Manufacturer)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Manufacturer)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.ModelNumber)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.ModelNumber)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Building)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Building)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Floor)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Floor)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Corridor)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Corridor)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.RoomNo)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.RoomNo)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.MERNo)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.MERNo)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.EquipSystem)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.EquipSystem)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Comments)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Comments)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Issued)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Issued)
                </dd>

            </dl>

        </div>

    </div>
</div>

Details Get Action Implementation:

Now it’s the time to define the controller action code which will retrieve the asset information from the data source and will pass it back to view to be displayed to the user. Here is the code for controller action:

        public async Task<ActionResult> Details(Guid id)
        {
            var asset = await DbContext.Assets.FirstOrDefaultAsync(x => x.AssetID == id);
            var assetVM = MapToViewModel(asset);

            if(Request.IsAjaxRequest())
                return PartialView("_detailsPartial", assetVM);

            return View(assetVM);
        }

Implementing Delete Operation:

We will add the GET action method for delete which will get the particular asset from the repository and will display the details in popup, where user would be able to delete it or cancel it. Here is the code for the action method:
        public ActionResult Delete(Guid id)
        {
            var asset = DbContext.Assets.FirstOrDefault(x => x.AssetID == id);

            AssetViewModel assetViewModel = MapToViewModel(asset);

            if (Request.IsAjaxRequest())
                return PartialView("_DeletePartial", assetViewModel);
            return View(assetViewModel);
        }

Delete Partial View Addition

We will now add another partial view in the solution for delete part, so navigate to the Views >> Asset folder in the Solution Explorer and from the context menu which appears by right clicking the Asset folder, add a new View using the Option Add >> MVC 5 Partial Page (Razor) and name the partial view to _DeletePartial and add the following code to it:
@model GridAdvancedSearchMVC.Models.AssetViewModel


<div class="modal-dialog">
    <div class="modal-content">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
            <h4 class="modal-title">Delete Asset</h4>
            <h3>Are you sure you want to delete this?</h3>

        </div>

        <div class="modal-body">
            <dl class="dl-horizontal">
                <dt>
                    @Html.DisplayNameFor(model => model.Barcode)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Barcode)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.SerialNumber)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.SerialNumber)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.FacilitySite)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.FacilitySite)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.PMGuide)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.PMGuide)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.AstID)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.AstID)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.ChildAsset)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.ChildAsset)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.GeneralAssetDescription)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.GeneralAssetDescription)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.SecondaryAssetDescription)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.SecondaryAssetDescription)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Quantity)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Quantity)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Manufacturer)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Manufacturer)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.ModelNumber)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.ModelNumber)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Building)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Building)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Floor)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Floor)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Corridor)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Corridor)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.RoomNo)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.RoomNo)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.MERNo)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.MERNo)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.EquipSystem)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.EquipSystem)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Comments)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Comments)
                </dd>

                <dt>
                    @Html.DisplayNameFor(model => model.Issued)
                </dt>

                <dd>
                    @Html.DisplayFor(model => model.Issued)
                </dd>

            </dl>

            @using (Ajax.BeginForm("Delete", "Asset", null, new AjaxOptions { HttpMethod = "Post", OnSuccess = "DeleteAssetSuccess" }, new { @class = "form-horizontal", role = "form" }))
            {

                <div class="form-actions no-color">
                    @Html.HiddenFor(x => x.AssetID)
                    <input type="submit" value="Delete" class="btn btn-default" /> |
                    @Html.ActionLink("Back to List", "Index",null,new { data_dismiss = "modal" })
                </div>
            }
        </div>
    </div>
</div>

Now we need to again a container div for delete popup as well which will be holding the html returned by the partial view _DeletePartial as we did for other three operations, so add the following html in the Index.cshtml view of Asset :

<div class="modal fade" id="deleteAssetModal" tabindex="-1" role="dialog" aria-labelledby="DeleteAssetModal" aria-hidden="true" data-backdrop="static">
    <div id="deleteAssetContainer">
    </div>
</div>

Handling Delete Link Event with JQuery:

We also need to implement the delete button click event which will be responsible for calling the action method asynchronously and will add the response html to the popup container and the popup container will be displayed to user, the code for which would be:
$('#assets-data-table').on("click", ".deleteAsset", function (event) {

                event.preventDefault();

                var url = $(this).attr("href");

                $.get(url, function (data) {
                    $('#deleteAssetContainer').html(data);

                    $('#deleteAssetModal').modal('show');
                });

            });

Handling DELETE Ajax POST Success Callback:

If you notice here as well we are using Ajax.BeginForm helper method to post the GUID of Asset row that needs to be deleted, and we have specified a JavaScript success callback, but we haven’t yet defined the function in the Index view, so let’s do that as well, here is the function definition:
function DeleteAssetSuccess(data) {

                if (data != "success") {
                    $('#deleteAssetContainer').html(data);
                    return;
                }
                $('#deleteAssetModal').modal('hide');
                $('#deleteAssetContainer').html("");
                assetListVM.refresh();

            }

This is also quite same as we did for other actions, we are making sure that if deletion was successful or not, we update the UI accordingly, that’s what the above code is doing, it closes the popup, clears the html of the container div and refreshes the datatable ViewModel to reflect the latest changes in the grid.

Implementing Delete POST Action:

Lastly, we need to define the POST action for delete which will be responsible for deleting the row from the database table and will return the status of the action back to view in either case i.e. success or failure, let’s do that, here is the code for Delete post action:

        [HttpPost, ActionName("Delete")]
        public async Task<ActionResult> DeleteAsset(Guid AssetID)
        {
            var asset = new Asset { AssetID = AssetID };
            DbContext.Assets.Attach(asset);
            DbContext.Assets.Remove(asset);

            var task = DbContext.SaveChangesAsync();
            await task;

            if (task.Exception != null)
            {
                ModelState.AddModelError("", "Unable to Delete the Asset");
                Response.StatusCode = (int)HttpStatusCode.BadRequest;
                AssetViewModel assetVM = MapToViewModel(asset);
                return View(Request.IsAjaxRequest() ? "_DeletePartial" : "Delete", assetVM);
            }

            if (Request.IsAjaxRequest())
            {
                return Content("success");
            }

            return RedirectToAction("Index");

        }

Now run the application and you should be able to add update and delete rows via Ajax without navigating between the pages.



Equality Operator (==) Problem with Inheritance and Generics in C#

Udemy

Background

In the previous post, we learned that == operator does not works for non-primitive value types unless we overload the operator for that value type, and we saw how we can overload the equality operator to enable == operator comparison for non-primitive value types. We also compare the == operator and Object.Equals method to see how their behavior differs for primitive types, value types and reference types.

Comparison of == operator and Object.Equals

Here is the comparison of both:
  • For Primitive Types e.g. int, float, long, bool etc both the == operator and Object.Equals method will compare the values i.e. 1 is equal to but 1, but 1 is not equal to 0
  • For most of the Reference Types both the == operator and Object.Equals method will by default compare the references, you can modify this behavior by overloading the == operator or overriding the Object.Equals method but if you want the behavior of both to be consistent and don’t want to surprise other developers and yourself you must do the both (overload == operator and override the Equals method).
  • For Non-primitive Value Types the Object.Equals method will do the value equality using Refection which is slow and this is overridden behavior of course, but the equality operator is by default not available for value types unless you overload the == operator for that type which we saw in the example above.
  • There is also another minor difference that for reference types the virtual Equals method cannot work if the first parameter is null but that is trivial, as a workaround the static Equals method can be used which takes both the objects to be compared as parameter.

Previous Posts in the Series

You might want to read the previous posts in this series, if yes following are the links to the previous content related to it: 
You might want to read the previous posts in this series, if yes following are the links to the previous content related to it:

Introduction

In this post we will be seeing that why it is not always a good option to use == operator for doing equality, we will be seeing that how == operator does not work well with inheritance and generics and will see that the better options is to rely on  virtual Equals method.

Equality Operator and Inheritance Problem

Let’s create some example code that will illustrate how == operator behaves when inheritance is in play in  our code, so let’s declare two string type variables and we will check the result once again by doing equality check in 4 different ways that are available in C#:

public class Program
{
    public static void Main(string[] args)
    {

        string str = "Ehsan Sajjad";
        string str1 = string.Copy(str);

        Console.WriteLine(ReferenceEquals(str, str1));
        Console.WriteLine(str.Equals(str1));
        Console.WriteLine(str == str1);
        Console.WriteLine(object.Equals(str, str1));
    }
}

Output:

 The following is the output of the above executed code:



 First we are checking that if both string variables have reference to same string object using ReferenceEquals method, next we check using the instance method Equals of String type, on third line we are again checking for equality but this time using the == operator and lastly we are checking using the static Equals method of Object so that we can compare the result of all these 4 techniques. We should be able to tell what would be the result from the previous posts which we have done so far:
  • ReferenceEquals will for sure return false as both are reference to different object not the same object.
  • The Equals method of String type will also return true as both strings are identical (i.e. same sequence or characters).
  • == Operator will also return true as both string values are equal.
  • virtual Object.Equals call will also return true as the overridden implementation of String would be called  and it checks the equality of values of string.
All the above until now makes sense, now we will modify the above example a little, the only change we will do is instead of using variables of type String, we will use variables of type Object and we will see how the output differs from the above code, here is the code after converting it to use Object type instead of String:

static void Main(string[] args)
{

    Object str = "Ehsan Sajjad";
    Object str1 = string.Copy((string)str);

    Console.WriteLine(ReferenceEquals(str, str1));
    Console.WriteLine(str.Equals(str1));
    Console.WriteLine(str == str1);
    Console.WriteLine(object.Equals(str, str1));

   
}


Output:


So we can see the result is different than what we had before when we were using variables of type String. The other three ways methods are still returning the same result but the == operator equality check is now returning false instead of true stating that the two strings are not equal contradicting with the fact that they are equal actually and it’s also conflicting with the result of other two methods. 

Why is That?

The reason behind this is that the == operator is equivalent to a static method, and a static method cannot be a virtual method, what is actually happening when comparing using == operator here is that we are trying to compare two variables of type Object, we know that they are of type String in actual, but the compiler is not aware of that, and we know that for non-virtual methods, it is decided at compile-time that which implementation needs to be invoked and as the variables have been declared of type Object the compiler will emit code for comparing instances of type Object and if you navigate the source code for Object you will see that there is  no overload for == operator for it, so what will happen here is that == operator will do what it always do for comparing two reference types when there is no overload found for that type (i.e. it will check for reference equality) and as these two string objects are separate instances, the reference equality will be evaluated to false saying that the two objects are not equal.

Object Equals Method Should be Preferred

The above problem will never come with the other two Equals methods as they are virtual and the specific type will provide the override for it and calling them will always call the overridden implementation to correctly evaluate the equality of them, so in the above case overridden methods of String type will be called.

For static Equals method we already discussed in one of the previous posts that it internally calls the same virtual Equals method, it is just there where we want to avoid NRE (Null Reference Exception) and there is chance that the object on which we will be calling Equals method could be null.

So in case of inheritance Equals method overrides should be preferred when checking for equality instead of using == operator.

== Operator and Reference Equals

Another thing to note is that casting the operands of equality operator to object before comparing will always give the same result as calling the ReferenceEquals method. 

Some developers write that way to check for reference equality. We should be careful when doing reference equality that way because someone else reading that code in future might not understand or not aware that casting to object causes the comparison to be based of reference.

 But if we explicitly call the ReferenceEquals method that would clarify the intent that we want to do reference equality and it would clear all the doubts that what the code needs to do.

== Operator and Generics Problem

Another reason to avoid the == operator is if we are using generics in our code. To illustrate that let’s create a simple method which takes two generic parameters of type T and compares to check if both the objects are equal and print the result on the Console, the code for which would be:

static void Equals<T%gt;(T a, T b)
{
    Console.WriteLine(a == b);
}

The above example is obviously pretty simple and one can easily tell what it is actually doing. We are using equality operator to compare the two objects of type T, but if you try to build the above example in Visual Studio, it wouldn’t build and a compile time error would come saying:

Error CS0019 : Operator '==' cannot be applied to operands of type 'T' and 'T'

The above error we are seeing because the T could be any type, it can be a reference type or a value type or a primitive type and there is no guarantee that the type parameter which is being passed provided implementation of == operator for itself.

In C# generics there is no way to apply a constraint on the generic type or method which could force the past type parameter to provide the overload implementation of the == operator, we can make the above code build successfully by putting the class constraint on type T like:

static void Equals<t>(T a, T b) where T : class
{
    Console.WriteLine(a == b);
}


So we have put a constraint on generic type T to be a reference type due to which we are now able to compile the code successfully, as == operator can always be used on reference types with no problems and it will check for reference equality of two objects of reference type.

Temporary Solution

We are able to build the code now, but there is problem because what if we need to pass value type parameters on primitive types to the generic Equals method, that we will not be able to do as we had put the restriction to this method to only be called for reference types.

Let’s write some code in the main method which would create two identical strings as we did couple of times in previous posts as well:

class Program
{
    static void Main(string[] args)
    {

        Object str = "Ehsan Sajjad";
        Object str1 = string.Copy((string)str);

        Equals(str, str1);
           

    }

    static void Equals<T>(T a, T b) where T : class
    {
        Console.WriteLine(a == b);
    }
}

So guess what would be the output of the above code when we run it, it will evaluate the two strings to be equal or not? If we recall what we saw before was that for String type the == operator overload defined by String compares the values of the objects, so the above should print true on the Console, but it we run the code we will see the opposite result that it printed false:


So, the above code has given us unexpected result, we were expecting True to be printed on Console. The question which comes in mind instantly is because why it is evaluating to false, it looks like the == operator is checking the two objects for Reference Equality instead of Value Equality, but the question is why it is doing reference equality.

This is happening because even though the compiler knows that it can apply == operator to whatever the type T being passed, In the above case String and it has == operator overload, but compiler does not know that whether the generic type which is being used when using the method overloads the == operator or not, so it assumes that T wouldn't overload it and it compiles the code considering that the == operator is called on instances of type Object which clearly happened that it checked for Reference Equality.

This is very important to keep in mind when using == operator with generics. The == operator  will not use the equality operator overload defined by the type T and it will consider it as Object.

Again Object.Equals to Rescue

Now let’s change our generic method to use Equals method instead of equality operator which would be:

static void Equals<t>(T a, T b)
{
   Console.WriteLine(object.Equals(a,b));
}

We have removed the class constraint as Object.Equals can be called on any type and we are using the static method for the same reason that if one of the parameter is passed null our code wouldn’t fail and will work as expected, and now this method will work for both value types and reference types.

Now if we run the code again we will see that it printed the result as expected because the object.Equals will call the appropriate overridden implementation of Equals method at run-time as static method will call the virtual Equals method and we see the expected result True as both string values are equal.

Summary

  • == Operator doesn’t work well with Inheritance and might give you unexpected result when used with inheritance because the == operator is not virtual, so wherever possible virtual Equals or static Equals methods should be preferred.
  • == Operator also doesn’t work as expected when used in Generic class or methods and Equals method should be used with generic to avoid unexpected behavior in the program.









GridView with Server Side Advanced Search Implementation using JQuery DataTables in ASP.NET MVC 5

Udemy

Download Source Code

Background:

In the last two posts about implementing GridView in asp.net mvc, we talked how we can create a grid like we had in asp.net webforms using JQuery DataTables plugin, then in the second post we saw that how we can enhance the performance of our gird by implementing the sorting, searching and paging, as in the first post we implemented a gird but the problem was all rows were getting rendered on the page as html when page is first time loaded and filtering, paging and sorting was being done on the client side and handled by the datables plugin.

If you are interested to read those, you can find both of those post here:

I hope that after reading previous posts on grid in asp.net mvc you are now in better position to create a grid view in asp.net mvc which is for most of the beginners a difficult thing especially for those who come from the web forms development experience.

Introduction 

In this post we will see that how we can add Advanced Search on our GridView, so that user gets more user friendly search experience while searching for data in the grid.

We won’t be repeating the steps from the previous which we have done already which includes database creation and inserting sample data, setting up a new web application project with the required nuget packages, if you are directly reading this, you might want to take a look on at least the last post about server side filtering to get familiar with what we are doing, so as being said, we will be reusing the same project and code and will continue adding the new portion to it.
At the end of previous article we had a working grid with server side pagination, filtering and sorting and after implementing the Advanced Search feature in the grid our application will look like:




Step 1 - Database Creation

We saw in previous posts that we had just one Assets table that we were using to display records in the Gird and we had all the data in just one table in De-normalized form, so we have normalized one of the column of Assets table and created a Lookup table named FacilitySites to demonstrate how advanced search can be implemented using datatables on server side, normalization is also done mostly to avoid data duplication so that instead of repeating same value in multiple rows we store it as a row in another table and just reference the unique identifier in the other table.
Following is the script which can be used to create database:

CREATE DATABASE [AdvancedSearchGridExampleMVC]  
 GO  

CREATE TABLE [dbo].[FacilitySites] (
    [FacilitySiteID] UNIQUEIDENTIFIER NOT NULL,
    [FacilityName]   NVARCHAR (MAX)   NULL,
    [IsActive]       BIT              NOT NULL,
    [CreatedBy]      UNIQUEIDENTIFIER NOT NULL,
    [CreatedAt]      DATETIME         NOT NULL,
    [ModifiedBy]     UNIQUEIDENTIFIER NULL,
    [ModifiedAt]     DATETIME         NULL,
    [IsDeleted]      BIT              NOT NULL
);
GO


CREATE TABLE [dbo].[Assets] (
    [AssetID]                   UNIQUEIDENTIFIER NOT NULL,
    [Barcode]                   NVARCHAR (MAX)   NULL,
    [SerialNumber]              NVARCHAR (MAX)   NULL,
    [PMGuide]                   NVARCHAR (MAX)   NULL,
    [AstID]                     NVARCHAR (MAX)   NOT NULL,
    [ChildAsset]                NVARCHAR (MAX)   NULL,
    [GeneralAssetDescription]   NVARCHAR (MAX)   NULL,
    [SecondaryAssetDescription] NVARCHAR (MAX)   NULL,
    [Quantity]                  INT              NOT NULL,
    [Manufacturer]              NVARCHAR (MAX)   NULL,
    [ModelNumber]               NVARCHAR (MAX)   NULL,
    [Building]                  NVARCHAR (MAX)   NULL,
    [Floor]                     NVARCHAR (MAX)   NULL,
    [Corridor]                  NVARCHAR (MAX)   NULL,
    [RoomNo]                    NVARCHAR (MAX)   NULL,
    [MERNo]                     NVARCHAR (MAX)   NULL,
    [EquipSystem]               NVARCHAR (MAX)   NULL,
    [Comments]                  NVARCHAR (MAX)   NULL,
    [Issued]                    BIT              NOT NULL,
    [FacilitySiteID]            UNIQUEIDENTIFIER NOT NULL
);
GO

CREATE NONCLUSTERED INDEX [IX_FacilitySiteID]
    ON [dbo].[Assets]([FacilitySiteID] ASC);


GO
ALTER TABLE [dbo].[Assets]
    ADD CONSTRAINT [PK_dbo.Assets] PRIMARY KEY CLUSTERED ([AssetID] ASC);


GO
ALTER TABLE [dbo].[Assets]
    ADD CONSTRAINT [FK_dbo.Assets_dbo.FacilitySites_FacilitySiteID] FOREIGN KEY ([FacilitySiteID]) REFERENCES [dbo].[FacilitySites] ([FacilitySiteID]) ON DELETE CASCADE;
GO


If database gets created successfully which will of course after that we need to dump some records in the table so that when we query from the application we could have something displaying on the page to see if the things are working correctly. So following is the script for that:

INSERT INTO [dbo].[FacilitySites] ([FacilitySiteID], [FacilityName], [IsActive], [CreatedBy], [CreatedAt], [ModifiedBy], [ModifiedAt], [IsDeleted]) VALUES (N'526fa0d5-1872-e611-b10e-005056c00008', N'FOR', 1, N'8de72a70-6a35-4658-ae0d-ca3cc55da752', N'2016-09-04 01:56:08', NULL, NULL, 0)
INSERT INTO [dbo].[FacilitySites] ([FacilitySiteID], [FacilityName], [IsActive], [CreatedBy], [CreatedAt], [ModifiedBy], [ModifiedAt], [IsDeleted]) VALUES (N'536fa0d5-1872-e611-b10e-005056c00008', N'Pryco', 1, N'8de72a70-6a35-4658-ae0d-ca3cc55da752', N'2016-09-04 01:56:08', NULL, NULL, 0)
INSERT INTO [dbo].[FacilitySites] ([FacilitySiteID], [FacilityName], [IsActive], [CreatedBy], [CreatedAt], [ModifiedBy], [ModifiedAt], [IsDeleted]) VALUES (N'546fa0d5-1872-e611-b10e-005056c00008', N'6rt', 1, N'8de72a70-6a35-4658-ae0d-ca3cc55da752', N'2016-09-04 01:56:08', NULL, NULL, 0)
GO

INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'd37cc16b-3d13-4eba-8c98-0008b409a77b', N'D04-056', N'N/A', N'D-04', N'D04-056', N'N/A', N'DOOR, HYDR/ELEC/PNEUM OPERATED', N'N/A', 1, N'KM', N'N/A', N'South', N'7', N'E', N'019', N'', N'', N'Swing', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'7be68b37-5ec3-4a8b-be48-00490049f66b', N'C06-114', N'N/A', N'C-06', N'C06-114', N'A11-13,C08-16', N'CONTROLS, CENTRAL SYSTEM, HVAC', N'N/A', 1, N'N/A', N'N/A', N'South', N'9', N'F', N'004', N'MER5 ', N'AC-SE-2', N'rtn damper', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'e8a8af59-a863-4757-93bd-00561f36122b', N'C03-069', N'N/A', N'C-03', N'C03-069', N'', N'COILS, REHEAT/PREHEAT (REMOTE)', N'N/A', 1, N'N/A', N'N/A', N'North', N'4', N'A', N'222', N'', N' RH-N-17', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'69dcdb07-8f60-4bbf-ad05-0078f3902c48', N'D06-300', N'N/A', N'D-06', N'D06-300', N'', N'DRAIN, AREAWAY/DRIVEWAY/STORM', N'N/A', 1, N'N/A', N'N/A', N'South', N'Exterior', N'', N'1s0?', N'SB areaway 1st', N'', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'5b229566-5226-4e48-a6c7-008d435f81ae', N'A05-46', N'N/A', N'A-05', N'A05-46', N'', N'Air Conditioning Machine, Split System Chilled Water Coils', N'10 Tons and Under', 1, N'Trane', N'N/A', N'South', N'1', N'G', N'022', N'Headquarter Protective Force', N'', N'Above Ceilg', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'108d1792-7aa1-4865-a3d3-00a0ea973aa3', N'C06-252', N'N/A', N'C-06', N'C06-252', N'F27-35,C08-33', N'CONTROLS, CENTRAL SYSTEM, HVAC', N'N/A', 1, N'N/A', N'N/A', N'South', N'9', N'F', N'004', N'MER5 ', N'E-SE-1', N'exh damper', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'80b9e4f9-71a4-4bd6-85c1-00a404cfee2b', N'D06-409', N'N/A', N'D-06', N'D06-409', N'', N'DRAIN, AREAWAY/DRIVEWAY/STORM', N'N/A', 1, N'N/A', N'N/A', N'North', N'Exterior', N'', N'eas0?', N'NB lawn east', N'', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'bdad32e0-9c21-4451-8cc9-00b47b155eb9', N'D04-182', N'N/A', N'D-04', N'D04-182', N'N/A', N'DOOR, HYDR/ELEC/PNEUM OPERATED', N'N/A', 1, N'N/A', N'N/A', N'South', N'2', N'E', N'2E-115', N'Bathrooms', N'', N'HYDR/ELEC/PNEUM', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'4d859a1b-10e0-4cb0-96a4-00c164a7237e', N'C03-222', N'N/A', N'C-03', N'C03-222', N'', N'COILS, REHEAT/PREHEAT (REMOTE)', N'N/A', 1, N'N/A', N'N/A', N'West', N'G', N'GJ, GI', N'086,052', N'MER8 ', N'SW-26', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'3df536d8-9f25-40dd-a83f-00c4434ad58e', N'D06-348', N'N/A', N'D-06', N'D06-348', N'', N'DRAIN, AREAWAY/DRIVEWAY/STORM', N'N/A', 1, N'N/A', N'N/A', N'West', N'Exterior', N'', N'2n4?', N'WB areaway 2nd', N'', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'26c671bc-47f1-4d0e-acc6-00cdfb94b67d', N'C06-165', N'N/A', N'C-06', N'C06-165', N'A11-17,C08-22', N'CONTROLS, CENTRAL SYSTEM, HVAC', N'N/A', 1, N'N/A', N'N/A', N'South', N'9', N'F', N'004', N'MER5 ', N'AC-SE-6', N'min OA', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'be09535a-0fb6-4f7b-a74e-00dab4730211', N'D04-034', N'N/A', N'D-04', N'D04-034', N'N/A', N'DOOR, HYDR/ELEC/PNEUM OPERATED', N'N/A', 1, N'Dor-O-Matic, Jr', N'N/A', N'North', N'G', N'A', N'064', N'', N'', N'Swing', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'65a0abaa-75cf-489a-9367-0118486218b9', N'D05-049', N'N/A', N'D-05', N'D05-049', N'N/A', N'DOOR, ENTRANCE, MAIN', N'N/A', 1, N'N/A', N'N/A', N'South', N'G                     1st', N'E', N'283', N'Ped Mall east', N'', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
INSERT INTO [dbo].[Assets] ([AssetID], [Barcode], [SerialNumber], [PMGuide], [AstID], [ChildAsset], [GeneralAssetDescription], [SecondaryAssetDescription], [Quantity], [Manufacturer], [ModelNumber], [Building], [Floor], [Corridor], [RoomNo], [MERNo], [EquipSystem], [Comments], [Issued], [FacilitySiteID]) VALUES (N'c0101cf3-d1f1-4d32-a4b5-0135dc54645a', N'C03-046', N'N/A', N'C-03', N'C03-046', N'', N'COILS, REHEAT/PREHEAT (REMOTE)', N'N/A', 1, N'N/A', N'N/A', N'North', N'5', N'A', N'084', N'', N'RH-N-30', N'', 0, N'526fa0d5-1872-e611-b10e-005056c00008')
GO


Step - 2 Advanced Search Form Creation

We will create a new view for our advanced search, which will contains a form with few input HTML controls that will be posted to controller action for filtering the records.
In Solution Explorer, Expand the Views folder, then again expand the Asset folder and open the Index.cshtml file, we will add the html for the Advanced Search button that will appear above the grid. Add the following HTML in the view:

<button type="button" class="btn btn-default btn-md" data-toggle="modal" 
        data-target="#advancedSearchModal" id="advancedsearch-button">
   <span class="glyphicon glyphicon-search" aria-hidden="true"></span> Advanced Search
</button>


If you note we have some new attributes in the button code, you don’t need to worry about that those are for bootstrap modal, as clicking the button will open a Modal dialog, and user would be able to select the search criteria and search for results. The data-toggle="modal" attribute dictates that this button will toggle a Modal Dialog and data-target="#advancedSearchModal" specifies the html element of the page which would be displayed as Modal Dialog.
After adding the above html code in the Index.cshtml, the view will have the following code in it:


<div class="row">
    <div class="col-md-12">
        <div class="panel panel-primary list-panel" id="list-panel">
            <div class="panel-heading list-panel-heading">
                <h1 class="panel-title list-panel-title">Assets</h1>
                <button type="button" class="btn btn-default btn-md" data-toggle="modal" data-target="#advancedSearchModal" id="advancedsearch-button">
                    <span class="glyphicon glyphicon-search" aria-hidden="true"></span> Advanced Search
                </button>
            </div>
            <div class="panel-body">
                <table id="assets-data-table" class="table table-striped table-bordered" style="width:100%;">
                </table>
            </div>
        </div>
    </div>
</div>

@section Scripts
{
    
<script type="text/javascript">
        var assetListVM;
        $(function () {
            assetListVM = {
                dt: null,

                init: function () {
                    dt = $('#assets-data-table').DataTable({
                        "serverSide": true,
                        "processing": true,
                        "ajax": {
                            "url": "@Url.Action("Get","Asset")"
                        },
                        "columns": [
                            { "title": "Bar Code", "data": "BarCode", "searchable": true },
                            { "title": "Manufacturer", "data": "Manufacturer", "searchable": true },
                            { "title": "Model", "data": "ModelNumber", "searchable": true },
                            { "title": "Building", "data": "Building", "searchable": true },
                            { "title": "Room No", "data": "RoomNo" },
                            { "title": "Quantity", "data": "Quantity" }
                        ],
                        "lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]],
                    });
                }
            }

            // initialize the datatables
            assetListVM.init();

        });

</script>
    
 }


Our modal popup will finally look like:


Step 4 - Adding Models with Entity Framework

The next step is to create a new Model (DTO) class named FacilitySite which will be used for getting the data from FacilitySites Lookup table which we created above with the database script. So add a new class in the Models folder in Solution Explorer named FacilitySite and following is the code for that:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;


namespace GridAdvancedSearchMVC.Models
{
    public class FacilitySite
    {
        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public System.Guid FacilitySiteID { get; set; }
        [Display(Name = "Facility-Site")]
        public string FacilityName { get; set; }
        public bool IsActive { get; set; }
        public System.Guid CreatedBy { get; set; }
        [Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public DateTime CreatedAt { get; set; }
        public System.Guid? ModifiedBy { get; set; }
        public DateTime? ModifiedAt { get; set; }
        public bool IsDeleted { get; set; }
    }
}


Right now we have just added the Model class which will hold data for FacilitySites table, but as we are using Entity Framework for Data Access purpose, we will have to let it know that there is new table added on which data operations can be performed. For that in Models folder open the IdentityModel.cs file and update the ApplicationDbContext code to include a new property of type DbSet<FacilitySite> :

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
        }

        public DbSet<Asset> Assets { get; set; }

        public DbSet<FacilitySite> FacilitySites { get; set; }

        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
    }


Update the Asset model as well by removing the FacilitySite column which was of type String before and instead add a new column named FacilitySiteId which will be foreign key in Asset table of FacilitySite table, updated Asset model should be:

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace GridAdvancedSearchMVC.Models
{
    public class Asset
    {
        public System.Guid AssetID { get; set; }
        [Display(Name = "Barcode")]
        public string Barcode { get; set; }

        [Display(Name = "Serial-Number")]
        public string SerialNumber { get; set; }
        //[Display(Name = "Facility-Site")]
        //public string FacilitySite { get; set; }
        [ForeignKey("FacilitySite")]
        public Guid FacilitySiteID { get; set; }
        [Display(Name = "PM-Guide-ID")]
        public string PMGuide { get; set; }
        [Required]
        [Display(Name = "Asset-ID")]
        public string AstID { get; set; }
        [Display(Name = "Child-Asset")]
        public string ChildAsset { get; set; }
        [Display(Name = "General-Asset-Description")]
        public string GeneralAssetDescription { get; set; }
        [Display(Name = "Secondary-Asset-Description")]
        public string SecondaryAssetDescription { get; set; }
        public int Quantity { get; set; }

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

        [Display(Name = "Model-Number")]
        public string ModelNumber { get; set; }
        [Display(Name = "Main-Location (Building)")]
        public string Building { get; set; }
        [Display(Name = "Sub-Location 1 (Floor)")]
        public string Floor { get; set; }
        [Display(Name = "Sub-Location 2 (Corridor)")]
        public string Corridor { get; set; }
        [Display(Name = "Sub-Location 3 (Room No)")]
        public string RoomNo { get; set; }
        [Display(Name = "Sub-Location 4 (MER#)")]
        public string MERNo { get; set; }
        [Display(Name = "Sub-Location 5 (Equip/System)")]
        public string EquipSystem { get; set; }
        public string Comments { get; set; }
        public bool Issued { get; set; }

        public virtual FacilitySite FacilitySite { get; set; }

    }
}


Step 4 - Creating ViewModel class

We will also need to create a ViewModel class which will be used for posting the search criteria to server side which will be controller action for performing the search. Let’s add the ViewModel then. Following is the code for the AdvancedSearchViewModel class:

using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace GridExampleMVC.Models
{
    public class AdvancedSearchViewModel
    {
        [Display(Name = "Facility-Site")]
        public Guid FacilitySite { get; set; }

        [Display(Name = "Main-Location (Building)")]
        public string Building { get; set; }

        public string Manufacturer { get; set; }

        public string Status { get; set; }

        public SelectList FacilitySiteList { get; set; }
        public SelectList BuildingList { get; set; }
        public SelectList ManufacturerList { get; set; }
        public SelectList StatusList { get; set; }

    }
}


Step 5 - Implement Advanced Search Get Action

Navigate to Controllers folder and expand it, and open the AssetController.cs file, we will add a new get action that will be used to populate the AdvancedSeachViewModel and we will be setting the SelectList properties with data from their respective data sources for populating the Dropdown List controls on the advanced search modal popup:

[HttpGet]
public ActionResult AdvancedSearch()
{
    var advancedSearchViewModel = new AdvancedSearchViewModel();

    advancedSearchViewModel.FacilitySiteList = new SelectList(DbContext.FacilitySites
                                                                    .Where(facilitySite => facilitySite.IsActive && !facilitySite.IsDeleted)
                                                                    .Select(x => new { x.FacilitySiteID, x.FacilityName }),
                                                                      "FacilitySiteID",
                                                                      "FacilityName");

   advancedSearchViewModel.BuildingList = new SelectList(DbContext.Assets
                                                                           .GroupBy(x => x.Building)
                                                                           .Where(x => x.Key != null && !x.Key.Equals(string.Empty))
                                                                           .Select(x => new { Building = x.Key }),
                                                                  "Building",
                                                                  "Building");

    advancedSearchViewModel.ManufacturerList = new SelectList(DbContext.Assets
                                                                               .GroupBy(x => x.Manufacturer)
                                                                               .Where(x => x.Key != null && !x.Key.Equals(string.Empty))
                                                                               .Select(x => new { Manufacturer = x.Key }),
                                                                      "Manufacturer",
                                                                      "Manufacturer");

   advancedSearchViewModel.StatusList = new SelectList(new List<SelectListItem>
            {
                                                                  new SelectListItem { Text="Issued",Value=bool.TrueString},
                                                                  new SelectListItem { Text="Not Issued",Value = bool.FalseString}
                                                                  },
                                                                  "Value",
                                                                  "Text"
                                                                );

    return View("_AdvancedSearchPartial", advancedSearchViewModel);
}


Step 6 - Advanced Search Post Action Implementation

Our AdvancedSearch post action will be almost same implementation wise as was the implementation of Search action for Server Side Sort, Filter and Paging one, but there will be small change in action signatures for AdvancedSearch, it will now take 2 parameter which is quite obvious, one for maintain the DataTables state which was already there before as well and the new one will be the instance of AdvancedSearchViewModel class which will have the state of controls of Advanced Search Modal popup.

We need to update the SearchAssets private method which we created in the previous post about Grid View Server Side Processing, add the advanced searching database logic in this method, so this method will not take another parameter which is we know instance of AdvancedSearchViewModel:

private IQueryable<Asset> SearchAssets(IDataTablesRequest requestModel, AdvancedSearchViewModel searchViewModel, IQueryable<Asset> query)
        {

            // Apply filters
            if (requestModel.Search.Value != string.Empty)
            {
                var value = requestModel.Search.Value.Trim();
                query = query.Where(p => p.Barcode.Contains(value) ||
                                         p.Manufacturer.Contains(value) ||
                                         p.ModelNumber.Contains(value) ||
                                         p.Building.Contains(value)
                                   );
            }

            /***** Advanced Search Starts ******/
            if (searchViewModel.FacilitySite != Guid.Empty)
                query = query.Where(x => x.FacilitySiteID == searchViewModel.FacilitySite);

            if (searchViewModel.Building != null)
                query = query.Where(x => x.Building == searchViewModel.Building);

            if (searchViewModel.Manufacturer != null)
                query = query.Where(x => x.Manufacturer == searchViewModel.Manufacturer);

            if (searchViewModel.Status != null)
            {
                bool Issued = bool.Parse(searchViewModel.Status);
                query = query.Where(x => x.Issued == Issued);
            }

            /***** Advanced Search Ends ******/

            var filteredCount = query.Count();

            // Sort
            var sortedColumns = requestModel.Columns.GetSortedColumns();
            var orderByString = String.Empty;

            foreach (var column in sortedColumns)
            {
                orderByString += orderByString != String.Empty ? "," : "";
                orderByString += (column.Data) + (column.SortDirection == Column.OrderDirection.Ascendant ? " asc" : " desc");
            }

            query = query.OrderBy(orderByString == string.Empty ? "BarCode asc" : orderByString);

            return query;

        }


Step 7 - Updating DataTables Post Call Action

Now update the action as well which is called for handles the grid server side processing to accept the advanced search parameter as well and pass them to the SearchAssets method for more granular filtering, here is the updated code of the action:

public ActionResult Get([ModelBinder(typeof(DataTablesBinder))] IDataTablesRequest requestModel, AdvancedSearchViewModel searchViewModel)
        {
            IQueryable<Asset> query = DbContext.Assets;
            var totalCount = query.Count();

            // searching and sorting
            query = SearchAssets(requestModel, searchViewModel,query);
            var filteredCount = query.Count();

            // Paging
            query = query.Skip(requestModel.Start).Take(requestModel.Length);

            

            var data = query.Select(asset => new
            {
                AssetID = asset.AssetID,
                BarCode = asset.Barcode,
                Manufacturer = asset.Manufacturer,
                ModelNumber = asset.ModelNumber,
                Building = asset.Building,
                RoomNo = asset.RoomNo,
                Quantity = asset.Quantity
            }).ToList();

            return Json(new DataTablesResponse(requestModel.Draw, data, filteredCount, totalCount), JsonRequestBehavior.AllowGet);

        }


Step 7 - Implement Modal Popup View for Advanced Search

Now we will move towards the view part, as you can see we have last four properties of type SelectList which are there because we will have few dropdown list controls in the advanced form which user will be selecting from pre-populated values for searching the records.

The data-target="#advancedSearchModal" which we added in the html of Index.cshtml view will be referenced in this partial view, so create a new partial view under Views >> Asset named _AdvancedSearchPartial, for that right click the Asset folder under View and navigate to Add Item, then from next Menu select MVC 5 Partial Page (Razor) :

Type the partial view name which would be _AdvancedSearchPartial in this case and Click the OK button:


And then open the file _AdvancedSearchPartial.cshtml and add the html in the partial view that will be displayed as modal popup when the user will click the Advanced Search button that we created in the Index.cshtml view, following the code of the advanced search partial view:

@model TA_UM.ViewModels.AdvancedSearchViewModel
@{
    Layout = null;
}

<div class="modal fade" id="advancedSearchModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" data-backdrop="static">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">Advanced Search</h4>
            </div>
            @using (Html.BeginForm("Get", "Asset", FormMethod.Get, new { id = "frmAdvancedSearch", @class = "form-horizontal", role = "form" }))
            {
                <div class="modal-body">
                    <div class="form-horizontal">
                        <hr />
                        <div class="form-group">
                            @Html.LabelFor(model => model.FacilitySite, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">
                                <div class="dropdown">
                                    @Html.DropDownListFor(model => model.FacilitySite, Model.FacilitySiteList, "Any", new { @class = "form-control" })
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Building, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">
                                <div class="dropdown">
                                    @Html.DropDownListFor(model => model.Building, Model.BuildingList, "Any", new { @class = "form-control" })
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Manufacturer, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">
                                <div class="dropdown">
                                    @Html.DropDownListFor(model => model.Manufacturer, Model.ManufacturerList, "Any", new { @class = "form-control" })
                                </div>
                            </div>
                        </div>

                        <div class="form-group">
                            @Html.LabelFor(model => model.Status, htmlAttributes: new { @class = "control-label col-md-3" })
                            <div class="col-md-8">
                                <div class="dropdown">
                                    @Html.DropDownListFor(model => model.Status, Model.StatusList, "Both", new { @class = "form-control" })
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="modal-footer">
                    <button id="btnPerformAdvancedSearch" type="button" class="btn btn-default btn-success" data-dismiss="modal">Search</button>
                    <button id="btnCancel" type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                </div>
            }
        </div>
    </div>
</div>


Final Step - Pass Advanced Search Parameters from View in POST

Finally open the Index.cshtml located in Views >> Asset and call the AdvancedSearch get action before the @section Scripts start for adding the Advanced Search Modal popup html in the browser which will be displayed when button is triggered, another thing to note is we have not specified anywhere about how the dropdown selected values will be posted with DataTables server side processing in the same action, though we have added the parameter in action but we haven't changed anything specific to that in View, we will have to update the jquery datatables initialization code for that, and specify the values for posting to the AdvancedSearchViewModel using data property for which we would have to define the property, so add the following code just after the line where we are specifying url for datatable which is "url": "@Url.Action("Get","Asset")", and after adding that final Index view code should be :

"data": function (data) {

         data.FacilitySite = $("#FacilitySite").val();
         data.Building = $("#Building").val();
         data.Manufacturer = $("#Manufacturer").val();
         data.Status = $("#Status").val();
       }


Our Index View would contain following code:

<div class="row">
    <div class="col-md-12">
        <div class="panel panel-primary list-panel" id="list-panel">
            <div class="panel-heading list-panel-heading">
                <h1 class="panel-title list-panel-title">Assets</h1>
                <button type="button" class="btn btn-default btn-md" data-toggle="modal" data-target="#advancedSearchModal" id="advancedsearch-button">
                    <span class="glyphicon glyphicon-search" aria-hidden="true"></span> Advanced Search
                </button>
            </div>
            <div class="panel-body">
                <table id="assets-data-table" class="table table-striped table-bordered" style="width:100%;">
                </table>
            </div>
        </div>
    </div>
</div>

@Html.Action("AdvancedSearch")

@section Scripts
{
    
<script type="text/javascript">
        var assetListVM;
        $(function () {
            assetListVM = {
                dt: null,

                init: function () {
                    dt = $('#assets-data-table').DataTable({
                        "serverSide": true,
                        "processing": true,
                        "ajax": {
                            "url": "@Url.Action("Get","Asset")",
                            "data": function (data) {

                                data.FacilitySite = $("#FacilitySite").val();
                                data.Building = $("#Building").val();
                                data.Manufacturer = $("#Manufacturer").val();
                                data.Status = $("#Status").val();
                            }
                        },
                        "columns": [
                            { "title": "Bar Code", "data": "BarCode", "searchable": true },
                            { "title": "Manufacturer", "data": "Manufacturer", "searchable": true },
                            { "title": "Model", "data": "ModelNumber", "searchable": true },
                            { "title": "Building", "data": "Building", "searchable": true },
                            { "title": "Room No", "data": "RoomNo" },
                            { "title": "Quantity", "data": "Quantity" }
                        ],
                        "lengthMenu": [[10, 25, 50, 100], [10, 25, 50, 100]],
                    });
                },

                refresh: function () {
                    dt.ajax.reload();
                }
            }

            // Advanced Search Modal Search button click handler 
            $('#btnPerformAdvancedSearch').on("click", assetListVM.refresh);
            }

            // initialize the datatables
            assetListVM.init();

        });

</script>
    
 }


You can see above we have add a new function in our datatable view model named refresh, whose purpose is to reload the datatable from server side using the model of DataTables, we have wrote the event handler for Advanced Search Popup button that when it is pressed it causes the datatable to be reloaded and in the ajax call of it we are passing the user selected search criteria from advanced search view as well using that data property of jQuery datatables.

Now build the project, and run it in browse to see the working server side Advanced Search using JQuery datatables and with server side filtering, paging, and sorting as well in action.

Source Code

You can download the source code from MSDN code samples gallery here