I have created the following custom attribute to assist me with validating a required checkbox field:
public class CheckboxRequired : ValidationAttribute, IClientValidatable
{
public CheckboxRequired()
: base("required") { }
public override bool IsValid(object value)
{
return (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
ModelClientValidationRule rule = new ModelClientValidationRule();
rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName());
rule.ValidationType = "mandatory";
yield return rule;
}
}
However, I am trying to get it to trigger client side, and not when I call my ActionResult (if (ModelState.IsValid))
The validation does work when I call my ActionResult, but I'd prefer it to validate before getting that far.
What modifications do I need to make to make the validation kick in client side?
Thanks
In order to implement the client side you can add for example a jQuery validator method and an unobtrusive adapter (simple example):
// Checkbox Validation
jQuery.validator.addMethod("checkrequired", function (value, element)
{
var checked = false;
checked = $(element).is(':checked');
return checked;
}, '');
jQuery.validator.unobtrusive.adapters.addBool("mandatory", "checkrequired");
I hope it helps.
How about the old good Regex?
[RegularExpression("^(true|True)$", ErrorMessage="Required...")]
public bool AgreeWithTos { get; set; }
Accepts both "true", and 'True' as javascript and .NET format booleans differently.
Related
I have a requirement in which i wanna filter the textbox value, that is should remove the bad words entered by the user. Once the user enters the bad words and click on submit button, action is invoked. Somewhere in the model(any place) i should be able to remove the bad words and rebind the filtered value back to the model.
How can i do this?
If you can update the solution to MVC 3 the solution is trivial. Just implement the word check in a controller and then apply the RemoteAttribute on the property that should be validated against bad words. You will get an unobtrusive ajax check and server side check with just one method and one attribute. Example:
public class YourModel
{
[Remote("BadWords", "Validation")]
public string Content { get; set; }
}
public class ValidationController
{
public JsonResult BadWords(string content)
{
var badWords = new[] { "java", "oracle", "webforms" };
if (CheckText(content, badWords))
{
return Json("Sorry, you can't use java, oracle or webforms!", JsonRequestBehavior.AllowGet);
}
return Json(true, JsonRequestBehavior.AllowGet);
}
private bool CheckText(string content, string[] badWords)
{
foreach (var badWord in badWords)
{
var regex = new Regex("(^|[\\?\\.,\\s])" + badWord + "([\\?\\.,\\s]|$)");
if (regex.IsMatch(content)) return true;
}
return false;
}
}
I have an MVC application which I have added custom cross-field validation. The cross-field validation isn't configured to be client-side however when I tab through my fields IE is throwing the following error "$.validator.method[...] is null or not an object" from within jquery.validate.js. I have attached the full version so I can debug what's going on and it seems to be trying to fire my "mandatoryif" custom validation below on the client-side and then throwing the error at the following line:
var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters );
Any ideas why it is trying to do this when the "mandatoryif" validation hasn't been added client-side?
I have also updated to the latest version as I read it could be the version of jQuery.validate but this didn't fix the issue either.
Here is my custom validation:
Attribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public class MandatoryIfAttribute : ValidationAttribute, ICrossFieldValidationAttribute
{
public string OtherProperty { get; set; }
public bool IsValid(ControllerContext controllerContext, object model, ModelMetadata modelMetadata)
{
if (model == null)
{
throw new ArgumentNullException("model");
}
// Find the value of the other property.
var propertyInfo = model.GetType().GetProperty(OtherProperty);
if (propertyInfo == null)
{
throw new InvalidOperationException(string.Format("Couldn't find {0} property on {1}.",
OtherProperty, model));
}
var otherValue = propertyInfo.GetGetMethod().Invoke(model, null);
if (modelMetadata.Model == null)
{
modelMetadata.Model = string.Empty;
}
if (otherValue == null)
{
otherValue = string.Empty;
}
return (String.IsNullOrEmpty(modelMetadata.Model.ToString()) && (String.IsNullOrEmpty(otherValue.ToString()) || otherValue.ToString() == "0")) || (!String.IsNullOrEmpty(modelMetadata.Model.ToString()) && String.IsNullOrEmpty(otherValue.ToString())) || (!String.IsNullOrEmpty(modelMetadata.Model.ToString()) && !String.IsNullOrEmpty(otherValue.ToString()));
}
public override bool IsValid(object value)
{
// Work done in other IsValid
return true;
}
Validator
public class MandatoryIfValidator : CrossFieldValidator<MandatoryIfAttribute>
{
public MandatoryIfValidator(ModelMetadata metadata, ControllerContext controllerContext,
MandatoryIfAttribute attribute) :
base(metadata, controllerContext, attribute)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule
{
ValidationType = "mandatoryif",
ErrorMessage = Attribute.FormatErrorMessage(Metadata.PropertyName),
};
rule.ValidationParameters.Add("otherProperty", Attribute.OtherProperty);
return new[] { rule };
}
Appreciate any help on this.
I have managed to find the answer to my query. Because some of my custom validation was doing lookups to the db I didn't want the overhead of this being done client-side so there was no client-side code. However, I didn't realise that by adding
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MandatoryIfAttribute),
typeof(MandatoryIfValidator));
in Global.asax that it was in affect adding this client-side. Of course because there was no code to process client-side it was throwing the validaiton error.
Does the ASP.NET MVC 2 Default View Model Binding support binding a multi-value cookie to a custom object? Before I write a custom Value Provider, I would like to be sure that the functionality didn't already exist.
So given an action like:
public ActionResult SomeAction(CustomObject foo)
where CustomObject is something like:
public class CustomObject
{
public string Name { get; set; }
public int Rank { get; set; }
}
and a cookie that is part of the request like:
foo=Name=John&Rank=10
Could I get the Default View Model Binding to map the cookie to the parameter with some clever tweaks to the naming of the cookie or cookie values like posting "foo.Name=John" and "foo.Rank=10" would do?
Well, there's one way to do it would be to implement IModelBinder
public class CustomObjectModelBinder : IModelBinder {
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
HttpCookie c = controllerContext.HttpContext.Request.Cookies["foo"]
CustomObject value = new CustomObject() {
foo.Name = c.Values["Name"],
foo.Rank = c.Values["Rank"]
}
return CustomObject
}
}
Then just add this to your Application_Start()
ModelBinders.Binders.Add(typeof(CustomObject), new CustomObjectModelBinder());
you can add the cookie object to any action as far as i know and it will attempt to bind it for you
In the end I created something to do this. Based on the work posted by Mehdi Golchin, I created a value provider that allows this kind of binding to happen.
For those interrested, the following are the custom changes I made to Mehdi's work linked above. See the link for full details on implementation. This doesn't support binding to nested objects (e.g., Foo.Cell.X) because I didn't need that level of complexity, but it would be possible to implement with a bit of recursion.
protected virtual bool ContainsPrefix(string prefix)
{
try
{
var parts = prefix.Split(new char[] { '.' }, 2, StringSplitOptions.RemoveEmptyEntries);
switch (parts.Length)
{
case 0:
return false;
case 1:
return this._context.HttpContext.Request.Cookies.AllKeys.Contains(parts[0]);
default:
var cookie = this._context.HttpContext.Request.Cookies[parts[0]];
if (cookie == null) { return false; }
return cookie.Values.AllKeys.Contains(parts[1]);
}
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, "Controller Policy");
return false;
}
}
protected virtual ValueProviderResult GetValue(string key)
{
try
{
var parts = key.Split(new char[] { '.' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2) { return null; }
var cookie = this._context.HttpContext.Request.Cookies[parts[0]];
if (cookie == null) { return null; }
var value = cookie.Values[parts[1]];
if (value == null) { return null; }
return new ValueProviderResult(value, value, CultureInfo.CurrentCulture);
}
catch (Exception ex)
{
ExceptionPolicy.HandleException(ex, "Controller Policy");
return null;
}
}
I have an action bean in my stripes application. The default handler/method will display a list of data, a list of all my MarketResearch objects
On my JSP, I can click on one to view its details, this takes me to a different JSP with a pre-populated form based on the particular MarketResearch object that you selected.
I have another method on my action bean which is mapped to the save submit button, this takes in what is on the amended form, and persists it. After this has taken place, I want it to redirect back to the form, rather than to the listing (default handler) action, is this possible?
My action is as follows :
public class MarketResearchAction extends BaseAction
{
#SpringBean
ClientService clientService;
private static final String VIEW = "/jsp/marketResearch.jsp";
private Client client;
private Client clientBeforeChanges;
public Client getClient()
{
return client;
}
public void setClient(Client client)
{
this.client = client;
}
#DefaultHandler
public Resolution viewAll()
{
return new ForwardResolution(VIEW);
}
public Resolution viewClientMarketResearch()
{
if (client.getSector().equals("Education"))
{
return new ForwardResolution("/jsp/marketResearchEducation.jsp");
} else if (client.getSector().equals("Local Government"))
{
return new ForwardResolution("/jsp/marketResearchLocalGovernment.jsp");
} else if (client.getSector().equals("Housing Association"))
{
return new ForwardResolution("/jsp/marketResearchHousing.jsp");
}
return new ForwardResolution("/jsp/viewClientMarketResearch.jsp");
}
public Resolution save()
{
clientBeforeChanges = clientService.getClientById(client.getId());
clientService.persistClient(client);
getContext().getMessages().add(new SimpleMessage("{0} updated", client.getName()));
return new RedirectResolution("/MarketResearch.action").flash(this);
}
public Client getClientBeforeChanges()
{
return clientBeforeChanges;
}
public void setClientBeforeChanges(Client clientBeforeChanges)
{
this.clientBeforeChanges = clientBeforeChanges;
}
public ClientService getClientService()
{
return clientService;
}
public void setClientService(ClientService clientService)
{
this.clientService = clientService;
}
}
Is it possible? Or am I approaching the situation from a bad angle and should re-factor?
Thanks
Yes. You could return a RedirectResolution to the form jsp. If you're having difficulty with the parameters, if you have them in the save() method, you could do like so:
return new RedirectResolution("/theJsp.jsp")
.addParameter("one", one)
.addParameter("two", two)
.addParameter("three", three)
.flash(this);
If you don't have the params that were passed to the form, you'll have to keep them going somehow. You could pass the MarketResearch object through the form so you'd have it there.
<stripes:hidden name="marketResearch" value="${ActionBean.marketResearch}"/>
And add the requisite instance variable/getter/setter on your MarketResearchActionBean.
Is there a proper way to access other form fields from inside a validator?
Is there another solution than:
context.getViewRoot().findComponent("formid:exampleField:example")?
f.e I want to validate a city field inside a custom validator and checking if country is US.
Re-read your question and I am going to interpret it as this:
"You would like to write a custom validator that checks that if a city field exists, the country field is equal to 'US'"
So, I would look at going about this in the following fashion:
First create a validator interface:
#Documented
#ValidatorClass(value=CountryEqualsUSValidator.class)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface CountryEqualsUS {
String message() default "The country should be US for the city provided";
}
Then create a validator class:
public class CountryEqualsUSValidator implements Validator<CountryEqualsUS> {
public void initialize(CountryEqualsUS arg0) {
}
public boolean isValid(Object value) {
if(value != null && value instanceof YourBeanClass) {
YourBeanClass yourBeanClass = (YourBeanClass) value;
if(/*some test logic here*/) {
return true;
else {
return false;
}
}
return false;
}
}
Then on the class that you want to validate:
#CountryEqualsUS
public class YourBeanClass {
...
}
Then, finally, on your controller/action class, when the form is submitted, the city is a value for which you want to check the country, add this method and call it:
public boolean doValidation(YourBeanClass yourBeanClass) {
ClassValidator requestValidator = new ClassValidator(yourBeanClass.getClass());
InvalidValue[] validationMessages = requestValidator.getInvalidValues(yourBeanClass);
if (validationMessages != null && validationMessages.length > 0) {
for (int i = 0; i < validationMessages.length; i++) {
//Add a validation message to be displayed to the user
}
return false;
}
return true;
}