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.
Related
This question raises after what I've tried from the answer mentioned in my previous question. I followed this article exactly the same way but validations for image files instead of doc files mentioned in the article.
Description: I have a input control of type=file which is to upload image files and this exists in one of the partialview. The partialview gets loaded on click of a button. And to apply validations mentioned in model, explicitly add unobtrusive to the form. But after following all the set-ups mentioned in the above-said article, I am not able to validate the file on submit also the the data-* created by unobtrusive validation is quite fishy or better say invalid. Below is the code to show how my setup looks like and here is the html which gets created by unobtrusive validation with invalid data-* attribute, may be because of which the validation fails to happen.
<input data-charset="file" data-val="true" data-val-fileextensions="" data-val-fileextensions-fileextensions="png,jpg,jpeg" id="File" multiple="multiple" name="File" type="file" value="">
Load Partial View Js
$('.getpartial').on('click', function () {
$('.loadPartial').empty().load('/Home/GetView',function () {
var form = $('form#frmUploadImages');
form.data('validator', null);
$.validator.unobtrusive.parse(form);
$(function () {
jQuery.validator.unobtrusive.adapters.add('fileextensions', ['fileextensions'], function (options) {
var params = {
fileextensions: options.params.fileextensions.split(',')
};
options.rules['fileextensions'] = params;
if (options.message) {
options.messages['fileextensions'] = options.message;
}
});
jQuery.validator.addMethod("fileextensions", function (value, element, param) {
var extension = getFileExtension(value);
var validExtension = $.inArray(extension, param.fileextensions) !== -1;
return validExtension;
});
function getFileExtension(fileName) {
var extension = (/[.]/.exec(fileName)) ? /[^.]+$/.exec(fileName) : undefined;
if (extension != undefined) {
return extension[0];
}
return extension;
};
}(jQuery));
})
})
ModelClass
public class ImageUploadModel
{
[FileValidation("png|jpg|jpeg")]
public HttpPostedFileBase File { get; set; }
}
View
#model ProjectName.Models.ImageUploadModel
#using (Html.BeginForm("UploadImages", "Admin", FormMethod.Post, htmlAttributes: new { id = "frmUploadImages", novalidate = "novalidate", autocomplete = "off", enctype = "multipart/form-data" }))
{
<div class="form-group">
<span class="btn btn-default btn-file">
Browse #Html.TextBoxFor(m => m.File, new { type = "file", multiple = "multiple", data_charset = "file" })
</span>
<span class="text-muted" id="filePlaceHolder">No files selected</span>
#Html.ValidationMessageFor(m => m.File, null, htmlAttributes: new { #class = "invalid" })
</div>
<div class="form-group">
<button class="btn btn-primary addImage pull-right">
<i class="fa fa-upload"></i> Upload
</button>
</div>
}
and finally my CustomFileValidation class
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FileValidationAttribute : ValidationAttribute, IClientValidatable
{
private List<string> ValidExtensions { get; set; }
public FileValidationAttribute(string fileExtensions)
{
ValidExtensions = fileExtensions.Split('|').ToList();
}
public override bool IsValid(object value)
{
HttpPostedFileBase file = value as HttpPostedFileBase;
if (file != null)
{
var fileName = file.FileName;
var isValidExtension = ValidExtensions.Any(y => fileName.EndsWith(y));
return isValidExtension;
}
return true;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientFileExtensionValidationRule(ErrorMessage, ValidExtensions);
yield return rule;
}
}
public class ModelClientFileExtensionValidationRule : ModelClientValidationRule
{
public ModelClientFileExtensionValidationRule(string errorMessage, List<string> fileExtensions)
{
ErrorMessage = errorMessage;
ValidationType = "fileextensions";
ValidationParameters.Add("fileextensions", string.Join(",", fileExtensions));
}
}
You need to move the block code
$(function () {
....
}(jQuery));
from inside the $('.getpartial').on(..) function to before it so that its is
<script>
$(function () {
....
}(jQuery));
$('.getpartial').on('click', function () { // or just $('.getpartial').click(function() {
$('.loadPartial').empty().load('/Home/GetView',function () { // recommend .load('#Url.Action("GetView", "Home")', function() {
var form = $('form#frmUploadImages');
form.data('validator', null);
$.validator.unobtrusive.parse(form);
});
});
</script>
Currently your load the content, re-parse the validator and then add add the methods to jquery validation but its to late (the validator has already been parsed)
Sidenote: You do not need to wrap the validation functions in $(function () {. It can be deleted and simply use $.validator... instead of jQuery.validator.... as your doing elsewhere in your code.
As for the 'fishy' data-val-* attributes, that is exactly what your code generates. Your generating a ClientValidationRule named fileextensions (the ValidationType = "fileextensions"; code) and then you add a property of it also named fileextensions (the ValidationParameters.Add("fileextensions", ..) code which generates data-val-fileextensions-fileextensions="png,jpg,jpeg". As for data-val-fileextensions="", that is generated to store the error message but you have not generated one so its an empty string.
I would suggest a few changes to your code.
Rename it to FileTypeAttribute so that you have the flexibility to
add other file validation attributes, for example
FileSizeAttribute to validate the maximum size.
In the constructor, generate a default error message, for example
add private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}"; and in the last line of the constructor include ErrorMessage = string.Format(_DefaultErrorMessage, string.Join(" or ", ValidExtensions));
Change ValidationParameters.Add("fileextensions", ...) to (say)
ValidationParameters.Add("validtypes", ...) so it generates
data-val-fileextensions-validtypes="png,jpg,jpeg" which is a bit
more meaningful (note you will need to change the script to
...add('fileextensions', ['validtypes'], function() ....
Edit
Your code will not work with <input type="file" multiple="multiple" ... /> In order to do so your property needs to be IEnumerable (note a few minor changes to your code)
[FileType("png, jpg, jpeg")]
public IEnumerable<HttpPostedFileBase> Files { get; set; }
Then the validation attribute needs to check each file in the collection
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FileTypeAttribute : ValidationAttribute, IClientValidatable
{
private const string _DefaultErrorMessage = "Only the following file types are allowed: {0}";
private IEnumerable<string> _ValidTypes { get; set; }
public FileTypeAttribute(string validTypes)
{
_ValidTypes = validTypes.Split(',').Select(s => s.Trim().ToLower());
ErrorMessage = string.Format(_DefaultErrorMessage, string.Join(" or ", _ValidTypes));
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
IEnumerable<HttpPostedFileBase> files = value as IEnumerable<HttpPostedFileBase>;
if (files != null)
{
foreach(HttpPostedFileBase file in files)
{
if (file != null && !_ValidTypes.Any(e => file.FileName.EndsWith(e)))
{
return new ValidationResult(ErrorMessageString);
}
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "filetype",
ErrorMessage = ErrorMessageString
};
rule.ValidationParameters.Add("validtypes", string.Join(",", _ValidTypes));
yield return rule;
}
}
And finally the script needs to check each file
$.validator.unobtrusive.adapters.add('filetype', ['validtypes'], function (options) {
options.rules['filetype'] = { validtypes: options.params.validtypes.split(',') };
options.messages['filetype'] = options.message;
});
$.validator.addMethod("filetype", function (value, element, param) {
for (var i = 0; i < element.files.length; i++) {
var extension = getFileExtension(element.files[0].name);
if ($.inArray(extension, param.validtypes) === -1) {
return false;
}
}
return true;
});
function getFileExtension(fileName) {
if (/[.]/.exec(fileName)) {
return /[^.]+$/.exec(fileName)[0].toLowerCase();
}
return null;
}
I am trying to make an online bank website(for learning ASP.NET MVC).I have a class Account
class Account
{
int account_id;
String account_number;
decimal balance;
}
and I have a model for transaction.
public class MakeTransactionModel
{
[Required]
public String AccountFrom { get; set; }
[Required]
public String AccountTo { get; set; }
public Decimal OrignalBalance { get; set; }
[Required]
public Decimal Amount { get; set; }
[Required]
public String TransactionFor { get; set; }
}
Then in controller, i am putting accounts in ViewBag.
ViewBag.account_from = new SelectList(db.Accounts, "account_id", "account_number");
In View, I created a drop down for showing all accounts
#Html.DropDownListFor(u => u.AccountFrom, (SelectList)ViewBag.account_from, htmlAttributes: new { #class = "form-control", #id = "AccountFrom", onchange=#"
#Model.OrignalBalance = 1000; // I tried this but did not work
" })
Now , I am trying to show balance of selected account in an EditorFor
#Html.EditorFor(model => model.OrignalBalance, new { htmlAttributes = new { #id="OrignalBalance", #class = "form-control", disabled = "disabled", #readonly = "readonly" } })
I have all accountsin ViewBag and I am showing that accounts number in drop down (those accounts also have balance in it). I am trying to change value of EditorFor on DropDownFor value change but still unable to do that. I tried to do that using jquery, but i don't know can I use LINQ in jquery
My jquery code is
<script type="text/javascript">
$(document).ready(function () {
$(function () {
$('#AccountFrom').change(function () {
var selectedValue = $('#AccountFrom').text();
$('#OrignalBalance').val(#{new BankEntities().Accounts.SingleOrDefault(acc => acc.account_number == $('#AccountFrom').text())}); // I am trying to do this
});
});
}
)
</script>
It will be good if i find a good solution to do that, so I can update EditorFor on change event.
Thank you.
You should make an ajax call and pass the account number and get the amount from the server.
$(function()
{
$('#AccountFrom').change(function() {
var accountId= $('#AccountFrom').val();
var url="#Url.Action("Balance","Account")";
$.post(url+"?accountNumber="+accountId,function(response){
if(response.Status==="success")
{
$("#OrignalBalance").val(response.Balance);
}
else
{
alert("Invalid Account");
}
});
});
});
Assuming you have an action method to return the balance
[HttpPost]
public ActionResult Balance(string accountNumber)
{
//Of course you want to authorize the call
var db=new BankEntities();
var a= db.Accounts.FirstOrDefault(x=> x.account_number ==accountNumber);
if(a!=null)
{
return Json( new { Status="success", Balance=a.Balance });
}
else
{
return Json( new { Status="error"});
}
}
If you do not wish to make an action method and the ajax way, What you can do is, Create a dictionary of your account number and the balance and pass that as part of your view model and in your razor view, set that to a js object and in the change event you can query the js dictionary to get the value.
Also, I recommend to NOT use ViewBag to transfer data between your action method and your view for rendering the dropdown. You should add a strongly typed property to handle that.
So let's add some new properties to your view model.
public class MakeTransactionModel
{
// Your other existing properties here
public Dictionary<string,decimal> AccountBalances { set; get; }
// These 2 properties are for rendering the dropdown.
public int FromAccountId { set; get; }
public List<SelectListItem> FromAccounts { set; get; }
}
And in your GET action, fill this property with account number and corresponding balance value.
public ActionResult Transfer()
{
var vm = new MakeTransactionModel();
vm.AccountBalances = new Dictionary<string, decimal>();
// Hard coded for demo. You may read it from your db tables.
vm.AccountBalances.Add("CHECKING0001", 3450.50M);
vm.AccountBalances.Add("SAVINGS0001", 4450.50M);
//load the data for accounts.pls change to get from db
vm.FromAccounts = new List<SelectListItem>
{
new SelectListItem { Value="CHECKING0001", Text="Checking" },
new SelectListItem { Value="SAVINGS0001", Text="Saving" }
};
// to do : Load other properties also
return View(vm);
}
And in your razor view, serialize this property and set to a js object.
#model MakeTransactionModel
#using(Html.BeginForm())
{
#Html.DropDownListFor(s=>s.FromAccountId,Model.FromAccounts,"Select")
#Html.EditorFor(model => model.OrignalBalance,
new { #id="OrignalBalance", #class = "form-control",
disabled = "disabled", #readonly = "readonly" } )
<input type="submit" />
}
#section Scripts
{
<script>
var balanceDict = #Html.Raw(Newtonsoft.Json.JsonConvert
.SerializeObject(Model.AccountBalances));
$(function () {
$('#FromAccountId').change(function() {
var accountId= $('#AccountFrom').val();
var v = balanceDict[accountId];
$("#OrignalBalance").val(v);
});
});
</script>
}
It may not seem like it, but this is pretty broad. Basic rundown, you'll either have to:
Serialize all accounts and balances into JSON and store them client-side:
This is more code than is appropriate here, but you could use JSON.net to get JSON for new BankEntities().Accounts.ToList() (something you should be getting from your controller code, by the way, not in your view), then set a window variable to that in your JavaScript, and call upon that whenever the value changes.
Untested, but something like:
var balances = #Html.Raw(JsonConvert.SerializeObject(new BankEntities()
.Accounts
// Filter by logged in user
.ToDictionary(c => c.account_number, c.balance));
$(document).ready(function () {
$(function () {
$('#AccountFrom').change(function () {
var selectedValue = $('#AccountFrom').text();
$('#OrignalBalance').val(balances[selectedValue]);
});
});
}
Introduce an API call performed through AJAX to get balances whenever the value changes.
Shyju beat me to this one, but it's probably a better way to do it, as long as you're comfortable introducing an API element. It's kind of advanced for first learning MVC, but it's not too complicated.
This is what I'd do in a production application (although I'd do it with Web API), but for just playing, the first option is a little quicker, and probably easier to understand and debug fully if you're just getting started.
The confusion here comes from where code is executed. Your script can't refer to the BankEntities because it's running client-side, as compared to server side.
JQuery knows nothing about LINQ, since it is client based. So, I suggest making an ajax request when the account from gets changed.
for example, in the view, make the ajax call
<script type="text/javascript">
$(document).ready(function() {
$('#AccountFrom').change(function() {
var selectedAccountNumber = $('#AccountFrom option:selected').text();
$.ajax({
url: "/Accounts/GetAccountBalance",
type: "POST",
dataType: "json",
data: { accountNumber: selectedAccountNumber },
success: function (
$('#OrignalBalance').val(data.Balance);
}
});
});
});
</script>
and have the following in the controller (let's say that you have a controller called Accounts)
public ActionResult GetAccountBalance(string accountNumber)
{
var account = db.Accounts.SingleOrDefault(a => a.account_number == accountNumber);
// add validation logic for account not exits
return Json(new { AccountNumber = accountNumber, Balance = account.balance });
}
I'm currently making a C# application with MVC. There's a pretty ordinary registration form, where a user has to fill in a username and password, among other things. I'd like to implement a client side validation that checks whether or not the given password is strong enough or not. I already have a check in the setter of the Model, but it only checks when the submit button is pressed and thus the information is lost.
This is a simplified version of my check method:
public static bool isThisPasswordValid(string pw)
{
int score = 0;
//PW length is 8+
if (pw.Length < 8)
return false;
else
return true;
//there's also a check of whether or not there's a lowercase, upper case letter,
//a number and a special character. Left that bit out
}
I already got the basics of a class, which I think I need, though I can't figure out how to finish and implement it. Any help would be much appreciated!
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited=false)]
public class MyPasswordValidator : ValidationAttribute, IClientValidatable
{
private string currentPassword;
protected override ValidationResult IsValid (object value, ValidationContext validationContext)
{
if (isThisPasswordValid(value.ToString()))
{
return ValidationResult.Success;
}
else{
return new ValidationResult("Please enter a valid password");
}
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
return new[] { new ModelClientValidationSelectOneRule
(FormatErrorMessage(metadata.DisplayName), currentPassword.Split(new char[] { ',' })) };
}
Are you aware of the RemoteAttribute?
This attribute takes an 'ActionMethod' and 'Controller' name as a string parameter. By returning a Json result this will be automatically called if you use the jquery.validation package/script.
The attribute could be used like:
[Remote("ValidatePassword", "Account")]
public string NewPassword { get; set; }
and the ActionMethod in the AccountController could look like this:
[Authorize]
public async Task<JsonResult> ValidatePassword (string NewPassword)
{
var result = await this.DoSomeValidationOfPassword(NewPassword);
if (result)
{
return this.Json(true, JsonRequestBehavior.AllowGet);
}
else
{
return this.Json("The password has the following demands:\r\n"
+ string.Join("\r\n", result.Errors), JsonRequestBehavior.AllowGet);
}
}
In your view you can use a normal ValidationMessageFor html attribute like this:
#Html.PasswordFor(m => m.NewPassword, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.NewPassword, "", new { #class = "text-danger" })
Be sure to include the jquery.validate.js script!
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'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