在 Toastr 或使用 MVC4 和实体框架的类似方法中显示错误和异常

Posted

技术标签:

【中文标题】在 Toastr 或使用 MVC4 和实体框架的类似方法中显示错误和异常【英文标题】:Displaying Errors and Exceptions in Toastr, or similar method using MVC4, and Entity Framework 【发布时间】:2013-08-30 16:59:46 【问题描述】:

我正在尝试找到一种方法来使用 Toastr 在我的应用程序中发生异常或错误时向用户显示错误。我遇到的问题似乎表明在控制器中发生异常,或者使用 Toastr 在当前视图中显示的数据访问层是不可能的。

我想知道你们中是否有人遇到过这种情况以及您的解决方案是什么?

我想要完成的是,只要有未处理的异常,或者有人手动处理异常,我们就有能力在不中断工作流程的情况下向用户显示错误。有人向我建议了 Toastr,但完全是 javascript,我不确定在我的 MVC4 应用程序中实现它的最佳方式。

我正在探索的一个选项是设置我的默认索引控制器来处理传入的错误字符串,以便我可以从 Global.asax.cs 中的 Application_Error 方法重定向到它,以便提供友好的重定向,然后如果传入的字符串不为空,那么我可以在索引视图上使用 toastr。然而,这并不理想,因为它需要重定向,并且会破坏工作流程。此外,它不允许我在不引发异常或在 javascript 中进行所有错误处理的情况下显示错误。

其他重要信息是我们正在使用 Telerik Kendo UI 和 Razor 语法,如果这对我有任何帮助的话。

【问题讨论】:

【参考方案1】:

对于那些与我有同样问题的人来说,解决方案是:

我在这里找到了解决方案的第一步:https://github.com/martijnboland/MvcNotification

他已经实现了自己的通知形式。但我希望能够使用 Toastr 或任何其他类型的通知选项。

注意:在任何你看到以“Res”结尾的类的地方都是资源文件。这是为了让我们的应用程序中的字符串更有条理。这样就没有人会搞混了。

这是我实施解决方案的方式。 注意:这也适用于 MVC5

首先要做的是在您的源代码中创建一个 Toastr 对象。这将用于最终在 UI 中向用户弹出消息。

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);
        
    

这使用了我为 Toastr 显示位置创建的两个特殊枚举和消息窗口类型,因此它们可以是动态的。

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

还有

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

创建 Toastr 类后,您必须覆盖控制器的 OnException 方法。如果您使用的是我还将展示的 ApiController,则必须以另一种方式发生这种情况。

您还需要创建一个 ToastrProperties 类,如下所示。

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>

控制器示例:

我建议为您的控制器创建一个特殊的基类,以便它们都继承自它,并且它可以帮助您稍后在应用程序中处理其他事情。这是我的基本控制器类。

    /// <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;
        

    

将它添加到项目后,只需将控制器设置为从此类而不是控制器派生,这将设置此方法。

WebAPI 控制器示例:

这个有点复杂,因为你不能像上面的例子那样仅仅从 ApiController 类继承。您必须创建一个将应用于每个 ApiController 的异常过滤器属性。我将向您展示如何在不手动应用它的情况下做到这一点,因为您很可能希望在每个控制器上都使用它。

首先你必须创建过滤器属性:

    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's null, and if it's 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);
        
    

接下来,您需要将过滤器属性添加到所有 Api 控制器。最简单的方法是进入您的“WebApiConfig.cs”文件,然后在 Register 方法中放入:

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

这将设置您的 WebApi 控制器。

下一步

添加其中一种/两种方法后,您需要做一些其他事情。

首先在我们开始之前,重要的是要让您知道我们在这两种方法中所做的实际上是处理错误,并将它们记录在我们的系统中。然后我们使用 Toast 对象静态方法将 JSON 序列化和反序列化为请求的 response/temp 标头,然后将其作为 JSON 传递回客户端,并且可以在异步或回发页面请求时由浏览器处理.但我们将在一秒钟内完成。

因为我不希望它仅用于将异常消息传递给客户端,所以我还为 BaseController 和 ApiController 方法设置了扩展,以便它们可以调用“ShowMessage”方法并将 Toastr 方法发送到客户。

这是扩展的基本控制器版本:

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));
            
        
    

这是同一扩展的 Web Api 版本:

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));
            
        
    

与任何标准扩展一样,您需要确保包含命名空间,否则它将无法工作。

最后一步:

安装 Toastr NUGET 包,或使其在线,并确保它已添加到您的包中,或您用于将脚本添加到视图中的方法。

现在您需要将 Javascript 添加到应用程序中的 _Layout.cshtml

<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>

这需要一些解释。脚本中的第一个函数加载初始响应/临时标头,因为在初始页面加载时,页面内没有触发标准请求。或者至少我找不到允许访问标题的。所以这些都放在使用 Razor 中。

其余的应该很简单。它使用 JSON 弹出 toastr 消息,并向 Ajax 请求添加事件,以便正确处理返回给它的任何 Toastr 消息。

我很确定我这里什么都有。如果您有任何疑问,或者在尝试实施时缺少某些内容,请在此处发帖或 PM 我,我会更新我的帖子。我希望这可以帮助其他尝试做同样事情的人。 :)

享受吧!

【讨论】:

非常感谢...我一直在寻找类似的东西!你能告诉我ToastrProperties 是如何定义的吗?它位于GetPositionClass,但我在其他任何地方都看不到代码。 @Robert 我已在控制器示例上方为您添加了此信息。我希望这有帮助。如果您有任何其他问题,请告诉我。 我正在尝试做类似的事情......你能看看我的问题***.com/questions/22167497/…吗?也许你能帮我解决它。 非常感谢!!这是一个很好的解决方案!我还利用displayMessages() 函数在布局文件中使用$(document).ajaxError() 方法来显示ajax 错误。所以现在我可以用同样的方式捕获全局控制器异常和 ajax 错误了。 @Stuart 我很高兴这对你有用。感谢您的反馈。

以上是关于在 Toastr 或使用 MVC4 和实体框架的类似方法中显示错误和异常的主要内容,如果未能解决你的问题,请参考以下文章

使用带有 MVC4 SQL Server 的实体框架插入后触发器不会触发

实体框架探查器 - 带有 EF 6 的 ASP.NET MVC4 - 无法确定提供程序名称

使用 MVC 4 将实体框架与 Oracle 11g 连接时面临的问题

如何在 Django 中使用 toastr 获取成功或失败消息

实体框架不允许在 localdb (Mvc 4- C#) 中插入列

MVC 4 后期绑定的 DataContext 实体 LINQ 参考