Mudblazor Select with multiselect and Fluentvalidation For-Expression - .net-core

I am binding to a select field in multiselect mode and I ran into a problem with the "For" property of the select field".
Here is a code snippet
When using a select field an options type must be set and in this example it will be string. To make validation work the "For"-Property needs to be set and pointing to a valid property of the same type as the select fields option (and thats string).
But I am expecting a multiselect, so I am binding to an IEnumerable<string> in my model and the validation code is also set for this property.
I don`t have the necessary property to bind to and even if I did, the validation would not work as expected.
How do I make this work? I tried building a custom expression which would point to the first element of the array, but I am bad with expressions and couldn`t make it work.
#using FluentValidation
<MudCard>
<MudForm Model="#model" #ref="#form" Validation="#(testValidator.ValidateValue)" ValidationDelay="0">
<MudCardContent>
<MudSelect T="string" Label="Name"
HelperText="Pick your favorite name" MultiSelection="false" #bind-Value="model.Name" For="() => model.Name">
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
<MudSelect T="string" Label="Names"
HelperText="Pick your favorite names" MultiSelection="true" #bind-SelectedValues="model.Names"
#* For="() => model.Names" This needs to be set to make validation work *#
>
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudForm>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="#(async () => await Submit())">Order</MudButton>
</MudCardActions>
</MudCard>
#code {
[Inject] ISnackbar Snackbar { get; set; }
private string[] _names = new string[] {
"Toni", "Matthew", "David"
};
MudForm form;
TestModelFluentValidator testValidator = new TestModelFluentValidator();
TestModel model = new TestModel();
public class TestModel
{
public string Name { get; set; }
public IEnumerable<string> Names { get; set; }
}
private async Task Submit()
{
await form.Validate();
if (form.IsValid)
{
Snackbar.Add("Submited!");
}
}
/// <summary>
/// A standard AbstractValidator which contains multiple rules and can be shared with the back end API
/// </summary>
/// <typeparam name="OrderModel"></typeparam>
public class TestModelFluentValidator : AbstractValidator<TestModel>
{
public TestModelFluentValidator()
{
RuleFor(x => x.Name)
.NotEmpty();
RuleFor(x => x.Names).Must((parent, property) => property.Contains("Toni"))
.WithMessage("Toni not found in those names!");
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<TestModel>.CreateWithOptions((TestModel)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
}
Edit: Added code sample and trimmed unecessary code.

Mudblazor snippet.
Ok, so you can trick the component by introducing a dummy property and binding the multi-select component to it then testing its name during validation.
When the form component passes the dummy property name to the validation method, you change the passed dummy name to the name of your collection so it's matched when fluent validation kicks in.
Something like this:
#using FluentValidation
#using System.Reflection
<MudCard>
<MudForm Model="#model" #ref="#form" Validation="#(testValidator.ValidateValue)" ValidationDelay="0">
<MudCardContent>
<MudSelect T="string" Label="Name"
HelperText="Pick your favorite name" MultiSelection="false" #bind-Value="model.Name" For="() => model.Name">
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
<MudSelect T="string" Label="Names"
HelperText="Pick your favorite names" MultiSelection="true" #bind-Value="model.NameCollection" #bind-SelectedValues="model.Names"
For="#(() => model.NameCollection)"
>
#foreach (var name in _names)
{
<MudSelectItem T="string" Value="#name">#name</MudSelectItem>
}
</MudSelect>
</MudCardContent>
</MudForm>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-auto" OnClick="#(async () => await Submit())">Order</MudButton>
</MudCardActions>
</MudCard>
#code {
[Inject] ISnackbar Snackbar { get; set; }
private string[] _names = new string[] {
"Toni", "Matthew", "David"
};
MudForm form;
TestModelFluentValidator testValidator = new TestModelFluentValidator();
TestModel model = new TestModel();
public class TestModel
{
public string Name { get; set; }
public string NameCollection { get; set; }
public IEnumerable<string> Names { get; set; }
}
private async Task Submit()
{
await form.Validate();
if (form.IsValid)
{
Snackbar.Add("Submited!");
}
}
/// <summary>
/// A standard AbstractValidator which contains multiple rules and can be shared with the back end API
/// </summary>
/// <typeparam name="OrderModel"></typeparam>
public class TestModelFluentValidator : AbstractValidator<TestModel>
{
public TestModelFluentValidator()
{
RuleFor(x => x.Name)
.NotEmpty();
RuleFor(x => x.Names).Must((parent, property) => property.Contains("Toni"))
.WithMessage("Toni not found in those names!");
}
private async Task<bool> IsUniqueAsync(string email)
{
// Simulates a long running http call
await Task.Delay(2000);
return email.ToLower() != "test#test.com";
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
propertyName = propertyName == nameof(TestModel.NameCollection) ? nameof(TestModel.Names) : propertyName;
var result = await ValidateAsync(ValidationContext<TestModel>.CreateWithOptions((TestModel)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
}

Related

Populate a select list ASP.NET Core MVC

I'm busy with an ASP.NET Core MVC application, and I'm trying to populate a drop down list. I've created a view model and I have added a method to my StoresController that returns a list of stores that I want to display in a dropdown. I've been working off some online tutorials as I'm very new to asp.
View model:
public class StoreListViewModel
{
public List<StoreList> StoreList { get; set; } = new List<StoreList>();
}
public class StoreList
{
public string StoreId { get; set; } = null!;
public string StoreName { get; set; } = null!;
}
StoresController:
public IActionResult LoadStoreList()
{
if (ModelState.IsValid)
{
var storeList = new StoreListViewModel().StoreList.Select
(x => new SelectListItem { Value = x.StoreId, Text = x.StoreName }).ToList();
ViewBag.Stores = storeList;
}
return NotFound();
}
I'm trying to use ViewBag to call my LoadStoreList() method.
<select name="storeList" class="form-control" asp-items="#(new SelectList(ViewBag.Stores, "Value", "Text"))"></select>
When I load my page I get the following error
Value cannot be null. (Parameter 'items')
The page I need the dropdown list on is my CreateUser.cshtml which is bound to my UserModel and has a UsersController. The method I have created for listing the stores is in my StoresController which is bound to my StoresModel. So I'm not sure if that's causing the issue.
I've been battling with this for days, if someone could help me get this working or show me a better method, that would be great.
*Edit
The UserIndex() method is the first method that fires when my users page opens, do I call the LoadStoreList() method from there ?
UserController
public async Task<IActionResult> UsersIndex()
{
return _context.UsersView != null ?
View(await _context.UsersView.ToListAsync()) :
Problem("Entity set 'ApplicationDbContext.Users' is null.");
}
I'm trying to use ViewBag to call my LoadStoreList() method.
ViewBag cannot be used to call any method. You just need set value for ViewBag in the method which renders your show dropdownlist's page.
From your description, you said the page you need the dropdown list on is CreateUser.cshtml. Assume that you render the CreateUser.cshtml page by using CreateUser action.
CreateUser.cshtml:
<select name="storeList" class="form-control" asp-items="#(new SelectList(ViewBag.Stores, "Value", "Text"))"></select>
Controller:
public class YourController : Controller
{
private readonly YourDbcontext _context;
public YourController(YourDbcontext context)
{
_context = context;
}
[HttpGet]
public IActionResult CreateUser()
{
var storeList = _context.StoreLists.Select
(x => new SelectListItem { Value = x.StoreId , Text = x.StoreName }).ToList();
ViewBag.Stores = storeList;
return View();
}
}
YourDbcontext should be something like:
public class YourDbcontext: DbContext
{
public YourDbcontext(DbContextOptions<MvcProjContext> options)
: base(options)
{
}
public DbSet<StoreList> StoreLists{ get; set; }
}
Dont use viewbag for storing list data. Make your view page model including List, for example:
public class UserCreationViewModel{
public int Id{ get; set; }
public string Name { get; set; }
// Any other properties....
public List<StoreList> StoreList { get; set; }
}
in your controller YourController:
[HttpGet]
public IActionResult CreateUser()
{
var storeList = new StoreListViewModel().StoreList.Select
(x => new SelectListItem { Value = x.StoreId, Text = x.StoreName }).ToList();
UserCreationViewModel model=new UserCreationViewModel{
StoreList = storeList
};
return View("createUserViewName", model);
}
in createUserViewName:
#Html.DropDownList("StoreId", new SelectList(Model.StoreList, "StoreId", "StoreName"), "Select", new { #class = "form-control" })
or
<select class="form-control" asp-for="#Model.StoreId" asp-items="#(new SelectList(Model.StoreList, "StoreId", "StoreName"))">
<option value="-1">Select</option>
</select>

Automapper can't map nested collection

I'm trying to figure out how to do this mapping in automapper 9.0.0. Can anybody help me out?
I have these classes
class User
{
public string Name { get; set; }
public Address[] Addresses { get; set; }
}
class Address
{
public string StreetName { get; set; }
}
class UserDto
{
public string Name { get; set; }
public PropertiesDto Properties { get; set; }
}
class PropertiesDto
{
public AddressDto[] Addresses { get; set; }
}
class AddressDto
{
public string StreetName { get; set; }
}
My goal is to place the array of addresses inside a 'PropertiesDto' object, where there eventually will be a lot of other arrays.
var user = new User { Name = "Foo", Addresses = new[] { new Address { StreetName = "Main St." } } };
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Address[], AddressDto[]>();
cfg.CreateMap<User, UserDto>()
.ForMember(d => d.Properties.Addresses, opt => opt.MapFrom(s => s.Addresses));
});
IMapper mapper = new Mapper(config);
var dtoUser = mapper.Map<UserDto>(user);
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(dtoUser));
Console.WriteLine("Hit enter...");
Console.ReadLine();
The code fails with this error message.
Unhandled exception. System.ArgumentException: Expression 'd => d.Properties.Addresses' must resolve to top-level member and not any child object's properties. You can use ForPath, a custom resolver on the child type or the AfterMap option instead. (Parameter 'lambdaExpression')
There is a special command for just this situation - if you're mapping to a property below the top level, then just use .ForPath (as the error message suggests) in place of .ForMember. Like this:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Address[], AddressDto[]>();
cfg.CreateMap<User, UserDto>()
.ForPath(d => d.Properties.Addresses, opt => opt.MapFrom(s => s.Addresses));
});
What worked for me was using reverse mapping.
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Address, AddressDto>();
cfg.CreateMap<UserDto, User>()
.ForMember(d => d.Addresses, opt => opt.MapFrom(s => s.Properties.Addresses))
.ReverseMap();
});

FluentValidation set validator on collection with Ruleset

I'm creating a POST Rest API(ASP.NET Web API) to perform write operations,so have to validate data before inserting them into Database.I'm pretty new to using FluentValidation to validate the data.
Suppose below are classes that I have and need to validate.
public class Listing
{
public Type Type { get; set; }
public Source Source { get; set; }
public List<ListingDetails> ListingDetails { get;set; }
}
public class ListingDetails
{
public int Id{ get; set; }
public ListingStatus Status { get; set; }
}
public enum ListingStatus
{
Active = 1,
Converted = 2,
LostToCompetitor = 3
}
Below code is responsible for validating the status based on the provided ruleset.
public class ListingStatusValidator : AbstractValidator<ListingDetails>
{
public ListingStatusValidator()
{
RuleSet("A", () =>
{
RuleFor(x=>x.InquiryId).GreaterThan(0);
});
RuleSet("B", () =>
{
RuleFor(x => x.Status).IsInEnum().NotEqual(ListingStatus.Active);
});
RuleSet("C", () =>
{
RuleFor(x => x.Status).IsInEnum();
});
}
}
Below is the piece of code used to validatelisting.
public class ListingValidator : AbstractValidator<Listing>
{
public ListingValidator()
{
RuleSet("common", () =>
{
When(x => x.ListingDetails != null && x.ListingDetails.Count <= 1000, () =>
RuleForEach(x => x.ListingDetails).SetValidator(new ListingStatusValidator()));
});
}
}
Now to validate we will call validate method of the validator like below.
var validation = new ListingValidator().Validate(listing,ruleSet:"common");
Is it possible to pass ruleset when validating using setvalidator on collection of objects.Please see below snippet to understand what I'm trying to achieve.
public class ListingValidator : AbstractValidator<Listing>
{
public ListingValidator()
{
When(x => x.ListingDetails != null && x.ListingDetails.Count <= 1000, () =>
RuleForEach(x => x.ListingDetails).SetValidator(new ListingStatusValidator(),ruleset:"A,B,C"));
}
}
You can execute more then one RuleSet using RulesetValidatorSelector
var validation = new ListingValidator()
.Validate(listing, new RulesetValidatorSelector("common", "A", "B", "C"));
In this case you don't need to specify RuleSets for ListingStatusValidator, RuleSets from ListingValidator will be passed to nested validator.

Client-side unobtrusive validation for EditorTemplate when sending IEnumerable<T>

There is a possibility that I just wasn't able to find the solution, or lack thereof, through my searches. Maybe I didn't word it properly, but my problem is trying to get client-side unobtrusive validation to fire on an EditorTemplate when I pass an IEnumerable<T> to it. My setup:
ParentModel.cs
[Validator(typeof(ParentModelValidator))]
public class ParentModel
{
...
public IEnumerable<ChildModel> ChildModels { get; set; }
}
public class ParentModelValidator : AbstractValidator<ParentModel>
{
public ParentModelValidator()
{
RuleFor(x => x.ChildModels).SetCollectionValidator(new ChildModelValidator());
}
}
ChildModel.cs
[Validator(typeof(ChildModelValidator))]
public class ChildModel
{
public bool IsRequired { get; set; }
public string foo { get; set; }
}
public class ChildModelValidator : AbstractValidator<ChildModel>
{
public ChildModelValidator ()
{
RuleFor(x => x.foo)
.NotEmpty().When(x => x.IsRequired);
}
}
ParentShell.cshtml
#model ParentModel
#using (Html.BeginForm("Index", "Application", FormMethod.Post))
{
#Html.AntiForgeryToken()
#Html.Partial("_Parent", Model)
#Html.EditorFor(m => m.ChildModels)
<input type="submit" value="submit" />
}
The _Parent partial just contains a handful of common, reusable #Html.TextBoxFor(m => m.bar) and #Html.ValidationMessageFor(m => m.bar) fields.
ChildModel.cshtml EditorTemplate
#model ChildModel
#Html.TextBoxFor(m => m.foo)
#if (Model.IsRequired)
{
#Html.ValidationMessageFor(m => m.foo)
}
The client-side validation fires for all fields in the _Parent partial, but I get nothing when IsRequired is true and should have a ValidationMessageFor. Is this a known constraint of the client-side unobtrusive validation with EditorTemplate that receives an IEnumerable<T>? Is it due to the indexer that gets inserted during rendering (ChildModels[0].foo and ChildModels_0__.foo)?
From the documentation for FluentValidation
Note that FluentValidation will also work with ASP.NET MVC's client-side validation, but not all rules are supported. For example, any rules defined using a condition (with When/Unless), custom validators, or calls to Must will not run on the client side
Because you have used a .When condition, you will not get client side validation.
Using an alternative such as foolproof [RequiredIfTrue] attribute will work for a simple property, but not for a complex object or collection.
You can solve this by creating you own custom ValidationAttribute that implements IClientValidatable
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ComplexRequiredIfTrue : ValidationAttribute, IClientValidatable
{
private const string _DefaultErrorMessage = "The {0} field is required.";
public string OtherProperty { get; private set; }
public ComplexRequiredIfTrue(string otherProperty) : base(_DefaultErrorMessage)
{
if (string.IsNullOrEmpty(otherProperty))
{
throw new ArgumentNullException("otherProperty");
}
OtherProperty = otherProperty;
}
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, OtherProperty);
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null)
{
PropertyInfo otherProperty = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty);
bool isRequired = (bool)otherProperty.GetValue(validationContext.ObjectInstance, null);
if (isRequired)
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var clientValidationRule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "complexrequirediftrue"
};
clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);
return new[] { clientValidationRule };
}
}
and the associated script
function nameToIndex (value) {
return value.replace(/[\[\].]/g, '_');
}
(function ($) {
$.validator.addMethod("complexrequirediftrue", function (value, element, params) {
// We need to get the prefix of the control we are validating
// so we can get the corresponding 'other property'
var name = $(element).attr('name');
var index = name.lastIndexOf('.');
var prefix = nameToIndex(name.substr(0, index + 1));
var otherProp = $('#' + prefix + params);
if (otherProp.val() == "True" && !value) {
return false;
}
return true;
});
$.validator.unobtrusive.adapters.addSingleVal("complexrequirediftrue", "otherproperty");
}(jQuery));
then apply it to you property
public class ChildModel
{
public bool IsRequired { get; set; }
[ComplexRequiredIfTrue("IsRequired")]
public string foo { get; set; }
}
and in the EditorTemplate, include #Html.HiddenFor(m => m.IsRequired)
#model ChildModel
#Html.HiddenFor(m => m.IsRequired)
#Html.TextBoxFor(m => m.foo)
#Html.ValidationMessageFor(m => m.foo)
Edit: Further to comments, if the controller is
model.ChildModels = new List<ChildModel>() { new ChildModel() { IsRequired = true }, new ChildModel() };
return View(model);
then the html generated when the submit button is clicked is:
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_0__IsRequired" name="ChildModels[0].IsRequired" type="hidden" value="True">
<input class="input-validation-error" data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_0__foo" name="ChildModels[0].foo" type="text" value="">
<span class="field-validation-error" data-valmsg-for="ChildModels[0].foo" data-valmsg-replace="true">The foo field is required.</span>
<input data-val="true" data-val-required="The IsRequired field is required." id="ChildModels_1__IsRequired" name="ChildModels[1].IsRequired" type="hidden" value="False">
<input data-val="true" data-val-complexrequirediftrue="The foo field is required." data-val-complexrequirediftrue-otherproperty="IsRequired" id="ChildModels_1__foo" name="ChildModels[1].foo" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="ChildModels[1].foo" data-valmsg-replace="true"></span>
Note the form did not submit and the error message was displayed for the first textbox

ASP.NET MVC how to achieve to use the same model with different error message

I am having this issue at the moment, I had address model (use required attribute to decorate) which can be used more than once on the same page, one is billing address and the other one is shipping address. when validation failed, I'd like to have suffix in front of my generic error message indicate which address is required e.g. "{0} - address line 1 required", either billing or shipping
Here is my model
public class AddressBaseModel
{
[Display(Name="Address line 1")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine1Required")]
public string AddressLine1 { get; set; }
[Display(Name="Address line 2")]
[Required(ErrorMessageResourceType = typeof(ModelValidation), ErrorMessageResourceName = "AddrLine2Required")]
public string AddressLine2 { get; set; }
[Display(Name="Address line 3")]
public string AddressLine3 { get; set; }
[Display(Name="Address line 4")]
public string AddressLine4 { get; set; }
}
}
Here is the code segment I used in my page
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
Thanks
Personally I use the FluentValidation.NET library instead of Data Annotations as it makes things so much easier and provides a lot more power. Here's an example of how to achieve your goal using this ilbrary.
Create a new ASP.NET MVC 3 project using the default Visual Studio template
Install the FluentValidation.MVC3 NuGet package.
Add the following line to Application_Start:
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(
new AttributedValidatorFactory()
)
);
Define the following models:
public class AddressBaseModel
{
public string AddressLine1 { get; set; }
}
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
public AddressBaseModel HomeAddress { get; set; }
public AddressBaseModel DeliveryAddress { get; set; }
}
And the following Validators:
public class AddressBaseModelValidator : AbstractValidator<AddressBaseModel>
{
private readonly string _addressType;
public AddressBaseModelValidator(string addressType)
{
_addressType = addressType;
RuleFor(x => x.AddressLine1)
.NotEmpty()
.WithMessage(string.Format("{0} - address line 1 required", addressType));
}
}
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.HomeAddress)
.SetValidator(new AddressBaseModelValidator("billing"));
RuleFor(x => x.DeliveryAddress)
.SetValidator(new AddressBaseModelValidator("shipping"));
}
}
Modify the HomeController:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
HomeAddress = new AddressBaseModel(),
DeliveryAddress = new AddressBaseModel()
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
And the corresponding Index.cshtml view:
#model MyViewModel
#using (Html.BeginForm())
{
<fieldset class="space-bottom">
<legend>Please enter your home address</legend>
<div id="home_fields">
#Html.EditorFor(m => m.HomeAddress)
</div>
</fieldset>
<fieldset class="space-bottom">
<legend>Please enter your delivery address</legend>
<div id="delivery_fields">
#Html.EditorFor(m => m.DeliveryAddress)
</div>
</fieldset>
<input type="submit" value="Register" />
}
You could create a custom attribute that does the dynamic formatting for you. You would just tag your address fields with the Address attribute like this:
[Address]
public string AddressLine1 { get; set; }
You would need to add a property in the AddressBaseModel where you tell the system what type of address this is (you would set this to "Billing" or "Shipping" when you instantiate the view model right before you pass the view model to the View in the controller get action):
public string AddressType { get; set; }
A custom attribute like this should work (I haven't tested it, I wrote it just now). This automatically gets the address type you specified when you create the model instance and formats it with the display name of the address field).
public class AddressAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "{0} - {1} required";
public AddressAttribute()
: base(DefaultErrorMessage) { }
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
if (value != null)
{
if (!base.IsValid(value))
{
// get the property called "AddressType" from the model so we know if it's Billing or Shipping
var addressType = validationContext.ObjectInstance.GetType()
.GetProperty("AddressType")
.GetValue(validationContext.ObjectInstance, null);
// use the display name of the address field in the error message
return new ValidationResult(
string.Format(DefaultErrorMessage, addressType, validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
This should work:
[Required(ErrorMessage = "The Address 2 is required.")]

Resources