ASP.NET MVC 2 - Validation attribute error messages not showing up in my view when using Simon Ince's conditional validation attributes - asp.net-mvc-2

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

Related

Maui Mvvm working with Observable Property Class Model, how to call a class object correctly in a method

Still pretty new to maui and mvvm, I'm trying to figure out of to access elements in a observable property class in a method. For example I have a login class like below:
public class LoginModel
{
public string Failmessage { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public bool Validusername { get; set; } = true;
public bool Vaildpassword { get; set; } = true;
}
In my modelview I call the observable property LoginModel loginModel
[ObservableProperty]
LoginModel LoginModel
public LoginPageViewModel()
{
LoginModel login= new();
}
I want to check the property value in a method like below but it keeps giving me errors. like Fields with [ObservableProperty] should not be directly referenced, and the generated properties should be used instead.
if ((string.IsNullOrEmpty(LoginModel.Username)) || (string.IsNullOrWhiteSpace(LoginModel.Username)))
{
IsBusy = false;
LoginModel.Validusername = false;
return;
}
Am I doing something wrong here?
I want to be able to access the element of the class in a method.
From your code, I found that you should have used nuget CommunityToolkit.Mvvm.
And the problem is that you didn't use CommunityToolkit.Mvvm correctly.
You can try to add prefix partial before class LoginPageViewModel and declare loginmodel starting with small letter,thenLoginmodel property will be generated for you by MVVM Toolkit.
You can refer to the following code:
public partial class LoginPageViewModel:ObservableObject
{
[ObservableProperty]
LoginModel loginmodel;
public LoginPageViewModel() {
//LoginModel login = new();
Loginmodel = new LoginModel();
}
public void Test() {
if ((string.IsNullOrEmpty(Loginmodel.Username)) || (string.IsNullOrWhiteSpace(Loginmodel.Username)))
{
//IsBusy = false;
Loginmodel.Validusername = false;
return;
}
}
}
Update
now the if statement is working but when I set
Loginmodel.Validusername = false; the view is not updating with this
new value.
If you want the UI update automatically once changing the value of model Loginmodel, you also need to implement interface INotifyPropertyChanged for class LoginModel.
Please refer to the following code:
public class LoginModel: INotifyPropertyChanged
{
public string Failmessage { get; set; } = string.Empty;
public string Username { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
// public bool Validusername { get; set; } = true;
// modify code as follows
private bool _validusername;
public bool Validusername
{
get => _validusername;
set
{
SetProperty(ref _validusername, value);
}
}
public bool Vaildpassword { get; set; } = true;
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
ViewModels are ObservableObjects, not ObservableProperties.
They contain ObservableProperties.
Do not construct manually your ViewModels. Use dependency injection and register them as services.
For this what you call "check property value", that are actually ObservableProperties in ObservableObject, there is ObservableValidator class.
https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablevalidator
Fix this, and ask if you have any questions.

EF6 - Get entity for DbUpdateCommandTree in DbCommandTreeInterceptor

I am trying to get the value of a "NotMapped" property for a Entity/class when intercepting a DbUpdateCommandTree.
I have looked through the various metadata, but I cannot find the "link" to the Entity from the CommandTree, so unfortunately I am stuck.
Is it even possible ?
public class SomeEntity
{
public int ID { get; set; }
[NotMapped]
public int SomeUnmappedProperty { get; set; }
}
public class CommandTreeInterceptor : IDbCommandTreeInterceptor
{
public void TreeCreated(DbCommandTreeInterceptionContext ctx)
{
if (ctx.OriginalResult.DataSpace == DataSpace.SSpace)
{
var updateCommand = ctx.OriginalResult as DbUpdateCommandTree;
if (updateCommand != null)
{
// I would like to get a value of a specific property here.
// Pseudo code
var val = updateCommand.Entity.GetPropertyValue("SomeUnmappedProperty") as int;
}
}
}
}

conditional either or validation in asp.net mvc2

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; }

Entity Framework Complex Type Custom Validation, Stopping Validation Recursion

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 {}

Custom Model Binder, asp.net mvc 2 rtm 2, Parsing ID to ComplexModel

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.