Reputation: 12376
In my ASP.NET MVC application I have an ErrorController, currently having only one action method that receives a custom class called Error and renders it to the view called Error.cshtml. For now, there're only 2 properties in the Error class:
public class Error{
public string UserFriendlyMmessage{get;set;}
public string ErrorMessage{get;set;}
}
The Error.cshtml view is strongly typed to the Error class. Whenever an exception is thrown inside an action method I do the following:
Error error = new Error { UserFriendlyText = "The friendly message", ErrorDescription = "Error to be logged" };
return RedirectToAction("Index", "Error", new System.Web.Routing.RouteValueDictionary(error));
And here's the action method of the ErrorController:
public ActionResult Index(Error error)
{
return View(model: error, viewName:"Error");
}
While this works well, this way all the model properties are shown in the url. Is there a better, more commonly used way of doing this?
Upvotes: 1
Views: 2078
Reputation: 3996
One of the reasons I don't like using RedirectToAction
for error handling is that it changes the url of the page you're browsing, which is not the best user experience.
The simplest way would be to move your Error.cshtml
into the /Views/Shared
folder and make sure that file doesn't exist in the Controller throwing the error. Then you can just do this:
Error error = new Error { UserFriendlyText = "The friendly message", ErrorDescription = "Error to be logged" };
return RedirectToAction("Error", new System.Web.Routing.RouteValueDictionary(error));
By default MVC convention, it will search for the Error.cshtml
file in the "Controller" directory, and upon not finding it then search in the Shared
directory.
If you ever need to customize the error page for that controller, you would just create a new Error.cshtml
page inside the Controller needing the custom error.
Upvotes: 1
Reputation:
Apart from the ugly query string, your at risk that you will exceed the query string limits, particularly if your messages are long and you add further properties to Error
.
One option would be to persist the Error
to a TempData
property, then return RedirectToAction("Index", "Error")
and in the Index()
method get the value from TempData
and render the view.
Another option would be to use the HandleErrorAttribute attribute to render views based on specific exception. For example
[HandleError(ExceptionType = typeof(MyException), View = "MyError")]
[HandleError(ExceptionType = typeof(AnotherException), View = "AnotherError")]
public class BaseController : Controller
{
}
and in the /Views/Shared
folder, add specific error views (MyError.cshtml)
@model System.Web.Mvc.HandleErrorInfo
....
@Model.Exception.Message // display error message
...
Note HandleErrorInfo gives you access to the controller and action names as well as the exception details.
Then, in a specific method, if you throw an exception, the associated view will be displayed
public class MyController : BaseController
{
public ActionResult SomeAction()
{
if(someCondition)
{
throw new MyException("Some friendly error message");
}
return View();
}
}
Where MyException
is an inbuilt exception, or you own custom exception that derives from Exception
Note you also need to set the following in web.config
<system.web>
<customErrors mode="On" defaultRedirect="Error" />
</system.web>
Upvotes: 5
Reputation: 971
Is there any need for redirect to special error action while you are filling Error class in each action and will have the same view ? You may just do the following:
try{
DoSomeStuff();
}
except (Exception e)
{
Error error = new Error { UserFriendlyText = "The friendly message", ErrorDescription = "Error to be logged" };
return View("Error", error); //error view
}
return View(); //Default way
For ourself we have choosen another approach to show errors. There is no special view to show error. In this case user will loose context where error happened. Instead of special view we created the following structure.
We have BaseController with couple of useful functions:
protected void AddErrorMessage(String errorMessage)
{
var errorMessages = TempData["ErrorMessages"] as List<String>;
if (errorMessages == null)
errorMessages = new List<String>();
errorMessages.Add(errorMessage);
TempData["ErrorMessages"] = errorMessages;
}
protected void AddSuccessMessage(String successMessage)
{
var successMessages = TempData["SuccessMessages"] as List<String>;
if (successMessages == null)
successMessages = new List<String>();
successMessages.Add(successMessage);
TempData["SuccessMessages"] = successMessages;
}
protected void AddWarningMessage(String warningMessage)
{
var warningMessages = TempData["WarningMessages"] as List<String>;
if (warningMessages == null)
warningMessages = new List<String>();
warningMessages.Add(warningMessage);
TempData["WarningMessages"] = warningMessages;
}
We have modified _Layout to display those messages:
@if (@TempData.ContainsKey("ErrorMessages"))
{
foreach (var errorMessage in TempData["ErrorMessages"] as List<String>)
{
<div class="alert alert-error">
<button type="button" class="close" data-dismiss="alert">×</button>
@(new HtmlString(errorMessage))
</div>
}
}
//The same stuff for warnings and messages
And in the controller we are using it as follows:
public ActionResult CreateCustomer(Customer customer) {
if (!ModelState.IsValid)
{
this.AddErrorMessage("Something wrong with your model");
return View(customer);
}
}
We just return the same view, but with additional messages. So customer may check problem or see some warning/information message without loosing context.
Upvotes: 1