Conditional Validation in asp.net MVC4 - jquery-validate

I want to be able to kick off some validation functions based upon what controller a view is called from... I will set a variable in ViewState or something and that will help me to know what controller this view was called from.
In other words, I want the validation to be required if a certain variable is set... Here is how I use to do in MVC2 when I just put Jquery into my code...
HospitalFinNumber: {
required: function (element) {
debugger;
return '#isFlagSet' != 'True';
},
minlength: 6,
remote: function () {
//debugger;
return {
url: '#Url.Action("ValidateHosFin", "EditEncounter")',
data: { hospitalFin: $('#HospitalFinNumber').val(), encflag: '#encflag' }
};
}
}
You see what I am doing there. This validation would only be required if a certain variable is set... In this case, the variable isFlagSet... I would then set min Length and call a remote function to ensure that the value is unique.
I don't want to do this in all cases.
From all I have read so far, there is no clear way to accomplish this using unobrtusive ajax? Am I wrong, is there a way you can do this? If not, how can I just place regular old jquery validation into my code?

ASP.NET MVC 3 uses jquery unobtrusive validation to perform client side validation. So you could either write a custom RequiredIf validation attribute or use the one provided in Mvc Foolproof Validation and then:
public class MyViewModel
{
[RequiredIf("IsFlagSet", true)]
[Remote("ValidateHosFin", "EditEncounter")]
[MinLength(6)]
public string HospitalFinNumber { get; set; }
public bool IsFlagSet { get; set; }
public string EncFlag { get; set; }
}
Then all that's left is to include the jquery.validate.js and jquery.validate.unobtrusive.js scripts or use the corresponding bundle in ASP.NET MVC 4 that includes them.

Another solution suggested by Andy West on his blog is to Conditionally Remove Fields from the Model State in the Controller:
When the form is posted, remove the fields from the model state so they’re not validated:
if (Request.IsAuthenticated)
{
ModelState.Remove("CommenterName");
ModelState.Remove("Email");
}
That worked for me.

Related

Passing HTML string to my controller action method

I am using a rich text editor to type formatted text, as shown below:
I can get the HTML formatted text, which would look like this:
<p>This is my rich HTML Text</p>
Now I want to pass this HTML formatted text to my controller and my controller would put the text in an email and send it to the receiver.
The problem is HTML string is considered unsafe, so in order to pass it to my controller, I need to add [ValidateInput(false)] attribute to my Action method, like below:
[ValidateInput(false)] // <-- not able to hit the action method without this
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<JsonResult> Contact(string message)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
return Json(new { Authorize = "false" });
}
// email message to receiver
}
And this is the Ajax method which contacts the controller:
$('#contactBtn').click(function () {
var form = $('#__AjaxAntiForgeryForm');
var token = $('input[name="__RequestVerificationToken"]', form).val();
var message = quill.root.innerHTML; // <-- HTML formatted message
$.ajax({
url: "/Communication/Contact",
data: { __RequestVerificationToken: token, message: message },
dataType: 'json',
type: "POST"
});
});
So the above code works, but I am not sure if this is the right thing to do? Is there any security issue with the above code? Is there any encoding that I need to do on the HTML?
Actually ValidateInput attribute is related to XSS (Cross Site Security) issue.
XSS(Cross Site Security) is a security attack where the attacker injects malicious code while doing data entry. This code can be a javascript, vbscript or any other scripting code. Once the code is injected in end user’s browser. This code can run and gain access to cookies, sessions, local files and so on.
Now the good news is that XSS is by default prevented in ASP.NET MVC. So if any one tries to post JavaScript or HTML code with input he lands with the following error.
A potentially dangerous Request.Form value was detected from the client.....
But in real life there are scenarios where HTML has to be allowed, like HTML editors. So for those kind of scenarios we decorate our action method with ValidateInput attribute as follows:
[ValidateInput(false)]
public async Task<JsonResult> Contact(string message)
{
}
But there is problem of doing this. We are allowing HTML and script on the complete action which can be dangerous. Suppose the form we are posting have five input text fields, now the all five text fields can contain HTML and scripts.
Instead this Microsoft article suggest:
For ASP.NET MVC 3 or greater applications, when you need to post HTML back to your model, don’t use ValidateInput(false) to turn off Request Validation. Simply add [AllowHtml] to your model property, like so:
public class BlogEntry
{
public int UserId {get;set;}
[AllowHtml]
public string BlogText {get;set;}
}
So bottom line is that ValidateInput allows scripts and HTML to be posted on the whole action level while AllowHTML is on a more granular level.
For more details you can read ASP.NET Security - Securing Your ASP.NET Applications
Using [ValidateInput(false)] on the action method is not a good approach, as there could be other input parameters that don't get validated... using [AllowHtml] works if we are passing in a Model...
For this scenario, we could do what is explained in this this tutorial:
My solution is based on the tutorial above, except I have added sanitization logic to model binder, which means we allow the HTML input, but use HTMLSanitizer to sanitize the input.
Defined a custom model binder:
public class AllowHtmlBinder: IModelBinder
{
// use HtmlSanitizer to remove unsafe HTML/JS from input
private HtmlSanitizer _htmlSanitizer = new HtmlSanitizer();
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var name = bindingContext.ModelName;
var unvalidatedInputMessage = request.Unvalidated[name]; // get the unvalidated input
var sanitizedMessage = _htmlSanitizer.Sanitize(unvalidatedInputMessage); // removed script or any XSS thread from user input
return sanitizedMessage;
}
}
And used it on the specific parameter:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<JsonResult> Contact([ModelBinder(typeof(AllowHtmlBinder))] string message)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
return Json(new { Authorize = "false" });
}
// email message to receiver
}

File posted to MVC Controller is null

I want to post three things to my MVC Controller: one image and two strings.
On the View, I've got a form that uses enctype="multipart/form-data" that automatically submits the form after an image file is selected. This is the submit handler for this form:
$("#PhotoUploadForm").on("submit", function (event) {
event.preventDefault();
var ImageData = $("#PhotoUploadFileInput").val();
var GuestNumber = $("#GuestID").val();
var TCSA_ID = vm.GetSelectedTreatmentAreaTCSA_ID(vm.Photographs.SelectedTreatmentArea());
var dto = {
ImageData: ImageData,
GuestNumber: GuestNumber,
TCSA_ID: TCSA_ID
}
$.ajax({
url: 'SaveImage',
type: "POST",
contentType: "multipart/form-data",
data: ko.toJSON(dto),
success: function (data) {
console.log(submitted);
}
});
});
The dto object is defined in my Model:
public class PhotoUploadDTO
{
public HttpPostedFileBase ImageData { get; set; }
public string GuestNumber { get; set; }
public string TCSA_ID { get; set; }
}
And in my Controller, I have an action that takes in dto as a parameter:
public ActionResult SaveImage(PhotoUploadDTO dto)
{
//etc.
}
When I try to post dto, everything gets posted as null. This problematic for me because I want to be able to post the image and two strings to the controller simultaneously.
I suspect that the issue is with var ImageData (which is set to the value of <input type="file" id="PhotoUploadFileInput"> on my View), and that it is being posted as C:/fakepath/etc. but not as the actual image file. It's frustrating because I know it wouldn't even be an issue if I had a form that just posted the image, but I need to use this submit handler and I don't know how to bring the actual image data into it.
Why is the data null when it hits the MVC Controller, and how can I post these three items while still being able to use a submit handler?
Uploading a file via ajax is a tricky thing. Some of the most modern web browsers handle this by using the File API which will indeed work for uploading a file via ajax. However, using this solution will not work with people on older browsers.
Your best bet is using a jquery plugin or something similar that will fall back on techniques such as uploading the file via an iframe or other workarounds.

Show validation messages on GET

We have a possibility that data loaded from a GET operation could be invalid for posting, and would like to be able to display the validation messages when the data is first loaded. The validation all takes place on server side using ValidationAttributes.
How can I force the validation summary to be displayed when the data is first loaded? I am guessing that I need to force errors into ModelState somehow, but I first need to get them out of the model class.
I ended up adding a validation method for the model class which adds errors to the ModelState. Then I created and added a custom ModelValidator and AssociatedValidatorProvider
for calling it during the normal validation that takes place during form binding. That way the controller actions that don't bind to the Model class directly can still have a call to the model's .Validate(ModelState) method to fake a validation. This approach works well for server-side-only validation.
UserInfo Model class:
private IEnumerable<RuleViolation> GetRuleViolations()
{
List<RuleViolation> violationList = new List<RuleViolation>();
if (String.IsNullOrWhiteSpace(FirstName))
violationList.Add(new RuleViolation("First Name is required.", FirstName"));
return violationList;
}
public void Validate(System.Web.Mvc.ModelStateDictionary ModelState)
{
foreach (RuleViolation violation in GetRuleViolations())
{
ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
}
}
This is how it can be used directly from a controller action. In this action the Model class object is returned as part of the UserSearch model.
public ActionResult Search(UserSearch model)
{
if (this.ModelState.IsValid)
{
model.Search();
if (model.UserInfo != null )
{
model.UserInfo.Validate(ModelState);
}
}...
That is all I had to do for the particular use case I was working on. But I went ahead and completed the work to do "normal" validation on a postback: created a simple ModelValidator, with the Validate override looking like this. If you followed the above pattern in all of your Model classes you could probably reusue this for them, too.
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var results = new List<ModelValidationResult>();
if (Metadata.Model != null)
{
UserInfoViewModel uinfo = Metadata.Model as UserInfoViewModel;
foreach (var violation in uinfo.GetRuleViolations())
{
results.Add(new ModelValidationResult
{
MemberName = violation.PropertyName,
Message = violation.ErrorMessage
});
}
}
return results;
}
Finally, extend AssociatedValidationProvider to return this ModelValidator and add it to the ModelValidationProviders collection in Application_Start. There is a writeup of this at http://dotnetslackers.com/articles/aspnet/Customizing-ASP-NET-MVC-2-Metadata-and-Validation.aspx#s2-validation
I don't know if understand what you need, but here is it...
run validation to display the validation summary when the form is loaded, using jquery
$(document).ready(function() {
$('#FormId').valid();
});

Using modelstate.isvalid to validate data from inside the controller in MVC3

I am pretty new to ASP.NET MVC3 but i have about 4 years of experience with PHP frameworks.
I am trying to build an MVC3 web app, but i am having issues with validationg my model.
Here is a test controller to show you what i am trying without success to do.
I am trying to pass a value to my model inside the controller, but it doesnt take it into account the parameter.
I tried using modelstate.setmodelvalue, for junk.sentence, but it keeps the value from the POST request which is invalid an that i want to change by default (for test purposes) in the controller.
Can anyone help?
Thanks in advance.
Michael
[HttpPost]
public ActionResult Create(Junk junk)
{
//ModelState.Clear();
junk.sentence = "coucou";
ModelState.SetModelValue("sentence", new ValueProviderResult(junk.sentence, junk.number, null));
//ModelState
if (ModelState.IsValid)
{
db.Junks.Add(junk);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(junk);
}
//
// GET: /Junk/Edit/5
public ActionResult Edit(int id)
{
Junk junk = db.Junks.Find(id);
return View(junk);
}
Try removing it from modelstate:
[HttpPost]
public ActionResult Create(Junk junk)
{
junk.sentence = "coucou";
//ModelState
if (ModelState.IsValid)
{
db.Junks.Add(junk);
db.SaveChanges();
return RedirectToAction("Index");
}
ModelState.Remove("sentence");
return View(junk);
}
This assumes that in your view you have a corresponding input field that was generated using some of the Html helpers such as EditorFor for example:
#Html.EditorFor(x => x.sentence)
or:
#Html.TextBoxFor(x => x.sentence)
ModelState.IsValid returns false when there are model errors added to the model state. MVC validates the properties on your model and creates a list of errors for you in the ModelState. You must remove the errors from the model state that you wish to be ignored from inside your controller action. You can then update the actual values on the model. (Darin Dimitrov shows you an example of doing this)

MVC Routes based on POST parameters

We have an a PHP application that we are converting to MVC. The goal is to have the application remain identical in terms of URLs and HTML (SEO and the like + PHP site is still being worked on). We have a booking process made of 3 views and in the current PHP site, all these view post back to the same URL, sending a hidden field to differentiate which page/step in the booking process is being sent back (data between pages is stored in state as the query is built up).
To replicate this in MVC, we could have a single action method that all 3 pages post to, with a single binder that only populates a portion of the model depending on which page it was posted from, and the controller looks at the model and decides what stage is next in the booking process. Or if this is possible (and this is my question), set up a route that can read the POST parameters and based on the values of the POST parameters, route to a differen action method.
As far as i understand there is no support for this in MVC routing as it stands (but i would love to be wrong on this), so where would i need to look at extending MVC in order to support this? (i think multiple action methods is cleaner somehow).
Your help would be much appreciated.
I have come upon two solutions, one devised by someone I work with and then another more elegant solution by me!
The first solution was to specify a class that extends MVcRouteHandler for the specified route. This route handler could examine the route in Form of the HttpContext, read the Form data and then update the RouteData in the RequestContext.
MapRoute(routes,
"Book",
"{locale}/book",
new { controller = "Reservation", action = "Index" }).RouteHandler = new ReservationRouteHandler();
The ReservationRouteHandler looks like this:
public class ReservationRouteHandler: MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var request = requestContext.HttpContext.Request;
// First attempt to match one of the posted tab types
var action = ReservationNavigationHandler.GetActionFromPostData(request);
requestContext.RouteData.Values["action"] = action.ActionName;
requestContext.RouteData.Values["viewStage"] = action.ViewStage;
return base.GetHttpHandler(requestContext);
}
The NavigationHandler actually does the job of looking in the form data but you get the idea.
This solution works, however, it feels a bit clunky and from looking at the controller class you would never know this was happening and wouldn't realise why en-gb/book would point to different methods, not to mention that this doesn't really feel that reusable.
A better solution is to have overloaded methods on the controller i.e. they are all called book in this case and then define your own custome ActionMethodSelectorAttribute. This is what the HttpPost Attribute derives from.
public class FormPostFilterAttribute : ActionMethodSelectorAttribute
{
private readonly string _elementId;
private readonly string _requiredValue;
public FormPostFilterAttribute(string elementId, string requiredValue)
{
_elementId = elementId;
_requiredValue = requiredValue;
}
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_elementId]))
{
return false;
}
if (controllerContext.HttpContext.Request.Form[_elementId] != _requiredValue)
{
return false;
}
return true;
}
}
MVC calls this class when it tries to resolve the correct action method on a controller given a URL. We then declare the action methods as follows:
public ActionResult Book(HotelSummaryPostData hotelSummary)
{
return View("CustomerDetails");
}
[FormFieldFilter("stepID", "1")]
public ActionResult Book(YourDetailsPostData yourDetails, RequestedViewPostData requestedView)
{
return View(requestedView.RequestedView);
}
[FormFieldFilter("stepID", "2")]
public ActionResult Book(RoomDetailsPostData roomDetails, RequestedViewPostData requestedView)
{
return View(requestedView.RequestedView);
}
[HttpGet]
public ActionResult Book()
{
return View();
}
We have to define the hidden field stepID on the different pages so that when the forms on these pages post back to the common URL the SelectorAttributes correctly determines which action method to invoke. I was suprised that it correctly selects an action method when an identically named method exists with not attribute set, but also glad.
I haven't looked into whether you can stack these method selectors, i imagine that you can though which would make this a pretty damn cool feature in MVC.
I hope this answer is of some use to somebody other than me. :)

Resources