I've create a custom data annotation to do some validation on my view model. The problem is that it doesn't validate on the client-side. Here's my model:
public class MemberViewModel
{
[ScaffoldColumn(false)]
public int MemberId { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
//My custom data annotation
[EnforceTrue(ErrorMessage = "You must agree to the Terms and Conditions")]
public bool AgreeTerms { get; set; }
}
My data annotation validation code:
public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
public EnforceTrueAttribute() { }
public override bool IsValid(object value)
{
return value != null && (bool)value == true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule() { ValidationType = "enforcetrue", ErrorMessage = this.ErrorMessageString };
}
}
My controller method:
[HttpPost]
public ActionResult Index(MemberViewModel viewModel)
{
Member member = new Member();
TryUpdateModel(member);
if (ModelState.IsValid)
{
_membersRepository.SaveMember(member);
return RedirectToAction("Index", "Home");
}
return View(viewModel); // validation error, so redisplay same view
}
And my view:
#using (Html.BeginForm("Index", "Members", FormMethod.Post)) {
#Html.HiddenFor(m => m.MemberId)
<div class="editor-label">#Html.LabelFor(model => model.Name)</div>
<div class="editor-field">#Html.TextBoxFor(model => model.Name)</div>
<div class="editor-field">#Html.CheckBoxFor(model => model.AgreeTerms) <label for="AgreeTerms">I agree to the Terms and Conditions</label></div>
<p>
<input type="submit" value="Submit" />
</p>
#Html.ValidationSummary()
}
So all my other error messages get displayed in the validation summary with client-side validation. But for my custom data annotation, the error message doesn't show until the rest of the model is valid, and after you submit the form and page reloads, that's when the error is displayed in the summary.
Is there something else I need to do here to get it to show up in the summary with the other errors?
I'm using C# and ASP.NET MVC 3
Had same issue recently. You can write:
$.validator.addMethod('enforcetrue', function (value, element) {
return $(element).is(":checked");
});
$.validator.unobtrusive.adapters.add('enforcetrue', [], function (options) {
options.messages['enforcetrue'] = options.message;
options.rules['enforcetrue'] = options.params;
});
Similar question here ASP.NET MVC 3 client-side validation
Implementing Iclientvalidatable only adds unobtrusive attributes to generated html inputs. To enable validation on client side you must write validators that use these unobtrusive attributes to validate the inputs. Here you can find very good explanation of client and server validation in asp.net mvc 3
A Remote Validator is what you need here is the link
http://www.devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1
Related
I am creating a web app in asp.net mvc-5,
I am using IValidatableObject interface for validations,
here is how my model looks,
public class LicenseInfo : IValidatableObject
{
public int LicenseId { get; set; }
//other properties
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
//Validate class which will be called on submit
}
}
My view
#using (Ajax.BeginForm("_AddEditLicense", "User", new AjaxOptions { HttpMethod = "POST", UpdateTargetId = "dvLicenseContent", OnSuccess = "fnAddEditOnSuccess" }))
{
#Html.ValidationSummary(false)
#Html.DropDownListFor(m => m.LicenseId, new SelectList(Model.LicenseData, "Value", "Text"), "Select....", new { #class = "form-control" })
#*other html elements*#
<input type="submit" value="#ViewBag.Submit" id="btnaddLicense" class="btn btn-primary btn-block" />
}
My Controller
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
//execution
}
}
when my LicenseId = 0 then my validation is not working and the debugger on my controller is executing directly, but when LicenseId > 0 then my validation method is executing.
You need to manually add the validation inside your controller method.
[HttpPost]
public ActionResult _AddEditLicense(LicenseInfo data)
{
if (ModelState.IsValid)
{
// Execute code
}
// Not validated, return to the view
return View(data);
}
EDIT
Well, 0 is a valid value for an int even if does not represent anything in your drop down.
Try to change it to int?, then the default value would be null and it should be easier to catch it in model validation.
I am starting in asp.net Mvc and making test, i am doing a simple chat using PubNub api and i want finish it using only razor code only and one page only.
Model Chat.cs:
namespace SimpleChat.Models
{
public class Chat
{
public string NuevoMensaje { get; set; }
public string TextArea { get; set; }
}
}
View:
#model SimpleChat.Models.Chat
#using (Html.BeginForm("Index","Chat",FormMethod.Post))
{
#Html.LabelFor(model => model.NuevoMensaje, "Nuevo Mensaje")
#Html.TextBoxFor(model => model.NuevoMensaje)
<input type="submit" class="btn-default" value="Enviar" />
#Html.TextAreaFor(model => model.TextArea)
}
Controller:
static string variante = "";
public ActionResult Index()
{
pubnub.Subscribe<string>("Chat", subCallback, connecCallBack, errorCallback);
//Chat nuevochat = new Chat();
return View();
}
[HttpPost]
public ActionResult Index(Chat chat)
{
pubnub.Publish<string>("Chat", chat.NuevoMensaje, userCallback, puberror);
chat.NuevoMensaje = "";
chat.TextArea =variante;
return View("Index",chat);
}
private void subCallback(string obj)
{
string[] retorno = obj.Split(',','"');
variante += "Richard dice:" + retorno[0] + "\n";
}
When i press submit don't get the new data, why?
If you want to render the updated value of TextArea property of your view model, You should clear the model state dictionary.
Model state dictionary has the initial values of your form inputs. So when razor (re)render the same view, It gives priority to the content in model state dictionary than the view model object you passed to the view.
You can use the ModelState.Clear() method to clear the model state dictionary values before returning to the view.
chat.TextArea = variante;
ModelState.Clear();
return View("Index",chat);
Assuming your variante variable has the updated text.
I'm working on a MVC5 Code-First application.
On one Model's Edit() view I have included [Create] buttons to add new values to other models from within the Edit() view and then repopulate the new value within DropDownFors() on the Edit().
For this first attempt, I am passing a model_description via AJAX to my controller method createNewModel():
[HttpPost]
public JsonResult createNewModel(INV_Models model)
{
// model.model_description is passed in via AJAX -- Ex. 411
model.created_date = DateTime.Now;
model.created_by = System.Environment.UserName;
model.modified_date = DateTime.Now;
model.modified_by = System.Environment.UserName;
// Set ID
int lastModelId = db.INV_Models.Max(mdl => mdl.Id);
model.Id = lastModelId+1;
//if (ModelState.IsValid == false && model.Id > 0)
//{
// ModelState.Clear();
//}
// Attempt to RE-Validate [model], still comes back "Invalid"
TryValidateModel(model);
// Store all errors relating to the ModelState.
var allErrors = ModelState.Values.SelectMany(x => x.Errors);
// I set a watch on [allErrors] and by drilling down into
// [allErrors]=>[Results View]=>[0]=>[ErrorMessage] I get
// "The created_by filed is required", which I'm setting....?
try
{
if (ModelState.IsValid)
{
db.INV_Models.Add(model);
db.SaveChangesAsync();
}
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
return Json(
new { ID = model.Id, Text = model.model_description },
JsonRequestBehavior.AllowGet);
}
What I cannot figure out is why my ModelState is coming up as Invalid?
All properties are being specified before the ModelState check; the Model is defined as follows:
public class INV_Models
{
public int Id { get; set; }
[Required(ErrorMessage = "Please enter a Model Description.")]
public string model_description { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime created_date { get; set; }
[Required]
public string created_by { get; set; }
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime modified_date { get; set; }
public string modified_by { get; set; }
}
EDIT:
Added View code:
Input Form:
<span class="control-label col-md-2">Type:</span>
<div class="col-md-4">
#Html.DropDownListFor(model => model.Type_Id, (SelectList)ViewBag.Model_List, "<<< CREATE NEW >>>", htmlAttributes: new { #class = "form-control dropdown" })
#Html.ValidationMessageFor(model => model.Type_Id, "", new { #class = "text-danger" })
</div>
<div class="col-md-1">
<div class="btn-group">
<button type="button" class="btn btn-success" aria-expanded="false">CREATE NEW</button>
</div>
</div>
SCRIPT:
$('#submitNewModel').click(function () {
var form = $(this).closest('form');
var data = { model_description: document.getElementById('textNewModel').value };
$.ajax({
type: "POST",
dataType: "JSON",
url: '#Url.Action("createNewModel", "INV_Assets")',
data: data,
success: function (resp) {
alert("SUCCESS!");
$('#selectModel').append($('<option></option>').val(resp.ID).text(resp.Text));
alert("ID: " + resp.ID + " // New Model: " + resp.Text); // RETURNING 'undefined'...?
form[0].reset();
$('#createModelFormContainer').hide();
},
error: function () {
alert("ERROR!");
}
});
});
When you cannot quickly deduce why your ModelState validation fails, it's often helpful to quickly iterate over the errors.
foreach (ModelState state in ModelState.Values.Where(x => x.Errors.Count > 0)) { }
Alternatively you can pull out errors directly.
var allErrors = ModelState.Values.SelectMany(x => x.Errors);
Keep in mind that the ModelState is constructed BEFORE the body of your Action is executed. As a result, IsValid will already be set, regardless of how you set your model's properties once you are inside of the Controller Action.
If you want the flexibility to manually set properties and then re-evalute the validity of the object, you can manually rerun the validation inside of your Action after setting the properties. As noted in the comments, you should clear your ModelState before attempting to revalidate.
ModelState.Clear();
ValidateModel(model);
try
{
if (ModelState.IsValid)
{
db.INV_Models.Add(model);
db.SaveChangesAsync();
}
}
...
As an aside, if the model is still not valid ValidateModel(model) will throw an exception. If you'd like to prevent that, use TryValidateModel, which returns true/false instead:
protected internal bool TryValidateModel(Object model)
You should not be using a hack like ModelState.Clear() nor is TryValidateModel(model); required. Your issue stems from the fact you have a [Required] attribute on both your created_date and created_by properties but you don't post back a value, so they are null and validation fails. If you were to post back a more complex model, then you would use a view model which did not even have properties for created_date and created_by (its a Create method, so they should not be set until you post back).
In your case a view model is not necessary since your only posting back a single value ( for model-description) used to create a new INV_Models model.
Change the 2nd line in the script to
var data = { description: $('#textNewModel').val() };
Change your post method to
[HttpPost]
public JsonResult createNewModel(string description)
{
// Initialize a new model and set its properties
INV_Models model = new INV_Models()
{
model_description = description,
created_date = DateTime.Now,
created_by = System.Environment.UserName
};
// the model is valid (all 3 required properties have been set)
try
{
db.INV_Models.Add(model);
db.SaveChangesAsync();
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(ex);
}
return Json( new { ID = model.Id, Text = model.model_description }, JsonRequestBehavior.AllowGet);
}
Side notes:
I suggest modified_date be DateTime? (nullable in database
also). You are creating a new object, and are setting the
created_date and created_by properties, but setting
modified_date and modified_by properties does not seem
appropriate (it hasn't been modified yet).
I suspect you don't really want to set created_by to
System.Environment.UserName (it would be meaningless to have
every record set to administrator or whatever UserName of the
server returns. Instead you need to get users name from Identity
or Membership whatever authorization system you are using.
The model state is calculated when the binding from your post data to model is done.
The ModelState.IsValid property only tells you if there are some errors in ModelState.Errors.
When you set your created date you will need to remove the error related to it from ModelState.Errors
I have followed some articles and tutorials over the internet in order to create a custom validation attribute that also supports client-side validation in an asp.net mvc 4 website. This is what i have until now:
RequiredIfAttribute.cs
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] //Added
public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
private readonly string condition;
private string propertyName; //Added
public RequiredIfAttribute(string condition)
{
this.condition = condition;
this.propertyName = propertyName; //Added
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
PropertyInfo propertyInfo = validationContext.ObjectType.GetProperty(this.propertyName); //Added
Delegate conditionFunction = CreateExpression(validationContext.ObjectType, _condition);
bool conditionMet = (bool)conditionFunction.DynamicInvoke(validationContext.ObjectInstance);
if (conditionMet)
{
if (value == null)
{
return new ValidationResult(FormatErrorMessage(null));
}
}
return ValidationResult.Success;
}
private Delegate CreateExpression(Type objectType, string expression)
{
LambdaExpression lambdaExpression = System.Linq.Dynamic.DynamicExpression.ParseLambda(objectType, typeof(bool), expression); //Added
Delegate function = lambdaExpression.Compile();
return function;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "requiredif",
ErrorMessage = ErrorMessage //Added
};
modelClientValidationRule.ValidationParameters.Add("param", this.propertyName); //Added
return new List<ModelClientValidationRule> { modelClientValidationRule };
}
}
Then i applied this attribute in a property of a class like this
[RequiredIf("InAppPurchase == true", "InAppPurchase", ErrorMessage = "Please enter an in app purchase promotional price")] //Added "InAppPurchase"
public string InAppPurchasePromotionalPrice { get; set; }
public bool InAppPurchase { get; set; }
So what i want to do is display an error message that field InAppPurchasePromotionalPrice is required when InAppPurchase field is true (that means checked in the form). The following is the relevant code form the view:
<div class="control-group">
<label class="control-label" for="InAppPurchase">Does your app include In App Purchase?</label>
<div class="controls">
#Html.CheckBoxFor(o => o.InAppPurchase)
#Html.LabelFor(o => o.InAppPurchase, "Yes")
</div>
</div>
<div class="control-group" id="InAppPurchasePromotionalPriceDiv" #(Model.InAppPurchase == true ? Html.Raw("style='display: block;'") : Html.Raw("style='display: none;'"))>
<label class="control-label" for="InAppPurchasePromotionalPrice">App Friday Promotional Price for In App Purchase: </label>
<div class="controls">
#Html.TextBoxFor(o => o.InAppPurchasePromotionalPrice, new { title = "This should be at the lowest price tier of free or $.99, just for your App Friday date." })
<span class="help-inline">
#Html.ValidationMessageFor(o => o.InAppPurchasePromotionalPrice)
</span>
</div>
</div>
This code works perfectly but when i submit the form a full post is requested on the server in order to display the message. So i created JavaScript code to enable client-side validation:
requiredif.js
(function ($) {
$.validator.addMethod('requiredif', function (value, element, params) {
/*var inAppPurchase = $('#InAppPurchase').is(':checked');
if (inAppPurchase) {
return true;
}
return false;*/
var isChecked = $(param).is(':checked');
if (isChecked) {
return false;
}
return true;
}, '');
$.validator.unobtrusive.adapters.add('requiredif', ['param'], function (options) {
options.rules["requiredif"] = '#' + options.params.param;
options.messages['requiredif'] = options.message;
});
})(jQuery);
This is the proposed way in msdn and tutorials i have found
Of course i have also inserted the needed scripts in the form:
jquery.unobtrusive-ajax.min.js
jquery.validate.min.js
jquery.validate.unobtrusive.min.js
requiredif.js
BUT...client side validation still does not work. So could you please help me find what am i missing? Thanks in advance.
Take a look at this: http://thewayofcode.wordpress.com/tag/custom-unobtrusive-validation/
Using this tutorial I got my custom validation code running with no problem. The only difference I can spot in your code is the way you created the $.validator.unobtrusive.adapters.add function. The parameters are a little bit different but, maybe, the problem is just that you have not defined the rule part of your adapter.
Try using something like this:
$.validator.unobtrusive.adapters.add("requiredif", ["requiredif"], function (options) {
options.rules["requiredif"] = "#" + options.params.requiredif;
options.messages["requiredif"] = options.message;
});
or this
$.validator.unobtrusive.adapters.add("requiredif", function (options) {
options.rules["requiredif"] = "#" + options.element.name.replace('.', '_'); // mvc html helpers
options.messages["requiredif"] = options.message;
});
About the rule (taken from the link):
The jQuery rules array for this HTML element. The adapter is expected
to add item(s) to this rules array for the specific jQuery Validate
validators that it wants to attach. The name is the name of the jQuery
Validate rule, and the value is the parameter values for the jQuery
Validate rule.
It's worth noting that the [RequiredIf] attribute needs to be added to the second form field in the view model in order for client validation to work.
I am trying to implement a custom validator for phone number on an ASP.NET MVC 3 App I am writing. I have wriiten the code for the custom validator as below
public class PhoneNumberValidator : ValidationAttribute
{
public PhoneNumberValidator() : base("The Phone Number is not Valid")
{
}
public override bool IsValid(object value)
{
if (value != null)
{
string phonenumber = value.ToString();
var regex = new Regex(#"^(?:[0-9]+(?:-[0-9])?)*$");
if (regex.IsMatch(phonenumber))
{
return true;
}
else
{
return false;
}
}
return false;
}
}
Then in my Model class I have the following :
[Display(Name = "PhoneNumber")]
[Required(ErrorMessage = "Is Phone Number Required")]
[PhoneNumberValidator]
public string PhoneNumber { get; set; }
However when I run my app and click the proceed button on the page it does not throw an error if the value entered is letters although if I set a breakpoint I can see that the value is being read in to string phonenumber ok. Am I missing something simple?
You seem to be reinventing a wheel. Why not use the existing regex validator:
public class MyViewModel
{
[Display(Name = "PhoneNumber")]
[Required(ErrorMessage = "Is Phone Number Required")]
[RegularExpression(#"^(?:[0-9]+(?:-[0-9])?)*$")]
public string PhoneNumber { get; set; }
}
This being said validation is triggered by the model binder, so make sure that the controller action you are submitting the form to takes the view model as argument:
[HttpPost]
public ActionResult Process(MyViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid => redisplay view
return View(model);
}
// at this stage the model is valid => you could do some processing here
// and redirect
...
}
or use the TryUpdateModel method (personally I prefer the previous approach though):
[HttpPost]
public ActionResult Process(FormCollection some_Dummy_Parameter_Thats_Not_Used_At_All_But_Which_We_Need_To_Avoid_The_Method_Overloading_Error_With_The_GET_Action_Which_Has_The_Same_Name)
{
var model = new MyViewModel();
if (!TryUpdateModel(model))
{
// the model is invalid => redisplay view
return View(model);
}
// at this stage the model is valid => you could do some processing here
// and redirect
...
}
Also in order to display the error message somewhere make sure that you have a corresponding placeholder in your view:
#Html.EditorFor(x => x.PhoneNumber)
#Html.ValidationMessageFor(x => x.PhoneNumber)
or use a validation summary helper:
#Html.ValidationSummary(false)