17

现在我已经看到了一些这样的问题,但这并不是我想问的,所以对于那些尖叫的重复,我道歉:)。

我几乎没有接触过 ASP.NET MVC,但据我了解,没有 ViewState/ControlState ... 很好。所以我的问题是保留控件状态的替代方法是什么?我们是否回到老式的 ASP 中,我们可以通过使用控件的状态或使用 MVC 创建隐藏的表单输入来模拟 ASP.NET ViewState/ControlState 所做的事情,我们只是假设 AJAX 始终并保留所有状态客户端并制作 AJAX来电更新?

这个问题有一些答案,Maintaining viewstate in Asp.net mvc? ,但不完全是我正在寻找的答案。

更新:感谢到目前为止的所有答案。只是为了清除我不寻找的东西和我正在寻找的东西:

不寻找:

  • 会话解决方案
  • 饼干解决方案
  • 不想在 MVC 中模仿 WebForms

我在/正在寻找什么:

  • 一种仅在数据未反弹到控件时保留回发状态的方法。将 WebForms 想象为仅在初始页面加载时绑定网格的场景,即仅在必要时重新绑定数据。正如我所提到的,我并不是想模仿 WebForms,只是想知道 MVC 提供了哪些机制。
4

7 回答 7

16

该约定已经可用,而无需跳过太多圈子。诀窍是根据您传递给视图的模型连接 TextBox 值。

[AcceptVerbs(HttpVerbs.Get)]   
public ActionResult CreatePost()
{
  return View();
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePost(FormCollection formCollection)
{
  try
  {
    // do your logic here

    // maybe u want to stop and return the form
    return View(formCollection);
  }
  catch 
  {
    // this will pass the collection back to the ViewEngine
    return View(formCollection);
  }
}

接下来发生的是 ViewEngine 采用 formCollection 并使用 Html 帮助程序将集合中的键与您在视图中拥有的 ID 名称/值进行匹配。例如:

<div id="content">

  <% using (Html.BeginForm()) { %>

  Enter the Post Title: <%= Html.TextBox("Title", Model["Title"], 50) %><br />
  Enter the Post Body: <%= Html.TextArea("Body", Model["Body"]) %><br />

  <%= Html.SubmitButton() %>

  <% } %>

</div>

注意 textbox 和 textarea 有 Title 和 Body 的 ID?现在,请注意我是如何从 View 的 Model 对象中设置值的?由于您传入了 FormCollection(并且您应该使用 FormCollection 将视图设置为强类型),您现在可以访问它。或者,没有强类型,你可以简单地使用 ViewData["Title"] (我认为)。

POOF你神奇的 ViewState。这个概念称为约定优于配置。

现在,上面的代码是使用 FormCollection 的最简单、最原始的形式。当您开始使用 ViewModels 而不是 FormCollection 时,事情会变得有趣。您可以开始添加自己的模型/视图模型验证,并让控制器自动冒泡自定义验证错误。不过,这是另一天的答案。

我会建议使用 PostFormViewModel 而不是 Post 对象,但要每个人自己。无论哪种方式,通过在 action 方法上需要一个对象,您现在可以获得一个可以调用的 IsValid() 方法。

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreatePost(Post post)
{

  // errors should already be in the collection here
  if (false == ModelState.IsValid())
    return View(post);

  try
  {
    // do your logic here

    // maybe u want to stop and return the form
    return View(post);
  }
  catch 
  {
    // this will pass the collection back to the ViewEngine
    return View(post);
  }
}

你的强类型视图需要调整:

<div id="content">

  <% using (Html.BeginForm()) { %>

  Enter the Post Title: <%= Html.TextBox("Title", Model.Title, 50) %><br />
  Enter the Post Body: <%= Html.TextArea("Body", Model.Body) %><br />

  <%= Html.SubmitButton() %>

  <% } %>

</div>

您可以更进一步,并直接从您在控制器中设置的 ModelState 在视图中显示错误。

<div id="content">

  <%= Html.ValidationSummary() %>

  <% using (Html.BeginForm()) { %>

  Enter the Post Title: 
    <%= Html.TextBox("Title", Model.Title, 50) %>
    <%= Html.ValidationMessage("Title") %><br />

  Enter the Post Body: 
    <%= Html.TextArea("Body", Model.Body) %>
    <%= Html.ValidationMessage("Body") %><br />

  <%= Html.SubmitButton() %>

  <% } %>

</div>

这种方法的有趣之处在于,您会注意到我没有设置验证摘要,也没有设置视图中的单个验证消息。我喜欢练习 DDD 概念,这意味着我的验证消息(和摘要)在我的域中进行控制,并以集合的形式传递。然后,我遍历集合(如果存在任何错误)并将它们添加到当前的 ModelState.AddErrors 集合中。当您返回 View(post) 时,其余部分是自动的。

很多很多的约定都出来了。我强烈推荐的几本书更详细地介绍了这些模式:

按照这个顺序,第一个涵盖了整个 MVC 框架的基本细节。后者涵盖了 Microsoft 官方领域之外的高级技术,以及一些使您的生活更轻松的外部工具(Castle Windsor、Moq 等)。

于 2009-11-05T03:32:58.967 回答
4

View 在 MVC 模式中应该是愚蠢的,只是显示 Controller 给它的东西(显然我们经常在那里有一些逻辑,但前提是它不是)结果,控件不负责他们的状态,每次都会来自控制器。

我不能推荐 Apress 的 Steven Sanderson 的书 Pro ASP.NET MVC 来掌握这种模式及其实现。

于 2009-09-24T18:57:57.520 回答
3

在 Web 窗体中,控件值保留在视图状态中,因此您(理论上)不需要在每次回发时重新初始化等。这些值(再次理论上)由框架维护。

在 ASP.NET MVC 中,如果遵循范式,则不需要维护表单元素的状态。表单元素值在您的控制器可以对其进行操作的帖子上可用(验证、数据库更新等)。对于帖子处理后显示的任何表单元素,您(开发人员)负责初始化它们 - 框架不会自动为您执行此操作。

也就是说,我已经阅读了一种名为 TempData 的机制,它允许您的控制器在重定向后将数据传递给另一个控制器。它实际上是一个会话变量(或 cookie,如果你这样配置它),但它会在下一次请求后自动清理。

于 2009-11-05T04:00:01.707 回答
3
  • 隐藏字段,例如:

    <% using (Html.BeginForm<SomeController>(c=>c.SomeAction(null))) {%>
      <%= Html.Hidden("SomeField", Model.SomeField)%>
      <%= Html.Hidden("AnotherField", Model.AnotherField)%>
    
  • 设置特定模型并且没有任何显式字段(给你隐藏字段)。在下面的示例中,控制器使用从上一篇文章中收到的值填充模型,因此这会在页面中启用一个 no js 选项,该选项可以根据状态进行过滤:

    Some Filter: <% using( Html.BeginForm<SomeController>(
            c => c.SomeAction(model.SomeField, model.AnotherField, model.YetAnotherField, null, model.SomeOtherField)
            )) { %>
                <%= Html.DropDownList("status", Model.StatusSelectList)%>
                <input type="submit" value="Filter" class="button" />
                <% } %>
    
  • 使用扩展方法创建字段,如果您只想在提交的表单上显示失败的验证消息时用发布的值填充字段
  • 在 asp.net mvc 2 上,他们引入了一种将实例保存在隐藏字段中的方法...编码 +(我认为)签名
  • TempData 如果上述所有内容都没有执行(通过会话 - 在下一个请求中清除)
  • 正如你所提到的,当使用 ajax 时,状态已经在客户端站点中先前加载的字段中。如果你需要做一个完整的帖子,用你的 js 更新你可能需要的任何字段。

以上都是实现它的不同的独立选项,可以在不同的场景中使用。还有更多我没有提到的选项,即cookies、会话、在数据库中存储东西(例如可恢复的多步骤向导)、传递给动作的参数。没有一种单一的机制可以统治所有这些,也不应该存在。

于 2009-11-05T17:38:35.193 回答
3

答案实际上取决于您尝试为其维护状态的控件类型。对于基本的 Html 控件,维护模型的状态非常容易,为此您需要创建一个强类型视图。

因此,如果我们有一个具有以下属性的用户模型:用户名、全名、电子邮件,我们可以在视图中执行以下操作:

<%= Html.ValidationSummary() %>

<% using (Html.BeginForm()) { %>
  <fieldset>
    <legend>User details</legend>
    <%= Html.AntiForgeryToken() %>

    <p>
      <label for="Username">Username:</label>
      <%= Html.Textbox("Username", Model.Username, "*") %>
    </p>
    <p>
      <label for="FullName">FullName:</label>
      <%= Html.Textbox("FullName", Model.FullName, "*") %>
    </p>
    <p>
      <label for="Email">Email:</label>
      <%= Html.Textbox("Email", Model.Email, "*") %>
    </p>
    <p>
       <input type+"submit" value="Save user" />
    </p>
  </fieldset>
<% } %>

然后,我们将有两个控制器操作来显示此视图,一个用于获取,另一个用于发布:

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult User()
{
  return View(new User())
}

[AcceptVerbs(HttpVerbs.Post)]
[ValidateAntiForgeryToken]
public ActionResult User([Bind(Include = "Username,FullName,Email")]User user)
{
   if (!ModelState.IsValid()) return View(user);

   try
   {
     user.save()
     // return the view again or redirect the user to another page
   }
   catch(Exception e)
   {
     ViewData["Message"] = e.Message;
     return View(user)
   }
}

这是你想要的?或者您想在请求之间保持未以表单形式显示的模型的状态?

要记住的关键是您的代码在请求期间在服务器上执行并结束,您可以在请求之间传递的唯一信息是基本的 html 表单数据、url 参数和会话信息。

正如其他人所提到的,我强烈推荐 Steve Sandersan 的 Pro ASP.NET MVC 框架,以全面了解如何使用 MVC 框架。

于 2009-11-05T17:59:14.300 回答
2

我认为最好的方法是将原始模型序列化为隐藏字段,然后对其进行反序列化并在发布时更新模型。这与视图状态方法有些相似,只是您必须自己实现它。我用这个:

首先,我需要一些使事情变得更容易的方法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using LuvDaSun.Extensions;
using System.Web.UI;

namespace LuvDaSun.Web.Mvc
{
    public static class HtmlHelperExtensions
    {
        static LosFormatter _losFormatter = new LosFormatter();
        public static string Serialize(this HtmlHelper helper, object objectInstance)
        {
            var sb = new StringBuilder();
            using (var writer = new System.IO.StringWriter(sb))
            {
                _losFormatter.Serialize(writer, objectInstance);
            }
            return sb.ToString();
        }


    }

    [AttributeUsage(AttributeTargets.Parameter)]
    public class DeserializeAttribute : CustomModelBinderAttribute
    {
        public override IModelBinder GetBinder()
        {
            return new DeserializeModelBinder();
        }
    }

    public class DeserializeModelBinder : IModelBinder
    {
        static LosFormatter _losFormatter = new LosFormatter();

        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            if (bindingContext.ModelType.IsArray)
            {
                var type = bindingContext.ModelType.GetElementType();
                var serializedObjects = (string[])bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(string[]));
                var deserializedObjects = Array.CreateInstance(bindingContext.ModelType.GetElementType(), serializedObjects.Length);

                for (var index = 0; index < serializedObjects.Length; index++)
                {
                    var serializedObject = serializedObjects[index];
                    var deserializedObject = _losFormatter.Deserialize(serializedObject);

                    deserializedObjects.SetValue(deserializedObject, index);
                }

                return deserializedObjects;
            }
            else
            {
                var serializedObject = (string)bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(string));
                var deserializedObject = _losFormatter.Deserialize(serializedObject);

                return deserializedObject;
            }
        }
    }

}

然后在我的控制器中我有这样的东西(更新产品)

    public ActionResult Update(string productKey)
    {
        var model = _shopping.RetrieveProduct(productKey);

        return View(model);
    }
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Update([Deserialize]Shopping.IProduct _model, FormCollection collection)
    {
        UpdateModel(model);

        model.Save();

        return RedirectAfterPost();
    }

我需要一个隐藏字段来保存表单中的序列化对象:

    <% 
        using (Html.BeginRouteForm("Product", FormMethod.Post, new { id = UniqueID, }))
        {
    %>
<%= Html.Hidden("Model", Html.Serialize(Model)) %>
    <h1>
        Product bewerken</h1>
    <p>
        <label for="<%=UniqueID %>_Name">
            Naam:</label>
        <input id="<%=UniqueID %>_Name" name="Name" type="text" value="<%= Html.AttributeEncode(Model.Name) %>"
            class="required" />
        <br />
    </p>
    <p>
        Omschrijving:<br />
        <textarea id="<%= UniqueID %>_Description" name="Description" cols="40" rows="8"><%= Html.Encode(Model.Description) %></textarea>
        <br />
    </p>
    <p>
        <label for="<%=UniqueID %>_Price">
            Prijs:</label>
        <input id="<%= UniqueID %>_Price" name="Price" type="text" value="<%= Model.Price.ToString("0.00") %>"
            class="required" />
        <br />
    </p>
    <ul class="Commands">
        <li><a href="" class="ClosePopup">Annuleren</a></li>
        <li>
            <input type="submit" value="Opslaan" /></li>
    </ul>
    <% 
        } 
    %>

    <script type="text/javascript">

        jQuery('#<%= UniqueID %>').validate();

    </script>

如您所见,表单中添加了一个隐藏字段(模型)。它包含原始对象的序列化信息。当表单被发布时,隐藏字段也被发布(当然)并且内容被自定义模型绑定器反序列化为原始对象,然后由控制器更新和保存。

请注意,您正在序列化的对象需要使用 Serializable 属性进行修饰,或者需要具有可以将对象转换为字符串的 TypeConverter。

网络表单中的视图状态使用 LosFormatter(有限对象序列化)。它还提供序列化数据的加密。

问候...

于 2010-02-26T17:23:07.640 回答
0

AJAX 调用是我们所做的。如果您在谈论一般的网格,请查看JQGrid以及他们如何推荐 AJAX 实现。

于 2009-11-04T22:13:34.817 回答