Request validation in asp.net mvc - asp.net-mvc

I'm designing 2 websites. The first to upload images and the second to store the images as an image hosting (both of them are using asp.net mvc 5).
The domain name of the first website is: vinachannel.com.
In the first website, I wanna send some images to the hosting via ajax:
var f = new FormData();
$.ajax({
url: 'https://myimagehosting.com/home/upload',
type: 'POST',
data: f,
processData: false,
contentType: false
}).done(function (data) {
// logic...
})
Action Upload in Home controller of the hosting:
[HttpPost]
public JsonResult Upload()
{
if (Request.Files.Count > 0)
{
// start uploading...
}
}
Now, my problem is: I wanna the image hosting accepts only the requests which are sent from vinachannel.com. Just like:
[HttpPost]
public JsonResult Upload()
{
if (Request.Files.Count > 0 && Request.Url.AbsoluteUri.StartsWith("vinachannel.com"))
{
// start uploading...
}
}
or using regex:
var reg = new Regex(#"^(https://)?(www\.)?(vinachannel\.com)(.+)$");
if (Request.Files.Count > 0 && reg.IsMatch(Request.Url.AbsoluteUri))
{
// start uploading...
}
My question: How can I custom an attribute to validate all requests for action Upload?
[VinaChannel] // only requests from site vinachannel.com
[HttpPost]
public JsonResult Upload()
{
// ...
}
UPDATE: (based on #David's comment and following the article)
public class VinaChannelFilter : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
var reg = new Regex(#"^(https://)?(www\.)?(vinachannel\.com)(.+)$");
if (reg.IsMatch(HttpContext.Current.Request.Url.AbsoluteUri))
{
// what's next here...?
}
this.OnActionExecuting(filterContext);
}
}

You may create a custom action filter which inspects the request headers and see where the request is coming from and use that values to determine, whether to allow /deny further processing. the Referer header is the one you might want to use.
public class VerifyDomain : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var expectedHost = "domainnameyouwanttocheck";
var headers = filterContext.HttpContext.Request.Headers;
if (!String.IsNullOrEmpty(headers["Referer"])
&& new Uri(headers["Referer"]).Host == expectedHost)
{
base.OnActionExecuting(filterContext);
}
else
{
filterContext.Result = new HttpUnauthorizedResult();
}
}
}
And decorate your action method/controller with the action filter
[VerifyDomain]
[HttpPost]
public ActionResult Upload()
{
// to do : return something
}
When this endpoint is being accessed from anywhere other than the value you have in the expectedHost variable, the caller will get 401 Unauthorized response. You may update the if condition to check for a list of expectedHost name's to support your local development environment as well.

Upload() is on myimagehosting.com, right? Then HttpContext.Current.Request.Url.AbsoluteUri will return Uri on myimagehosting.com domain, to get source address you need to get referrer: HttpContext.Current.Request.UrlReferrer.AbsolutePath. But the problem is, it can be easily forged, so depending on your needs, you probably will have to implement more complex Authentication/Authorization logic

Related

Best Method to run Subdomains in MVC4

I have a website that is in a pre-integration phase. In other words, I have ensured that the site runs fine on my local Development Server (VS2012) utilizing the dynamically generated ASP.NET Development Server that runs at the time of debug executions; and I have now created a sub-domain of my domain on the Web Host Server and deployed my site there.
My decision to do this was because I obviously don't want users accessing the site until it has undergone thorough testing on the actual Host. My problem is though most of the site functions without issue (including URL's), there are a few links that produce the HTTP 404 error.
"The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly."
It then references the View/Controller path in the context of the Error that I know exists.
Why it only happens for certain Uri requests and not others is somewhat puzzling to me.
So I am strongly suspecting that it has something to do with the Default Routing Configuration for MVC and I believe if I were to move the site to the main domain, the issue will likely resolve but then again, it would defeat the purpose of setting it up in the SubDomain for testing before public access.
I need some viable options here but don't no where to start.
Should I address the issue from the perspective of the Routing Configuration and create 2 separate Global.asax.cs files? One for the domain and the other for the subdomain testing? And if so, How should I modify the file to accomodate for the Subdomain.
Or is there a more elegant solution for approaching the Integration process?
----------------- UPDATE ---------------------
So I've been troubleshooting the problem and it appears as though the 404 Error is only being generated for a method in my Controller that is returning a string.
I have a function that is being called in my View that looks like this:
<script>
function Subscribe(slvl) {
$.ajax({
type: 'POST',
datatype: "text",
data: { level: slvl },
url: '#Url.Action("Upgrade", "Profile")',
success: function (result) {
if (result) {
window.location = result;
}
},
error: function onError(result) {
alert(result.responseText);
}
});
}
</script>
I cannot post the full details of the Controller but it simply returns a string and whether I were to post the entire method or a simple one, the results would be the same. So for illustration only it looks something like this.
[HttpPost]
public string Upgrade(string level)
{
var uri = "http://www.someUri.com?Upgrade=" + level;
return uri;
}
This code is producing a HTTP 404 Error complaining that the path Profile/Upgrade cannot be found.
But I've found that if I use reference to a different method being called in the same Controller with the only exception being that it returns an ActionResult to a different View, the Error goes away and I'm redirected to the alternate view.
Any Ideas? So maybe it has nothing to do with the Subdomain???
First of all you need to create your own Custom Route
public class SubDomainRoute : Route
{
private readonly string[] namespaces;
public SubDomainRoute(string url, object defaults, string[] namespaces)
: base(url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
this.namespaces = namespaces;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
if (the subdomain is the expected one, then update your route data)
{
var routeData = base.GetRouteData(httpContext);
if (this.namespaces != null && this.namespaces.Length > 0)
{
routeData.DataTokens["Namespaces"] = this.namespaces;
}
routeData.DataTokens["Area"] = "Your Subdomain Area";
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
return routeData;
}
return null;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
if (is your expected subdomain)
{
return base.GetVirtualPath(requestContext, values);
}
return null;
}
}
Then you have to use the custom route in the area registration like this:
public override void RegisterArea(AreaRegistrationContext context)
{
context.Routes.Add("YourAreaName_default", new CustomSubDomainRoute(
"{controller}/{action}/{id}",
new { area = "Your Area", controller = "Home", action = "Index", id = UrlParameter.Optional },
new[] { typeof(Controllers.HomeController).Namespace } // Namespaces defaults
));
}
And finally, my advise is to use redirect to route, because the routeData Area is lost between the different requests.
Use your global property route name when redirecting.
[Authorize]
public class YourController : Controller
{
protected virtual string RouteName
{
get
{
return "YourRouteName";
}
}
public ActionResult Test(params here)
{
return RedirectToRoute(this.RouteName, new { action = "your action" });
}
}
Ref: http://forums.asp.net/t/1967197.aspx?Subdomain+in+mvc4+can+not+redirect+to+controller+in+area+

How to return javascript in mvc when I click the button to jump from one page to another and cookie overtime?

When user log in my web,I set the cookie's timeout to be 30 min,after timeout,when user want to go to page B from page A,I will check the cookie time out and return the javascript to the brower,what's wrong with my codes?It seems that I return the content because all the codes below will display in the browser but not execute(and I have to refresh the url and is OK).
Method 1:not work
public override void OnAuthorization(AuthorizationContext filterContext)
{
var url = string.Format("{0}?ReturnUrl={1}",
FormsAuthentication.LoginUrl,
filterContext.HttpContext.Request.RawUrl);
if (SessionHelper.Get("UserName") == null)
filterContext.HttpContext.Response.Write(
"<script>alert('Login overtime!Loggin again please!');
window.location.href='"+url +"';</script>");
}
Method 2:not work
public override void OnAuthorization(AuthorizationContext filterContext)
{
var url = string.Format("{0}?ReturnUrl={1}",
FormsAuthentication.LoginUrl,
filterContext.HttpContext.Request.RawUrl);
if (SessionHelper.Get("UserName") == null)
filterContext.Result = new ContentResult
{
ContentType = "text/html",
Content = "<script>alert('Login overtime!Loggin again please!');
window.location.href='" + url + "';<script>"
};
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (SessionHelper.Get("UserName") == null)
{
filterContext.Controller.TempData.Add("RedirectReason", "Login overtime!Loggin again please!");
filterContext.Result = new RedirectResult("~/Login");
}
}
Then add this to your view...
#TempData["RedirectReason"]
and the error will be displayed on your login form.
Hope this helps!
we should not return javascript alerts..instead see what the error code you are getting when you make a service call after the cookie timesout. based on that, add the below to your master page
$.ajaxSetup({
statusCode: {
407: function () {
//your redirection code here
window.location.reload();
}
},
});
//do not use 407 in your code..see what code u get,then replace 407 with ur status code

MVC extending controller to have ajax-aware Redirect functionality

Hi I am learning Ajax + MVC. I figured it would be nice for the Controller to automatically handle ajax-aware Redirect(). After some digging, I found the code from this link. The code below is totally transparent to user, a user can just call Redirect(someUrlString) without needing to worry about difference between normal/ajax calls. Makes it very neat and cool.
public abstract class BaseController : System.Web.Mvc.Controller {
//turn into ajax aware redirect
protected override RedirectResult Redirect(string url) {
return new AjaxAwareRedirectResult(url);
}
}
and ...
public class AjaxAwareRedirectResult : RedirectResult {
public AjaxAwareRedirectResult(string url) : base(url) { }
public override void ExecuteResult(ControllerContext context) {
if (context.RequestContext.HttpContext.Request.IsAjaxRequest()) {
string desturl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
JavaScriptResult result = new JavaScriptResult() {
Script = "window.location='" + desturl + "';" };
result.ExecuteResult(context);
}
else { base.ExecuteResult(context); }
}
}
However, it is not complete. Challenge is:
RedirectToRouteResult RedirectToAction(ActionResult result)
is not there yet (Very handy especially for T4MVC).
As I am still new to MVC, I tried, but I am not knowledgeable enough to sufficiently figure out how to write this myself. Could any of you experts please help me with this? so I can learn it from your code? Thank you very much.
Here is the quick simple solution I use for Ajax aware redirection in my project..
Create a class AjaxRedirectAttribute for action.
public class AjaxRedirectAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var result = filterContext.Result as RedirectResult;
if (result != null && filterContext.HttpContext.Request.IsAjaxRequest())
{
string destinationUrl = UrlHelper.GenerateContentUrl(result.Url, filterContext.HttpContext);
filterContext.Result = new JavaScriptResult()
{
Script = "window.location = '" + destinationUrl + "';"
};
}
}
}
User this attribute as below to either redirect to other page or to return some result from action.
[AjaxRedirect]
public ActionResult MyAction(FormCollection frmcol)
{
// some code here
if (UserId != 0)
{
return Redirect(this.Url.Action("Action", "Controller"));
}
else
{
return Content("Error message here.");
}
}
This is a good solution that work with MVC exception handling for Ajax (Ajax.BeginForm or jquery.ajax) call.
So, as you know there is an Application_Error in global.asax.cs file. Can you let me know how to handle ajax call here with for "JavaScriptResult" result.
I am using "RedirectToRoute" to handle it, but in some of code it is appending the html with existing header. My error controller has no layout (_Layout=null), but still it is showing header and footer.
I am just working to a fresh RedirectToRoute call.
Response.RedirectToRoute("Default", new {controller = "Error", action = "Error"});
protected void Application_Error(object sender, EventArgs e)
{
RouteValueDictionary routeDataCollection = HttpContext.Current.Request.RequestContext.RouteData.Values;
if (null != routeDataCollection && routeDataCollection.Count()>1)
{
Server.ClearError();
Response.RedirectToRoute("Default", new { controller = "Error", action = "Error"});
}
}
Thanks
Sagar

How can I supply an AntiForgeryToken when posting JSON data using $.ajax?

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.

asp.net mvc custom exception filter to force a return of full view, not partial

I have a custom exception filter that I'm calling by virtue of adding a [CustomExceptionFilter] attribute to my class. It works as I'd like it to, however if the action method is returning a partial view (through an ajax request), the exception (which is basically a redirect to a not authorized page), is loading up the partial view with that page. Is there a way I can force it to reload the 'parent' url?
Here is the code for the custom exception filter
public class CustomExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception.GetType() == typeof(CustomSecurityException))
{
filterContext.ExceptionHandled = true;
RequestContext rc = new RequestContext(filterContext.HttpContext, filterContext.RouteData);
string url = RouteTable.Routes.GetVirtualPath(rc, new RouteValueDictionary(new { Controller = "NoAccess", action = "Index", message = filterContext.Exception.Message })).VirtualPath;
filterContext.HttpContext.Response.Redirect(url, true);
}
}
}
This is something you need to handle on the browser. Try handling the error() on jQuery.ajax() call for example (and obviously don't return redirect..).
I would suggest letting the exception bubble up to the client and handle it like Maxwell suggested.
In our previous project we used a specific actionfilter for handling ajax errors (borrowed from Suteki Shop). Note that the response status is 500 (internal server error). An error status is required for the response in order to call de Error() delegate within a JQuery.ajax() call.
public class HandleErrorWithAjaxAttribute : HandleErrorAttribute
{
public HandleErrorWithAjaxAttribute()
{
ShowStackTraceIfNotDebug = true;
}
public bool ShowStackTraceIfNotDebug { get; set; }
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
string content = ShowStackTraceIfNotDebug ||
filterContext.HttpContext.IsDebuggingEnabled
?
filterContext.Exception.StackTrace
:
string.Empty;
filterContext.Result = new ContentResult
{
ContentType = MediaTypeNames.Text.Plain,
Content = content
};
filterContext.HttpContext.Response.Status =
"500 " + filterContext.Exception.Message
.Replace("\r", " ")
.Replace("\n", " ");
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
else
{
base.OnException(filterContext);
}
}
}
I use the OnFailure hanlder in form tag.
<form id="AJAXForm" method="post" action=""
onsubmit="Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event),
{ insertionMode: Sys.Mvc.InsertionMode.replace, httpMethod: 'POST',
updateTargetId: 'myPartialPage', onSuccess: Function.createDelegate(this, ajaxFormSucced),
onFailure: Function.createDelegate(this, ajaxFormFailure) });" >
...
function ajaxFormSucced(){
// Code for success
}
function ajaxFormFailure(){
// Code for failure
}
You can verify if the request is an ajax request or not.
You could for example do the following...
if (!filterContext.HttpContext.Request.IsAjaxRequest()){
//Return a ViewResult
//filterContext.ExceptionHandled = true;
//filterContext.Result = new ViewResult { ViewName = "Error" ... };
}
else{
//An ajax request.
//return a partial view
}
However, as Maxwell said you could let the exeption bubble up if it is an ajax request and handle the error on the client. You can setup globally a way of handling exceptions in ajax requests like it is described here
Did you try clearing the response? The controller may still be setting response content.
filterContext.HttpContext.Response.Clear()
filterContext.Result = new JsonResult { Data = new { Message = message } };
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
This link helped me
Handling ASP.NET MVC exceptions when posting to the controller via Ajax using jQuery
Lastly, When testing the javascript function, start with alert on the first line. Any javascript errors in your function are likely to stop the function in mid execution without javascript error feedback via the browser (depending on your setup).
This will help you.
Just add .IsAjaxRequest extension method and return 403 status code to browser, jquery ajaxError will handle it redirecting to login page
As Maxwell says, handle on the client, using something like this
function handleError(ajaxContext) {
// Load parent
}
#using (Ajax.BeginForm("Index", "Home", new AjaxOptions
{
UpdateTargetId = "MyDiv",
OnFailure = "handleError"
}))
What you must do though is make sure in the controller ActionResult code for NoAccess contains the following code, so that your ajax error is triggered.
HttpContext.Response.StatusCode = 401;

Resources