asp.net mvc handle http 400 error - asp.net-mvc

I have an ASP.NET MVC 4 application and I want to redirect all HTTP 400 errors to my custom error page. I was searching an hours to find a solution though HTTP 400 error isn't handled like 404 error. There are many solutions that show how to escape 400(bad request error), i.e. to allow using special characters for example in url. But I wouldn't able to find some solution to catch the exception.
Please help me to catch somehow all HTTP bad requests and redirect them to my error page.

Never redirect users in case of errors, instead return a response body for the failed request. The feature of IIS (and ASP.NET) to redirect to an error page, I believe, is fundamentally wrong, incorrect, and against the HTTP specification (because then the error is being returned for the error page resource itself, not the original request. And if it's a web-browser the user has no way of retrying, because reloading the page will return the error page again, not retrying their original failed request, which is what they want).
Anyway...
A HTTP 400 response must be generated by your application code, it isn't something that will be done automatically. A bad request is typically used when informing non-human agents (i.e. web service clients, not web browsers) that their HTTP request was missing required values or had malformed values.
You can do this in MVC by having a base controller class for all of your controllers like so:
public abstract class BaseController : Controller {
protected ActionResult Http400(String message) {
Response.StatusCode = 400;
return View(message); // you need to define a view file called "Http400.aspx" (or cshtml if you're using Razor) in your application's shared views folder
}
}
so in your application logic:
public ActionResult Foobar() {
if( IsBadRequest() ) return Http400("Bad request, try again");
}

You could do something as simple as adding adding this to your web.config
<customErrors mode="RemoteOnly">
<error statusCode="400" redirect="errorpage.html"/>
</customErrors>

Related

ASP.NET MVC 4: Handle exception caused by JsonValueProvider

I have an action that is called using requests with application/jsonin the Content-type header. These requests will automatically create a JsonValueProvider that tries to deserialize the request's content. When the json is malformed, the value provider will throw an exception leading to the application's error page.
To reproduce this behavior, simply POST invalid json data to an action sending application/json as the Content-type header. This will trigger the exception.
[Edit]
Not much code is needed. Simply create an empty controller method and use a tool like Firefox "Poster" to send an invalid request to the action.
public class HomeController
{
public ActionResult Index()
{
return this.Json(true);
}
}
Then use Poster:
Set Content-type to application/json
Set Request content to {"This is invalid JSON:,}
Send the request
The result will be the full-blown standard ASP.NET HTML error page (either generic or custom, depending on your application).
[/Edit]
Since my action is called by embedded devices, I would like to send short responses, instead of the HTML error page. I would like to be able to create a response with status code 500, Content-type: text/plain, and the exception's message as it's content.
I have already tried a custom model binder and a custom error handler attribute but neither are called since the exception occurs earlier on in the processing pipeline. Is there a way to handle this error?
As a workaround, I have currently disabled the JsonValueProvider for the whole application and load the values from the request body myself. If there is a way to disable the JsonValueProvider on a per action basis, this would also help.
Thanks in advance for any pointers!
You could subscribe to the Application_Error event in your Global.asax and handle the exception as you want:
protected void Application_Error(object sender, EventArgs e)
{
var exception = Server.GetLastError();
Response.TrySkipIisCustomErrors = true;
Response.Clear();
Server.ClearError();
Response.StatusCode = 500;
Response.ContentType = "text/plain";
Response.Write("An error occured while processing your request. Details: " + exception.Message);
}

FormAuthentication with WebAPI using Breeze

I am protecting WebAPI using forms Authentication, that is using Breezecontroller
When i try to call WebAPi method i am getting back the following error.
status:404
statusText: "Not Found"
message:"MetaData query failed for:'';, No Http resource was found tha matches...
My question is why am i not getting back "UnAuthorized error(401)" ?
metadata is decorated with [Authorize] as well.
Seems like FormsAuthentication's redirect is giving problem.
It is redirecting to Login(has AllowAnonymous) WebApi method and reports it cannot find, eventhough i have. Also i am applying the Authrozie to the methods instead of controller. the exact error is
{"$id":"1","$type":"System.Web.Http.HttpError,System.Web.Http","Message":"NoHTTPresourcewasfoundthatmatchestherequestURI'http://localhost:40678/api/Country/Login?ReturnUrl=/api/Country/Metadata'.","MessageDetail":"Noactionwasfoundonthecontroller'Country'thatmatchestherequest."}
Just tried and working fine. I'm betting you have a mistake in your URL.
Here is the prelim to my controller:
[Authorize]
[BreezeController]
public class BreezeTodoController : ApiController
{
private readonly BreezeTodoContext _context;
public BreezeTodoController() {
_context = new BreezeTodoContext(User);
}
[HttpGet]
public string Metadata() {
return _context.Metadata();
}
// ... more
I hit it with this URL
http://localhost:32377/api/breezetodox/metadata
And I get back the 401
Request URL:http://localhost:32377/api/breezetodo/metadata
Request Method:GET
Status Code:401 Unauthorized
But if I make a mistake in the URL (see 'x' after breezetodo)
Request URL:http://localhost:32377/api/breezetodox/metadata
Request Method:GET
Status Code:404 Not Found
Same thing if my action name doesn't match (see 'x' after metadata):
Request URL:http://localhost:32377/api/breezetodo/metadatax
Request Method:GET
Status Code:404 Not Found
In other words, HTTP can't report that a resource is unauthorized if it can't find that resource in the first place.
when tagging the BreezeController with [Authorize] and then trying to retrieve the Breeze Metadata directly with this link:
Request URL:http://localhost/breeze/breeze/metadata
redirects to:
http://localhost/Login?ReturnUrl=%2Fbreeze%2Fbreeze%2Fmetadata with a 404
Without the [Authorize] the access to the Breeze metadata with the same link works fine.

Asp.Net MVC: requests with too long URLs and error handling

By checking my elmah log, I see I keep receiving "too long url" requests. The error is:
System.Web.HttpException (0x80004005): The length of the URL for this request exceeds the configured maxUrlLength value.
at System.Web.HttpRequest.ValidateInputIfRequiredByConfig()
at System.Web.HttpApplication.PipelineStepManager.ValidateHelper(HttpContext context)
Here is the kind of requests I receive:
/index.php/blog/post/the_ten_deceptions_of_the_datetimepicker/+[PLM=0]+GET+http:/www.visualhint.com/index.php/blog/post/the_ten_deceptions_of_the_datetimepicker/+[0,23778,23037]+->+[N]+POST+http:/www.visualhint.com/index.php/blog/post/the_ten_deceptions_of_the_datetimepicker/+[0,0,2007]
(don't be surprised by the php thing... before being an asp.net mvc site, my site was in php and now I need to redirect this kind of old URLs to my new url format, which works well when the url stops at /index.php/blog/post/the_ten_deceptions_of_the_datetimepicker)
What could generate these requests? Does it sound malicious?
I have custom errors setup, so I though such a request would be redirected to my custom error page, but it's not. Instead, people get the typical yellow screen (firebug mentions this is a 400 Bad Request). If you look at the above stack trace, it is very short and the exception seems to be caught very early (Application_BeginRequest is not even called). Is it possible to show my custom error page or can I at least redirect to my homepage when such an exception occurs?
I tried adding a line for error 400 in my web.config:
<customErrors mode="RemoteOnly">
<error statusCode="400" redirect="/" />
</customErrors>
This redirects to the homepage right, but it adds the complete url in an aspxerrorpath query string value.
Thanks for your help.
A google search helped me find that some other people get this kind of request. Someone answered with:
I am pretty sure it is an automated way of submitting spam. There must
be an error in the configuration because it should not leave such a
juicy trail in the referrer field!
First it tells some script to get an URL, then it instructs to post to
an URL (it is easy to block spam that POSTs directly without getting
first).
The numbers could be relating to what spam message(s) to post (think
of it as indexes in a spam DB).
So, since there are good chances that these requests are not from humans following normal links, I ended up with the following solution. This avoids polluting my elmah log and this serves a blank page to the caller with a 404 code:
public void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
HttpException hExc = e.Exception.GetBaseException() as HttpException;
if (hExc != null)
{
if (hExc.ErrorCode == unchecked((int)0x80004005))
{
e.Dismiss();
return;
}
}
// Here I do some stuff if the error has to be logged.
}
void Application_Error(object sender, EventArgs e)
{
Exception exc = Server.GetLastError();
HttpException hExc = exc as HttpException;
if (hExc != null)
{
if (hExc.ErrorCode == unchecked((int)0x80004005))
{
Uri uri = HttpContext.Current.Request.Url;
Response.Clear();
Response.StatusCode = 404;
Response.End();
Server.ClearError();
}
}
}
Instead of returning a 404 code, I would have preferred to not send a response (the caller would have a timeout) but is it possible with asp.net? No idea...

How to implement proper HTTP error handling in .NET MVC 2?

I've been struggling all day to implement error handling in my ASP.NET MVC 2 app. I've looked at a variety of techniques, but none work properly. I'm using MVC2 and .NET 4.0 (started the project before MVC3 was released; we'll upgrade after we deliver our initial release).
At this point, I'll be happy to properly handle 404 and 500 errors -- 403 (authorization required) would be great, too, followed by various other specific responses. Right now, I either get all 404s, all 500s, all 302s before the 404, or all 302s before the 500.
Here are my requirements (which should be pretty close to the basic requirements of HTTP):
If a resource is not found, throw a 404, and display a 404-specific page with the requested URL. DO NOT return an intermediate response code like 302. Ideally, keep the requested URL, rather than showing a new URL like /Error/NotFound -- but if the latter displays, be sure we didn't return a redirect response to get it.
If an internal server error occurred, throw a 500, and display a 500-specific error with some indication of what went wrong. Again, don't return an intermediate response code, and ideally don't change the URL.
Here's what I'd consider a 404:
Static file not found: /Content/non-existent-dir/non-existent-file.txt
Controller not found: /non-existent-controller/Foo/666
Controller found, but Action not found: /Home/non-existent-action/666
Controller and action found, but the action can't find the requested object: /Home/Login/non-existent-id
Here's what I'd consider a 500:
Post a bad value: POST /User/New/new-user-name-too-long-for-db-column-constraint
Non-data-related problem, like a Web Service endpoint not responding
Some of these problems need to be identified by specific controllers or models, and then the controllers should throw the appropriate HttpException. The rest should be handled more generically.
For 404 case #2, I tried to use a custom ControllerFactory to throw a 404 if the controller can't be found.
For 404 case #3, I've tried to use a custom base controller to override HandleUnknownAction and throw a 404.
In both cases, I get a 302 before the 404. And, I never get 500 errors; if I modify Web.config to put a typo in my Web Service endpoint, I still get a 302, then a 404 saying the URL (controller/action) which uses the Web Service can't be found.
I also get the requested URL as a(n unwanted) querystring param: /Error/NotFound?aspxerrorpath=/Home/non-existent-action
Both of these techniques came from http://www.niksmit.com/wp/?p=17 (How to get normal 404 (Page not found) error pages using ASP.Net MVC), pointed to from http://richarddingwall.name/2008/08/17/strategies-for-resource-based-404-errors-in-aspnet-mvc/
If in Web.config I have <customErrors mode="On" defaultRedirect="~/Error/Unknown" redirectMode="ResponseRedirect" />, I get the appropriate response code, but my Error controller never gets called. Taking out the redirectMode attribute gets me the MVC error views, but with an intervening 302 and a changed URL -- and always the same controller (Unknown = 500; if I change it to NotFound everything looks like a 404).
Here are some of the other things I've read and tried to implement:
http://www.davidjuth.com/asp-net-mvc-error-handler.aspx
http://sanjayuttam.com/wordpress/index.php/c-sharp/c-sharp-code-examples/error-handling-in-asp-net-mvc-1-part-2-of-2/
http://blog.hebbink.com/post/2010/12/14/NET-custom-404-error-page-returns-302-for-http-status.aspx
http://blog.dantup.com/2009/04/aspnet-mvc-handleerror-attribute-custom.html
http://weblogs.asp.net/scottgu/archive/2008/07/14/asp-net-mvc-preview-4-release-part-1.aspx
.. along with a bunch of StackOverflow posts.
Seems to me this sort of error handling is pretty basic to Web apps, and the MVC framework ought to have defaults that do this out of the box, and let people extend it to work otherwise. Perhaps they'll do it in a future release. In the meantime, can someone give me comprehensive details on how to implement proper HTTP responses?
Here's one technique you could use. Define an ErrorsController which will serve the error pages:
public class ErrorsController : Controller
{
public ActionResult Http404()
{
Response.StatusCode = 404;
return Content("404", "text/plain");
}
public ActionResult Http500()
{
Response.StatusCode = 500;
return Content("500", "text/plain");
}
public ActionResult Http403()
{
Response.StatusCode = 403;
return Content("403", "text/plain");
}
}
and then in Global.asax you could subscribe for the Application_Error event where you could log the exception and execute the corresponding action of the ErrorsController:
protected void Application_Error(object sender, EventArgs e)
{
var app = (MvcApplication)sender;
var context = app.Context;
var ex = app.Server.GetLastError();
context.Response.Clear();
context.ClearError();
var httpException = ex as HttpException;
var routeData = new RouteData();
routeData.Values["controller"] = "errors";
routeData.Values["exception"] = ex;
routeData.Values["action"] = "http500";
if (httpException != null)
{
switch (httpException.GetHttpCode())
{
case 404:
routeData.Values["action"] = "http404";
break;
case 403:
routeData.Values["action"] = "http403";
break;
case 500:
routeData.Values["action"] = "http500";
break;
}
}
IController controller = new ErrorsController();
controller.Execute(new RequestContext(new HttpContextWrapper(context), routeData));
}
And now all that's left is to start throwing proper exceptions:
public class HomeController : Controller
{
public ActionResult Index()
{
throw new HttpException(404, "NotFound");
}
}
For HTTP 404 errors (without redirects) take a look at my blog post on the subject. This might give you some good ideas:
http://hectorcorrea.com/blog/returning-http-404-in-asp-net-mvc/16
This doesn't answer your question, but it is important to note that HTTP status 500 indicates that something went wrong on the server, so your example:
POST /User/New/new-user-name-too-long-for-db-column-constraint
Is not valid grounds to throw a 500, its a data validation issue and should be handled by MVC data annotations or a jQuery validation framework or etc. Just showing an error message next to the TextBox saying "User Name too long" is much better.
This is a very old question. but I thought It's worth it if I introduce you to a much much cleaner way to handle Http Exceptions that I saw in dear "Jesse Webb's answer".
The solution is to use the httpErrors element of the system.webServer section:
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="404" subStatusCode="-1" />
<remove statusCode="500" subStatusCode="-1" />
<error statusCode="404" path="/Error/NotFound" responseMode="ExecuteURL" />
<error statusCode="500" path="/Error" responseMode="ExecuteURL" />
</httpErrors>
You also can log all exceptions in this way. "Read the "Jesse Webb's answer"".
This really feels much cleaner and also works as well as every other solution (without redirect).
Note: This only works work in IIS 7 and and newer. (Because of the httpErrors element which was recently added.

What is the correct response to an HTTP POST request?

For a POST method, the W3 specs say:
If a resource has been created on the origin server, the response
SHOULD be 201 (Created) and contain an entity which describes the
status of the request and refers to the new resource, and a Location
header (see Section 10.4).
http://www.ietf.org/internet-drafts/draft-ietf-httpbis-p2-semantics-05.txt (section 8.5)
The standard response actually seems to be to send a Redirect to the newly created resource.
I'm building my site with ASP.NET MVC, and tried to follow the spec, so created a ResourceCreatedResult class:
public class ResourceCreatedResult : ActionResult
{
public string Location { get; set; }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
context.HttpContext.Response.StatusCode = 201;
context.HttpContext.Response.ClearHeaders();
context.HttpContext.Response.AddHeader("Location", Location);
}
}
And my action looks something like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateNew(string entityStuff)
{
Entity newEntity = new Entity(entityStuff);
IEntityRepository entityRepository = ObjectFactory.GetInstance<IEntityRepository>();
entityRepository.Add(newEntity);
ActionResult result = new ResourceCreatedResult()
{ Location = Url.Action("Show", new { id = newEntity.Id }) };
return result;
}
However, IE, Firefox and Chrome all fail to redirect to the new resource. Have I messed up generating the correct response, or do web browsers not expect this type of response, instead relying on servers to send a Redirect response?
To be explicit, browsers (including modern browsers like Firefox 3 and IE8) do not "take the hint" and follow up an HTTP 201: Created response with a GET request to the URI supplied in the Location header.
If you want browsers to go to the URI supplied in the Location header, you should send an HTTP 303: See Other status instead.
Redirect after post or post/redirect/get is something your application must do to be user friendly.
Edit. This is above and beyond the HTTP specifications. If we simply return a 201 after a POST, the browser back button behaves badly.
Note that Web Services requests (which do NOT respond to a browser) follow the standard completely and do NOT redirect after post.
It works like this.
The browser POSTS the data.
Your application validates the data. If it's invalid, you respond with the form so they can fix it and POST.
Your application responds with a redirect.
The browser gets the redirect and does a GET.
Your application sees the GET and responds.
Now -- hey presto! -- the back button works.
My solution is to respond with a '201 Created' containing a simple page with a link to the new resource, and a javascript redirect using location.replace().
This lets the same code work for API and browser requests, plays nicely with Back and Refresh buttons, and degrades gracefully in old browsers.
As stated in the spec the response SHOULD be a HTTP 201 with redirect. So it isn't mandatory for a browser vendor to implement the correct answer...
You should try to change to a 30x code to see if it is correctly redirected. If so, it's a browser problem, else it may come from your code (I don't know anything in ASP.NET so I can't "validate" your code)
Shouldn't that only count for when something is "Created" and therefore a simple redirect to action should be genuinely sufficient?

Resources