【问题标题】:I created my own view state facility for MVC. Good or weak practice?我为 MVC 创建了自己的视图状态工具。好习惯还是坏习惯?
【发布时间】:2009-07-11 17:06:58
【问题描述】:

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

问题形式:

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

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

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

在实践中,我使用一对函数 Encode/Decode 来序列化数据并计算 HMAC 代码,并使用 Html 助手 Html.FormState() 将序列化后的数据存储在表单上。 (编码/解码 API 比我展示的要复杂一些,使我能够存储多个对象等。)我还将状态作为参数传递回操作方法。这保持了具有功能性的设计,从而提高了可测试性。这是一个示例(对 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;
    }

【问题讨论】:

  • 在我看来是不合时宜的。
  • 如果您决定使用 ViewState,为什么不尝试使用已经存在并使用 ASP.NET 开发的编码。虽然我敢打赌它不会那么直截了当 ;-)
  • @KingNestor:我很想知道您提出意见的具体原因。 @mfx:和你一样,我想 WebForms 视图状态机制与 Webforms 紧密相连。另外,EncodeState/DecodeState 非常轻量级,实际上只是一小部分代码。我花了大约 1/2 小时来改编 Pro ASP.NET MVC Framework 书中的一些代码。
  • 其实并没有那么复杂,你只需创建一个新的 ObjectStateFormatter 实例,然后在其上调用 Serialize 或 Deserialize。然后,如果您在 web.config 中设置,它将使用机器密钥进行签名和加密。
  • 感谢您的评论。我正在使用 LosFormatter 进行(反)序列化,而后者又在内部使用 ObjectStateFormatter。我将检查 ObjectStateFormatter 看看它是否提供了一些优势。也就是说,我实际上是在询问以我所描述的方式使用表单持久数据的价值,而不是关于如何实现持久性。

标签: asp.net-mvc


【解决方案1】:

这个问题是有道理的。

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

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

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

【讨论】:

  • 感谢您的评论。自定义模型绑定器是一个好主意。我可以看到只有解码的应用程序级数据进入操作方法确实有助于可测试性。
猜你喜欢
  • 2010-10-16
  • 2013-07-17
  • 2011-11-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多