I have a custom ValidationAttribute in my ASP.NET MVC3 project which has two conditions which need to be met. It works well but I would like to let the user now which validation rule has been broken by returning a customised error message.
With the method I am using (inherit error message from base class) I know I can not change the value of the _defaultError constant after it has been initialised, so....
How do I return different error messages depending on which condition was not met?
Here is my ValidationAttribute code:
public class DateValidationAttribute :ValidationAttribute
{
public DateValidationAttribute()
: base(_defaultError)
{
}
private const string _defaultError = "{0} [here is my generic error message]";
public override bool IsValid(object value)
{
DateTime val = (DateTime)value;
if (val > Convert.ToDateTime("13:30:00 PM"))
{
//This is where I'd like to set the error message
//_defaultError = "{0} can not be after 1:30pm";
return false;
}
else if (DateTime.Now.AddHours(1).Ticks > val.Ticks)
{
//This is where I'd like to set the error message
//_defaultError = "{0} must be at least 1 hour from now";
return false;
}
else
{
return true;
}
}
}
I could advise you to create two different implementation of DateValidator class, each having different message. This is also in line with SRP as you just keep the relevant validation information in each validator separate.
public class AfternoonDateValidationAttribute : ValidationAttribute
{
// Your validation logic and message here
}
public class TimeValidationAttribute : ValidationAttribute
{
// Your validation logic and message here
}
Related
<BitDatePicker #bind-Value="Model.Date"
AllowTextInput="true"
DateFormat="yyyy/M/d"
GoToToday="امروز" Placeholder="تاریخ را وارد کنید"
Culture="PersianCultureHelper.GetFaIrCultureByFarsiNames()"
Style="width:150px; display:inline-block;">
</BitDatePicker>
(https://i.stack.imgur.com/B45TB.png)
how to change(modify) the default validation message of this component?
I create a class that inherits from "ValidationAttribute" to override the error message by custom regex validation. but two messages show when the input is not valid.
I don't want to use "Require" attribute. it should show the message when the input is not valid.
Not that simple. It's hard coded into the component.
However there is a way.
BitDatePicker is a component that emulates a standard InputBase type component, though it doesn't implement InputBase. The validation message is generated in `TryParseValueFromString' which looks like this:
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out DateTimeOffset? result, [NotNullWhen(false)] out string? validationErrorMessage)
{
if (value.HasNoValue())
{
result = null;
validationErrorMessage = null;
return true;
}
if (DateTime.TryParseExact(value, DateFormat ?? Culture.DateTimeFormat.ShortDatePattern, Culture, DateTimeStyles.None, out DateTime parsedValue))
{
result = new DateTimeOffset(parsedValue, DateTimeOffset.Now.Offset);
validationErrorMessage = null;
return true;
}
result = default;
validationErrorMessage = $"The {DisplayName ?? FieldIdentifier.FieldName} field is not valid.";
return false;
}
So we can create a child component and override TryParseValueFromString. Note that you have to "capture" the content generated in the parent and re-gurgitate it in the child.
MyBitDatePicker
#using System.Diagnostics.CodeAnalysis;
#inherits BitDatePicker
#this.ParentContent
#code {
public RenderFragment ParentContent;
public MyBitDatePicker()
{
ParentContent = (builder) => base.BuildRenderTree(builder);
}
/// <inheritdoc />
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out DateTimeOffset? result, [NotNullWhen(false)] out string? validationErrorMessage)
{
var isValid = base.TryParseValueFromString(value, out result, out validationErrorMessage);
//Custom message defined here
validationErrorMessage = $"The {DisplayName ?? FieldIdentifier.FieldName} field ain't right!";
return false;
}
}
You could prevent the problem in the first place by disabling AllowTextInput. The user then can't select an invalid date.
I have a signup action that attempts checks if the model state is valid, attempts a sign in, if fail revalidates the model. I am unable to revalidate the model using TryValidateModel. I have modified some functions to force the model to be invalid.
Here is my sign up action:
[HttpPost]
public IActionResult SignUp(SignUpModel signUp, string returnURL)
{
if (ModelState.IsValid)
{
if (loginManager.SignUp(signUp)) return Ok("Account created");
TryValidateModel(signUp); // breakpoint 1
}
return View(signUp); // breakpoint 3
}
This is the SignUp() function that forces the invalid state:
public bool SignUp(SignUpModel signUp)
{
signUp.ForceInvalid();
return false;
}
This is the forced function inside SignUpModel
internal void ForceInvalid() => Validation.IsDuplicateEmail = true;
Here is my validation attribute class used for email:
class EmailExistsAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (validationContext.ObjectInstance is SignUpModel signUp && signUp.Validation.IsDuplicateEmail)
new ValidationResult("Email already exists"); // breakpoint 2
return ValidationResult.Success;
}
}
I have used the attribute like this:
[EmailExists]
public string Email { get; set; }
The code breaks at the breakpoints in the order: 1 2 3. Since the code breaks at 2, the model state should be invalid. However at breakpoint 3, the ModelState.IsValid is still true and it has no errors. I have even tried ModelState.Clear() even though it doesn't make sense in this context. The output is still the same.
Consider, for example's sake, the logic "A user may only edit or delete a comment that the user has authored".
My Controller Actions will repeat the logic of checking whether the currently logged in user can affect the comment. Example
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
// Cannot find comment, return bad input
return new HttpStatusCodeResult(400);
if(comment.author != User.Identity.Name)
// User not allowed to delete this comment, return Forbidden
return new HttpStatusCodeResult(403);
// Error checking passed, continue with delete action
return new HttpStatusCodeResult(200);
}
Of course, I can bundle that logic up in a method so that I'm not copy / pasting that snippet; however, taking that code out of the controller and putting it in a ValidationAttribute keeps my Action smaller and easier to write tests for. Example
public class MustBeCommentAuthorAttribute : ValidationAttribute
{
// Import attribute for Dependency Injection
[Import]
ICommentRepository CommentRepository { get; set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
int comment_id = (int)value;
var comment = CommentsRepository.getCommentById(comment_id);
if(comment == null)
return new ValidationResult("No comment with that ID");
if(comment.author != HttpContext.Current.User.Identity.Name)
return new ValidationResult("Cannot edit this comment");
// No errors
return ValidationResult.Success;
}
}
public class DeleteCommentModel
{
[MustBeCommentAuthor]
public int comment_id { get; set; }
}
Is Model Validation the right tool for this job? I like taking that concern out of the controller Action; but in this case, it may complicate things further. This is especially true when you consider that this Action is part of a RESTful API and needs to return a different HTTP Status Code depending on the Validation errors in the ModelState.
Is there "best practice" in this case?
Personally, I think that it looks nice, but you are getting carried away with annotations. I think that this does not belong in your presentation layer and it should be handled by your service layer.
I would have something on the lines of:
[Authorize]
public ActionResult DeleteComment(int comment_id)
{
try
{
var result = CommentsService.GetComment(comment_id, Auth.Username);
// Show success to the user
}
catch(Exception e)
{
// Handle by displaying relevant message to the user
}
}
I've implemented localized validation, client-side, using the DataAnnotations attributes successfully. Now, I want to implement custom validation running server-side using the CustomValidationAttribute but my problem is that I can't find a way to get the client-side culture while executing the validation.
Here's the setup for the custom validation method:
public static ValidationResult ValidateField( string fieldValue, ValidationContext validationContext )
{
#if !SILVERLIGHT
// Get the message from the ValidationResources resx.
return new ValidationResult( ValidationResources.Message, new string[]{ "Field" } );
#else
return ValidationResult.Success;
#endif
}
This code returns the message but from the culture that the server is currently set.
I also tried to set the attribute on the property this way with same result:
[CustomValidation( typeof( CustomValidation ), "ValidateField", ErrorMessageResourceName = "Message", ErrorMessageResourceType = typeof( ValidationResources ) )]
I also tried to expose a method on my DomainService to change the Culture on the ValidationResources resx but this seems to be changing the culture not only or the current connection but for all the connections.
Since the validation is ran by Ria Services and not something I am calling directly, how can I tell the validation method to use a specific culture?
I came across this thread and I was able to fix my issue and have the culture name pass to every request made by the DomainContext (client) to the server.
First, we need to create a custom IClientMessageInspector that will be responsible to set a parameter for the CurrentUICulture for every requests.
public class AppendLanguageMessageInspector : IClientMessageInspector
{
#region IClientMessageInspector Members
public void AfterReceiveReply( ref Message reply, object correlationState )
{
// Nothing to do
}
public object BeforeSendRequest( ref Message request, IClientChannel channel )
{
var property = request.Properties[ HttpRequestMessageProperty.Name ] as HttpRequestMessageProperty;
if( property != null )
{
property.Headers[ "CultureName" ] = Thread.CurrentThread.CurrentUICulture.Name;
}
return null;
}
#endregion // IClientMessageInspector Members
}
Next, we need to create a custom WebHttpBehavior that will inject our custom IClientMessageInspector.
public class AppendLanguageHttpBehavior : WebHttpBehavior
{
public override void ApplyClientBehavior( ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime )
{
clientRuntime.MessageInspectors.Add( _inspector );
}
private readonly AppendLanguageMessageInspector _inspector = new AppendLanguageMessageInspector();
}
Finally, we extend the client DomainContext.OnCreate method to add our custom WebHttpBehavior. NOTE: The namespace of the extended DomainContext class must be the same as the generated one...
public partial class DomainService1
{
partial void OnCreated()
{
var domainClient = this.DomainClient as WebDomainClient<IDomainService1Contract>;
if( domainClient != null )
{
domainClient.ChannelFactory.Endpoint.Behaviors.Add( DomainService1.AppendLanguageHttpBehavior );
}
}
private static readonly AppendLanguageHttpBehavior AppendLanguageHttpBehavior = new AppendLanguageHttpBehavior();
}
Now, on the server-side, when we want to get the language code we can simply access it like this:
var cultureName = System.Web.HttpContext.Current.Request.Headers[ "CultureName" ];
To enjoy even more of the DataAnnotation magic, we can even change the CurrentUICulture in the Initialize of the DomainService like this:
public override void Initialize( DomainServiceContext context )
{
var cultureName = System.Web.HttpContext.Current.Request.Headers[ "UICultureName" ];
Thread.CurrentThread.CurrentUICulture = new CultureInfo( cultureName );
base.Initialize( context );
}
I have an ASP MVC 3 application and in my Model I have implemented IValidatableObject.
When my controller posts for a create or edit, I obviously only want to save the model if it is valid.
I see many blogs and posts and answers that say something like
if(!ModelState.IsValid)
{
return View();
}
My question. Why is it that ModelState.IsValid is always true in a unit test on the Controller?
Example:
[Test]
public void InValidModelsAreNotAdded()
{
var invalidModel = new MyModel() { SomeField = "some data", SomeOtherField = "" };
var result = _controller.Submit(invalidModel);
_repository.AssertWasNotCalled(r => r.Add(Arg.Is.Anything));
}
Model code:
public class MyModel : IValidatableObject
{
public string SomeField { get; set; }
public string SomeOtherField { get; set; }
public IEnumerable Validate(ValidationContext validationContext)
{
if(string.IsNullOrWhiteSpace(SomeOtherField))
{
yield return
new ValidationResult("Oops invalid.", new[] {"SomeOtherField"});
}
}
}
The AssertWasNotCalled always fails this test.
I stepped through the test and noticed that the ModelState.IsValid is true for this test. It is as if the IValidatableObject.Validate is not being invoked. It seems to work when I run the project, but thats not much of a way to test drive an application.
Also, I realize I could use the [Required] attribute for my example, but my real code has much more complex validation to it.
Thoughts?
It's true because you haven't called anything which sets it false.
This normally happens during binding, but since you just pass the model directly in the test you skip that altogether.
If you're trying to test validation, do that directly. If you're trying to test the error path in your controller, your test's arrange can call _controller.ModelState.AddModelError( //...
Well, insted of simulate the model binding behavior you can do that:
public class YourController : Controller
{
//some code
public ViewResult someAction(Model model)
{
try
{
ValidateModel(model);
}
catch
{
// deal with errors
}
}
//some code
}
ValidateModel with "try catch" blocks are much more readable for me. But you can still use "if" blocks with the method TryValidateModel
Hope that helps!!