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;
}
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'm unable to pass the RequestVerificationToken from webpage to server using AngularJs.
My AngularJs Code is:
var app = angular.module('validation', []);
app.controller('SignUpController', function ($scope, $http) {
$scope.model = {};
$scope.email = {};
$scope.sendEmail = function () {
$http({
method: 'POST',
url: '/Contact/Test',
data: $scope.email,
headers: {
'RequestVerificationToken': $scope.antiForgeryToken
}
}).success();
};
});
Custom Attribute Code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CustomAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
private void ValidateRequestHeader(HttpRequestBase request)
{
string cookieToken = String.Empty;
string formToken = String.Empty;
string tokenValue = request.Headers["RequestVerificationToken"];
if (!String.IsNullOrEmpty(tokenValue))
{
string[] tokens = tokenValue.Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
try
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
ValidateRequestHeader(filterContext.HttpContext.Request);
}
else
{
AntiForgery.Validate();
}
}
catch (HttpAntiForgeryException e)
{
throw new HttpAntiForgeryException("Anti forgery token cookie not found");
}
}
}
Form is:
#functions{
public string GetAntiForgeryToken()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
<div ng-app="validation" ng-controller="SignUpController">
<form role="form" id="frmContact" action="#Url.Action("Index", "Contact")" method="POST">
<input id="antiForgeryToken" ng-model="antiForgeryToken" type="hidden" ng-init="antiForgeryToken='#GetAntiForgeryToken()'" />
<fieldset class="form-group">
#Html.LabelFor(x => x.EmailTitle)
#Html.TextBoxFor(x => x.EmailTitle, new { placeholder = #Resource.EmailTitle, #class = "form-control", data_ng_model = "new.email.title" })
</fieldset>
<fieldset class="form-group">
#Html.LabelFor(x => x.EmailAddress)
#Html.TextBoxFor(x => x.EmailAddress, new { placeholder = #Resource.EmailAddress, #class = "form-control", data_ng_model = "new.email.address" })
</fieldset>
<fieldset class="form-group">
#Html.LabelFor(x => x.EmailMessage)
#Html.TextAreaFor(x => x.EmailMessage, new { placeholder = #Resource.EmailMessage, #class = "form-control", data_ng_model = "new.email.message" })
</fieldset>
<div>
<button type="submit" name="btnEmailForm" id="btnEmailForm" class="btnLogin" ng-click="sendEmail()" value="sendMessage">#Resource.ContactFormSendMessageButton</button>
</div>
<div id="errorMessages" class="error">{{message}}</div>
</form>
</div>
I have read the following posts, but cannot seem to solve the problem, and also took code from https://github.com/techbrij/angularjs-asp-net-mvc which works in that example but not in my MVC application:
http://techbrij.com/angularjs-antiforgerytoken-asp-net-mvc
https://parthivpandya.wordpress.com/2013/11/25/angularjs-and-antiforgerytoken-in-asp-net-mvc/
AngularJS Web Api AntiForgeryToken CSRF
http://bartwullems.blogspot.co.uk/2014/10/angularjs-and-aspnet-mvc-isajaxrequest.html
Where exactly to put the antiforgeryToken
http://www.ojdevelops.com/2016/01/using-antiforgerytokens-in-aspnet-mvc.html
Can anyone help with this problem
At this case you perform form submit and $scope.sendEmail operations and they may conflict one with another, to prevent this behavior you can use ng-submit directive. And also add attributes: name= '__RequestVerificationToken' and ng-value="antiForgeryToken" to corresponding input.
Important: Default behavior of [ValidateAntiForgeryToken] expects __RequestVerificationToken token in form values. To send request to server in form values format one requires content-type to be set to application/x-www-form-urlencoded. But I didn't have this option unfortunately and my content-type was application/json. Hence I took this custom path.
Let me explain the approach I took which worked.
Step 1: Declare #Html.AntiForgeryToken() in your view (.cshtml) as shown below:
<form id="inputForm" name="inputForm" ng-submit="submit(broker)" novalidate>
#Html.AntiForgeryToken()
/* other controls of form */
</form>
Step 2: #Html.AntiForgeryToken() will render an hidden field which will hold the token value as:
<input name="__RequestVerificationToken" type="hidden" value="GvTcz2tTgHOS2KK_7jpHvPWEJPcbJmHIpSAlxY1">
Step 3: Create custom attribute for Anti-forgery token verification as
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class HbValidateAntiForgeryToken : FilterAttribute, IAuthorizationFilter, IExceptionFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
try
{
var antiForgeryCookie = filterContext.HttpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(antiForgeryCookie != null ? antiForgeryCookie.Value : null,
filterContext.HttpContext.Request.Headers["__RequestVerificationToken"]);
}
catch (Exception ex)
{
throw new SecurityException("Unauthorised access detected and blocked");
}
}
public void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception != null &&
filterContext.Exception is System.Security.SecurityException)
{
filterContext.Result = new HttpUnauthorizedResult();
// Handle error page scenario here
}
}
}
Step 4: Declare the above attribute wherever required (Only on HttpPost methods of controller. DO NOT declare on HttpGet)
[HttpPost]
[HbValidateAntiForgeryToken]
public JsonResult IsUsernameExists(string username)
{
}
Step 5: In AngularJS, in factory pass __RequestVerificationToken as header.
hbServices.factory('RegistrationService', ['$resource',
function ($resource) {
return $resource(applicationPath + 'api/MyUserMembership/:dest', {}, {
createNewUser: { method: 'POST', isArray: false, params: { dest: 'CreateNewUser' },
headers: {
'__RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
}
},
isUsernameExists: { method: 'POST', isArray: false, params: { dest: 'IsUsernameExists' },
headers: {
'__RequestVerificationToken': $('input[name="__RequestVerificationToken"]').val()
}
}
});
}]);
Please note the way I am passing value of __RequestVerificationToken read from hidden field that was rendered by ASP.NET MVC's #Html.AntiForgeryToken().
My application was using jquery and has reference of jquery already so reading the value was easy. You can try other methods to read this value
Summary
The AntiForgery.Validate() does the magic of validating the value of forgery token here and is wonderful so far. Hope this helps!
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 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'm currently working on a ASP.NET MVC 4 project as a trainee and I'm trying to implement an admin panel. The goal is to show all the users on a grid (MVC.GRID) and edit them on the same page.
I've managed to show all the users on the grid and once a user is selected it shows the info below the grid and puts it in a form (via ajax/jquery).
The problem is: the form validation is being displayed on a new page and not on the page where the grid is at. And I've no idea why..
Below is my code.
This is where the form is placed:
<div id="order-content">
<p class="muted">
Select a user to see his or her information
</p>
</div>
The form itself (partial view "_UserInfo):
#using (Ajax.BeginForm("EditUser", "Admin", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "order-content"
}))
{
#Html.Bootstrap().ValidationSummary()
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Id)
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Name)
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Password)
#Html.Bootstrap().SubmitButton().Text("Opslaan").Style(ButtonStyle.Primary)
}
JQuery to show the user info once a row is selected:
$(function () {
pageGrids.usersGrid.onRowSelect(function (e) {
$.post("/Admin/GetUser?id=" + e.row.Id, function (data) {
if (data.Status <= 0) {
alert(data.Message);
return;
}
$("#order-content").html(data.Content);
});
});
});
My AdminController:
[HttpPost]
public JsonResult GetUser(int id)
{
var user = _UserService.Get(id);
var input = _EditInputMapper.MapToInput(user);
if (user == null)
return Json(new { Status = 0, Message = "Not found" });
return Json(new { Content = RenderPartialViewToString("_UserInfo", input) });
}
[HttpPost]
public ActionResult EditUser(AdminUserEditInput input)
{
if (ModelState.IsValid)
{
// todo: update the user
return View();
}
// This is where it probably goes wrong..
return PartialView("_UserInfo",input);
}
Can anyone see what is wrong with my code?
Thank you.
When the ModelState is valid and you return View(), does this full view gets embedded in order-content? I suspect not, and if so it would be because the ajax request is not being sent . You may not have included the jquery.unobtrusive-ajax js file
I've got it working now.. With the use of an jsonvalidator. I don't know if it's a good solution but it does the job for now..
This is what I've changed in my AdminController
[HttpPost]
public ActionResult EditUser(AdminUserEditInput input)
{
int id = (int)TempData["UserID"];
if (ModelState.IsValid)
{
_UserService.ChangeMail(id, input.Mail);
_UserService.ChangeName(id, input.Firstname, input.Name);
return new RedirectResult(Url.Action("Users", "Admin") + "#id=" + id);
}
else
{
return new JsonResult { Data = new { Valid = false, Errors = Validator.CheckModelErrors(ModelState) } };
}
}
Added a JsonValidator class:
public static class Validator // todo: doesn't belong in the controllers directory ?
{
public static List<JsonError> CheckModelErrors(System.Web.Mvc.ModelStateDictionary modelstate)
{
List<JsonError> errorlist = new List<JsonError>();
if (modelstate != null && modelstate.Values != null)
{
foreach (var m in modelstate.Values)
{
if (m.Errors != null)
{
foreach (var e in m.Errors)
{
errorlist.Add(new JsonError() { ErrorMessage = e.ErrorMessage ?? "" });
}
}
}
}
return errorlist;
}
}
And a JsonError class..
public class JsonError // todo: doesn't belong in the controllers directory ?
{
public string ErrorMessage { get; set; }
public bool HasError { get; set; }
public bool CanPerform { get; set; }
}
Last but not least, js:
$(document).on('submit', '#UserForm', function (e) {
e.defaultPrevented();
$('#UserForm').validate();
$.post($(this).attr("action"), $(this).serialize(), function (json) {
if (json.Valid) {
$("#order-content").html('<p class="muted"> Select a user.</p>');
}
else {
$("#ShowValidation").empty();
var list = $("#ShowValidation").append('<ul></ul>').find('ul');
$.each(json.Errors, function (index, optionData) {
list.append('<li>' + optionData.ErrorMessage + '</li>');
})
}
}, "json");
});
I was thinking about another way to manage this because this is just temporary..
Should it be a good idea to store the input with the validation messages in a session and let js put it in the _UserInfo view?