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
}
Related
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.
So I'm creating a asp.NET MVC3 application and want to apply single page application functionality to parts of the application. I think the easiest way to explain is with an example:
The app consists of an admin area and a public area and is built using ordinary link-structure. I want to convert the admin area to an single page application reusing view and models from the existing application. Is it possible to do this and in that case how?
You have to face two main problems, which makes the difference between SPA and standard application:
Links: In standard application, each link redirects you to a different page.
Forms: When a form is been submitted, a request is been issued with the HTTP method you've specified in the post (usually POST) and it contains in the payload the data the user has entered.
In order to solve that problems, you have to take action both in client-side and server-side.
For explaining propose, lets take the following code:
HomeController.cs:
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
public ActionResult Contact() {
return View(new ContactUsViewModel());
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Contact(ContactUsViewModel model) {
if (ModelState.IsValid) {
/* Send mail / Save in DB etc. */
return Redirect("Index");
}
return View(model);
}
}
Index.cshtml:
<p>This is a simple page.</p>
<p>#Html.ActionLink("Click here to contact us", "Contact")
Client-Side:
We should fix up linking between pages, as well as forms submittions.
Links: You can wire up an event in JS (jQuery if you'd like) that'll observe for each link click in the areas you'd like to apply on SPA - then, instead of redirecting the user - you'll load the content via AJAX.
For instance, take a look at this sample:
$("a").click(function(e) {
e.preventDefault(); // Disable standard redirecting
var href = $(e.currentTarget).attr("href");
$.get(href, function(responseText) {
$("#main-content-wrapper").html(responseText);
});
});
Forms: Just like in the approch we've used for links, we can wire up an observer to the form submit event in JS and then transfer the data using AJAX.
For instance:
$("form").submit(function(e) {
e.preventDefault(); // Disable standard submittion
var data = $(e.currentTarget).serialize(); // Serializing the form data
var method = $(e.currentTarget).attr("method");
if (typeof (method) == "undefined") { method = "POST"; }
$.ajax({
method: $(e.currentTarget).attr("method"),
parameters: data,
statusCodes: {
404: function() { /* Handle it somehow */ }
403: function() { /* Handle it... */ }
200: function(response) {
/* Since we've done a form submittion, usurally if we're getting standard OK (200) status code - we've transffered a data - such as JSON data - indicating if the request success or we got errors etc. The code you're writing here depends on how your current application works. */
},
});
});
Server-Side:
Since you don't wish to break your current application logic - you have to still be able to use standard ASP.NET MVC methods - such as View(), Redirect() and so on.
In this case, I recommend to create your own custom base Controller class - which will override ASP.NET basic implementation.
For instance, this is a starting point:
public class MyController : System.Web.Mvc.Controller {
public override View(string viewName) {
if (Request.IsAjaxRequest()) {
return PartialView(viewName); // If this is an AJAX request, we must return a PartialView.
}
return base.View(viewName);
}
}
Few things you have to keep in mind:
You have to somehow distinguish between standard requests and AJAX requests - the way I've used with Request.IsAjaxRequest() is a great way to do so.
Many times when you're handling a form, In the form submittion action, after you finish with the form logic, you're using Redirect() to redirect the user to another page. As you may have guessed, you can't take this approch when developing SPA. However, I can think of few solutions for this problem:
You can create a status handler in the JS code so when redirecting is been issued by the server - you can load the content via AJAX / display a message and so on.
You can override Redirect() and add a specific logic to perform in case of redirection when the request was done by AJAX - for instance, you can request from ASP.NET to perform the action that you're going to be transfered into and then return its content etc.
You can decide that although its an SPA app - when a redirect was issued - you allows the server to perform this redirection.
As you can see - there're many approches you can take, and they depends on the way you've developed your site, how you wish it to work and what is the basic rules you're defining (e.g. "No redirection is permitted never - even after submitting a form", "After form submittion - always in case that the operation success - I'm displaying a message or performing other JS action. Because of that, I can override Redirect() and if this is an AJAX request I can return a JSON object." etc.)
I have simple controller:
public class TestController : Controller
{
public ActionResult Test(string r)
{
return View();
}
}
I have simple View Test.cshtml:
<h2>#ViewContext.RouteData.Values["r"]</h2>
#using (Html.BeginForm("Test", "Test"))
{
<input type="text" name="r" />
<button>Submit</button>
}
I have route rule in Global.asax:
routes.MapRoute(
null,
"Test/{r}",
new { action = "Test", controller = "Test",
r = UrlParameter.Optional }
);
I want to make such thing: user types route value in input, press submit and controller redirects him to page Test/value. But controller show just page with name Test everytime. ViewContext.RouteData.Values["r"] is empty too. I check in debug, Test action recieves user value of r correctly.
How can I realize my idea?
Thanks.
I'm super late to the party, but just wanted to post a solution for reference. Let's assume that this form has more than just a strong as it's input. Assuming there are other inputs, we can wrap up the inputs of the form into a class in our model, called TestModel whose properties maps to the id's of the form's inputs.
In our post, we redirect to the get, passing in the route values we need in the URL. Any other data can then be shuttled to the get using a TempData.
public class TestController : Controller
{
[HttpGet]
public ActionResult Test(string r)
{
TestModel model = TempData["TestModel"] as TestModel;
return View(model);
}
[HttpPost]
public ActionResult Test(string r,TestModel model) //some strongly typed class to contain form inputs
{
TempData["TestModel"] = model; //pass any other form inputs to the other action
return RedirectToAction("Test", new{r = r}); //preserve route value
}
}
You cannot do this without javascript. There are two types of methods that exist when submitting a <form>: GET and POST. When you use POST (which is the default), the form is POSTed to the url but all data entered in input fields is part of the POST body, so it is not part of the url. When you use GET, the input fields data is part of the query string but of the form /Test?r=somevalue.
I wouldn't recommend you trying to send user input as part of the path but if you decide to go that route you could subscribe to the submit event of the form and rewrite the url:
$('form').submit(function() {
var data = $('input[name="r"]', this).val();
window.location.href = this.action + '/' + encodeURIComponent(data);
return false;
});
As far as you are saying to post the form to Html.BeginForm("Test", "Test") you will be always posted back to the same page.
A solution could be to use an explicit Redirect to the action using 'RedirectToAction' (in view) or you can use javascript to change the form's action:
<input type="text" name="r" onchange="this.parent.action = '\/Test\/'+this.value"/>
When Urls are autogenerated using the Url.Action helper, if a page contains a line similar to
#Url.Action("Edit","Student")
is expected to generate a url like domain/student/edit and its working as expected.
But if the requested url contains some parameters, like domain/student/edit/210, the above code uses these parameters from the previous request and generates something similar even though I've not provided any such parameter to the Action method.
In short, if the requested url contains any parameters, any auto generated links of the page (served for that request) will include those parameters as well no matter if I specify them or not in the Url.Action method.
What's going wrong?
Use Darin's answer from this similar question.
#Url.Action("Edit","Student", new { ID = "" })
Weird, can't seem to reproduce the problem:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
return View();
}
public ActionResult About(string id)
{
return View();
}
}
and inside Index.cshtml:
#Url.Action("About", "Home")
Now when I request /home/index/123 the url helper generates /home/about as expected. No ghost parameters. So how does your scenario differs?
UPDATE:
Now that you have clarified your scenario it seems that you have the following:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
return View();
}
}
and inside Index.cshtml you are trying to use:
#Url.Action("Index", "Home")
If you request /home/index/123 this generates /home/index/123 instead of the expected /home/index (or simply / taken into account default values).
This behavior is by design. If you want to change it you will have to write your own helper which ignores the current route data. Here's how it might look:
#UrlHelper.GenerateUrl(
"Default",
"index",
"home",
null,
Url.RouteCollection,
// That's the important part and it is where we kill the current RouteData
new RequestContext(Html.ViewContext.HttpContext, new RouteData()),
false
)
This will generate the proper url you were expecting. Of course this is ugly. I would recommend you encapsulating it into a reusable helper.
Use ActionLink overload that uses parameters and supply null
You could register custom route for this action for example:
routes.MapRoute("Domain_EditStudentDefault",
"student/edit",
new {
controller = MVC.Student.Name,
action = MVC.Student.ActionNames.Edit,
ID = UrlParameter.Optional
},
new object(),
new[] { "MySolution.Web.Controllers" }
);
you then could use url.RouteUrl("Domain_EditStudentDefault") url RouteUrl helper override with only routeName parameter which generates url without 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. :)