Display errors and exceptions in Toastr or a similar method using MVC4 and Entity Framework

I am trying to find a way to use Toastr to display errors to users, since Exceptions or Errors occur in my application. The problems I ran into seem to suggest that the exception might be at the controller level or the data access level displayed in the current view using Toastr.

I am wondering if any of you have come across this scenario and what is your solution?

What I'm trying to accomplish is that at any time an unhandled exception occurs, or someone manually handles the exception so that we can display the error to the user without breaking the workflow. Toastr was offered to me, but being completely javascript, I am not sure what the best way to implement it in my MVC4 application.

One option I'm exploring is setting my default index controller to handle an incoming error string so that I can redirect it from the Application_Error method to Global.asax.cs to give a friendly redirect, and then if that incoming string is not equal zero, I can use toastr in the index view. However, this is not ideal because it requires redirection and disrupts the workflow. Also, it will not let me display the error without throwing an exception or do all the error handling in javascript.

Other important information is that we use the Telerik Kendo user interface and Razor syntax if this helps me in any way.

+4
source share
1 answer

For those of you who have the exact same question that I had, this solution is:

I found the first step of my solution here: https://github.com/martijnboland/MvcNotification

He implemented his own Notification form. But I wanted to be able to use Toastr or any other notification options that were there.

NOTE. Anywhere you see a class that ends in "Res" is a resource file. This should keep our lines in our application more organized. So no one mixes with this.

This is how I implemented my solution. NOTE. This also works with MVC5

The first thing to do is create a Toastr object in the source code. This will be used to ultimately display a message to the user in the user interface.

public class Toast { public string type { get; set; } public string message { get; set; } public string title { get; set; } public string positionClass { get; set; } public int fadeIn { get; set; } public int fadeOut { get; set; } public int timeOut { get; set; } public int extendedTimeOut { get; set; } public bool debug { get; set; } /// <summary> /// /// </summary> /// <param name="type"></param> /// <param name="message"></param> /// <param name="dtype"></param> public Toast(MessageType type, string message, DisplayType dtype = DisplayType.TopRight) { this.type = type.ToString(); this.message = message; this.DType = dtype; this.fadeIn = 300; this.fadeOut = 1000; this.timeOut = 5000; this.extendedTimeOut = 1000; this.debug = false; } /// <summary> /// /// </summary> public DisplayType DType { set { this.positionClass = GetPositionClass(value); } } /// <summary> /// /// </summary> /// <param name="dtype"></param> /// <returns></returns> private string GetPositionClass(DisplayType dtype) { string position = string.Empty; switch (dtype) { case DisplayType.TopLeft: position = ToastrProperties.TopLeft; break; case DisplayType.TopFull: position = ToastrProperties.TopFull; break; case DisplayType.BottomRight: position = ToastrProperties.BottomRight; break; case DisplayType.BottomLeft: position = ToastrProperties.BottomLeft; break; case DisplayType.BottomFull: position = ToastrProperties.BottomFull; break; case DisplayType.TopRight: default: position = ToastrProperties.TopRight; break; }; return position; } /// <summary> /// /// </summary> /// <param name="json"></param> /// <returns></returns> public static List<Toast> DeserializeAll(string json) { return Newtonsoft.Json.JsonConvert.DeserializeObject<List<Toast>>(json); } /// <summary> /// /// </summary> /// <param name="allToast"></param> /// <returns></returns> public static string SerializeAll(List<Toast> allToast) { return Newtonsoft.Json.JsonConvert.SerializeObject(allToast); } } 

This uses two special enumerations that I created for Toastr display locations, and message box types so that they can be dynamic.

 public enum MessageType { success, info, warning, error, }; 

AND

 public enum DisplayType { TopRight, TopLeft, TopFull, BottomRight, BottomLeft, BottomFull, }; 

Once you have created the Toastr class, you need to override the OnException method of your controller. There is another way that should happen if you use ApiController, which I will also show.

You will also need to create the ToastrProperties class, as shown below.

 public static class ToastrProperties // TODO: Add in the descriptions for each of these properties { /// <summary> /// /// </summary> public const string MessagesKey = "messages"; /// <summary> /// /// </summary> public const string BottomFull = "toast-bottom-full-width"; /// <summary> /// /// </summary> public const string BottomLeft = "toast-bottom-left"; /// <summary> /// /// </summary> public const string BottomRight = "toast-bottom-right"; /// <summary> /// /// </summary> public const string TopFull = "toast-top-full-width"; /// <summary> /// /// </summary> public const string TopLeft = "toast-top-left"; /// <summary> /// /// </summary> public const string TopRight = "toast-top-right"; /// <summary> /// /// </summary> } 

Controller example:

I suggest creating a special base class for your controllers so that they all inherit it, and it can help with other things later in your application. Here is my base controller class.

  /// <summary> /// The Base Controller for the P3 Application. All Controllers that are not /// API Controllers should derive from this /// </summary> public abstract class BaseController : Controller { // TODO: Preferably, new up through injection through constructor protected Services.P3KendoDataAccess Data = PortalServices.DataAccess; /// <summary> /// Handles any and all unhandled exceptions that occur /// within a standard MVC controller. This will Log the Error /// using NLog, and then display an error to he user using Toastr /// which will show that there was a problem within the controller /// </summary> /// <param name="filterContext"></param> protected override void OnException(ExceptionContext filterContext) { try { // Log the original error, and mark it as fixed so that the message isn't displayed to the User // TODO: Assign a GUID to the error, and display that to the user so that it can be referenced back to the exception P3Log.Error(filterContext.Exception, System.Web.HttpContext.Current); filterContext.ExceptionHandled = true; ((BaseController)filterContext.Controller).ShowMessage(new Toast(MessageType.error, filterContext.Exception.Message, DisplayType.TopRight), false); } catch (Exception excep) { P3Log.Error(new Exception(ToastrRes.BaseControllerException, excep)); } return; } } 

After you have added this to your project, just install the controllers for this class instead of Controller and set this method.

WebAPI Controller Example:

This is a bit more because you cannot just inherit from the ApiController class, as in the example above. You must create an exception filter attribute that will be applied to each ApiController. I will show you how you can do this without manual use, as you will want it on every controller in any case.

First you need to create a filter attribute:

  public class P3ApiExceptionFilterAttribute : ExceptionFilterAttribute // TODO: Add information to the summaries { /// <summary> /// /// </summary> /// <param name="Context"></param> public override void OnException(HttpActionExecutedContext Context) { try { List<Toast> Toasts = new List<Toast>(); // Create a response and add a header for the Message to be displayed using the ajaxError event Context.Response = Context.Request.CreateResponse(); // Log the error that occurred here P3Log.Error(Context.Exception); // Go through all of the Headers that match our messages key. There should only ever be // one, but since the Web API stuff handles this differently I want to cover our bases foreach (var header in Context.Request.Headers.Where(x => x.Key.Equals(ToastrProperties.MessagesKey))) { // Check the header to see if it null, and if it not, and there are values for // the header, add them to the Toasts list so that they will be re-added to the error // response header, and actually be received by the client if (header.Value != null) { foreach (string str in header.Value) { if (!string.IsNullOrEmpty(str)) { try { Toasts.AddRange(Toast.DeserializeAll(str)); } catch { } // Do nothing here } } } } // Add the Exception Toast Toasts.Add(new Toast(MessageType.error, GlobalRes.ApplicationError, DisplayType.TopRight)); // Add the header for the response so that the messages will be displayed // once the response gets back to the client if (Toasts != null && Toasts.Any()) { string Messages = Toast.SerializeAll(Toasts); if (!string.IsNullOrEmpty(Messages)) { // Adding a single Response Header Context.Response.Headers.Add(ToastrProperties.MessagesKey, Messages); } } } catch (Exception excep) { P3Log.Error(ToastrRes.ApiToastrException, excep); } base.OnException(Context); } } 

Then you need to add your filter attribute to all your Api controllers. The easiest way to do this is to enter your WebApiConfig.cs file, and inside the Register method, simply enter:

  // Add the exception handler for the API controllers config.Filters.Add(new P3ApiExceptionFilterAttribute()); 

This will configure your WebApi controllers.

TRACK. Step

After you have added both methods, you need to do a few other things.

First of all, before we get to this, although it is important to tell you that what we do here in these two methods actually processes errors and logs them to our system. Then we use the static methods of Toast objects to serialize and deserialize the JSON into the response headers / temp of the request so that it returns to the client as JSON and can be processed by the browser for both async and reverse page requests, but we will do it in a second.

Since I did not want this to be used only to pass exception messages to the client, I also configured the extensions for the BaseController and ApiController methods so that they could call the ShowMessage method and send the Toastr methods to the client.

Here is the version of the base expansion controller:

 public static class ControllerExtensions { /// <summary> /// /// </summary> /// <param name="controller"></param> /// <param name="toast"></param> /// <param name="showAfterRedirect"></param> public static void ShowMessage(this Controller controller, Toast toast, bool showAfterRedirect = false) { try { if (toast != null) { List<Toast> allToast = new List<Toast>(); // Pull the existing messages from the Temp, or Response // based on the redirect option, and assign it to a string variable string messagesJson = showAfterRedirect ? controller.TempData[ToastrProperties.MessagesKey].ToString() : controller.Response.Headers[ToastrProperties.MessagesKey]; // Deserialize the JSON into the toast list if (!string.IsNullOrEmpty(messagesJson)) { try { allToast = Toast.DeserializeAll(messagesJson as string); } catch { } // Do nothing here } // Add a new Toast to the list allToast.Add(toast); // Serialize the List string SerializedString = Toast.SerializeAll(allToast); if (!string.IsNullOrEmpty(SerializedString)) { if (showAfterRedirect) { controller.TempData[ToastrProperties.MessagesKey] = SerializedString; } else { controller.Response.Headers[ToastrProperties.MessagesKey] = SerializedString; } } } } catch (Exception excep) { P3Log.Error(new Exception(ToastrRes.ShowMessageException, excep)); } } } 

Here is a version of Web Api of the same extension:

 public static class ApiControllerExtensions { /// <summary> /// Show a message to the user Using Toastr /// </summary> /// <param name="controller"></param> /// <param name="messageType"></param> /// <param name="message"></param> public static void ShowMessage(this ApiController controller, Toast ToastMessage) { try { string message = string.Empty; List<Toast> Messages = new List<Toast>(); var header = controller.Request.Headers.FirstOrDefault(x => x.Key.Equals(ToastrProperties.MessagesKey)); if (header.Value != null && header.Value.Any()) { string hString = header.Value.FirstOrDefault(); if (!string.IsNullOrEmpty(hString)) { try { Messages = Toast.DeserializeAll(hString); } catch {} // Do nothing here } } // Add the message to the existing messages in the // header Messages.Add(ToastMessage); message = Toast.SerializeAll(Messages); if (!string.IsNullOrEmpty(message)) { // Remove the old header, and put the new one in controller.Request.Headers.Remove(ToastrProperties.MessagesKey); controller.Request.Headers.Add(ToastrProperties.MessagesKey, message); } } catch (Exception excep) { // Log here with NLog P3Log.Error(new Exception(ToastrRes.ShowMessageException, excep)); } } } 

Like any standard extension, you need to make sure that the namespace is enabled, otherwise it will not work.

The final step:

Install the Toastr NUGET package or download it on the Internet and make sure that it is added to your packages or the method that you use to add scripts to your views.

Now you need to add Javascript to _Layout.cshtml in your application.

 <script type="text/javascript"> // Setup message triggers and display all messages for this page $(document).ready(function () { var tempMessages = '@Html.Raw(TempData[ToastrProperties.MessagesKey])'; if (!tempMessages) { tempMessages = '[]'; } var viewMessages = '@Html.Raw(Response.Headers[ToastrProperties.MessagesKey])'; if (!viewMessages) { viewMessages = '[]'; } var allMessages = $.parseJSON(tempMessages).concat($.parseJSON(viewMessages)); handleAjaxMessages(); displayMessages(allMessages); }); // Display all messages that are listed within the Header of the call. // These messages are all stored in a serialized XML string that is then Decoded by the RenderMessages method function displayMessages(messages) { $.each(messages, function (idx, msg) { toastr[msg.type](msg.message, msg.title, { fadeIn: msg.fadeIn, fadeOut: msg.fadeOut, timeOut: msg.timeOut, positionClass: msg.positionClass, onclick: function() { var wnd = $("#AppMessageWindow").data("kendoWindow"); wnd.content(msg.message).center().open(); } }); }); } // Add methods for events that are both ajaxSuccess, and ajaxError function handleAjaxMessages() { $(document).ajaxSuccess(function (event, request) { checkAndHandleMessageFromHeader(request); }).ajaxError(function (event, request) { checkAndHandleMessageFromHeader(request); }); } // Get messages from the Response header of the request, and display them as // a message using Toastr function checkAndHandleMessageFromHeader(request) { // pull the messages from the Response Header var msgs = request.getResponseHeader('@ToastrProperties.MessagesKey'); if (!msgs) { msgs = '[]' } var allMessages = $.parseJSON(msgs) displayMessages(allMessages); } </script> 

This requires some explanation. The first function in the script loads the initial headers of the / temp response, because there is no standard request on the page that starts on the page. Or at least I couldn’t find one that would allow access to the headers. Thus, they are put into use by Razor.

The rest should be fairly straightforward. It uses JSON to send a toastr message and adds events to Ajax requests so that any Toastr messages that return to it are handled properly.

I am sure that I have everything here. If you have any questions or something is missing when you are trying to implement it, write here or PM, and I will update the message. Hope this helps others who are trying to do the same. :)

Enjoy it!

+9
source

Source: https://habr.com/ru/post/1499218/


All Articles