65.9K
CodeProject 正在变化。 阅读更多。
Home

Post/Redirect/Get ASP.NET MVC 用户通知

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (5投票s)

2015年5月5日

MIT

7分钟阅读

viewsIcon

31035

downloadIcon

395

基于 Twitter Bootstrap 提示框,为 ASP.NET MVC 提供易于使用的跨请求“闪存”通知

引言

在 Web 应用程序开发中,对于包含表单的页面,通常会遵循 Post/Redirect/Get (PRG) 模式。创建、编辑和更新数据通常使用 POST 请求执行,然后使用 RedirectToAction() 方法向用户发送重定向响应。然而,当您想向用户闪示一条消息,告知其操作已成功(或未成功)完成时,这会带来一个问题。本文为 ASP.NET MVC 提出了一个名为 FlashMessage 的解决方案。

背景

PRG 应用程序中的通知问题

当 Web 应用程序使用表单时,用户提交表单并尝试刷新页面时可能会出现一个潜在问题。响应表单提交的操作将再次执行,可能导致不良结果。下图说明了这个问题。

为了规避这个问题,通常采用 Post/Redirect/Get (PRG) 模式。表单提交后,客户端总是被直接重定向。在 ASP.NET MVC 中,通常使用 RedirectToAction() 控制器方法来实现此目的。如果用户然后刷新页面,无论是通过刷新还是使用前进和后退按钮,表单都不会再次提交。下图说明了这一点。

然而,如果我们在表单提交后想通知用户操作的结果,这种解决方法会带来一个问题。因为在使用负载均衡时,GET 请求可能指向群集中的另一个服务器,所以不能仅仅假设您可以将通知本地存储。

图片来源:Wikipedia Post/Redirect/Get (PRG)

解决方案

本文为 ASP.NET MVC 应用程序提出了一个使用 cookie 和/或会话状态来解决此问题的方法。它允许您在返回重定向之前向用户“闪示”一条消息。该消息将在下一个请求中持久化,并在那里实际渲染。

消息排队

消息必须能够在任何有 HTTP 上下文可用点进行排队显示。此外,如果需要,可以包含完整的 HTML,以便例如可以将链接传递给消息。几种不同类型的消息也将很有用,以便能够将它们样式化为警告、确认等。

为了涵盖所有这些情况,每个消息表示如下:

/// <summary>
/// The FlashMessageModel class represents an individual flash message.
/// </summary>
public class FlashMessageModel
{

    /// <summary>
    /// Gets / sets if the message contains raw HTML. If set to true, 
    /// the title will not be rendered and the contents of the message parameter will
    /// be written directly to the output.
    /// </summary>
    public bool IsHtml { get; set; }

    public string Title { get; set; }
    public string Message { get; set; }

    public FlashMessageType Type { get; set; }

    public FlashMessageModel()
    {
        IsHtml = false;
        Type = FlashMessageType.Info;
    }
}

/// <summary>
/// The FlashMessageType enum indicates the type of flash message.
/// </summary>
public enum FlashMessageType : byte
{
    Info,
    Warning,
    Danger,
    Confirmation
}

IsHtml 属性指示 Message 属性中的消息内容是否应呈现为原始 HTML(请注意此处可能存在 HTML 注入风险)。Title 属性存储可选标题,Type 属性存储 FlashMessageType enum 定义的消息类型之一。

为了对消息进行排队,实现了 static FlashMessage.Queue() 方法。该方法检索当前排队中的闪存消息集,并将新消息追加到队列中,然后再次存储队列。

为了方便使用,提供了一些快捷方法,可以直接对各种类型的消息进行排队。这些方法还接受格式化字符串和可变参数,以避免程序员编写冗长的语句,例如:一直写 FlashMessage.Confirmation(string.Format("format...", args))

序列化消息

为了将消息大小降至最低并减少开销,消息使用二进制序列化进行序列化。BinaryReaderBinaryWriter 类用于此目的。虽然性能不是真正的问题,因为消息应该很短,但二进制序列化应该会表现得相当好。

存储消息

为了在一次请求到另一次请求之间存储消息,需要一个“transfer”方法,该方法负责将数据从 POST 请求传输到 GET 请求。

TempData

ASP.NET MVC 为每个控制器提供一个 TempData 字典,用于在请求之间存储临时数据。在下一个请求中,其内容通常会被丢弃,这最初表明它适合在这种场景中存储用户通知。然而,由于以下原因,我选择不使用 TempData

  • TempData 在后台使用会话。我倾向于在大多数应用程序中不使用会话。
  • TempData 需要访问当前控制器,而当前控制器可能并非始终可用,例如在使用 ASP.NET MVC 以外的情况下。
  • TempData 默认情况下总是在下一个请求时丢弃所有数据,而我希望通知一直保留,直到它们实际显示为止。

为了绕过这些缺点,此框架中提供了两种传输方法。传输提供程序继承 IFlashMessageTransport 接口。

通过 Cookie 传输

因为大多数消息都很短,所以我认为通过 cookie 传输消息是一个可行的选项。通过 cookie 传输由 FlashMessageCookieTransport 类实现。这是框架的默认传输方式,因为它几乎适用于所有场景。

使用 cookie 传输时,防止客户端篡改 cookie 至关重要。cookie 会暴露给客户端上潜在的有害 JavaScript,因此如果未正确保护,可能会被操纵。为了防止这种情况,使用 System.Web.Security.MachineKey.Encode() 方法对 cookie 进行编码。

通过会话传输

另一种选择是通过 session 状态传输消息,这本质上与 TempData 的功能相同。直接使用会话状态的区别在于消除了对控制器方法的依赖,使得代码可以在 ASP.NET MVC 框架之外使用。

通过会话状态传输由 FlashMessageSessionTransport 类提供。请注意,在使用 session 传输时,您需要确保 session 状态存储在所有服务器进程之间共享。

显示消息

显示消息时,消息会从传输提供程序中检索并从消息存储中移除。显示它们可以使用自定义 PartialView,或者使用包含的 FlashMessageHtmlHelper 类,该类扩展了 ASP.NET HtmlHelper 类。

显示消息的典型方式是在主布局或布局模板的页面顶部包含对 Html.RenderFlashMessages() 的调用,如下所示:

<div class="container">
    @Html.RenderFlashMessages()
    @RenderBody()
</div>

包含的 FlashMessageHtmlHelper 类为广泛使用的 Twitter Bootstrap 框架格式化消息。

Using the Code

  1. 首先,您需要添加对 ASP.NET MVC 和此 FlashMessage 包的引用。同时确保 Web.Vereyon 命名空间已在相关代码文件中导入。
  2. 接下来,您需要确保消息已正确渲染。最明显的方法是在 RenderBody() 调用之前,在您的 Razor 布局模板中包含以下代码:
@Html.RenderFlashMessages()

确保也包含必要的 Twitter Bootstrap 样式表,或者编写自己的样式表。

3. 现在,使用以下调用之一在重定向之前排队消息:

FlashMessage.Info("Your informational message");
FlashMessage.Confirmation("Your confirmation message");
FlashMessage.Warning("Your warning message");
FlashMessage.Danger("Your danger alert");

4. 完成!您的代码现在应该看起来像这样:

[HttpPost]
public ActionResult Save()
{

    // Do you usual controller action stuff here.

    // Redirect the user.
    FlashMessage.Confirmation("The entity was saved.");
    return RedirectToAction("View", new { Id = entity.id });
}

高级选项

上面介绍的每种对消息进行排队的方法都内部使用了 FlashMessage.Queue() 方法,其定义如下:

FlashMessage.Queue(string message, string title, FlashMessageType messageType, bool isHtml);

FlashMessage.Queue() 方法允许指定 FlashMessage 类每个属性的值。

为了减少安排消息所需的代码长度,所有标准消息安排方法的实例都接受格式化字符串和可变参数列表。这可以防止用户不得不包含冗长的 String.Format() 调用。

FlashMessage.Info(string format, params object[] args);

自定义消息渲染(可选)

如果您不想使用 FlashMessageHtmlHelper 中的 Twitter Bootstrap 风格消息,那么您需要编写自己的消息渲染代码。开始的最佳方法是在您的 Razor 布局模板中包含以下代码:

@foreach(var message in FlashMessage.Retrieve(Html.ViewContext.HttpContext))
{
    Html.RenderPartial("FlashMessage", message);
}

调用 FlashMessage.Retrieve() 会检索所有排队中的闪存消息,并从队列中移除它们。您应该创建一个名为 FlashMessage 的部分视图,该视图渲染每个 FlashMessageModel

配置传输(可选)

如果您想配置正在使用的传输方式,最佳的设置位置是在应用程序启动时。传输提供程序通过静态 FlashMessage.Transport 属性公开。设置不同的传输方式如下:

FlashMessage.Transport = new FlashMessageSessionTransport();

关注点

源包含一个测试项目,其中定义了一些单元测试来验证代码的功能。虽然测试覆盖率不是非常广泛,但对一些序列化和反序列化以及各种传输提供程序进行了测试。还测试了一些可能的情况,例如丢失或格式错误的 cookie 数据。

历史

https://github.com/Vereyon/FlashMessage 上查找最新版本

© . All rights reserved.