I have an MVC5 application that has a method populates and returns a partial view. Since the method accepts an ID as a parameter, Id like to return an error if it is not supplied.
[HttpGet] public PartialViewResult GetMyData(int? id)
{
if (id == null || id == 0)
{
// I'd like to return an invalid code here, but this must be of type "PartialViewResult"
return new HttpStatusCodeResult(HttpStatusCode.BadRequest); // Does not compile
}
var response = MyService.GetMyData(id.Value);
var viewModel = Mapper.Map<MyData, MyDataViewModel>(response.Value);
return PartialView("~/Views/Data/_MyData.cshtml", viewModel);
}
What is the proper way to report an error for a method that returns a PartialViewResult as its output?
You could create a friendly error partial and do the following:
[HttpGet]
public PartialViewResult GetMyData(int? id)
{
if (id == null || id == 0)
{
// I'd like to return an invalid code here, but this must be of type "PartialViewResult"
return PartialView("_FriendlyError");
}
var response = MyService.GetMyData(id.Value);
var viewModel = Mapper.Map<MyData, MyDataViewModel>(response.Value);
return PartialView("~/Views/Data/_MyData.cshtml", viewModel);
}
This way there is a better user experience rather than just throwing them anything. You can customise that error partial to include some details that they did wrong etc.
You can use manual Exception
if (id == null || id == 0)
{
throw new Exception("id must have value");
}
if you work with ajax, you can handle error by error callback function
$.ajax({
type: 'POST',
url: '/yourUrl',
success: function (response) {
// call here when successfully // 200
},
error: function (e) {
// handle error in here
}
})
While you cannot return HttpStatusCodeResult as a PartialViewResult to set the response status code for you, you can certainly still do the manual labour yourself to achieve the same result.
Using the OP's example:
[HttpGet]
public PartialViewResult GetMyData(int? id)
{
if (id == null || id == 0)
{
// Return an invalid code here
HttpContext.Response.StatusCode = HttpStatusCode.BadRequest;
// Optionally set the description as well
HttpContext.Response.StatusDescription = "Bad Request";
// Return null as it doesn't matter anymore
return null;
}
var response = MyService.GetMyData(id.Value);
var viewModel = Mapper.Map<MyData, MyDataViewModel>(response.Value);
return PartialView("~/Views/Data/_MyData.cshtml", viewModel);
}
Success, by setting the status code directly on HttpContext.Response which is accessible from the base class Controller, and what HttpStatusCodeResult would've done anyway.
nJoy!
How to use $.post or $.getJSON to get json from mvc controlller but not working below? Would you like help me?
var controlRole = function () {
var _url = 'IsStudent/';
console.log('IsStudent');
$.post(_url, {}, function (data) {
console.log('IsStudent2');
if (data == "true") {
$('#btnSent_').hide();
$('#btnDraft_').hide();
$('#btn_Inbox_').show();
$('#btnTrash_').show();
$.post('FillProgramListByUser/', {}, function (result) {
console.log('IsStudent3');
console.log(result);
$("#liProgramContainer ul").append('<li ><a class="btn" href="javascript:;" data-title="Sent">'+result.Name+'</a><b></b></li>');
});
// $.getJSON("FillProgramListByUser/", user, updateFields);
}
else {
$('#btnSent_').show();
$('#btnDraft_').show();
$('#btn_Inbox_').show();
$('#btnTrash_').show();
}
});
}
Controller side:
public JsonResult FillProgramListByUser()
{
string UserName = SessionVariables.CurrentUser.UserName;
int OrganizationId = SessionVariables.CurrentUser.OrganizationId;
IList<Program> programs = new List<Program>();
if (UserName != "system_admin")
{
programs = Uow.Programs.GetAll().Where(q => q.OrganizationId == OrganizationId).ToList();
}
return Json(programs, "application/json", Encoding.UTF8, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public string IsStudent()
{
string UserName = SessionVariables.CurrentUser.UserName;
if (UserName != "system_admin")
{
return "true";
}
else
{
return "false";
}
}
Your controller action should return a JsonResult and not some strings:
[HttpPost]
public ActionResult IsStudent()
{
string UserName = SessionVariables.CurrentUser.UserName;
if (UserName != "system_admin")
{
return Json(new { success = true });
}
return Json(new { success = false });
}
Also in your FillProgramListByUser action you don't need to be explicitly setting the content type response header nor the encoding:
public ActionResult FillProgramListByUser()
{
string UserName = SessionVariables.CurrentUser.UserName;
int OrganizationId = SessionVariables.CurrentUser.OrganizationId;
IList<Program> programs = new List<Program>();
if (UserName != "system_admin")
{
programs = Uow.Programs.GetAll().Where(q => q.OrganizationId == OrganizationId).ToList();
}
return Json(programs, JsonRequestBehavior.AllowGet);
}
Also adapt your script so that the urls are not hardcoded as in your example but you used URL helpers to generate them:
<script type="text/javascript">
var controlRole = function () {
var isStudentUrl = '#Url.Action("IsStudent")';
$.post(isStudentUrl, function (data) {
if (data.success) {
$('#btnSent_').hide();
$('#btnDraft_').hide();
$('#btn_Inbox_').show();
$('#btnTrash_').show();
var fillProgramListByUserUrl = '#Url.Action("FillProgramListByUser")';
$.post(fillProgramListByUserUrl, function (result) {
$("#liProgramContainer ul").append('<li><a class="btn" href="javascript:;" data-title="Sent">'+result.Name+'</a><b></b></li>');
});
} else {
$('#btnSent_').show();
$('#btnDraft_').show();
$('#btn_Inbox_').show();
$('#btnTrash_').show();
}
});
};
</script>
Next put breakpoints in your controller actions and see if they are hit. Also don't forget to look at the network tab of your javascript debugging tool (FireBug or Chrome Developer Toolbar) which is where you will see the exact AJAX request being sent to the server and what does the server respond to. You will see the HTTP status code returned and you could also see the contents of the response. If the status code is non 2xx the success callback of your AJAX request will not be executed.
Another thing you should check is the Program model which is being returned by your FillProgramListByUser controller action. In there you are attempting to JSON serialize an IList<Program> but be careful: if this Program class has some circular references (often happens if you don't use view models but are directly passing your EF domain models to the views) you won't be able to JSON serialize it. The answer is of course obvious: use a view model.
Suppose you have the following controller action
[HttpPost]
public ActionResult Save( CustomerModel model )
{
if (!ModelState.IsValid) {
//Invalid - redisplay form with errors
return PartialView("Customer", model);
}
try {
//
// ...code to save the customer here...
//
return PartialView( "ActionCompleted" );
}
catch ( Exception ex ) {
ActionErrorModel aem = new ActionErrorModel() {
Message = ex.Message
};
return PartialView( "ActionError", aem );
}
}
And suppose you call this action using jQuery:
$.ajax({
type: "post",
dataType: "html",
url: "/Customer/Save",
sync: true,
data: $("#customerForm").serialize(),
success: function(response) {
/*
??????
*/
},
error: function(response) {
}
});
I would like to be able to distinguish between the results I am getting to handle them in different ways on the client. In other words how can I understand that the action
returned the same model because has not passed validation
returned one of the views that represents error info/messages
Any suggestion?
One way to handle this is to append a custom HTTP header to indicate in which case we are falling:
[HttpPost]
public ActionResult Save( CustomerModel model )
{
if (!ModelState.IsValid) {
//Invalid - redisplay form with errors
Response.AppendHeader("MyStatus", "case 1");
return PartialView("Customer", model);
}
try {
//
// ...code to save the customer here...
//
Response.AppendHeader("MyStatus", "case 2");
return PartialView( "ActionCompleted" );
}
catch ( Exception ex ) {
ActionErrorModel aem = new ActionErrorModel() {
Message = ex.Message
};
Response.AppendHeader("MyStatus", "case 3");
return PartialView( "ActionError", aem );
}
}
And on the client side test this header:
success: function (response, status, xml) {
var myStatus = xml.getResponseHeader('MyStatus');
// Now test the value of MyStatus to determine in which case we are
}
The benefit of this is that the custom HTTP header will always be set in the response no matter what content type you've returned. It will also work with JSON, XML, ...
Remark 1: To avoid cluttering you controller action with all those Response.AppendHeader instructions you could write a custom ActionResult allowing you to directly specify the value of this header so that you simply return this.MyPartialView("Customer", model, "case 1")
Remark 2: Remove this sync: true attribute from the request because it makes my eyes hurt (in fact I think you meant async: 'false').
You could check for an element unique to that view, for example:
$.ajax({
type: "post",
dataType: "html",
url: "/Customer/Save",
sync: true,
data: $("#customerForm").serialize(),
success: function(response) {
var resp = $(response);
if($(resp.find("#customer").length) {
//customer returned
} else if($(resp.find("#completed").length) {
//completed view
} else if($(resp.find("#error").length) {
//error view
}
},
error: function(response) {
}
});
I am using the code as below of this post:
First I will fill an array variable with the correct values for the controller action.
Using the code below I think it should be very straightforward by just adding the following line to the JavaScript code:
data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();
The <%= Html.AntiForgeryToken() %> is at its right place, and the action has a [ValidateAntiForgeryToken]
But my controller action keeps saying: "Invalid forgery token"
What am I doing wrong here?
Code
data["fiscalyear"] = fiscalyear;
data["subgeography"] = $(list).parent().find('input[name=subGeography]').val();
data["territories"] = new Array();
$(items).each(function() {
data["territories"].push($(this).find('input[name=territory]').val());
});
if (url != null) {
$.ajax(
{
dataType: 'JSON',
contentType: 'application/json; charset=utf-8',
url: url,
type: 'POST',
context: document.body,
data: JSON.stringify(data),
success: function() { refresh(); }
});
}
You don't need the ValidationHttpRequestWrapper solution since MVC 4. According to this link.
Put the token in the headers.
Create a filter.
Put the attribute on your method.
Here is my solution:
var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers['__RequestVerificationToken'] = token;
$.ajax({
type: 'POST',
url: '/MyTestMethod',
contentType: 'application/json; charset=utf-8',
headers: headers,
data: JSON.stringify({
Test: 'test'
}),
dataType: "json",
success: function () {},
error: function (xhr) {}
});
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var httpContext = filterContext.HttpContext;
var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]);
}
}
[HttpPost]
[AllowAnonymous]
[ValidateJsonAntiForgeryToken]
public async Task<JsonResult> MyTestMethod(string Test)
{
return Json(true);
}
What is wrong is that the controller action that is supposed to handle this request and which is marked with the [ValidateAntiForgeryToken] expects a parameter called __RequestVerificationToken to be POSTed along with the request.
There's no such parameter POSTed as you are using JSON.stringify(data) which converts your form to its JSON representation and so the exception is thrown.
So I can see two possible solutions here:
Number 1: Use x-www-form-urlencoded instead of JSON for sending your request parameters:
data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();
data["fiscalyear"] = fiscalyear;
// ... other data if necessary
$.ajax({
url: url,
type: 'POST',
context: document.body,
data: data,
success: function() { refresh(); }
});
Number 2: Separate the request into two parameters:
data["fiscalyear"] = fiscalyear;
// ... other data if necessary
var token = $('[name=__RequestVerificationToken]').val();
$.ajax({
url: url,
type: 'POST',
context: document.body,
data: { __RequestVerificationToken: token, jsonRequest: JSON.stringify(data) },
success: function() { refresh(); }
});
So in all cases you need to POST the __RequestVerificationToken value.
I was just implementing this actual problem in my current project. I did it for all Ajax POSTs that needed an authenticated user.
First off, I decided to hook my jQuery Ajax calls so I do not to repeat myself too often. This JavaScript snippet ensures all ajax (post) calls will add my request validation token to the request. Note: the name __RequestVerificationToken is used by the .NET framework so I can use the standard Anti-CSRF features as shown below.
$(document).ready(function () {
securityToken = $('[name=__RequestVerificationToken]').val();
$('body').bind('ajaxSend', function (elm, xhr, s) {
if (s.type == 'POST' && typeof securityToken != 'undefined') {
if (s.data.length > 0) {
s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
}
else {
s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
}
}
});
});
In your Views where you need the token to be available to the above JavaScript code, just use the common HTML-Helper. You can basically add this code wherever you want. I placed it within a if(Request.IsAuthenticated) statement:
#Html.AntiForgeryToken() // You can provide a string as salt when needed which needs to match the one on the controller
In your controller simply use the standard ASP.NET MVC anti-CSRF mechanism. I did it like this (though I actually used a salt).
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
// Do something
return Json(true);
}
With Firebug or a similar tool you can easily see how your POST requests now have a __RequestVerificationToken parameter appended.
You can set $.ajax 's traditional attribute and set it to true, to send json data as url encoded form. Make sure to set type:'POST'. With this method you can even send arrays and you do not have to use JSON.stringyfy or any changes on server side (e.g. creating custom attributes to sniff header )
I have tried this on ASP.NET MVC3 and jquery 1.7 setup and it's working
following is the code snippet.
var data = { items: [1, 2, 3], someflag: true};
data.__RequestVerificationToken = $(':input[name="__RequestVerificationToken"]').val();
$.ajax({
url: 'Test/FakeAction'
type: 'POST',
data: data
dataType: 'json',
traditional: true,
success: function (data, status, jqxhr) {
// some code after succes
},
error: function () {
// alert the error
}
});
This will match with MVC action with following signature
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult FakeAction(int[] items, bool someflag)
{
}
You won't ever have to validate an AntiForgeryToken when you receive posted JSON.
The reason is that AntiForgeryToken has been created to prevent CSRF. Since you can't post AJAX data to another host and HTML forms can't submit JSON as the request body, you don't have to protect your app against posted JSON.
I have resolved it globally with RequestHeader.
$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
if (options.type.toUpperCase() === "POST") {
// We need to add the verificationToken to all POSTs
if (requestVerificationTokenVariable.length > 0)
jqXhr.setRequestHeader("__RequestVerificationToken", requestVerificationTokenVariable);
}
});
where the requestVerificationTokenVariable is an variable string that contains the token value.
Then all ajax call send the token to the server, but the default ValidateAntiForgeryTokenAttribute get the Request.Form value.
I have writed and added this globalFilter that copy token from header to request.form, than i can use the default ValidateAntiForgeryTokenAttribute:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new GlobalAntiForgeryTokenAttribute(false));
}
public class GlobalAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly bool autoValidateAllPost;
public GlobalAntiForgeryTokenAttribute(bool autoValidateAllPost)
{
this.autoValidateAllPost = autoValidateAllPost;
}
private const string RequestVerificationTokenKey = "__RequestVerificationToken";
public void OnAuthorization(AuthorizationContext filterContext)
{
var req = filterContext.HttpContext.Request;
if (req.HttpMethod.ToUpperInvariant() == "POST")
{
//gestione per ValidateAntiForgeryToken che gestisce solo il recupero da Request.Form (non disponibile per le chiamate ajax json)
if (req.Form[RequestVerificationTokenKey] == null && req.IsAjaxRequest())
{
var token = req.Headers[RequestVerificationTokenKey];
if (!string.IsNullOrEmpty(token))
{
req.Form.SetReadOnly(false);
req.Form[RequestVerificationTokenKey] = token;
req.Form.SetReadOnly(true);
}
}
if (autoValidateAllPost)
AntiForgery.Validate();
}
}
}
public static class NameValueCollectionExtensions
{
private static readonly PropertyInfo NameObjectCollectionBaseIsReadOnly = typeof(NameObjectCollectionBase).GetProperty("IsReadOnly", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
public static void SetReadOnly(this NameValueCollection source, bool readOnly)
{
NameObjectCollectionBaseIsReadOnly.SetValue(source, readOnly);
}
}
This work for me :)
You can't validate an content of type contentType: 'application/json; charset=utf-8' because your date will be uploaded not in the Form property of the request, but in the InputStream property, and you will never have this Request.Form["__RequestVerificationToken"].
This will be always empty and validation will fail.
I hold the token in my JSON object and I ended up modifying the ValidateAntiForgeryToken class to check the InputStream of the Request object when the post is json. I've written a blog post about it, hopefully you might find it useful.
Check out Dixin's Blog for a great post on doing exactly that.
Also, why not use $.post instead of $.ajax?
Along with the jQuery plugin on that page, you can then do something as simple as:
data = $.appendAntiForgeryToken(data,null);
$.post(url, data, function() { refresh(); }, "json");
AJAX based model posting with AntiForgerytoken can be made bit easier with Newtonsoft.JSON library
Below approach worked for me:
Keep your AJAX post like this:
$.ajax({
dataType: 'JSON',
url: url,
type: 'POST',
context: document.body,
data: {
'__RequestVerificationToken': token,
'model_json': JSON.stringify(data)
};,
success: function() {
refresh();
}
});
Then in your MVC action:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FormCollection data) {
var model = JsonConvert.DeserializeObject < Order > (data["model_json"]);
return Json(1);
}
Hope this helps :)
I had to be a little shady to validate anti-forgery tokens when posting JSON, but it worked.
//If it's not a GET, and the data they're sending is a string (since we already had a separate solution in place for form-encoded data), then add the verification token to the URL, if it's not already there.
$.ajaxSetup({
beforeSend: function (xhr, options) {
if (options.type && options.type.toLowerCase() !== 'get' && typeof (options.data) === 'string' && options.url.indexOf("?__RequestVerificationToken=") < 0 && options.url.indexOf("&__RequestVerificationToken=") < 0) {
if (options.url.indexOf('?') < 0) {
options.url += '?';
}
else {
options.url += '&';
}
options.url += "__RequestVerificationToken=" + encodeURIComponent($('input[name=__RequestVerificationToken]').val());
}
}
});
But, as a few people already mentioned, the validation only checks the form - not JSON, and not the query string. So, we overrode the attribute's behavior. Re-implementing all of the validation would have been terrible (and probably not secure), so I just overrode the Form property to, if the token were passed in the QueryString, have the built-in validation THINK it was in the Form.
That's a little tricky because the form is read-only, but doable.
if (IsAuth(HttpContext.Current) && !IsGet(HttpContext.Current))
{
//if the token is in the params but not the form, we sneak in our own HttpContext/HttpRequest
if (HttpContext.Current.Request.Params != null && HttpContext.Current.Request.Form != null
&& HttpContext.Current.Request.Params["__RequestVerificationToken"] != null && HttpContext.Current.Request.Form["__RequestVerificationToken"] == null)
{
AntiForgery.Validate(new ValidationHttpContextWrapper(HttpContext.Current), null);
}
else
{
AntiForgery.Validate(new HttpContextWrapper(HttpContext.Current), null);
}
}
//don't validate un-authenticated requests; anyone could do it, anyway
private static bool IsAuth(HttpContext context)
{
return context.User != null && context.User.Identity != null && !string.IsNullOrEmpty(context.User.Identity.Name);
}
//only validate posts because that's what CSRF is for
private static bool IsGet(HttpContext context)
{
return context.Request.HttpMethod.ToUpper() == "GET";
}
...
internal class ValidationHttpContextWrapper : HttpContextBase
{
private HttpContext _context;
private ValidationHttpRequestWrapper _request;
public ValidationHttpContextWrapper(HttpContext context)
: base()
{
_context = context;
_request = new ValidationHttpRequestWrapper(context.Request);
}
public override HttpRequestBase Request { get { return _request; } }
public override IPrincipal User
{
get { return _context.User; }
set { _context.User = value; }
}
}
internal class ValidationHttpRequestWrapper : HttpRequestBase
{
private HttpRequest _request;
private System.Collections.Specialized.NameValueCollection _form;
public ValidationHttpRequestWrapper(HttpRequest request)
: base()
{
_request = request;
_form = new System.Collections.Specialized.NameValueCollection(request.Form);
_form.Add("__RequestVerificationToken", request.Params["__RequestVerificationToken"]);
}
public override System.Collections.Specialized.NameValueCollection Form { get { return _form; } }
public override string ApplicationPath { get { return _request.ApplicationPath; } }
public override HttpCookieCollection Cookies { get { return _request.Cookies; } }
}
There's some other stuff that's different about our solution (specifically, we're using an HttpModule so we don't have to add the attribute to every single POST) that I left out in favor of brevity. I can add it if necessary.
Unfortunately for me, the other answers rely on some request formatting handled by jquery, and none of them worked when setting the payload directly. (To be fair, putting it in the header would have worked, but I did not want to go that route.)
To accomplish this in the beforeSend function, the following works. $.params() transforms the object into the standard form / url-encoded format.
I had tried all sorts of variations of stringifying json with the token and none of them worked.
$.ajax({
...other params...,
beforeSend: function(jqXHR, settings){
var token = ''; //get token
data = {
'__RequestVerificationToken' : token,
'otherData': 'value'
};
settings.data = $.param(data);
}
});
```
You should place AntiForgeryToken in a form tag:
#using (Html.BeginForm(actionName:"", controllerName:"",routeValues:null, method: FormMethod.Get, htmlAttributes: new { #class="form-validator" }))
{
#Html.AntiForgeryToken();
}
Then in javascript modify the following code to be
var DataToSend = [];
DataToSend.push(JSON.stringify(data), $('form.form-validator').serialize());
$.ajax({
dataType: 'JSON',
contentType: 'application/json; charset=utf-8',
url: url,
type: 'POST',
context: document.body,
data: DataToSend,
success: function() {
refresh();
}
});
Then you should be able to validate the request using ActionResult annotations
[ValidateAntiForgeryToken]
[HttpPost]
I hope this helps.
How can you enable multiple selection in a jqGrid, and also allow users to delete all of the selected rows using an ASP.NET MVC controller?
I have set the delete url property to my /Controller/Delete method, and this works fine if one record is selected. However, if multiple records are selected, it attempts to send a null value back to the controller where an integer id is required.
You can, but you have to write code for it:
deleteSelected: function(grid) {
if (!grid.jqGrid) {
if (console) {
console.error("'grid' argument must be a jqGrid");
}
return;
}
var ids = grid.getGridParam('selarrrow');
var count = ids.length;
if (count == 0) return;
if (confirm("Delete these " + count + " records?")) {
$.post("DeleteMultiple",
{ ids: ids },
function() { grid.trigger("reloadGrid") },
"json");
}
}
[HttpPost]
public ActionResult DeleteMultiple(IEnumerable<Guid> ids)
{
if (!Request.IsAjaxRequest())
{
// we only support this via AJAX for now.
throw new InvalidOperationException();
}
if (!ids.Any())
{
// JsonError is an internal class which works with our Ajax error handling
return JsonError(null, "Cannot delete, because no records selected.");
}
var trans = Repository.StartTransaction();
foreach (var id in ids)
{
Repository.Delete(id);
}
trans.Commit();
return Json(true);
}
I want to update this for MVC2 and jquery 1.4.2, if you want to pass array parameters to mvc2:
var ids = $("#grid").getGridParam('selarrrow');
var postData = { values: ids };
if (confirm("Delete these " + count + " records?")) {
$.ajax({
type: "POST",
traditional: true,
url: "GridDBDemoDataDeleteMultiple",
data: postData,
dataType: "json",
success: function() { $("#grid").trigger("reloadGrid") }
});
}
check http://jquery14.com/day-01/jquery-14 ajax part
thx