Data Validation Techniques in ASP.NET Core FlexGrid
FlexGrid is a feature-rich DataGrid control that provides built-in Filtering, Grouping, Sorting, Editing & an extensible API for implementing customizations, among other capabilities. These features set make it one of the best DataGrid controls out there. FlexGrid’s rich features are available across cross-platform, including Winforms, WPF, ASP.NET MVC, Javascript, Blazor, and Xamarin.
For this blog, we are going to focus on the C1 ASP. Net Core FlexGrid & various validation techniques that can be applied for both Client & Server. Let us explore a use case for a Web-based Online Inventory System used by a Home Décor Business to manage its Inventory using FlexGrid.
Let’s take an example of an Inventory Manager. They are required to update the inventory records at the end of the week based on sales, returns, or any price changes for an Item. There could be field-level validations that must be remembered when updating each record, like ensuring that Descriptions should not be empty or that price should be between a certain range. Ensuring these validations adhere to every time would not be easy. However, using FlexGrid, there is an effortless way to ensure that these validations are adhered to and provide helpful messages for the user to understand the reason for these errors.
Here is an example of validations that we will apply using FlexGrid:
ComponentOne FlexGrid allows the following Data Validation techniques to validate the data:
- Unobtrusive Validation
- CollectionView’s getError method
- FlexGrid’s itemValidator method
- FlexGrid’s cellEditEnding event
Unobtrusive Validation
Unobtrusive Validation can be used to apply the already-existing validation attributes from the Model class and re-use them in the client-side to validate the data by defining the rules for the Model class without defining the rules at the client-side.
For this, the following files should be included:
<script src="~/lib/jquery/dist/jquery.min.js"></script> @*If not already included in the project*@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
After adding the required files, the validation rules can be added with the help of attributes Required and Range. These attributes are mandatory to add the rules for Unobtrusive Validation.
Model class with Validation Rules:
public class Stocks
{
[Required(AllowEmptyStrings =false,ErrorMessage ="{0} cannot be empty!")]
public string StockID { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} cannot be empty!")]
public string Description { get; set; }
[Required(AllowEmptyStrings = false, ErrorMessage = "{0} cannot be empty!"), StringLength(11, ErrorMessage = "The value should be length of 11!")]
[RegularExpression("LOT-GCF-[0-9]{3}",ErrorMessage = "It should start with “LOT - GCF -” and last 3 character should be digits!")]
public string LotNo { get; set; }
[Required]
[Range(10,1000,ErrorMessage = "The {0} should be between 10 and 1000!")]
public double Price { get; set; }
[Required]
[Range(0, 0.20,ErrorMessage = "The {0} should be between 0 and 20%")]
public double Discount { get; set; }
[Required]
[Range(0, int.MaxValue, ErrorMessage = "Please enter a positive number!")]
public int Quantity { get; set; }
public bool InStock { get; set; }
}
The required files are included in the project and the rules are defined for the Model class. After this, the Model is ready for databinding with FlexGrid.
<script>
// this script is to remove the % from the editing cell.
function onBeginningEdit(s, e) {
if (s.columns[e.col].binding == "Discount") {
setTimeout(function () {
s.activeEditor.value = s.getCellData(e.row, e.col);
})
}
}
</script>
<c1-flex-grid id="unobtrusiveGrid" style="max-height:500px;" auto-generate-columns="false" beginning-edit="onBeginningEdit">
<c1-items-source source-collection="Model" disable-server-read="true"></c1-items-source>
<c1-flex-grid-column binding="StockID" header="Stock ID" is-read-only="true"></c1-flex-grid-column>
<c1-flex-grid-column binding="LotNo" header="Lot"></c1-flex-grid-column>
<c1-flex-grid-column binding="Description" header="Description"></c1-flex-grid-column>
<c1-flex-grid-column binding="Price" header="Price" format="c2"></c1-flex-grid-column>
<c1-flex-grid-column binding="Discount" header="Discount" format="p2"></c1-flex-grid-column>
<c1-flex-grid-column binding="Quantity" header="Qunatity"></c1-flex-grid-column>
<c1-flex-grid-column binding="InStock" header="In Stock?"></c1-flex-grid-column>
</c1-flex-grid>
Since this is a built-in validation, it adds the data- attribute to the editing cell Input, and the error message is shown using the Title attribute. Hence the error messages displayed cannot be customized.
Note: While working with the Unobtrusive validation, the cellEditEnding event’s cancel property is set to true if the input value is not valid. By checking if the cancel is true, the cell can be preserved in edit mode by setting the stayInEditMode property to true for CellEditEndingEventArgs.
If looking for the online demo, here is the Unobtrusive Validation demo.
CollectionView’s getError method
CollectionView’s getError method is used to validate the FlexGrid at the client-side using the CollectionView. It highlights the cells and RowHeaders if there is an error in the row’s data item. It accepts a callback function that accepts the three parameters, returns the error message based on the condition, and returns the null if there is no error. The getError method iterates for each data item and its properties and validates the value based on defined conditions.
<script>
function getGridErrors(item, prop) {
switch (prop) {
case "Price":
return (item[prop] < 100) ? "Price can't be less than 100!" : null;
break;
case "Discount":
return (item[prop] > 0.20) ? "Discount can't be greater than 20%!" : null;
break;
case "InStock":
if (item[prop] && item["Quantity"] <= 0) {
return "Quantity can't be 0 or lesser when InStock is true!";
} else if (!item[prop] && item["Quantity"] > 0) {
return "Quantity can't be greater than 0 when InStock is false!";
} else {
return null;
}
break;
case "Quantity":
if (item[prop] < 0) {
return "Quantity should not be less than 0!";
} else if (!item["InStock"] && item[prop] > 0) {
return "Quantity can't be greater than 0 if InStock is false!";
} else if (item["InStock"] && item[prop] == 0) {
return "Qunatity can't be 0 if the InStock is true!"
} else {
return null;
}
break;
default:
return null;
}
}
</script>
By setting the FlexGrid's showErrors to true, the erroneous cells can be highlighted with a red border, and by setting the validateEdits to true, the cell cannot be exited from edit mode until the value is incorrect
Here is the implementation for the FlexGrid with CollectionView validation.
<c1-flex-grid id="getErrorGrid" style="max-height:500px;" auto-generate-columns="false" show-errors="true" validate-edits="true">
<c1-items-source source-collection="Model" disable-server-read="true" get-error="getGridErrors"></c1-items-source>
<c1-flex-grid-column binding="StockID" header="Stock ID"></c1-flex-grid-column>
<c1-flex-grid-column binding="LotNo" header="Lot"></c1-flex-grid-column>
<c1-flex-grid-column binding="Description" header="Description"></c1-flex-grid-column>
<c1-flex-grid-column binding="Price" header="Price" format="c2"></c1-flex-grid-column>
<c1-flex-grid-column binding="Discount" header="Discount" format="p2"></c1-flex-grid-column>
<c1-flex-grid-column binding="Quantity" header="Qunatity"></c1-flex-grid-column>
<c1-flex-grid-column binding="InStock" header="In Stock?"></c1-flex-grid-column>
</c1-flex-grid>
Note: This method can work only when the FlexGrid is inbound mode.
If looking for the online demo, here is the CollectionView's getError demo.
FlexGrid’s itemValidator method
The ItemValidator method also accepts a callback, but it iterates through each cell and validates the cell value. It works for both bound and unbound FlexGrid. It accepts a row and column index, and the data can be fetched using the getCellData() method. Based on the column index, the binding of the column is based on the row index. The data item can be fetched to apply validation on FlexGrid.
<script>
function getGridItemErrors(row, col, parsing) {
var grid = wijmo.Control.getControl("#itemValidatorGrid");
var prop = grid.columns[col].binding;
var item = grid.rows[row].dataItem;
switch (prop) {
case "Price":
return (item[prop] < 100) ? "Price can't be less than 100!" : null;
break;
case "Discount":
return (item[prop] > 0.20) ? "Discount can't be greater than 20%!" : null;
break;
case "InStock":
if (item[prop] && item["Quantity"] <= 0) {
return "Quantity can't be 0 or lesser when InStock is true!";
} else if (!item[prop] && item["Quantity"] > 0) {
return "Quantity can't be greater than 0 when InStock is false!";
} else {
return null;
}
break;
case "Quantity":
if (item[prop] < 0) {
return "Quantity should not be less than 0!";
} else if (!item["InStock"] && item[prop]>0) {
return "Quantity can't be greater than 0 if InStock is false!";
} else if (item["InStock"] && item[prop] == 0) {
return "Qunatity can't be 0 if the InStock is true!"
} else {
return null;
}
break;
default:
return null;
}
}
</script>
<c1-flex-grid id="itemValidatorGrid" style="max-height:500px;" auto-generate-columns="false" item-validator="getGridItemErrors" show-errors="true" validate-edits="true">
<c1-items-source source-collection="Model" disable-server-read="true" ></c1-items-source>
<c1-flex-grid-column binding="StockID" header="Stock ID"></c1-flex-grid-column>
<c1-flex-grid-column binding="LotNo" header="Lot"></c1-flex-grid-column>
<c1-flex-grid-column binding="Description" header="Description"></c1-flex-grid-column>
<c1-flex-grid-column binding="Price" header="Price" format="c2"></c1-flex-grid-column>
<c1-flex-grid-column binding="Discount" header="Discount" format="p2"></c1-flex-grid-column>
<c1-flex-grid-column binding="Quantity" header="Qunatity"></c1-flex-grid-column>
<c1-flex-grid-column binding="InStock" header="In Stock?"></c1-flex-grid-column>
</c1-flex-grid>
FlexGrid’s cellEditEnding event
Validation can also be dependent on another field of the dataItem or FlexGrid cells. In this case, the cellEditEnding event would be useful, as it provides the new and old values. The other values can also be fetched using the getCellData() method.
For example: The Stocks have InStock property, and it depends on the Quantity. If the Quantity is 0, the InStock cannot be true and vice versa. In this case, the cellEditEnding event would be handled to stay in edit mode if the input value is not valid and add the error class and show the Tooltip.
<script>
var tip;
c1.documentReady(function () {
tip = new wijmo.Tooltip();
tip.cssClass = "wj-error-tip";
})
function gridCellEditEnding(s, e) {
var prop = s.columns[e.col].binding;
if (prop == "Quantity") {
var value = parseFloat(s.activeEditor.value);
if (s.getCellData(e.row, "InStock", false) && value <= 0) {
var cellHost = s.cells.getCellElement(e.row, e.col);
wijmo.addClass(cellHost, "wj-state-invalid");
e.stayInEditMode = true;
tip.setTooltip(cellHost, "Quantity can't be less than 0!");
} else if (!s.getCellData(e.row, "InStock", false) && value > 0) {
var cellHost = s.cells.getCellElement(e.row, e.col);
wijmo.addClass(cellHost, "wj-state-invalid");
e.stayInEditMode = true;
tip.setTooltip(cellHost, "Quantity can't be greater than 0!");
} else {
var cellHost = s.cells.getCellElement(e.row, e.col);
wijmo.removeClass(cellHost, "wj-state-invalid");
tip.setTooltip(cellHost, null);
}
}
if (prop == "InStock") {
var value = s.activeEditor.checked;
if (value && s.getCellData(e.row, "Quantity") <= 0) {
var cellHost = s.cells.getCellElement(e.row, e.col);
wijmo.addClass(cellHost, "wj-state-invalid");
e.stayInEditMode = true;
tip.setTooltip(cellHost, "InStock can't be true if Quantity less than or equal 0!");
} else if (!value && s.getCellData(e.row,"Quantity")>0) {
var cellHost = s.cells.getCellElement(e.row, e.col);
wijmo.addClass(cellHost, "wj-state-invalid");
e.stayInEditMode = true;
tip.setTooltip(cellHost, "InStock can't be false if Quantity greater than 0!");
} else {
var cellHost = s.cells.getCellElement(e.row, e.col);
wijmo.removeClass(cellHost, "wj-state-invalid");
tip.setTooltip(cellHost, null);
}
}
}
</script>
<c1-flex-grid id="cellEditGrid" style="max-height:500px;" auto-generate-columns="false" cell-edit-ending="gridCellEditEnding">
….
<c1-flex-grid-column binding="Quantity" header="Qunatity"></c1-flex-grid-column>
<c1-flex-grid-column binding="InStock" header="In Stock?"></c1-flex-grid-column>
</c1-flex-grid>
Follow along and download the demo sample.
Download ComponentOne and view ComponentOne Asp.Net Core MVC Documentation.