In my registration page I have land line phone number and mobile number fields.
I need to ensure that the user needs to add at least one phone number either the land line or mobile.
How do I do this?
Thanks
Arnab
You could write a custom validation attribute and decorate your model with it:
[AttributeUsage(AttributeTargets.Class)]
public class AtLeastOnePhoneAttribute: ValidationAttribute
{
public override bool IsValid(object value)
{
var model = value as SomeViewModel;
if (model != null)
{
return !string.IsNullOrEmpty(model.Phone1) ||
!string.IsNullOrEmpty(model.Phone2);
}
return false;
}
}
and then:
[AtLeastOnePhone(ErrorMessage = "Please enter at least one of the two phones")]
public class SomeViewModel
{
public string Phone1 { get; set; }
public string Phone2 { get; set; }
}
For more advanced validation scenarios you may take a look at FluentValidation.NET or Foolproof.
Adding a solution that can be applied to individual properties, rather than overriding the validation method at the class level...
Create the following custom attribute. Note the "otherPropertyName" parameter in the constructor. This will allow you to pass in the other property to use in validation.
public class OneOrOtherRequiredAttribute: ValidationAttribute
{
public string OtherPropertyName { get; set; }
public OneOrOtherRequiredAttribute(string otherPropertyName)
{
OtherPropertyName = otherPropertyName;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var otherPropertyInfo = validationContext.ObjectType.GetProperty(OtherPropertyName);
var otherValue = (string)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
if (string.IsNullOrEmpty(otherValue) && string.IsNullOrEmpty((string)value))
{
return new ValidationResult(this.ErrorMessage); //The error message passed into the Attribute's constructor
}
return null;
}
}
You can then decorate your properties like so: (be sure to pass in the name of the other property to compare with)
[OneOrOtherRequired("GroupNumber", ErrorMessage = "Either Group Number or Customer Number is required")]
public string CustomerNumber { get; set; }
[OneOrOtherRequired("CustomerNumber", ErrorMessage="Either Group Number or Customer Number is required")]
public string GroupNumber { get; set; }
Related
I have a PUT Rest API that I want to do binding from both body and route parameters.
Code
[HttpPut("{Id}/someStuffApi")]
public ActionResult UpdateStatus([FromBody] StatusRequest StatusRequest)
{
// code ...
}
And the model class is
public class StatusRequest
{
[FromRoute(Name = "Id")]
[Required(ErrorMessage = "'Id' attribute is required.")]
public string Id { get; set; }
[FromBody]
[Required(ErrorMessage = "'Status' attribute is required.")]
public string Status { get; set; }
}
When I made a request to this API, the Id is not mapped to the model even though I added the FromRoute attribute explicitly. Any suggestions?
The [FromBody] model binding will effectively override the [FromRoute] option in your model class. This is by design (why, I'm not sure, but an MS decision). See the "[FromBody] attribute" section of this doc: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding. As pointed out there: "When [FromBody] is applied to a complex type parameter, any binding source attributes applied to its properties are ignored." So adding the "[FromRoute]" attribute inside your model does nothing...it's ignored. You can remove both of those attributes from your model.
So the way around this is to put the route binding in the Put action as a method parameter and then manually add it to your model in the controller before using the model.
[HttpPut("{Id}/someStuffApi")]
public ActionResult UpdateStatus(int Id, [FromBody] StatusRequest StatusRequest)
{
StatusRequest.Id = Id;
// remaining code...
}
The downside to this method is that the Required attribute cannot remain on the Id parameter. It will be null at the time of model binding and if you have .Net Core 3.1 automatic model validation active, then that will always return a 422. So if you would have to manually check that yourself before adding it to the model.
If you want even more flexibility, you can look at something like the HybridModelBinding NuGet package that allows various combinations of model binding using attributes. But this is a 3rd party dependency that you may not want. (https://github.com/billbogaiv/hybrid-model-binding/)
You can use custom model binding,here is a demo:
TestModelBinderProvider:
public class TestModelBinderProvider : IModelBinderProvider
{
private readonly IList<IInputFormatter> formatters;
private readonly IHttpRequestStreamReaderFactory readerFactory;
public TestModelBinderProvider(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
{
this.formatters = formatters;
this.readerFactory = readerFactory;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(StatusRequest))
return new StatusBinder(formatters, readerFactory);
return null;
}
}
Startup.cs:
services.AddMvc()
.AddMvcOptions(options =>
{
IHttpRequestStreamReaderFactory readerFactory = services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
options.ModelBinderProviders.Insert(0, new TestModelBinderProvider(options.InputFormatters, readerFactory));
});
StatusBinder:
public class StatusBinder: IModelBinder
{
private BodyModelBinder defaultBinder;
public StatusBinder(IList<IInputFormatter> formatters, IHttpRequestStreamReaderFactory readerFactory)
{
defaultBinder = new BodyModelBinder(formatters, readerFactory);
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
// callinng the default body binder
await defaultBinder.BindModelAsync(bindingContext);
if (bindingContext.Result.IsModelSet)
{
var data = bindingContext.Result.Model as StatusRequest;
if (data != null)
{
var value = bindingContext.ValueProvider.GetValue("Id").FirstValue;
data.Id = value.ToString();
bindingContext.Result = ModelBindingResult.Success(data);
}
}
}
}
StatusRequest:
public class StatusRequest
{
[Required(ErrorMessage = "'Id' attribute is required.")]
public string Id { get; set; }
[Required(ErrorMessage = "'Status' attribute is required.")]
public string Status { get; set; }
}
Action:
[HttpPut("{Id}/someStuffApi")]
public ActionResult UpdateStatus(StatusRequest StatusRequest)
{
return Ok();
}
result:
I have a select field
<select asp-for="UserCitizenships" asp-items="Model.CitizenshipsList" class="select2 required">
that is populated by a multiselect list
public MultiSelectList CitizenshipsList { get; set; }
And returns a List of Int
public List<int> UserCitizenships
that I handle manually in the backend. Validation from Data Annotations is not working. I need at least one property to be populated when the form submits. Any ideas?
[Required(ErrorMessage = Helpers.ErrorMessages.Required)]
public List<int> UserCitizenships
{
get
{
....
I would have thought the following would work
[Required, MinLength(1, ErrorMessage = "At least one item required")]
public List<int> UserCitizenships { get; set; }
Alternatively you could create a custom data annotation or use class level validation
public class YourClass : IValidatableObject
{
[Required]
List<int> UserCitizenships
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (UserCitizenships.Count < 1)
{
yield return new ValidationResult(
$"At least one UserCitizenship should be specified.",
new[] { nameof(UserCitizenships) });
}
}
}
I'm trying to use Simon Ince's conditional validation attributes for one of my view models. The logic seems to be working spot on, but the attribute's error message isn't appearing in my view's ValidationFor() methods.
The attribute:
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
The validator:
public class RequiredIfValidator : DataAnnotationsModelValidator<RequiredIfAttribute>
{
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
// no client validation - I might well blog about this soon!
return base.GetClientValidationRules();
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// get the value of the dependent property
var value = field.GetValue(container, null);
// compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value.Equals(Attribute.TargetValue)))
{
// match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
}
How they're hooked up (Global.asax.cs):
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequiredIfValidator));
}
}
And how I'm trying to use it:
public class AdminGameViewModel
{
public bool IsCreated { get; set; }
[Required]
public int GameID { get; set; }
[Required(ErrorMessage = "A game must have a title")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string GameTitle { get; set; }
[Required(ErrorMessage = "A short URL must be supplied")]
[DisplayFormat(ConvertEmptyStringToNull=false)]
public string Slug { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A box art image must be supplied")]
public HttpPostedFileBase BoxArt { get; set; }
[RequiredIf("IsCreated", true, ErrorMessage = "A large image for the index page is required")]
public HttpPostedFileBase IndexImage { get; set; }
// other props of the class....
}
I don't know enough about the inner workings of MVC's validation mechanism in order to troubleshoot my problem. Any ideas?
Have you tried updating to my MVC3 implementation? It is cleaner than the hack needed with the validator in MVC2.
One thing that is missing from even the MVC3 code is the need to override FormatErrorMessage on the attribute, which will likely be similar to what you're seeing here. For the MVC 3 code I use;
public override string FormatErrorMessage(string name)
{
if (!String.IsNullOrEmpty(this.ErrorMessage))
innerAttribute.ErrorMessage = this.ErrorMessage;
return innerAttribute.FormatErrorMessage(name);
}
HTH
Simon
We are using complex types to manage our translatable fields like this:
[ComplexType]
public class Translated
{
[Required]
public string NL { get; set; }
[Required]
public string EN { get; set; }
[ScaffoldColumn(false)]
public string TranslatedText
{
get
{
return Util.Translate(NL, EN);
}
}
}
We require the fields to be present. But in some cases the whole Translated field is optional as in:
public class Question
{
...
[Optional(ErrorMessage="Foo")]
public Translated Description { get; set; }
...
}
However, it seems that the Optional attribute gets calculated, and when it returns false nothing is done with the result.
class OptionalAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return false;
}
}
When I put the optional attribute on a non-complex type it works as expected, the error message will always be Foo.
The ultimate goal is to allow the Description to be empty in both cases, but when one of the fields is filled the errors should of course propagate.
Stopping the validation recursion would cause the field to be optional, but it would also prevent the validation of the fields in case they are filled in.
Any ideas on how to accomplish this?
Using the data annotation [Required] on your string properties will create non nullable fields in the database. From your description it seems that sometimes you'll want both of those values to be null.
I would suggest implementing your own validation defining what makes those fields optional.
[ComplexType]
public class Translated : IValidatableObject
{
public string NL { get; set; }
public string EN { get; set; }
[NotMapped]
public string TranslatedText
{
get
{
return Util.Translate(NL, EN);
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!string.IsNullOrEmpty(NL) && string.IsNullOrEmpty(EN))
yield return new ValidationResult("EN is required if NL is entered.");
if (!string.IsNullOrEmpty(EN) && string.IsNullOrEmpty(NL))
yield return new ValidationResult("NL is required if EN is entered.");
}
}
This is what I'm doing now. It has the disadvantage that for each kind of Translated (Translated, TranslatedOptional, TranslatedMultiline and TranslatedMultilineOptional) you have seperate classes.
Another disadvantage is that I don't know how to add the validation results to the NL en EN fields themselves instead of to the Translated.
[ComplexType]
public class TranslatedMultiline : IValidatableObject
{
[DataType(DataType.MultilineText)]
[Display(Name = "dutch", ResourceType = typeof(Caracal.Resources.GUI))]
public string NL { get; set; }
[DataType(DataType.MultilineText)]
[Display(Name = "english", ResourceType = typeof(Caracal.Resources.GUI))]
public string EN { get; set; }
[ScaffoldColumn(false)]
public string TranslatedText
{
get
{
return Util.Translate(NL, EN);
}
}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this is IOptional)
{
if (!string.IsNullOrEmpty(NL) && string.IsNullOrEmpty(EN))
yield return new ValidationResult("EN is required if NL is entered.");
// TODO: Translate
if (!string.IsNullOrEmpty(EN) && string.IsNullOrEmpty(NL))
yield return new ValidationResult("NL is required if EN is entered.");
// TODO: Translate
}
else
{
if (string.IsNullOrEmpty(NL))
yield return new ValidationResult("NL is required");
if (string.IsNullOrEmpty(EN))
yield return new ValidationResult("EN is required");
}
}
}
[ComplexType]
public class TranslatedMultilineOptional : TranslatedMultiline, IOptional { }
public interface IOptional {}
I have found myself with at little problem, and I think a custom model binder is the way to go.
My Domain model looks like this,readly standard
I got a Page and a Template. The Page has the Template as a ref.
So the Default asp.net mvc Binder, does not know how to bind it, therefore I need to make some rules for it. (Custom Model Binder)
public class PageTemplate
{
public virtual string Title { get; set; }
public virtual string Content { get; set; }
public virtual DateTime? Created { get; set; }
public virtual DateTime? Modified { get; set; }
}
public class Page
{
public virtual string Title { get; set; }
public virtual PageTemplate Template { get; set; }
public virtual string Content { get; set; }
public virtual DateTime? Created { get; set; }
public virtual DateTime? Modified { get; set; }
}
So I have Registreted the ModelBinder in globals.asax
ModelBinders.Binders.Add(typeof(Cms.Domain.Entities.Page),
new Cms.UI.Web.App.ModelBinders.PageModelBinder(
new Services.GenericApplicationService<Cms.Domain.Entities.Page>().GetEntityStore()
)
);
My ModelBinder tage a paremeter, witch is my Repository, where I get all my Entities ( Page, Template )
My Controller for a Page looks like this.
I have posted into a Create Controler, it does not matter for now, if it was a Update method.
Since I in this case have a dropdown, that represents the Template, I will get an ID in my form collection.
I then call: TryUpdateModel and I got a hit in my PageModelBinder.
[AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
[ValidateInput(false)]
public ActionResult Create(FormCollection form)
{
Page o = new Page();
string[] exclude = new { "Id" }
if (base.TryUpdateModel<Page>(o, string.Empty, null, exclude, form.ToValueProvider()))
{
if (ModelState.IsValid)
{
this.PageService.Add(o);
this.CmsViewData.PageList = this.PageService.List();
this.CmsViewData.Messages.AddMessage("Page is updated.", MessageTypes.Succes);
return View("List", this.CmsViewData);
}
}
return View("New", this.CmsViewData);
}
So I end op with the Model Binder.
I have search the internet dry for information, but im stock.
I need to get the ID from the FormCollection, and parse it to at Model from my IEntityStore.
But how ?
public class PageModelBinder : IModelBinder
{
public readonly IEntityStore RepositoryResolver;
public PageModelBinder(IEntityStore repositoryResolver)
{
this.RepositoryResolver = repositoryResolver;
}
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
if (modelType == typeof(Cms.Domain.Entities.Page))
{
// Do some magic
// Get the Id from Property and bind it to model, how ??
}
}
}
// Dennis
I hope, my problom is clear.
Did find a work around.
I download the sourcecode for asp.net r2 rtm 2
And did copy all code for the default ModelBinder, and code it need. Did some minor change, small hacks.
the work around is doing a little hack in this method:
[SuppressMessage("Microsoft.Globalization", "CA1304:SpecifyCultureInfo", MessageId = "System.Web.Mvc.ValueProviderResult.ConvertTo(System.Type)",
Justification = "The target object should make the correct culture determination, not this method.")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We're recording this exception so that we can act on it later.")]
private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType)
{
try
{
object convertedValue = valueProviderResult.ConvertTo(destinationType);
return convertedValue;
}
catch (Exception ex)
{
try
{
// HACK if the binder still fails, try get the entity in db.
Services.GenericApplicationService<Cms.Domain.Entities.PageTemplate> repo;
repo = new Services.GenericApplicationService<Cms.Domain.Entities.PageTemplate>();
int id = Convert.ToInt32(valueProviderResult.AttemptedValue);
object convertedValue = repo.Retrieve(id);
return convertedValue;
}
catch (Exception ex1)
{
modelState.AddModelError(modelStateKey, ex1);
return null;
}
}
}
This question is closed.