How to prevent Grid to show wrong data after validation?

1 Answer 624 Views
Grid
CHIHPEI
Top achievements
Rank 2
Iron
Iron
Iron
CHIHPEI asked on 14 Dec 2021, 02:21 AM

Scenario

A grid showing datas, with an add new record function,

checking the data submitted from client with custom validation,

if the result isn't valid, directly return View("gridPage").

However, the grid still shows the data with the one client had just submiitted, which isn't valid.

How can I prevent from this situation?

Grid Code

    <div>
        @(Html.Kendo().Grid<Sinotech.AntiSeismic.Digitalization.Web.Controllers.ViewModels.AgentViewModel>()
        .Name("grid")
        .Columns(columns =>
        {
            columns.Bound(c => c.Code).Title("經銷商代號");
            columns.Bound(c => c.Name).Title("經銷商名稱");
            columns.Bound(c => c.TaxIdnum).Title("統一編號");
            columns.Bound(c => c.PersonInCharge).Title("負責人姓名");
            columns.Bound(c => c.TelNum).Title("電話");
            columns.Bound(c => c.Addr).Title("地址");
            columns.Bound(c => c.MailAddr).Title("通訊地址");
            columns.Bound(c => c.BankCode).Title("銀行代號");
            columns.Bound(c => c.BankName).Title("銀行別");
            columns.Bound(c => c.BankAccount).Title("銀行帳號");
            columns.Bound(c => c.BankAccountName).Title("銀行帳號戶名");
            columns.Bound(c => c.Sales).Title("業務姓名");
            columns.Bound(c => c.SalesTelNum).Title("業務電話");
            columns.Bound(c => c.Accountant).Title("會計姓名");
            columns.Bound(c => c.AccountantTelNum).Title("會計電話");
            columns.Command(c => c.Edit());
            columns.Command(c => c.Destroy());
        })
        .Pageable()
        .Scrollable()
        .Sortable()
        .Editable(editable => editable.Mode(GridEditMode.PopUp))
        .DataSource(dataSource => dataSource
            .Ajax()
            .PageSize(20)
            .Create(create => create.Action("AddAgent", "Vendor"))
            .Read(read => read.Action("GetAgentData", "Vendor", new { Id = Id }))
            .Update(update => update.Action("UpdateAgent", "Vendor"))
            .Destroy(destroy => destroy.Action("DeleteAgent", "Vendor"))
            .Model(m => {
                m.Id(id => id.Id);
                m.Field(f => f.Id).Editable(false);
            })
        )
        .ToolBar(tools =>
        {
            tools.Create();
        })
        )
    </div>

Thought that the DataSource.Read function would refresh the data on grid,

since I've just return back to the page after validation error, and it automatically runs through the DataSource.Read.

But it's not working

CHIHPEI
Top achievements
Rank 2
Iron
Iron
Iron
commented on 14 Dec 2021, 02:26 AM

An extension question had poped up after I submitted this question,

How can the grid automatically refresh after Creat, Update operations?

CHIHPEI
Top achievements
Rank 2
Iron
Iron
Iron
commented on 16 Dec 2021, 03:40 AM

Update

Finding out after the create function failure of the grid,

the processs didn't run through the grid code section as I thought before,

So it did not automatically refresh datas on the grid,

How could I achieve this ?

Stoyan
Telerik team
commented on 16 Dec 2021, 04:42 PM | edited

Hi Chihpei,

Thank you for sharing the configuration of your Grid, but it is difficult for me to understand the scenario at hand without knowing the custom validation you apply. Is it in any way similar to the example in the Custom Validator Demo of the Grid?

That said when a Read, Create, Update or Destroy operations are done the Grid refreshes - meaning that its DataBound event is thrown and all of its data rebinds to the DOM.

Therefore at this point I suppose that the reported issues could be related to the way the data values are added to the DataSourceResult returned by Controller Action methods.

public ActionResult Update([DataSourceRequest] DataSourceRequest request, ProductViewModel product)
{
         //validate product based on the ProductViewModel DataAnnotations
         if (product != null && ModelState.IsValid)
            {
                 //persist the updated data item
                productService.Update(product);
            }
         return Json(new[] { product }.ToDataSourceResult(request, ModelState));
}     


Please consider sharing additional information about the scenario at hand. Ideally a runnable project with dummy data that showcases the observed behaviors will enable us to research the matter in more depth.

Thank you for your cooperation.

 

CHIHPEI
Top achievements
Rank 2
Iron
Iron
Iron
commented on 20 Dec 2021, 08:15 AM

Hi Stoyan,

My scenario is quite complicated to provide a runnable project since I'm trying it in a three tier structure,
thought it would be confusing.

What I can say is that I'm using a common three tier structure( with Controller / Service / Repository Layer),
the DataAnnotation works fine for me, 
but I'm adding custom validations in the Service layer,
I'll try to provide some code snippets

Once the user adds a new record, it will lead the process into "AddCustomer" action,
In this action will do the custom validation with the "IsInsertValid" function from service layer,
this function is checking wether if the data add from the user was already in DB,
If so,  the service layer will throw an exception back to controller.
Then, it will directly return back to View.
And here is the issue,
the picture is a Grid sample,
Grid Columns
the yellow row is the one that user add, which was a data already in DB,
so after the vlaidation from the Service layer and went back to the view,
the Grid still adds the new data on grid,
How can I solve this problem, since the grid hasn't automatically refresh with the DataSource.Read() function after the validation ?

customer.cshtml (Telerik UI Grid)

<div>
        @(Html.Kendo().Grid<Sinotech.AntiSeismic.Digitalization.Web.Controllers.ViewModels.CustomerViewModel>()
        .Name("grid")
        .Columns(columns =>
        {
            columns.Bound(c => c.Code).Title("Code");
            columns.Bound(c => c.Name).Title("Name");
            columns.Bound(c => c.Sex).Title("Sex");
            columns.Bound(c => c.PhoneNum).Title("PhoneNum");
            columns.Command(c => c.Edit());
            columns.Command(c => c.Destroy());
        })
        .Pageable()
        .Scrollable()
        .Sortable()
        .Editable(editable => editable.Mode(GridEditMode.PopUp))
        .DataSource(dataSource => dataSource
            .Create(create => create.Action("AddCustomer", "Customer"))
            .Read(read => read.Action("GetCustomer", "Customer", new { Id = Id }))
            .Update(update => update.Action("UpdateCustomer", "Customer"))
            .Destroy(destroy => destroy.Action("DeleteCustomer", "Customer"))
            .Model(m => {
                m.Id(id => id.Id);
                m.Field(f => f.Id).Editable(false);
            })
        )
        .ToolBar(tools =>
        {

            tools.Create();
        })

        )
</div>

Controller (UI Layer)

public async Task<IActionResult> AddCustomer(CustomerViewModel customerViewModel)
        {
            try
            {
                await this._customerService.IsInsertValid(customerViewModel.Code, customerViewModel.Name);
            }
            catch(Exception ex)
            {
                TempData["error"] = ex.Message;

                return View("Customer");
            }

            try
            {
                customerDto agentDto = this._mapper.Map<AgentDto>(customerViewModel);

                string msg = await this._customerService.Add(customerDto);

                TempData["success"] = msg;
            }
            catch(Exception ex)
            {
                TempData["error"] = ex.Message;
            }

            return View("customer");

        }

Service Layer (Custom Validations)

public async Task<string> IsInsertValid(string code, string name)
        {
            var obj = await this._unitOfWork.Repository<Customer>().GetAsync(x => x.Code == code && x.Name == name);
            
            if(obj != null)
            {
                throw new System.Exception("Same data already exists");
            }

            return await Task.FromResult("Validated");
        }

1 Answer, 1 is accepted

Sort by
1
Accepted
Stoyan
Telerik team
answered on 21 Dec 2021, 01:56 PM | edited on 21 Dec 2021, 01:59 PM

Hi Chihpei,

Thank you for sharing those snippets, they allowed me to get a better understanding of the issue.

Based on the Controller code you have shared the Grid shouldn't show the invalid row entry, unless it is saved to the dataBase. However I'd like to suggest an alternative approach similar to the one in the Handling Server-Side Validation Errors In Your Kendo UI Grid blog post.

  1. In the Controller's Create Action method check, if the posted dataItem already exists in the database
  2. If it does, add a ModelState error
  3. Then instead of returning a View, return Json that takes DataSourceResult as parameter. If you pass the ModelState to the DataSourceResult, the response will contain an error, instead of updating the Grid.
     public ActionResult Orders_Create([DataSourceRequest] DataSourceRequest request,
               OrderViewModel order)
            {
                if (data.Any(x => x.OrderID == order.OrderID))
                {
                    ModelState.AddModelError("DataBase Error", "Row already exists");
                }
                var results = new List<OrderViewModel>();
                if (order != null && ModelState.IsValid)
                {
    //persist the data results.Add(order); } return Json(results.ToDataSourceResult(request, ModelState)); }

Please note that not returning an entire View but just a Json response that contains Grid DataItems avoids forcing the entire page to reload when a newly created row of the Grid is saved.

Next to show the error on the client, subscribe to the Error event of the Grid's DataSource:

 .DataSource(dataSource => dataSource
                                .Ajax()
                                .PageSize(20)
                                .Model(model => {
                                    model.Id(p => p.OrderID);
                                })
                                .Create(create => create.Action("Orders_Create", "Grid"))
                                .Destroy(destroy => destroy.Action("Orders_Destroy", "Grid"))
                                .Read(read => read.Action("Orders_Read", "Grid"))
                                .Update(read => read.Action("Orders_Update", "Grid"))
                                .Events(ev => ev.Error("error_handler"))
                             )

 

  1. Then to display the error message in a dialog window, initialize a new Dialog.
        @(Html.Kendo().Dialog()
                      .Name("error")
                      .Title("Server Error")
                      .Content("<p>some message<p>")
                      .Width(400)
                      .Visible(false)
                      .Modal(false)
                      .Actions(actions =>
                      {
                          actions.Add().Text("Ok").Primary(true);
                      })
        )
  2. In the handler of the Error event
    1. Get the Grid and cancel the pending change with its cancelChanges method
    2. Get the PopUp Editor of the Grid and close it
    3. Get the error message, set it as the content of the Dialog and open it
          function error_handler(e) {
      
              if (e.errors) {
                  //1
                  var grid = $("#grid").data("kendoGrid");
                  grid.dataSource.cancelChanges();
                  //2
                  var window = grid.editable.element.data("kendoWindow");
                  window.close();
                  //3
                  console.log("Errors!!!")
                  var message = "Errors:\n";
                  $.each(e.errors, function (key, value) {
                      if ('errors' in value) {
                          $.each(value.errors, function () {
                              message += this + "\n";
                          });
                      }
                  });
                  var errorDialog = $("#error").data("kendoDialog");
                  errorDialog.content(message);
                  errorDialog.open()
              }
          }

For your convenience I am attaching a sample project that showcases the above. I hope this approach is useful in the scenario at hand.

Regards,
Stoyan
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

CHIHPEI
Top achievements
Rank 2
Iron
Iron
Iron
commented on 22 Dec 2021, 06:47 AM

Hi Stoyan,

I've checked out the solution and made a combination with my project,
It works perfectly! 
This sample project really gave me great lesson,
again thanks for the quick response and big help!

Regards,
Chihpei
Tags
Grid
Asked by
CHIHPEI
Top achievements
Rank 2
Iron
Iron
Iron
Answers by
Stoyan
Telerik team
Share this question
or