对于那些和我有同样问题的人来说,解决方案是:
我在这里找到了我的解决方案的第一步: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;
}
}
将它添加到项目后,只需将控制器设置为从此类而不是 Controller 派生,这将设置此方法。
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 我,我会更新我的帖子。我希望这可以帮助其他尝试做同样事情的人。:)
享受!