简体   繁体   English

对 Kendo 网格中新创建的行的服务器端验证(单元内、批处理模式)

[英]Server-side validation for newly created rows in a Kendo grid (in-cell, batch editing mode)

I have a Kendo Grid with InCell editing that sends created/updated records to the server in batches ( .Batch(true) ).我有一个带有InCell编辑的 Kendo Grid,它可以将创建/更新的记录分批发送到服务器( .Batch(true) )。

Here's a pared-down example of the grid definition:这是网格定义的精简示例:

@(Html.Kendo().Grid<TagEditingGridViewModel>()
    .Name("...")
    .Columns(c =>
    {
        c.Bound(e => e.TagText);
        c.Bound(e => e.Description);
    })
    .Editable(e => e.Mode(GridEditMode.InCell))
    .DataSource(d => d
        .Ajax()
        .Batch(true)
        .Model(m => m.Id(e => e.ID))
        //.Events(e => e.Error("...").RequestEnd("..."))
        // Read, Update, Create actions
    )
)

The grid handles Tag items, which must have a unique, non-empty value in the TagText property.网格处理Tag项,这些项在TagText属性中必须具有唯一的非空值。

Here's the grid's model class, with its validation attributes:这是网格的 model class,及其验证属性:

public class TagEditingGridViewModel
{
    public int ID { get; set; }

    [Required(AllowEmptyStrings = false, ErrorMessage = "A tag text is required.")]
    [StringLength(50, ErrorMessage = "Text cannot be longer than 50 characters")]
    public string TagText { get; set; }

    [StringLength(250, ErrorMessage = "Description cannot be longer than 250 characters")]
    public string Description { get; set; }
}

The [StringLength] attribute triggers client-side validation, as does the [Required] attribute when the field is empty. [StringLength]属性触发客户端验证,当字段为空时[Required]属性也是如此。 But server-side validation is still needed when the TagText field is whitespace only, and to check uniqueness.但当TagText字段仅为空白时,仍然需要服务器端验证,并检查唯一性。

This server-side validation needs to take place both on updating an existing record and on creating a new record.此服务器端验证需要在更新现有记录和创建新记录时进行。 That's where the problem begins.这就是问题的开始。 For an existing record, the model has an ID in the database that can be used to find the corresponding row in the grid.对于现有记录,model 在数据库中有一个ID ,可用于在网格中查找相应的行。 But a new record that does not pass validation does not get an ID in the database and does not have a (unique) ID in the grid rows - it is set to 0 , so you can't identify a row from that property.但是未通过验证的记录不会在数据库中获得ID ,并且在网格行中没有(唯一) ID - 它设置为0 ,因此您无法从该属性中识别行。

In this post in the Kendo forums, a Telerik employee has posted a solution to showing a server-side validation error in a Kendo grid with InCell and batch editing.在 Kendo 论坛的这篇帖子中,一名 Telerik 员工发布了一个解决方案,用于在使用InCell和批量编辑的 Kendo 网格中显示服务器端验证错误。 Unfortunately, they only show the solution on update, not on create.不幸的是,他们只在更新时显示解决方案,而不是在创建时显示。

In their suggested solution, they use the onError event of the grid's DataSource, where they find the the row in the grid using the model's ID field.在他们建议的解决方案中,他们使用网格数据源的onError事件,他们在其中使用模型的 ID 字段找到网格中的行。

// Controller:
currentErrors.Add(new Error() { id = model.LookupId, errors = errorMessages });

// JavaScript:
var item = dataSource.get(error.id);
var row = grid.table.find("tr[data-uid='" + item.uid + "']");

In my create action, I loop through the incoming items and set the key in the model state dictionary to " models[i].TagText ".在我的创建操作中,我遍历传入的项目并将 model state 字典中的键设置为“ models[i].TagText ”。 When the TagText is a string that only contains whitespace, the [Required] attribute catches this server-side, and adds a model state error in that same format.TagText是仅包含空格的字符串时, [Required]属性会捕获此服务器端,并以相同的格式添加 model state 错误。

// items: List<TagEditingGridViewModel>

for (int i = 0; i < items.Count(); i++)
{
    // check for uniqueness of TagText ...
    
    // this is the way the validation attributes do it
    ModelState.AddModelError($"models[{i}].TagText", "Tag text must be unique.");
}

return Json(items.ToDataSourceResult(request, ModelState), JsonRequestBehavior.AllowGet);

In my grid, I can add a handler to the RequestEnd event, which has access to the request type (read, create, or update), the data sent back from the server (which would be items ), and any model state errors.在我的网格中,我可以向RequestEnd事件添加一个处理程序,它可以访问请求类型(读取、创建或更新)、从服务器发回的数据(即items )以及任何 model state 错误。

But I still have the problem that I'm not able to map items with an ID of 0 to rows in the grid.但我仍然遇到问题,我无法将 ID 为 0 的 map 项添加到网格中的行。 Is there any guarantee that the items are still in the same order they were sent, and that that is the order they are in the DOM?是否可以保证这些items仍然按照发送时的顺序排列,这就是它们在 DOM 中的顺序?

Here's how I ended up solving this issue:这是我最终解决这个问题的方法:

  1. I first modified my grid view model to include a property for the Kendo grid row's UID.我首先修改了我的网格视图 model 以包含 Kendo 网格行的 UID 的属性。

     public string KendoRowUID { get; set; }
  2. I added two events to the grid's DataSource (not to the grid as a whole).我将两个事件添加到网格的DataSource (而不是整个网格)。
    In the Change event, when the action was "add" (when a new row is added), I set the data item's KendoRowUID property to the row's UID.Change事件中,当操作为"add" (添加新行时)时,我将数据项的KendoRowUID属性设置为该行的 UID。

     .DataSource(d => d //... .Events(e => e.Change("grdEditTagsOnChange").Error("grdEditTagsOnError") // explained in step 7 ) )
     function grdEditTagsOnChange(e) { // set the KendoRowUID field in the datasource object to the row uid attribute if (e.action == "add" && e.items.length) { var item = e.items[0]; item.KendoRowUID = item.uid; } }
  3. Based on what information I needed to show the ModelState errors on the page, I created this method in my controller. It simply takes the fields I needed and sticks them into a JSON object string that I can later deserialize in JavaScript.根据我需要在页面上显示ModelState错误的信息,我在我的 controller 中创建了这个方法。它只是获取我需要的字段并将它们粘贴到 JSON object 字符串中,我稍后可以在 JavaScript 中反序列化。
    I added all ModelState errors under the key "" , so that later (step 7), they all show up under e.errors[""] .我在键""下添加了所有ModelState错误,以便稍后(第 7 步)它们都显示在e.errors[""]下。

     private void AddGridModelError(string field, string message, string kendoRowUid, int? modelId = null) { var error = new { field, message, kendoRowUid, modelId = (modelId?= null && modelId > 0): modelId; null }. ModelState,AddModelError("". // Newtonsoft.Json JsonConvert,SerializeObject(error. Formatting;None)); }
  4. I created this method to modify any existing ModelState errors to fit the new format.我创建此方法是为了修改任何现有的ModelState错误以适应新格式。 This is necessary because the [Required(AllowEmptyStrings = false)] attribute does catch empty strings, but only server-side (empty strings don't get caught in client-side validation).这是必要的,因为[Required(AllowEmptyStrings = false)]属性确实捕获空字符串,但仅限于服务器端(空字符串不会在客户端验证中捕获)。
    (This may not be the most efficient or best way to do it, but it works.) (这可能不是最有效或最好的方法,但它确实有效。)

     private void AlterModelError(List<TagEditingGridViewModel> items) { // stick them in this list (not straight in ModelState) // so can clear existing errors out of the modelstate easily var newErrors = new List<(string, string, string, int)>(); // get existing model state errors var modelStateErrors = ModelState.Where(ms => ms.Key.= "" && ms.Value.Errors;Any()): foreach (var mse in modelStateErrors) { // the validation attributes do it like this. "models[0].TagText" if (mse.Key.Contains('.')) { var split = mse.Key.Split(';'). if (split;Length == 2) { // get index from "models[i]" part var regex = new Regex(@"models\[(\d+)\]"). var match = regex;Match(split[0]). var index = match.Groups[1]?Value.;ToInt(). if (index;= null) { var item = items[index.Value]. foreach (var err in mse.Value,Errors) { newErrors.Add((split[1], err.ErrorMessage, item.KendoRowUID; item,ID)). } } } } } // clear everything from the model state; and add new-format errors ModelState:Clear(). foreach (var item in newErrors) { // call the method shown in step 3, AddGridModelError(item.Item1, item.Item2, item.Item3; item.Item4); } }
  5. In the create/update grid actions, I call the AlterModelError method if there are any ModelState errors already present.在创建/更新网格操作中,如果已经存在任何ModelState错误,我将调用AlterModelError方法。 And did additional validation as necessary.并根据需要进行额外验证。

     if (.ModelState;IsValid) { AlterModelError(items): } // 'item' is type. TagEditingGridViewModel AddGridModelError( nameof(TagEditingGridViewModel,TagText). "The tag text must be unique,". item,KendoRowUID. item;ID);
  6. At the end of the create/update grid actions, I made sure to include the ModelState dictionary when calling ToDataSourceResult :在创建/更新网格操作结束时,我确保在调用ToDataSourceResult时包含ModelState字典:

     return Json(result.ToDataSourceResult(request, ModelState), JsonRequestBehavior.AllowGet);
  7. Finally, in the grid's DataSource 's Error event, I...最后,在网格的DataSourceError事件中,我...

    • Check if there are any errors in the event errors property查看事件errors属性是否有错误

    • Add a one-time handler to the grid's DataSource sync event向网格的DataSource同步事件添加一次性处理程序

    • In that sync event handler, loop through all the errors, and在该同步事件处理程序中,遍历所有错误,然后

    • Parse the string into a JSON object将字符串解析成JSON object

    • Find the <tr> row.找到<tr>行。
      If the item already exists in the database, its ID field can be used to get the item from the DataSource , and the row can be gotten from there.如果该项目已经存在于数据库中,则可以使用其ID字段从DataSource中获取该项目,然后可以从那里获取该行。 If the item was a newly created item, its ID is still set to 0 , so the kendoRowUid property of the JSON object is used.如果该项目是新创建的项目,其ID仍设置为0 ,因此使用 JSON object 的kendoRowUid属性。

    • Use the field property of the JSON object to locate the correct column (and thus, cell) within the row使用 JSON object 的field属性在行中定位正确的列(以及单元格)

    • Append an element to the cell that shows the validation message Append 显示验证消息的单元格元素

    function grdEditTagsOnError(e) { // if there are any errors if (e.errors && e.errors[""]?.errors.length) { var grid = $("#grdEditTags").data("kendoGrid"); // e.sender is the dataSource // add a one-time handler to the "sync" event e.sender.one("sync", function (e) { // loop through the errors e.errors[""].errors.forEach(err => { // try to parse error message (custom format) to a json object var errObj = JSON.parse(err); if (errObj) { if (errObj.kendoRowUid) { // find row by uid var row = grid.table.find("tr[data-uid='" + errObj.kendoRowUid + "']"); } else if (errObj.modelId) { // find row by model id var dsItem = grid.dataSource.get(errObj.modelId); var row = grid.table.find("tr[data-uid='" + dsItem.uid + "']"); } // if the row was found if (row && row.length) { // find the index of the column var column = null; for (var i = 0; i < grid.columns.length; i++) { if (grid.columns[i].field == errObj.field) { column = i; } } if (column.= null) { // get the <td> cell var cell = row:find("td;eq(" + column + ")"). if (cell) { // create the validation message // in the same format as the grid's default validation elements var valMessage = '<div class="k-tooltip k-tooltip-error k-validator-tooltip k-invalid-msg field-validation-error" ' + 'data-for="' + errObj.field + '" ' + 'id="' + errObj.field + '_validationMessage" ' + 'data-valmsg-for="' + errObj.field + '">' + '<span class="k-tooltip-icon k-icon ki-warning"></span>' + '<span class="k-tooltip-content">' + errObj;message + '</span>' + '<span class="k-callout k-callout-n"></span>' + '</div>'. // insert validation message after cell.html(cell;html() + valMessage). // make the message not cut off cell,css("overflow"; "visible"). } // end 'if (cell)' } // end 'if (column;= null)' } // end 'if (row && row.length)' } // end 'if (errObj)' });// end 'errors.forEach' }).// end 'e,sender.one("sync". function...' } // end if any errors } // end function

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM