Wednesday, 2 July 2014

use of WebGrid in MVC

  1. Let's get started by creating a sample application by selecting ASP.NET Web Application template, naming project as DemoMVCApp and click ok.

    Project Template
     
  2. Now select MVC template as shown below and click ok.

    MVC Selected

    Visual Studio will create a ASP.NET MVC project structure for us. We will modify it in next steps to implement required functionalities.
     
  3. Under Models folder, add a class called as Customer and write following code in it as shown below:
        public class Customer
        {
            public int CustomerId { get; set; }
            [Required]
            [DisplayName("Name")]
            public string CustomerName { get; set; }
            
            [Required]
            [DisplayName("Address")]        
            public string CustomerAddress { get; set; }
        }
     }
    
  4. Under Controller folder, Add a controller named as CustomerController. This controller will have action methods for Get, Post, Put and Delete action for Customer model. In CustomerController class write the code as shown below:
    readonly string customerServiceUri = "http://localhost:1750/CustomerService.svc/";
    // customerServiceUri is a global variable for this customercontroller, having the path of CustomerService. 
    // Please change URI path if service is hosed at some different URI in your case. 
            public ActionResult Index()
            {
                List<Customer> customerList = new List<Customer>();
                using (WebClient webClient = new WebClient())
                {
                    string dwml;
                    dwml = webClient.DownloadString(customerUri + "GetAllCustomers");
                    customerList = JsonConvert.DeserializeObjectAsync<List<Customer>>(dwml).Result;
                }
                return View(customerList);
            }
    
    We just implemented a action method called Index which makes call to WCF services, deserialize the recieved data and returns the list of customers.
     

Displaying Data into WebGrid control

  1. Add a view by right clicking on Index method in CustomerController and naming it as Index. It will add aIndex.cshtml file inside Views > Customer folder. Write the following code in Index view. This code will create a WebGrid and data will be binded to this grid. We are giving references to script and css files directly. (we are not focusing on bundling and minifications feature here.)

    Note: All custom css is written in index.css under Content folder and custom script is written in index.js file under Scripts folder, Please create those files in corresponding folders, code accordingly and give the reference in Index page. Caution: Put reference to index.js at the end of the page.
    @model  IEnumerable<DemoMVCWebApp.Models.Customer>
    <script src="~/Scripts/jquery-1.10.2.min.js"></script>
    <link href="~/Content/index.css" rel="stylesheet" />
    @{
        var grid = new WebGrid(Model, canPage: true, rowsPerPage: 5,
        selectionFieldName: "selectedRow");
        grid.Pager(WebGridPagerModes.NextPrevious);}
    <div id="gridContent">
        @grid.GetHtml(tableStyle: "webGrid",
                headerStyle: "header",
                alternatingRowStyle: "alt",
                selectedRowStyle: "select",
                rowStyle: "webgrid-row-style",
                columns: grid.Columns(
                grid.Column("CustomerId", "Customer Id", @<text> <span id="spnCustomerId">@item.CustomerId</span></text>,
                           canSort: false),
                grid.Column("CustomerName", "Customer Name", format: @<text>
                                     <span id="spnCustomerName" class="display-mode">@item.CustomerName</span>
                                     <input type="text" id="CustomerName" value="@item.CustomerName" class="edit-mode"/></text>,
                           canSort: false),
                           
                grid.Column("CustomerAddress", "Customer Address", format: @<text>
                                     <span id="spnCustomerAddress" class="display-mode">@item.CustomerAddress</span>
                                     <input type="text" id="CustomerAddress" value="@item.CustomerAddress" class="edit-mode"/></text>,
                           canSort: false),
                grid.Column("Action", format: @<text>
                                    <img src="~/Content/images/edit.jpg" title="Edit Customer" id="btnEdit" class="edit-button"/>
                                    <img src="~/Content/images/delete.jpg" title="Delete Customer" class="delete-customer"/>
                                     </text>, style: "width:220px", canSort: false)
                          ))
    </div>
    
  2. Under App_Start folder, in RoutConfig.cs file, set default route as shown below
    defaults: new { controller = "Customer", action = "Index", id = UrlParameter.Optional }
    
  3. Run the application, hope you will get all customers in WebGrid as shown below:

    Output for Customer page
     

Adding inline Edit operation

For editing any customer, we will use inline editing into WebGrid. As you have seen in fourth column of WebGrid, we are using images - one for edit and another for delete operation.

In this section, we will write some code so that on edit image click, data in corresponding row will be editable. Along with that, instead of edit image, save image will appear at same place with appropriate tool tip. So edit functionality looks like:

Project Template

while clicking on save image, edited customer object will go to EditCustomer action method of CustomerController. Then that action method will call WCF service and service will save update customer in database using repository.

Programmatically, in WebGrid each row is having span tag and an input tag with different CSS classes. On page load all text boxes which are having CSS class “edit-mode” will be hidden. Another thing we are considering is that, at a time only one row should be editable. To enable inline editing, on edit button click we are toggling the CSS classes because now span tag should be hidden and textboxe should appear in corresponding row.

If the edit fuctionality is not clear yet, please run the application and see the behaviour of edit button then look into JavaScript code written in index.js file. Or you just keep following the steps below, things will be obivious as you type and understand the code.
  1. Open index.js file under Scripts folder and write following code. This code hides the input boxes (having CSS class "edit-mode") while page load which are not needed in viewing mode.
    $(document).ready(function () {
    $('.edit-mode').hide();
    });
    
  2. Further we need to write JavaScript to handle click on Edit button. Write following code in index.js. Hope the comments written with code are enough to expain it.
                    $('.edit-button').on("click", function () {
        // allEditableRow : will have all rows which are editable (where save button is visible).
        var allEditableRow = $("[src='/Content/images/save.jpg']");
        
        // make row editable only if no other row is editable as of now by toggle
        // otherwise go to else and save data of editable row first or alert to save that"
        if (allEditableRow.length == 0) {
            var tr = $(this).parents('tr:first');
            tr.find('.edit-mode,.display-mode').toggle();
            var imageSource = $(this).attr('src');
            if (imageSource == '/Content/images/edit.jpg') {
                $(this).attr('src', '/Content/images/save.jpg');
                $(this).attr('title', 'Save Customer');
            }
        }
        else {
            // making sure that only one row is editable and save button of editable row is clicked
            if (allEditableRow.length == 1 && $(this).attr('src') == '/Content/images/save.jpg')
            {
                          
                var selectedId = $(this).parents('tr').find('td:nth-child(1)').text().trim();
                var selectedName = $(this).parents('tr').find('#CustomerName').val();
                var selectedAddless = $(this).parents('tr').find('#CustomerAddress').val();
                // create object with updated values
                var customerModel =
                    {
                        "CustomerId": selectedId,
                        "CustomerName": selectedName,
                        "CustomerAddress": selectedAddless
                    };
                $.ajax({
                    url: '/Customer/EditCustomer',
                    type: 'POST',
                    contentType: 'application/json; charset=utf-8',
                    data: JSON.stringify(customerModel)
                });
                alert("Your data is saved");
                window.location.reload(true);
                $(this).attr('src', '/Content/images/edit.jpg');
            }
            else {
                alert("Please finish the editing of current editable row. Save first");
            }
        }
    
  3. In CustomerController, we need to implement EditCustomer action method as shown below.
    public void EditCustomer(Customer customer)
            {
                using (WebClient wc = new WebClient())
                {
                    MemoryStream ms = new MemoryStream();
                    DataContractJsonSerializer serializerToUplaod = new DataContractJsonSerializer(typeof(Customer));
                    serializerToUplaod.WriteObject(ms, customer);
                    wc.Headers["Content-type"] = "application/json";
                    wc.UploadData(customerServiceUri + "EditCustomer", "PUT", ms.ToArray());
                }
            }
    
  4. Run the application, edit any customer then save it, updated data will be saved into database and reflected back in WebGrid.

Adding inline Delete operation

To delete a record, user will click on delete image in corresponding row. User will get a confirmation popup. If user confirms to delete, corresponding row data would be deleted. The confirmation popup (on delete button click) is shown in below screen shot.

Delete Operation

we will write the code for delete fuctioanlity:
  1. Open index.js file in Scripts folder and write following code:
    $('.delete-customer').click(function () {
        var selectedId = $(this).parents('tr').find('td:nth-child(1)').text().trim();
        var selectedName = $(this).parents('tr').find('#CustomerName').val();
        var message = "Please confirm delete for Customer ID " + selectedId + ", and Name: " + selectedName + " ? \nClick 'OK' to delete otherwise 'Cancel'.";
        if (confirm(message) == true) {
            selectedId = $(this).parents('tr:first').children('td:first').text();
            $.ajax({
                url: '/Customer/DeleteCustomer',
                data: { id: selectedId },
                type: 'POST',
                success: function () {
                    window.location.reload();
                },
                error: function (xhr) {
                    alert("something seems wrong");
                }
            });
        }
        else
        {
            alert("Delete is Canceled!");
        }
    });
    
  2. In CustomerController class write following code. This action method will fire for delete operation.
    public void DeleteCustomer(int id)
            {
                using (WebClient wc = new WebClient())
                {
                    MemoryStream ms = new MemoryStream();
                    DataContractJsonSerializer serializerToUplaod = new DataContractJsonSerializer(typeof(Customer));
                    serializerToUplaod.WriteObject(ms, id);
                    wc.Headers["Content-type"] = "application/json";
                    wc.UploadData(customerServiceUri + "DeleteCustomer", "DELETE", ms.ToArray());
                }
            }
    
  3. Run the application, try to delete any record from webgrid, it should work as expected.

Adding Create operation

To implement this functionality, we will use partial view. While user click on New Customer button, ajax call goes toGetCustomerPV action method of CustomerController and gets the partial view. A form will apprear and user will fill data and click Save button inside that form. Then call will go to CreateCustomer action method ofCustomerController to save new customer. We are not using extensive validation here but both fields are mandatory to fill. If you click on save button without filling any data, error meesage will appear as shown in below screen show:

HTML Layout of Views

Let's follow below steps to implement this functionality:
  1. Write following line of code in CustomerController.
    public ActionResult GetCustomerPV()
            {
                return PartialView("CreateCustomerPV");
            }
    
  2. Create a partial view by right clicking on GetCustomerPV action method and naming it asCreateCustomerPV. Open newly created CreateCustomerPV partial view and write following code:
    @model DemoMVCWebApp.Models.Customer
    @*Below scripts files are used to provide the client side validation*@
    <script src="~/Scripts/jquery.validate.min.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.js"></script>
    <div class="createDiv">
        <h4>Please fill below Customer Form:</h4>
        <br />
        @using (Html.BeginForm("CreateCustomer", "Customer"))
        {
            @Html.AntiForgeryToken()
            <div class="form-horizontal">
                @Html.ValidationSummary(true)
                <div class="form-group">
                    @Html.LabelFor(model => model.CustomerName, new { @class = "control-label col-md-2" })
                    <div class="col-md-10">
                        @Html.EditorFor(model => model.CustomerName)
                        @Html.ValidationMessageFor(model => model.CustomerName)
                    </div>
                </div>
                <div class="form-group">
                    @Html.LabelFor(model => model.CustomerAddress, new { @class = "control-label col-md-2" })
                    <div class="col-md-10">
                        @Html.EditorFor(model => model.CustomerAddress)
                        @Html.ValidationMessageFor(model => model.CustomerAddress)
                    </div>
                </div>
                <div class="form-group">
                    <div class="col-md-offset-2 col-md-10">
                        <input type="submit" value="Save" class="btnCustomerPV" />
                    </div>
                </div>
            </div>
        }
    </div>
    
  3. Now we need to add a button called New Customer on main page and on click that button partial view will be called. Add following code just below the div tag in Index.cshtm file (Index View). We are adding a "div" tag where the partial view will be added.
    <input type="button" value="New Customer" class="btnCustomerPV" />
    <div id="placeHolderCustomerPV">
    </div>
    
    Now open index.js file, add following lines of code which will call to GetCustomerPV action method.
    $('.btnCustomerPV').click(function () {
        $('.btnCustomerPV').hide();
        $.ajax({
            // Call CreatePartialView action method
            url: '/Customer/GetCustomerPV',
            type: 'Get',
            success: function (data) {
                $("#placeHolderCustomerPV").empty().append(data);
            },
            error: function () {
                alert("something seems wrong");
            }
        });
    });
    
  4. In partial view we have written following code, so on submit button of partial view, call will go toCreateCustomer action method of CustomerController.
    @using (Html.BeginForm("CreateCustomer", "Customer"))
    
    In the controller write following lines of code to add CreateCustomer action method:
    public ActionResult CreateCustomer(Customer customer)
            {
                using (WebClient wc = new WebClient())
                {
                    MemoryStream ms = new MemoryStream();
                    DataContractJsonSerializer serializerToUplaod = new DataContractJsonSerializer(typeof(Customer));
                    serializerToUplaod.WriteObject(ms, customer);
                    wc.Headers["Content-type"] = "application/json";
                    wc.UploadData(customerServiceUri + "AddCustomer", "POST", ms.ToArray());
                }
                int pageToShow;
                int totalCustomers;
                List<Customer> customerList = new List<Customer>();
                using (WebClient webClient = new WebClient())
                {
                    string customerCount;
                    customerCount = webClient.DownloadString(customerServiceUri + "GetCustomersCount");
                    totalCustomers = Convert.ToInt32(customerCount);
                }
                if (totalCustomers % 5 != 0)
                    pageToShow = (totalCustomers / 5) + 1;
                else pageToShow = totalCustomers / 5;
              
                return Redirect(HttpRuntime.AppDomainAppVirtualPath + "?page=" + pageToShow);
            }
    
    Here after saving new the customer, we are redirecting to appropriate index to show the recently added data.
     
  5. Now run the application and click on New Customer button fill data into form then click on save button. Data will be saved and as we have written the logic for redirection, it will be displayed in the last row in WebGrid control.

No comments:

Post a Comment