Reputation: 465
I have an MVC project similar to this...
Model
Public Class ItemDetails
Public Property SerialNo As Integer
Public Property Description As String
Public Property GroupNo As Integer
Public Property Price As String
Public Property Quantity As Integer
End Class
Controller
Function ListItems() As ActionResult
' GetItems retrieves the items from the database
Dim i As List(Of ItemDetails) = ItemsRepository.GetItems
Return View(i)
End Function
View
@ModelType List(Of MyApp.ItemDetails)
@Using Html.BeginForm()
Dim RowNo As Integer
For i As Integer = 0 To Model.Count - 1
RowNo = i
@Html.HiddenFor(Function(model) model(RowNo).SerialNo)
@Html.LabelFor(Function(model) model(RowNo).Description)
@Html.HiddenFor(Function(model) model(RowNo).GroupNo)
@Html.LabelFor(Function(model) model(RowNo).Price)
@Html.TextBoxFor(Function(model) model(RowNo).Quantity)
Next
End Using
Note: This is done from memory so may not be completely accurate.
As you can see this displays a list of items. The items are retrieved from a database. Each item has a description and a Group No. The user can enter how many of each item they would like to order.
Two or more items can be in the same group. For examle there might be: Item 1 in Group 1, Item 2 in Group 1, Item 3 in Group 2 and Item 4 in Group 3.
When the user clicks submit I would like to validate that for each Group No the combined Quantity does not exceed 10. So in the example above if I enter a Quantity of 7 for Item 1 then quantity for Item 2 must be 3 or less.
I can validate this easily client side.
What is the best way to go about validating this server side (where and how)?
I need to total the combined value of Quantity does not exceed 10 for each Group No and if it does display an error.
Upvotes: 1
Views: 1070
Reputation: 1038890
Personally I use FluentValidation.NET for this kind of tasks. It allows you to define your complex validation rules for a given view model into a separate layer, express them in a fluent manner, it has an absolutely marvelous and seamless integration with ASP.NET MVC and allows you to unit test your validation logic in isolation.
If you don't want to use third party libraries you could always write a custom validation attribute and decorate your view model property with it. You could introduce a view model that will contain the collection of ItemDetails
as a property and then decorate this property with your custom validator.
public class ItemDetails
{
public int SerialNo { get; set; }
public string Description { get; set; }
public int GroupNo { get; set; }
// Please take a note that I have allowed myself
// to use the decimal datatype for a property called
// Price as I was a bit shocked to see String in your code
public decimal Price { get; set; }
public int Quantity { get; set; }
}
public class MyViewModel
{
[EnsureMaxGroupItems(10, ErrorMessage = "You cannot have more than 10 items in each group")]
public IList<ItemDetails> Items { get; set; }
}
and the validation attribute itself:
[AttributeUsage(AttributeTargets.Property)]
public class EnsureMaxGroupItemsAttribute : ValidationAttribute
{
public int MaxItems { get; private set; }
public EnsureMaxGroupItemsAttribute(int maxItems)
{
MaxItems = maxItems;
}
public override bool IsValid(object value)
{
var items = value as IEnumerable<ItemDetails>;
if (items == null)
{
return true;
}
return items
.GroupBy(x => x.GroupNo)
.Select(g => g.Sum(x => x.Quantity))
.All(quantity => quantity <= MaxItems);
}
}
and finally your controller actions will work with the view model:
public ActionResult ListItems()
{
var model = new MyViewModel
{
Items = ItemsRepository.GetItems()
};
return View(model);
}
[HttpPost]
public ActionResult ListItems(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
...
}
and next the corresponding strongly typed view:
@model MyViewModel
@Html.ValidationSummary()
@using (Html.BeginForm())
{
@Html.EditorFor(x => x.Items)
<button type="submit">Go go go</button>
}
and the last bit is the corresponding editor template that will automatically be rendered for each element of the Items collection so that you don't even need to write for loops (~/Views/Shared/EditorTemplates/ItemDetails.cshtml
):
@model ItemDetails
@Html.HiddenFor(x => x.SerialNo)
@Html.LabelFor(x => x.Description)
@Html.HiddenFor(x => x.GroupNo)
@Html.LabelFor(x => x.Price)
@Html.TextBoxFor(x => x.Quantity)
Upvotes: 3