在Web应用开发中,用户在提交表单后,通常需要得到一个视觉反馈,以确认操作是否成功。本文将探讨如何利用ASP.NET MVC框架中的TempData来实现这一功能。TempData是一个键值存储,用于在当前请求和下一个请求期间保存数据。这意味着可以在POST操作执行时存储一个成功消息,如果操作成功,该消息将在重定向到GET操作时仍然可用。这种模式被称为PRG(Post-Redirect-Get),它有助于保护数据的完整性,并避免用户在刷新页面时重复提交表单。
要实现用户反馈机制,需要完成以下步骤:
1. 创建一个Notifier类来存储消息。
2. 通过Action Filter将消息写入TempData。
3. 创建一个Html Helper来从TempData读取并显示消息。
4. 将INotifier接口注入到控制器中。
5. 提供写入成功、警告和错误消息的方法。
Notifier类是一个简单直接的类,它包含一个消息列表和不同严重性级别的消息添加方法。每个消息都有一个Generate方法,允许每个消息创建自己的显示标记。
public enum MessageSeverity
{
None,
Info,
Success,
Warning,
Danger
}
public class Message
{
public MessageSeverity Severity { get; set; }
public string Text { get; set; }
public string Generate()
{
var isDismissable = Severity != MessageSeverity.Danger;
if (Severity == MessageSeverity.None) Severity = MessageSeverity.Info;
var sb = new StringBuilder();
var divTag = new TagBuilder("div");
divTag.AddCssClass("alert fade in");
divTag.AddCssClass("alert-" + Severity.ToString().ToLower());
var spanTag = new TagBuilder("span");
spanTag.MergeAttribute("id", "MessageContent");
if (isDismissable)
{
divTag.AddCssClass("alert-dismissable");
}
sb.Append(divTag.ToString(TagRenderMode.StartTag));
if (isDismissable)
{
var buttonTag = new TagBuilder("button");
buttonTag.MergeAttribute("class", "close");
buttonTag.MergeAttribute("data-dismiss", "alert");
buttonTag.MergeAttribute("aria-hidden", "true");
buttonTag.InnerHtml = "×";
sb.Append(buttonTag.ToString(TagRenderMode.Normal));
}
sb.Append(spanTag.ToString(TagRenderMode.StartTag));
sb.Append(Text);
sb.Append(spanTag.ToString(TagRenderMode.EndTag));
sb.Append(divTag.ToString(TagRenderMode.EndTag));
return sb.ToString();
}
}
public interface INotifier
{
IList Messages { get; }
void AddMessage(MessageSeverity severity, string text, params object[] format);
}
public class Notifier : INotifier
{
public IList Messages { get; private set; }
public Notifier()
{
Messages = new List();
}
public void AddMessage(MessageSeverity severity, string text, params object[] format)
{
Messages.Add(new Message { Severity = severity, Text = string.Format(text, format) });
}
}
每个消息都会生成一个Bootstrap警告框,严重性级别允许改变背景颜色。
已经完成了第一步,现在需要在视图中显示这些消息。通过一个Action Filter将消息添加到TempData中,然后在视图中通过一个扩展方法读取它们。
public static class Constants
{
public const string TempDataKey = "Messages";
}
public class NotifierFilterAttribute : ActionFilterAttribute
{
public INotifier Notifier { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var messages = Notifier.Messages;
if (messages.Any())
{
filterContext.Controller.TempData[Constants.TempDataKey] = messages;
}
}
}
public static class NotifierExtensions
{
public static void Error(this INotifier notifier, string text, params object[] format)
{
notifier.AddMessage(MessageSeverity.Danger, text, format);
}
public static void Info(this INotifier notifier, string text, params object[] format)
{
notifier.AddMessage(MessageSeverity.Info, text, format);
}
public static void Success(this INotifier notifier, string text, params object[] format)
{
notifier.AddMessage(MessageSeverity.Success, text, format);
}
public static void Warning(this INotifier notifier, string text, params object[] format)
{
notifier.AddMessage(MessageSeverity.Warning, text, format);
}
public static MvcHtmlString DisplayMessages(this ViewContext context)
{
if (!context.Controller.TempData.ContainsKey(Constants.TempDataKey))
{
return null;
}
var messages = (IEnumerable)context.Controller.TempData[Constants.TempDataKey];
var builder = new StringBuilder();
foreach (var message in messages)
{
builder.AppendLine(message.Generate());
}
return builder.ToHtmlString();
}
private static MvcHtmlString ToHtmlString(this StringBuilder input)
{
return MvcHtmlString.Create(input.ToString());
}
}
DisplayMessages方法需要ViewContext以便访问TempData。需要使用相同的键来访问TempData中的消息。为了避免使用“魔术字符串”,添加了TempData键作为常量。还添加了一些方便的扩展方法快捷方式,用于不同类型的消息。
最后一步是将Notifier连接到DI容器。使用流行的DI容器Autofac。如果不熟悉Autofac,他们有很棒的文档。
using Autofac;
using Autofac.Integration.Mvc;
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
var builder = RegisterDependencies();
var container = builder.Build();
var mvcResolver = new AutofacDependencyResolver(container);
DependencyResolver.SetResolver(mvcResolver);
}
private static ContainerBuilder RegisterDependencies()
{
var builder = new ContainerBuilder();
var currentAssembly = Assembly.GetExecutingAssembly();
builder.RegisterType().AsImplementedInterfaces().InstancePerRequest();
builder.RegisterType().AsActionFilterFor().PropertiesAutowired();
builder.RegisterFilterProvider();
builder.RegisterControllers(currentAssembly);
return builder;
}
}
主要需要注意的是,在注册过滤器时,包括了PropertiesAutowired()。这确保了过滤器中的Notifier属性被连接起来。
public class HomeController : Controller
{
private readonly INotifier notifier;
public HomeController(INotifier notifier)
{
this.notifier = notifier;
}
public ActionResult NotifyTest()
{
return View();
}
[HttpPost]
public ActionResult NotifyTest(FormCollection form)
{
notifier.Success("一个成功消息");
return RedirectToAction("NotifyTest");
}
}