just wondering if there is any point validating a view model using TryUpdateModel(), when calling a Post method through Ajax. I have the following code which is called via a jquery Ajax post:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult SubmitForm(ViewModel viewModel)
{
var valid = TryUpdateModel(viewModel);
if (valid)
{
var service = new Service();
var result = _tmpRepository.ExecuteService(viewModel));
return Json(new { Valid = valid, Response = result });
}
return Json(new { Valid = valid });
}
The way I see it, I'm using unobtrusive validation with data annotation on my view model. So the only way that validation wouldn't occur is if javascript was disabled and if javascript is disabled my Ajax request isn't going to do a fat lot!
You still need to validate any data server side. There's nothing stopping someone from submitting the request themselves, or modifying the parameters of the ajax call in the browser. Client side validation should only be a convenience for the user.
You can avoid the TryUpdateModel() though:
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult SubmitForm(ViewModel viewModel)
{
if (ModelState.IsValid)
{
var service = new Service();
var result = _tmpRepository.ExecuteService(viewModel));
return Json(new { Valid = valid, Response = result });
}
return Json(new { Valid = valid });
}
Related
I have a complex type that I return when I perform various operations.And I want to know how I can pass back a custom JSON object to show any errors. Consider the following code
Business Layer
public ResultObject StartJob(string jobName){
...
return new ResultObject{ErrorMessage = "Job cannot be started due to ..."};
}
Controller
[HttpPost]
public ActionResult StartJob(string jobName){
var resultObject = BusinessLayer.StartJob(jobName);
if (resultObject.HasErrors){
return Json(new {success = false, message = resultObject.message}, JsonRequestBehavior.AllowGet);
}
else{
return Json(new {success = true}, JsonRequestBeahvior.AllowGet);
}
}
When I perform the ajax post, despite me returning success = false, the ajax call is still successful and jQuery does not call error() method.
This type of pattern is repeated multiple times.
The "issue" here is that the ajax call IS a success. JQuery will only execute the 'error()' method when the ajax call fails. You should read the value of the success variable on the client side callback and if it is false then call 'error()' i.e.
if (!data.success)
{
error();
}
Its going to call success Event
If you want to trigger error event then throw an custom exception in between.
Keep exception unhandled it will go inside error Scope.
error: function() {
.......
.......
}
Or if you want to handle it inside Ajax success
go for
success: function(data) {
if (!data.success)
{
error();
}
Using ASP.NET, C# and Javascript, I'm trying to dynamically get Data for the user, POST it to a controller, and return a view that changes depending on the Data.
Here's the code :
Javascript function :
function editEntry(id) {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "Edit?id=" + id, true);
xmlhttp.send({ id: id });
//xmlhttp.send();
}
Controller handling post (a portion) :
[HttpPost]
public ActionResult Edit(EditEvenementiel edit)
{
var contexte = new intranetEntities1();
SqlParameter Id_viewBag = new SqlParameter("#id", edit.id);
ViewBag.edit = contexte.evenementiel
.SqlQuery("SELECT * FROM evenementiel WHERE id_evenementiel = #id", Id_viewBag);
return View();
}
when i fire the javascript, i can see the POST in the firebug console (working fine), i can see the variable getting the correct value in Visual Studio's Debugger, but the view doesn't change.
I even see the expected view (with all the treatements expected) returned in the firebug console; but my page still doesn't change.
How can i do that ?
By default, you should have 2 Actions, one that should process/get the data through a Post method and one that collects data for the View. (it's called Post/Redirect/Get - more details on wiki)
Having this in mind, you can leave your post method as :
[HttpPost]
public ActionResult Edit(int id)
{
var contexte = new intranetEntities1();
SqlParameter Id_viewBag = new SqlParameter("#id", id);
EditEvenementiel edit = contexte.evenementiel.SqlQuery("SELECT * FROM evenementiel WHERE id_evenementiel = #id", Id_viewBag);
return RedirectToAction("Edit",new { edit = edit} );
}
and create a new action which sends the data to the view.
Something like:
public ActionResult Edit(EditEvenementiel edit)
{
//logic here
return View(edit);
}
Please be aware that this is just an example, modify it according to your scenario.
As you are using Ajax (XMLHttpRequest) to fetch this data you also need to present it on your page, it wont happen automatically.
Maybe something like this?
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
alert(xmlhttp.responseText); // or put the responseText in a HTML element of your choice to do whatever you want to do
}
}
Where do you actually update anything on the page? All you do is send the request:
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "Edit?id=" + id, true);
xmlhttp.send({ id: id });
But you ignore the response. The browser isn't going to know what you want to do with that response, you have to tell it. Which could be something as simple as:
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
document.getElementById('someElement').innerHTML = xmlhttp.responseText;
}
}
Basically, use the AJAX response (which is an HTML view?) to update the page content.
I have a C# page that checks that a user is logged in when making ajax calls and regular calls. I run the following check after determining the user is not logged in:
base.OnActionExecuting(filterContext);
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
return Content("<script type='text/javascript'> window.location = '/login' </script>");
}
else
{
filterContext.Result = new RedirectResult("~/Login/?referURL=" + filterContext.HttpContext.Server.UrlEncode(filterContext.HttpContext.Request.Url.PathAndQuery));
}
The problem I am running into is in the 'if' part. I get an error saying that the name 'Content' does not exist. I need a way to redirect my window location to '/login'.
Instead of returning plain text return Json data and grab your redirect url from there and set redirect on Ajax success.
var data = new { IsSucess = true,
redirectUrl = Url.Action("Action", "Controller")}
filterContext.Result = new JsonResult() { data ,
JsonRequestBehavior = JsonRequestBehavior.AllowGet };
Then use JsonResult data like this in your Ajax call,
success: function (data) {
if (data.IsSucess)
window.location = data.redirectUrl;
The idee is to post form data from a normal external Html page to another MVC site controller. Then the data is processed almost like using a webservice.
$(document).ready(function () {
var options = {
target: '#output',
success: function(data){ alert('test success'); },
url: http://localhost:57232/Services/SendFormData,
dataType: json
};
$('form').ajaxForm(options);
});
The ActionResult receives the data correctly in the FormCollection object.
[HttpPost]
public ActionResult SendFormData(FormCollection collection)
{
string s = string.Empty;
return Json(new { Success = true, Message = "Message!" }, JsonRequestBehavior.AllowGet);
}
At this point the success result is returned but when it gets to the external form my browser which is in this case IE tries to save or open the bytes returned instead of calling the success callback function.
Because this page is an external page, and not part of the MVC site I cannot use a View or Partial View. What should the return type be?
You need to return partialview result :
[HttpPost]
public ActionResult Form(Comment feedback)
{
if (feedback != null)
{
feedback.CommentedOn = DateTime.Now;
feedback.CommentId += 1;
if (ModelState.IsValid)
{
BlogPost blogpost = db.BlogPosts.Find(feedback.BlogId);
if (blogpost != null)
blogpost.NoofComments += 1;
db.Entry(blogpost).State = EntityState.Modified;
db.Entry(feedback).State = EntityState.Modified;
db.Comments.Add(feedback);
db.SaveChanges();
return PartialView("CommentSuccess", feedback);
}
}
return PartialView("Comment", feedback);
}
Also in the AjaxForm you need to set the UpdateTargetID:
#using (Ajax.BeginForm("Form", new AjaxOptions() { UpdateTargetId = "FormContainerdiv" , OnSuccess = "$.validator.unobtrusive.parse('form');", OnComplete = "OnComplete();" }))
in the targetId of the Ajax Form you need to mention the div id where you have to display the response data.
<div id="FormContainerdiv">.</div>
#Html.Partial("Comment", item);
</div>
How can I return the result of a different action or move the user to a different action if there is an error in my ModelState without losing my ModelState information?
The scenario is; Delete action accepts a POST from a DELETE form rendered by my Index Action/View. If there is an error in the Delete I want to move the user back to the Index Action/View and show the errors that are stored by the Delete action in the ViewData.ModelState. How can this be done in ASP.NET MVC?
[AcceptVerbs(HttpVerbs.Post | HttpVerbs.Delete)]
public ActionResult Delete([ModelBinder(typeof(RdfUriBinder))] RdfUri graphUri)
{
if (!ModelState.IsValid)
return Index(); //this needs to be replaced with something that works :)
return RedirectToAction("Index");
}
Store your view data in TempData and retrieve it from there in your Index action, if it exists.
...
if (!ModelState.IsValid)
TempData["ViewData"] = ViewData;
RedirectToAction( "Index" );
}
public ActionResult Index()
{
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
...
}
[EDIT] I checked the on-line source for MVC and it appears that the ViewData in the Controller is settable, so it is probably easiest just to transfer all of the ViewData, including the ModelState, to the Index action.
Use Action Filters (PRG pattern) (as easy as using attributes)
Mentioned here and here.
Please note that tvanfosson's solution will not always work, though in most cases it should be just fine.
The problem with that particular solution is that if you already have any ViewData or ModelState you end up overwriting it all with the previous request's state. For example, the new request might have some model state errors related to invalid parameters being passed to the action, but those would end up being hidden because they are overwritten.
Another situation where it might not work as expected is if you had an Action Filter that initialized some ViewData or ModelState errors. Again, they would be overwritten by that code.
We're looking at some solutions for ASP.NET MVC that would allow you to more easily merge the state from the two requests, so stay tuned for that.
Thanks,
Eilon
In case this is useful to anyone I used #bob 's recommended solution using PRG:
see item 13 -> link.
I had the additional issue of messages being passed in the VeiwBag to the View being written and checked / loaded manually from TempData in the controller actions when doing a RedirectToAction("Action"). In an attempt to simplify (and also make it maintainable) I slightly extended this approach to check and store/load other data as well. My action methods looked something like:
[AcceptVerbs(HttpVerbs.Post)]
[ExportModelStateToTempData]
public ActionResult ChangePassword(ProfileViewModel pVM) {
bool result = MyChangePasswordCode(pVM.ChangePasswordViewModel);
if (result) {
ViewBag.Message = "Password change success";
else {
ModelState.AddModelError("ChangePassword", "Some password error");
}
return RedirectToAction("Index");
}
And my Index Action:
[ImportModelStateFromTempData]
public ActionResult Index() {
ProfileViewModel pVM = new ProfileViewModel { //setup }
return View(pVM);
}
The code in the Action Filters:
// Following best practices as listed here for storing / restoring model data:
// http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx#prg
public abstract class ModelStateTempDataTransfer : ActionFilterAttribute {
protected static readonly string Key = typeof(ModelStateTempDataTransfer).FullName;
}
:
public class ExportModelStateToTempData : ModelStateTempDataTransfer {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
//Only export when ModelState is not valid
if (!filterContext.Controller.ViewData.ModelState.IsValid) {
//Export if we are redirecting
if ((filterContext.Result is RedirectResult) || (filterContext.Result is RedirectToRouteResult)) {
filterContext.Controller.TempData[Key] = filterContext.Controller.ViewData.ModelState;
}
}
// Added to pull message from ViewBag
if (!string.IsNullOrEmpty(filterContext.Controller.ViewBag.Message)) {
filterContext.Controller.TempData["Message"] = filterContext.Controller.ViewBag.Message;
}
base.OnActionExecuted(filterContext);
}
}
:
public class ImportModelStateFromTempData : ModelStateTempDataTransfer {
public override void OnActionExecuted(ActionExecutedContext filterContext) {
ModelStateDictionary modelState = filterContext.Controller.TempData[Key] as ModelStateDictionary;
if (modelState != null) {
//Only Import if we are viewing
if (filterContext.Result is ViewResult) {
filterContext.Controller.ViewData.ModelState.Merge(modelState);
} else {
//Otherwise remove it.
filterContext.Controller.TempData.Remove(Key);
}
}
// Restore Viewbag message
if (!string.IsNullOrEmpty((string)filterContext.Controller.TempData["Message"])) {
filterContext.Controller.ViewBag.Message = filterContext.Controller.TempData["Message"];
}
base.OnActionExecuted(filterContext);
}
}
I realize my changes here are a pretty obvious extension of what was already being done with the ModelState by the code # the link provided by #bob - but I had to stumble on this thread before I even thought of handling it in this way.
Please don't skewer me for this answer. It is a legitimate suggestion.
Use AJAX
The code for managing ModelState is complicated and (probably?) indicative of other problems in your code.
You can pretty easily roll your own AJAX javascript code. Here is a script I use:
https://gist.github.com/jesslilly/5f646ef29367ad2b0228e1fa76d6bdcc#file-ajaxform
(function ($) {
$(function () {
// For forms marked with data-ajax="#container",
// on submit,
// post the form data via AJAX
// and if #container is specified, replace the #container with the response.
var postAjaxForm = function (event) {
event.preventDefault(); // Prevent the actual submit of the form.
var $this = $(this);
var containerId = $this.attr("data-ajax");
var $container = $(containerId);
var url = $this.attr('action');
console.log("Post ajax form to " + url + " and replace html in " + containerId);
$.ajax({
type: "POST",
url: url,
data: $this.serialize()
})
.done(function (result) {
if ($container) {
$container.html(result);
// re-apply this event since it would have been lost by the form getting recreated above.
var $newForm = $container.find("[data-ajax]");
$newForm.submit(postAjaxForm);
$newForm.trigger("data-ajax-done");
}
})
.fail(function (error) {
alert(error);
});
};
$("[data-ajax]").submit(postAjaxForm);
});
})(jQuery);
Maybe try
return View("Index");
instead of
return Index();