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 amodel
to bind for itself and when posting theform
contents, how wouldserver
comes to know that, there is amodel
to be received. So on the first instance, you need to construct your view binding themodel
. To bind amodel
in aview
, you need to first get a reference/declare it at the top, letting view know that, ok, here is amodel
for you to generate myview
.Well, you have
ValidationSummary
totrue
, then I would suggest that, instead of usingViewData
to pass error message, you can useModelState.AddModelError
and letValidationSummary
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 useHtml.ValidationSummary
, then you can stick to your current view.Now, to display
Success
message, you can either useTempData
orViewData
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 bindingmodel
properties toView
elements. UseRazor View
extension helpers to generateView
for yourmodel
. You have@Html.TextBoxFor
,@Html.TextAreaFor
etc., You also have@Html.TextBox
,@Html.TextArea
which is not for bindingmodel properties
, but just to generate a plainHTML view
. You can add otherhtml
properties within thesehelpers
as shown in the updatedview
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);
andHtml.EnableUnobtrusiveJavaScript(true);
to your application. There are various possible ways to add this. You can add this onWeb.config
file underappSettings
for global implication Or you can add this in particularview
as mentioned in below updatedView
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 thejquery
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 thesebundles
either in_Layout.cshtml
or in any specificview
. When you include this in_Layout.cshtml
. these scripts/bundles are rendered wherever you use thislayout
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 letMVC
do the binding of error messages on particular properties. Also for these error messages to be displayed in theView
, you need to specifyErrorMessage
for each property in yourmodel
as you are doing it now withRequired(ErrorMessage=...
for each properties inmodel
. 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
Sqlparameter Does Not Allows Table Name - Other Options Without SQL Injection Attack
Accordion in Windows Forms Datagridview
Convert from Scientific Notation String to Float in C#
Insert into C# with SQLcommand
Why Is Try {...} Finally {...} Good; Try {...} Catch{} Bad
Can You Explain Liskov Substitution Principle with a Good C# Example
Resharper Warning - Access to Modified Closure
Determining If File Exists Using C# and Resolving Unc Path
Selenium Stops When Browser Is Manually Interrupted
Use of "This" Keyword in Formal Parameters for Static Methods in C#
How to Pass Values Between Forms in C# Windows Application
Processinfo and Redirectstandardoutput
How to Effectively Draw on Desktop in C#
System.Web.Httpcontext.Current.User.Identity.Name VS System.Environment.Username in ASP.NET
Set Cultureinfo in ASP.NET Core to Have a . as Currencydecimalseparator Instead of ,