ASP.NET MVC 5 Error Handling

ASP.NET MVC 5 error handling

The best way is using Global.Asax, because you can manage all types of errors (Ajax calls/ all of unexpected Errors). with others you can't do it.

Like this:

protected void Application_Error()
{
HttpContext httpContext = HttpContext.Current;
if (httpContext != null)
{
RequestContext requestContext = ((MvcHandler)httpContext.CurrentHandler).RequestContext;
/* When the request is ajax the system can automatically handle a mistake with a JSON response.
Then overwrites the default response */
if (requestContext.HttpContext.Request.IsAjaxRequest())
{
httpContext.Response.Clear();
string controllerName = requestContext.RouteData.GetRequiredString("controller");
IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
IController controller = factory.CreateController(requestContext, controllerName);
ControllerContext controllerContext = new ControllerContext(requestContext, (ControllerBase)controller);

JsonResult jsonResult = new JsonResult
{
Data = new { success = false, serverError = "500" },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
jsonResult.ExecuteResult(controllerContext);
httpContext.Response.End();
}
else
{
httpContext.Response.Redirect("~/Error");
}
}
}

ASP.NET MVC 5 Form Validation and Error Handling

There are multiple things you need to understand here. Let me go point by point.

  • Its good to know that you have your model designed, but how your view gets to know that it has a model to bind for itself and when posting the form contents, how would server comes to know that, there is a model to be received. So on the first instance, you need to construct your view binding the model. To bind a model in a view, you need to first get a reference/declare it at the top, letting view know that, ok, here is a model for you to generate my view.

  • Well, you have ValidationSummary to true, then I would suggest that, instead of using ViewData to pass error message, you can use ModelState.AddModelError and let ValidationSummary take care of that. As a side note, you might also want to take care of this issue and you can resolve the same with answers mentioned in the same post. If you are not using or do not want to use Html.ValidationSummary, then you can stick to your current view.

  • Now, to display Success message, you can either use TempData or ViewData and follow the same structure as you have in your view now. Here is one more post to let you work on that.

  • Last and most important on View part is binding model properties to View elements. Use Razor View extension helpers to generate View for your model. You have @Html.TextBoxFor,@Html.TextAreaFor etc., You also have @Html.TextBox, @Html.TextArea which is not for binding model properties, but just to generate a plain HTML view. You can add other html properties within these helpers as shown in the updated view below. I would suggest to dig down more on the overloads available for these helpers.

So here is your updated view.

@model SOTestApplication.Models.ContactModel   @*getting model reference*@

@using (Html.BeginForm("Contact", "Home", FormMethod.Post))
{
<h3>Send us an email</h3>
Html.ValidationSummary(true);
<div class="form-group">
<label class="sr-only" for="contact-email">Email</label>
@Html.TextBoxFor(m => m.Email, new { type = "text", name = "email", placeholder = "Email..", @class = "contact-email" })
@*Usage of helpers and html attributes*@
</div>
<div class="form-group">
<label class="sr-only" for="contact-subject">Subject</label>
@Html.TextBoxFor(m => m.Subject, new { type = "text", name = "subject", placeholder = "Subject..", @class = "contact-subject" })
</div>
<div class="form-group">
<label class="sr-only" for="contact-message">Message</label>
@Html.TextAreaFor(m => m.Message, new { name = "message", placeholder = "Message..", @class = "contact-message" })
</div>
<button type="submit" class="btn">Send it</button>
<button type="reset" class="btn">Reset</button>
}
if (ViewData["Success"] != null)
{
<h4>We will get back to you as soon as possible!</h4>
<p>
Thank you for getting in touch with us. If you do not hear
from us within 24 hours, that means we couldn't contact you
at the email provided. In that case, please feel free to call
us at (xxx) xxx-xxxx at any time.
</p>
}

Controller Side validation

Not much to say on this part as it looks good. But based on few of my points above, I would suggest you to add ModelState.AddModelError instead of using ViewData for error messages. Eliminate your if conditions in view, so that contact form remains, even after postback. Now if you want to persist the values after server side validation, then just pass back the model to your view in your post method. Updated Controller would be:

[HttpPost]
public ActionResult Contact(ContactModel contactModel)
{
if (ModelState.IsValid)
{
try
{
MailMessage message = new MailMessage();
using (var smtp = new SmtpClient("mail.mydomain.com"))
{
// Standard mail code here
ViewData["Success"] = "Success";
}
}
catch (Exception)
{
ModelState.AddModelError("Server side error occurred", ex.Message.ToString());
}
}
return View(contactModel); //this will persist user entered data on validation failure
}

Client Side Validation

As far as this portion is considered, you have few more things to set up in your application.

  • You need to add Html.EnableClientValidation(true); and Html.EnableUnobtrusiveJavaScript(true); to your application. There are various possible ways to add this. You can add this on Web.config file under appSettings for global implication Or you can add this in particular view as mentioned in below updated View example.

Global Implication in Web.Config ex:

<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
  • If you have noticed your BundleConfig.cs file under App_Start directory, you would have seen below entries created by default. These are the jquery stuffs responsible for your Client Side validation.

jQuery and jQueryVal entries

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
  • Next Step is to add reference to these files/use @section Scripts to render these bundles either in _Layout.cshtml or in any specific view. When you include this in _Layout.cshtml. these scripts/bundles are rendered wherever you use this layout with other views. So basically, its your call on where to render these.

For example here, I would render these in Contact.cshtml view soon after adding reference to model.

@section Scripts
{
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
}
  • One Last thing to make this work here is that you need to use @Html.ValidationMessageFor razor extension and let MVC do the binding of error messages on particular properties. Also for these error messages to be displayed in the View, you need to specify ErrorMessage for each property in your model as you are doing it now with Required(ErrorMessage=... for each properties in model. There are more to know about these stuffs if you explore it in detail.

Your updated view with proper validations added.

@model SOTestApplication.Models.ContactModel
@section Scripts
{
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryval")
}
@using (Html.BeginForm("Contact", "Contacts", FormMethod.Post))
{

<h3>Send us an email</h3>
Html.ValidationSummary(true);
Html.EnableClientValidation(true);
Html.EnableUnobtrusiveJavaScript(true);
<div class="form-group">
<label class="sr-only" for="contact-email">Email</label>
@Html.TextBoxFor(m => m.Email, new { type = "text", name = "email", placeholder = "Email..", @class = "contact-email" })
@Html.ValidationMessageFor(m => m.Email)
</div>
<div class="form-group">
<label class="sr-only" for="contact-subject">Subject</label>
@Html.TextBoxFor(m => m.Subject, new { type = "text", name = "subject", placeholder = "Subject..", @class = "contact-subject" })
@Html.ValidationMessageFor(m => m.Subject)
</div>
<div class="form-group">
<label class="sr-only" for="contact-message">Message</label>
@Html.TextAreaFor(m => m.Message, new { name = "message", placeholder = "Message..", @class = "contact-message" })
@Html.ValidationMessageFor(m => m.Message)
</div>
<button type="submit" class="btn">Send it</button>
<button type="reset" class="btn">Reset</button>
if (ViewData["Success"] != null)
{
<h4>We will get back to you as soon as possible!</h4>
<p>
Thank you for getting in touch with us. If you do not hear
from us within 24 hours, that means we couldn't contact you
at the email provided. In that case, please feel free to call
us at (xxx) xxx-xxxx at any time.
</p>
}
}

Hope I have clarified most of your doubts with these points. Happy Coding.. :)

Exception Handling in ASP.NET MVC 4, 5

http://www.codeguru.com/csharp/.net/net_asp/mvc/handling-errors-in-asp.net-mvc-applications.htm

Shows different ways of handling errors globally. Setting a global exception handling filter is the usual.

GlobalFilters.Filters.Add(new SpecialHandleErrorAttributeCreatedByYou());

You would derive a new Attribute from HandleErrorAttribute and put your logic in there, overriding the OnException method.

public override void OnException(ExceptionContext filterContext)

However this will not show you exceptions that are handled by other code. That is the point, they are handled so they do not continue to bubble up the stack. You would have to put code in each of those catch blocks to either do what you want.

Error Handling for both JsonResult and ActionResult

What are you supposed to do in that situation? I rather have it to
redirect to errorpage or login screen.

You are on the right track. We definitely do not want to return View (redirect to custom error page), if the client expects Json response from server. Otherwise, it will mess up the client's logic.

This answer might not answer your question directly. However, if you see yourself returning JsonResult a lot, you might want to consider using Web API Controller in ASP.NET MVC 5.

Web API 2.1 supports GlobalExceptionHandler in which you can customize the Http response that is sent when an unhanded application expcetion occurs.

In my case, I use Angular with ASP.NET MVC and Web API. So, I have to return unhandled exception in Json format for Ajax requests.

WebApiExceptionHandler.cs

If your application throws custom exception, you can even filter them here, and return appropriate message.

public class WebApiExceptionHandler : ExceptionHandler
{
public override void Handle(ExceptionHandlerContext context)
{
var exception = context.Exception;
var httpException = exception as HttpException;
if (httpException != null)
{
context.Result = new WebApiErrorResult(context.Request,
(HttpStatusCode) httpException.GetHttpCode(), httpException.Message);
return;
}
/*if (exception is MyCustomException)
{
context.Result = new WebApiErrorResult(context.Request,
HttpStatusCode.NotFound, exception.Message);
return;
}*/
context.Result = new WebApiErrorResult(context.Request,
HttpStatusCode.InternalServerError,
"An error occurred while processing your request.");
}
}

WebApiErrorResult.cs

public class WebApiErrorResult : IHttpActionResult
{
private readonly string _errorMessage;
private readonly HttpRequestMessage _requestMessage;
private readonly HttpStatusCode _statusCode;

public WebApiErrorResult(
HttpRequestMessage requestMessage,
HttpStatusCode statusCode,
string errorMessage)
{
_requestMessage = requestMessage;
_statusCode = statusCode;
_errorMessage = errorMessage;
}

public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_requestMessage.CreateErrorResponse(_statusCode, _errorMessage));
}
}

WebApiConfig.cs

Finally, we register our custom exception handler with framework.

public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional });

config.Services.Replace(typeof(IExceptionHandler), new WebApiExceptionHandler());

config.Filters.Add(new AuthorizeAttribute());
}
}

Confused with error handling in ASP.net 5 MVC 6

It sounds like you're confusing unhandled exceptions (which are, by default, returned to the client as an HTTP 500 Internal Server Error) and correctly-handled error cases caused by invalid action on behalf of the user/client (where a 4xx HTTP code is returned to the user).

Only the former of these has anything to do with the UseExceptionHandler call - by default it will catch any unhandled exceptions and route them to whatever you provide (in your case, a view, but it could just as easily be a piece of code which inspects the unhandled exceptions to convert certain error cases into HTTP 4xx return codes - for example, authentication errors into HTTP 401 responses).

UseStatusCodePagesWithReExecute will step in where a status code has been generated of 400-599, so long as no response body has been generated already. The source code in question shows how this is determined.

In your second code block, you've used UseExceptionHandler - I think you should have the following:

if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
// Handle unhandled errors
app.UseExceptionHandler("/Home/Error");
// Display friendly error pages for any non-success case
// This will handle any situation where a status code is >= 400
// and < 600, so long as no response body has already been
// generated.
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}

How to handle 404 error in config and code in MVC5?

web.config

Turn off custom errors in system.web

<system.web>
<customErrors mode="Off" />
</system.web>

configure http errors in system.webServer

<system.webServer>
<httpErrors errorMode="Custom" existingResponse="Auto">
<clear />
<error statusCode="404" responseMode="ExecuteURL" path="/NotFound" />
<error statusCode="500" responseMode="ExecuteURL" path="/Error" />
</httpErrors>
</system.webServer>

Create simple error controller to handle those requests ErrorContoller.cs

[AllowAnonymous]
public class ErrorController : Controller {
// GET: Error
public ActionResult NotFound() {
var statusCode = (int)System.Net.HttpStatusCode.NotFound;
Response.StatusCode = statusCode;
Response.TrySkipIisCustomErrors = true;
HttpContext.Response.StatusCode = statusCode;
HttpContext.Response.TrySkipIisCustomErrors = true;
return View();
}

public ActionResult Error() {
Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError;
Response.TrySkipIisCustomErrors = true;
return View();
}
}

configure routes RouteConfig.cs

public static void RegisterRoutes(RouteCollection routes) {

//...other routes

routes.MapRoute(
name: "404-NotFound",
url: "NotFound",
defaults: new { controller = "Error", action = "NotFound" }
);

routes.MapRoute(
name: "500-Error",
url: "Error",
defaults: new { controller = "Error", action = "Error" }
);

//..other routes

//I also put a catch all mapping as last route

//Catch All InValid (NotFound) Routes
routes.MapRoute(
name: "NotFound",
url: "{*url}",
defaults: new { controller = "Error", action = "NotFound" }
);
}

And finally make sure you have views for the controller actions

Views/Shared/NotFound.cshtml
Views/Shared/Error.cshtml

If there are any additional error you want to handle you can follow that pattern and add as needed. This will avoid redirects and maintain the original http error status that was raised.



Related Topics



Leave a reply



Submit