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.
Related
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
}
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.
In asp.net mvc3,
I am using a javascript API to dynamically render a user interface. Part of the input section is going to be dependent on how many items the user wants to enter data for. As a result, something like this wont work
#(Html.EditorFor(m => m.P[5].C.Description))
because that cannot be done during runtime. What type of process would I use to call that helper during runtime with AJAX? Would I have a controller action which only returned that information which was called using $.ajax()? Would it be in a different place than a controller action?
At run time you could perform an ajax get to a controller action that will render a view as a string, which in turn could be inserted / appended into the DOM.
Create a new action result that returns JSON as per below:
return new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new { html = this.RenderPartialViewToString("YourPartialView", model) }
};
Note, the above makes use of the following controller extension:
public static string RenderPartialViewToString(this Controller controller, string viewName = null, object model = null)
{
if (string.IsNullOrEmpty(viewName))
{
viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
}
controller.ViewData.Model = model;
using (var sw = new StringWriter())
{
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
For further reading regarding this extension method: http://craftycodeblog.com/2010/05/15/asp-net-mvc-render-partial-view-to-string/
All that would remain would be to perform a get passing a parameter signifying the number of items to render and append the returned content into your view. Perhaps something like:
$.getJSON('url', numberofitems, function (data) {
$('#somecontainer').html(data.html);
});
If you are pulling HTML and inserting it into the DOM, you don't have to go via JSON.
Just have your action return a PartialView. It's already in the form of Html, and ready to be inserted into your DOM
JS
$.getJSON('/someurl/GetMyView',{count:10}, function (data) {
$('#target').html(data);
});
Controller:
[HttpGet]
public ActionResult GetMyView(int count)
{
MyModel model = //Get the model from somewhere
return PartialView(model);
}
View:
#model MyModel
<div>
#Model.SomeProperty
<div>
If I understand you correctly, you want to dynamically insert fields on the client to allow users to add N-number of fields without having a bazillion pre-rendered fields on the form and you are trying to use ajax to do this?
I'm sure you could do it, by rendering the html on the server and pushing it up to the client... have you considered dynamically adding the to the page via javascript? Unlike Webforms, MVC does not care what elements were on the page when it was rendered, it only cares about the data it receives in the HttpPost.
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"/>
I'm trying to call an action in a separate controller with an actionlink.
The problem is that both the [HttpGet] and the [HttpPost] action gets called, and since the post-method returns the view from which the action is called, nothing gets displayed.
Get method:
[HttpGet]
public ActionResult View(int id, int index)
{
var form = formService.GetForm(id);
var pageModel = new PageViewModel();
var page = form.Pages.ElementAt(index);
ModelCopier.CopyModel(page, pageModel);
ModelCopier.CopyModel(form, pageModel);
return View(pageModel);
}
Post Method
[HttpPost]
public ActionResult View(PageViewModel pageViewModel)
{
if (!ModelState.IsValid)
{
return RedirectToAction("Details", "Forms", new { id = pageViewModel.FormId });
}
var pageToEdit = pageService.GetPage(pageViewModel.PageId);
ModelCopier.CopyModel(pageViewModel, pageToEdit);
pageService.SavePage();
return RedirectToAction("Details", "Forms", new {id = pageViewModel.FormId});
}
How it's called from the View (from a view returned by another controller):
#Html.ActionLink("View", "View", "Pages", new { id = Model.FormId, index = item.Index-1 }, null)
What am I doing wrong here? I essentially want it to work as an update/edit function. And the view returned contains a simple form for the viewmodel.
An action link issues a GET request. You will have to implement some JavaScript function that captures the url and parameters, and dynamically creates and submits a new form with a POST method, or does an Ajax POST. You could write your own HTML Helper, to wrap this functionality, but the default functionality of clicking an <a> tag (which is what is generated by a Html.ActionLink) will issue a GET request.
I don't think you'll be able to use an action link - see this question
The options are to use jQuery to post data or swap to a simple form and submit