Validation
FluentValidation
Smartstore uses FluentValidation to create strongly-typed validation rules to validate view models on the server-side. This is done by defining rules for properties of the associated view model. When the value of a property is changed via an edit page, it is validated against its rule and, if necessary, an error message is issued if the rule is not fulfilled. Typically, the validation class is located directly below the view model in the same file as the model.
The class of a validator inherits from AbstractValidator
, to which the view model type is passed as a generic parameter.
public partial class CustomerValidator : AbstractValidator<CustomerModel>
{
public CustomerValidator(CustomerSettings customerSettings)
{
RuleFor(x => x.Password).NotEmpty().When(x => x.Id == 0);
if (customerSettings.FirstNameRequired)
RuleFor(x => x.FirstName).NotEmpty();
if (customerSettings.LastNameRequired)
RuleFor(x => x.LastName).NotEmpty();
if (customerSettings.CompanyRequired && customerSettings.CompanyEnabled)
RuleFor(x => x.Company).NotEmpty();
if (customerSettings.PhoneRequired && customerSettings.PhoneEnabled)
RuleFor(x => x.Phone).NotEmpty();
// Further code has been omitted for clarity.
}
}
FluentMigrator
provides a number of ways to specify complex validation rules.
public AddressValidator(Localizer T, AddressSettings addressSettings)
{
// Validate email address.
RuleFor(x => x.Email).EmailAddress();
if (addressSettings.ValidateEmailAddress)
{
// Validate additional input field to confirm the entered e-mail address.
RuleFor(x => x.EmailMatch)
.NotEmpty()
.EmailAddress()
.Equal(x => x.Email)
.WithMessage(T("Admin.Address.Fields.EmailMatch.MustMatchEmail"));
}
}
public ConfigurationValidator(Localizer T)
{
// Validate a list of entered IP addresses.
RuleFor(x => x.WebhookIps)
.Must(ips =>
{
if (ips != null)
{
foreach (var ip in ips)
{
if (!IPAddress.TryParse(ip, out _))
{
return false;
}
}
}
return true;
})
.WithMessage(T("Plugins.SmartStore.PostFinance.InvalidIp"));
}
SmartValidator
Typically, the properties of view models have the same names as the corresponding object that is to be modified (e.g. an entity such as a category or a settings class). This way, the values of the properties can be copied from an object to its model and vice versa with a single MiniMapper
statement. This allows Smartstore to provide additional utilities.
SmartValidator.ApplyEntityRules
copies common validation rules from the entity type over to the corresponding view model type. Common rules are Required
and MaxLength
rules on string properties (either fluently mapped or annotated). It also adds the Required
rule to non-nullable intrinsic model property types to bypass MVC's non-localized RequiredAttributeAdapter
.
public partial class CategoryValidator : SmartValidator<CategoryModel>
{
public CategoryValidator(SmartDbContext db)
{
ApplyEntityRules<Category>(db);
}
}
model.ApiPassword = model.ApiPassword.TrimSafe();
model.ApiSecretWord = model.ApiSecretWord.TrimSafe();
MiniMapper.Map(model, settings);
SettingModelValidator
The SettingModelValidator
is an abstract validator that can ignore rules for unchecked setting properties in a store-specific edit session.
public class SearchSettingValidator : SettingModelValidator<SearchSettingsModel, SearchSettings>
{
public SearchSettingValidator(Localizer T)
{
RuleFor(x => x.InstantSearchNumberOfProducts)
.Must(x => x >= 1 && x <= 16)
.WhenSettingOverriden((m, x) => m.InstantSearchEnabled)
.WithMessage(T("Admin.Validation.ValueRange", 1, 16));
}
}
The rule validates InstantSearchNumberOfProducts
with a value between 1 and 16 under one of the following conditions:
In case of store-agnostic setting mode: the result of the predicate parameter returns
true
In case of store-specific setting mode: the validated
InstantSearchNumberOfProducts
setting is overridden for the current store.
Manual validation
Use IValidator
to manually validate a view model.
[SystemName("Payments.IPaymentCreditCard")]
[FriendlyName("ipayment Credit Card")]
public class CreditCardProvider : IonosProviderBase<IonosPaymentCreditCardSettings>, IConfigurable
{
private readonly IValidator<CCPaymentInfoModel> _validator;
public override async Task<PaymentValidationResult> ValidatePaymentDataAsync(IFormCollection form)
{
var model = new CCPaymentInfoModel
{
CardholderName = form["CardholderName"],
CardNumber = form["CardNumber"],
CardCode = form["CardCode"]
};
var result = await _validator.ValidateAsync(model);
return new PaymentValidationResult(result);
}
}
MVC model validation
Since Smartstore is based on ASP.NET Core MVC, the server-side and client-side validations of this framework are also available.
Server-side validation
The model state represents errors that come from the MVC model binding or from model validation. If the model state is not valid (i.e. it contains errors), no data should be saved and instead the edit page should be reloaded with the model state errors.
[AuthorizeAdmin, Permission(DevToolsPermissions.Read), LoadSetting]
public IActionResult Configure(ProfilerSettings settings)
{
var model = MiniMapper.Map<ProfilerSettings, ConfigurationModel>(settings);
return View(model);
}
[HttpPost, AuthorizeAdmin, Permission(DevToolsPermissions.Update), SaveSetting]
public IActionResult Configure(ConfigurationModel model, ProfilerSettings settings)
{
if (!ModelState.IsValid)
{
return Configure(settings);
}
ModelState.Clear();
MiniMapper.Map(model, settings);
return RedirectToAction(nameof(Configure));
}
Data annotation attributes let you specify validation rules for model properties. The most common built-in validation attributes are:
Compare
Validates that two properties in a model match.
EmailAddress
Validates that the property has an email format.
Range
Validates that the property value falls within a specified range.
RegularExpression
Validates that the property value matches a specified regular expression.
Required
Validates that the field is not null, is not an empty string and does not only contain white-space characters.
StringLength
Validates that a string property value does not exceed a specified length limit.
Url
Validates that the property has a URL format.
ValidateNever
Indicates that a property or parameter should be excluded from validation.
Custom validation can be added to action methods.
if (model.Email.IsEmpty())
{
ModelState.AddModelError(nameof(model.Email),
T("Account.Register.Errors.EmailIsNotProvided"));
}
The key (first parameter) of AddModelError
can also be string.Empty
. In this case, the error message will be displayed in the error summary and not directly at the related input field.
The validation summary tag helper targets the HTML div
element inside your razor view, and is used to render a summary of form validation error messages.
<div asp-validation-summary="All"></div>
Possible ValidationSummary
enumeration values are:
None
Nothing.
ModelOnly
Model-level errors only (excludes all property errors).
All
Model and property validation errors.
The validation tag helper targets the HTML span
element inside your razor view, and is used to render property-specific validation error messages.
<span asp-validation-for="ParentCategoryId"></span>
Client-side validation
Client-side validation prevents an HTML form from being submitted until it is valid. The Submit button runs JavaScript that either submits the form or displays error messages. This avoids unnecessary round-trips to the server when there are input errors on a form. The validation messages correspond to the validation attributes specified for the model property. For instance, data type validation is based on the .NET type of a property, unless that is overridden by a DataType
attribute.
Last updated
Was this helpful?