我为MVC创建了自己的视图状态工具。好的还是弱的做法?

时间:2009-07-11 17:06:58

标签: asp.net-mvc

好的,我承认 - 我为ASP.NET MVC编写了自己的视图状态工具。我对其他人的批评很感兴趣,特别是考虑到与WebForms相关的所有视图状态抨击。另一方面,在 Pro ASP.NET MVC框架(p405-406)中,Steven Sanderson说:“我觉得作为一般网页设计模式,[ViewState]完全合理:Web开发人员始终保留隐藏表单字段中的数据;这只是通过形式化该技术并提供一个整洁的抽象层将其提升到一个新的水平。“鉴于我的具体问题,在保持MVC透明性和可测试性的优势的同时,创建这样的轻量级抽象层似乎是一种合理的方法。

以问题形式:

  • 使用ViewData是解决我问题的最佳方式还是至少一种强有力的方法?
  • 在我的具体方法中是否存在严重缺陷(例如,性能,安全性)?
  • 这种方法与MVC设计美学的匹配程度如何?
  • 有更好的解决方案吗?如果是这样,它是什么?为什么?

我正在编写一个安全的界面来管理用户/角色/帐户 - 这种事情。从数据库检索的数据具有标识令牌和用于乐观并发控制的时间戳。对于编辑等操作,标识和时间戳必须与客户端操作相关联,这需要某种客户端持久性。时间戳是此客户端持久性的关键驱动因素,因为更新记录需要根据当前时间戳检查检索时间戳,以查看自最初检索以来其他用户是否已更新它。必须保持时间戳的完整性,因为恶意用户可以通过操纵它来覆盖数据库记录。

通常的持久性选项是ViewData,TempData和会话状态。我没有认真考虑其他选项,比如编写我自己的数据库工具。我之所以选择ViewData,是因为数据可以保留多次往返(例如,即使客户端跳转到另一个页面也会保留状态),因为我想避免大量的会话数据管理。我的想法是,如果只有选择数据存储在ViewData中,并且使用HMAC(散列码消息验证)代码保护它,那么该方法的开销和安全性都相当低。

在实践中,我使用一对函数Encode / Decode来序列化数据并计算HMAC代码,并使用Html帮助程序Html.FormState()将序列化数据存储在表单上。 (Encode / Decode API比我展示的更多,使我能够存储多个对象等)。我还将状态作为参数传递回action方法。这保持了具有功能性风味的设计,从而提高了可测试性。这是一个示例(ViewData的内联赋值仅用于说明):

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Edit(Guid? id) {
        User user = _crmContext.Users.GetUser(id ?? Guid.Empty);
        if (user == null) {
            TempMessage = "User not found";
            return RedirectToAction("Index");
        }
        else {
            ViewData["formState"] = EncodeState("user", user);
            return View(user);
        }
    }

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken]
    public ActionResult Edit(Guid? id, string formState) {
        User user = DecodeState("user", formState) as User;
        if (user == null || id != user.UserId) {
            TempMessage = "User not found";
            return RedirectToAction("Index");
        }
        else {
            try {
                UpdateModel(user, "user");
                _crmContext.Users.UpdateUser(user);
                TempMessage = "User changes saved.";
                return RedirectToAction("Details", new { id = user.UserId });
            }
            catch (RulesException e) {
                e.AddModelStateErrors(ModelState, "user");
                ViewData["formState"] = EncodeState("user", user);
                return View(user);
            }
        }
    }

    public static string FormState(this HtmlHelper html) {
        string anti = html.AntiForgeryToken();
        string data = html.Hidden("formState");
        return "\n" + anti + "\n" + data;
    }

1 个答案:

答案 0 :(得分:1)

这个问题是合理的。

Web应用程序需要在与用户或特定请求关联的请求之间存储数据。典型的机制 - 隐藏的表单值,服务器端状态和cookie - 都有其优点和缺点。

当存储特定于给定请求的信息时,我倾向于默认为隐藏的表单值,因为它提供了最佳的可伸缩性(没有服务器端信息存储)。当然,缺点是,如果您不确切地知道存储了多少信息,页面会变得臃肿。您还需要确保回发数据有效,因为它可能被坏人篡改(数字签名和加密都是合理的解决方案)。

所以对我来说,你的解决方案似乎非常合理。我过去做过类似的事情(使用我的动态数据MVC示例),甚至可以构建一个自定义模型绑定器,它允许我直接在我的操作方法中访问反序列化对象(这使得单元测试它们更简单,因为他们不依赖于在表单字段中加密数据。)