【问题标题】:ASP.Net MVC and state - how to keep state between requestsASP.Net MVC 和状态 - 如何在请求之间保持状态
【发布时间】:2012-05-25 14:23:52
【问题描述】:

作为一个相当有经验的 ASP.Net 开发人员,最近才开始使用 MVC,我发现自己正在努力将自己的思维方式从传统的“服务器控制和事件处理程序”的做事方式转变为更动态的 MVC 做事方式.我想我正在慢慢到达那里,但有时 MVC 的“魔法”会让我失望。

我目前的方案是创建一个网页,允许用户浏览本地文件,将其上传到服务器并重复此操作,直到他有一个文件列表可供使用。当他对文件列表(将显示在页面上的网格中)感到满意时,他将单击一个按钮来处理文件并提取一些将存储在数据库中的数据。

最后一部分不是那么重要,现在我正在努力解决一些琐碎的事情,比如建立一个文件列表,并在请求之间保留该列表。在传统方法中,这将非常简单——数据将保存在 ViewState 中。但在 MVC 中,我需要在控制器和视图之间传递数据,但我并不完全了解它应该如何工作。

我想我最好发布我相当不完整的编码尝试来解释问题。

为了保留我的文件列表数据,我创建了一个视图模型,它基本上是一个类型化的文件列表,以及一些额外的元数据:

public class ImportDataViewModel
{
    public ImportDataViewModel()
    {
        Files = new List<ImportDataFile>();
    }

    public List<ImportDataFile> Files { get; set; }
...

在视图中,我有一个用于浏览和上传文件的表单:

<form action="AddImportFile" method="post" enctype="multipart/form-data">
  <label for="file">
    Filename:</label>
  <input type="file" name="file" id="file" />
  <input type="submit" />
  </form>

视图使用 viewmodel 作为其模型:

@model MHP.ViewModels.ImportDataViewModel

这会将文件发送到我的操作:

public ActionResult AddImportFile(HttpPostedFileBase file, ImportDataViewModel importData)
    {

        if (file.ContentLength > 0)
        {
            ImportDataFile idFile = new ImportDataFile { File = file };
            importData.Files.Add(idFile);
        }

        return View("DataImport", importData);
    }

此操作返回 DataImport 页面的视图以及包含文件列表的视图模型实例。

这在一定程度上工作得很好,我可以浏览文件并上传它,我可以看到动作中的视图模型数据,然后如果我在视图中放置一个断点并调试“this.Model” ,一切都很好。

但是,如果我尝试上传另一个文件,在 AddImportFile 操作中放置断点时,importData 参数为空。所以视图显然没有将其模型的当前实例传递给动作。

在我经历过的MVC示例中,模型实例被“神奇”地作为参数传递给action方法,那为什么现在是空的呢?

我认为真正的问题是我对 MVC 的理解有限,并且可能有一个非常简单的解决方案。无论如何,如果有人能指出我正确的方向,我将非常感激。

【问题讨论】:

  • 您没有将上传的文件存储在服务器上的任何位置。您如何期望它们在回发之间持续存在?您在&lt;form&gt; 中拥有的只是一个文件输入字段。所以你可以期望在控制器动作中得到的就是这个文件。而已。没有魔法。 ASP.NET MVC 中没有 ViewState。
  • 达林,感谢您的评论。我没有将文件存储在服务器上是正确的。我可以将它存储在数据库中,但这会浪费数据库空间和用户耐心,因为它需要很长时间并且没有真正的需要。我想做的只是以某种方式将它作为变量保存在服务器内存中,直到用户决定处理它。然后我将该处理的输出存储到数据库中。我想要么我试图以错误的方式使用 MVC,要么我只是不知道该怎么做。我只是不知道它是哪一个......
  • 将文件保存在服务器内存中是最糟糕的事情之一。不惜一切代价避免这种情况。这不仅会消耗服务器上的大量内存,而且不要忘记 IIS 可以随时回收应用程序域,从而丢失存储在内存中的所有数据。另外,如果您在服务器场中运行怎么办?如果您存储在服务器的内存中,这意味着场中只有一台服务器具有此信息,如果负载平衡器在场的另一台服务器上调度后续请求,您将丢失信息。
  • 阅读这些“老”问题很有趣,想想世界在短短五年内发生了多大的变化!从那时起,我已经多次处理文件上传问题,现在我对它的看法与当时大不相同。我同意 Steves 的建议,即暂时存储文件可能是最好的选择,尽管我倾向于使用 Azure blob 存储而不是 Web 服务器上的文件夹。哎呀,我什至不再使用 IIS,它只是 Azure Web 应用程序或 Azure 函数。
  • 我也收回了我之前关于在内存中存储东西的说法,我几乎不敢相信我真的认为这是个好主意! :) 我认为,为我辩护,有问题的应用程序是一个小型企业应用程序,用户很少,文件也很小,但是,我今天绝不会想到这样做!我想我们都应该感谢能够回顾过去的可怕错误并得出结论,从那时起我们已经发展了很多:) 干杯!

标签: asp.net-mvc action viewmodel state


【解决方案1】:

自从我发布这个问题以来已经有一段时间了,由于我对 MVC 的一点经验和知识,这个问题很有趣。尽管如此,我还是收到了一些非常有用的意见,最终让我找到了解决方案,并获得了一些 MVC 的见解。

首先让我失望的是,您可以拥有一个带有强类型对象作为参数的控制器,如下所示:

public ActionResult DoSomething(MyClass myObject)...

这个对象来自同一个控制器:

...
return View(myObject);
...

这使我相信对象在这两个步骤中都存在,并且我可以期望您可以将其发送到视图,执行某些操作,然后“神奇地”再次将其返回到控制器。

在阅读了有关模型绑定的内容后,我明白这当然不是这种情况。视图完全是死的和静态的,除非您将信息存储在某个地方,否则它就消失了。

回到问题,从客户端选择和上传文件,并建立这些文件的列表以显示,我意识到在 MVC 中通常有三种方式来存储请求之间的信息:

  1. 您可以将信息存储在视图的表单字段中,稍后再将其发布回控制器
  2. 您可以将其保存在某种存储中,例如文件或数据库
  3. 您可以通过访问存在于整个请求中的对象将其存储在服务器内存中,例如会话变量

就我而言,我基本上有两种类型的信息需要保留: 1.文件元数据(文件名、文件大小等) 2.文件内容

“按本书”的方法可能是将元数据存储在表单字段中,并将文件内容存储在文件或数据库中。但还有另一种方式。因为我知道我的文件非常小,而且只有少数几个,而且这个解决方案永远不会部署在服务器场或类似的地方,所以我想探索会话变量的#3 选项。这些文件在会话之外持续存在也没有什么意义——它们被处理和丢弃,所以我不想将它们存储在我的数据库中。

看完这篇精彩的文章后: Accessing ASP.NET Session Data Using Dynamics

我被说服了。我只是按照文章中的描述创建了一个 sessionbag 类,然后我可以在我的控制器中执行以下操作:

    [HttpPost]
    public ActionResult AddImportFile(HttpPostedFileBase file)
    {

        ImportDataViewModel importData = SessionBag.Current.ImportData;
        if (importData == null) importData = new ImportDataViewModel();

        if (file == null)
            return RedirectToAction("DataImport");

        if (file.ContentLength > 0)
        {
            ImportDataFile idFile = new ImportDataFile { File = file };
            importData.Files.Add(idFile);
        }

        SessionBag.Current.ImportData = importData;

        return RedirectToAction("DataImport");
    }

我完全清楚,在大多数情况下,这将是一个糟糕的解决方案。但是对于文件占用的服务器内存的几 kb 以及这一切的简单性,我认为它对我来说非常好。

使用 SessionBag 的额外好处是,如果用户输入了不同的菜单项然后返回,文件列表仍然存在。情况并非如此,例如选择表单域/文件存储选项时。

最后,我意识到 SessionBag 很容易被滥用,因为它使用简单。但如果你将它用于它的用途,即会话数据,我认为它可以成为一个强大的工具。

【讨论】:

【解决方案2】:

关于上传

1) 可以考虑使用带有 HTML 的 AJAX 上传器,以允许您的用户在发送到服务器之前选择多个文件。这个 BlueImp Jquery AJAX 文件上传器非常棒,有一个非常棒的 api:Blueimp Jquery File Upload。它将允许用户拖放或多选多个文件并编辑文件顺序、包含/排除等。然后当他们满意时,他们可以按上传发送到您的控制器或上传处理程序以在服务器端进行处理。

2) 你可以让每次上传都持久化到数据库中,尽管你会重新加载整个页面并编写一些额外的视图模型和剃须刀代码来实现列表效果。这可能不会响应......

关于保持状态 WebForms/MVC

在请求之间保持状态有点黑魔法和巫术。进入 ASP.NET MVC 时,了解 Web 应用程序使用请求和响应进行通信。所以去拥抱无状态的网络并从那里发展吧!当您的模型通过您的控制器发布时,它与控制器中的所有变量一起消失!但是,在它消失之前,您可以将其内容存储在数据库中以供以后检索。

Web 应用程序无法像桌面应用程序那样保持真实状态。 ajax 框架和一些巫毒工具可以用来模拟 HTTP 环境中的状态。对状态的模拟实际上只是对状态的错误模仿。 ASP.NET Web 窗体试图通过向开发人员隐藏 HTTP 的无状态特性来尽可能地模拟状态。在尝试将自己的 AJAX 代码与 Web 表单标记代码及其自己的 Ajax 框架结合使用时,您可能会遇到很多麻烦。

很高兴您正在学习 MVC

抛开所有的玩笑不谈,如果你有 MVC/HTTP/Statelessness 的心态,很容易将这些模式应用到其他超级流行的框架,如 Ruby on Rails、SpringMVC (java)、Django (python)、CakePHP 等... 这种简单的知识转移将帮助您成为更好的开发人员并真正擅长 Ajax。

我真的很高兴你正在学习 MVC 3,我曾在一些非常大的公司实习过,这些公司有这些疯狂的大型 ASP.NET Web 窗体项目,代码到处乱飞,只是为了改变一些数值在数据库中 (-_-') 感觉就像我在用剪刀编织婴儿的袜子。一个简单的错误举动,一切都崩溃了。感觉就像在 PHP 中开发一样,你出来时大汗淋漓,并不确定发生了什么以及发生在哪一行。调试和更新几乎是不可能的。

【讨论】:

  • 虽然有点明显,但我会在上面添加 2)a) 以使用隐藏字段来维护请求之间的文件列表。最终,Web 表单中的视图状态抽象使用了隐藏字段,所以这毕竟不是一个坏主意,并且比数据库选项更容易实现。
  • Max,感谢您的详尽回答。至于 1) - 是的,我可能会在稍后阶段尝试实现多文件上传功能以使其更加用户友好,但这并不是真正的核心问题。我认为这里的核心问题更多是关于有状态与无状态。我之前遇到过这个讨论,但我并没有完全理解这整个“拥抱 MVC 的无状态本质”的东西。你是说我们不应该在我们的网络应用程序中实现状态吗?并非所有内容都应存储在数据库中。以购物车为例。人类本质上是有状态的,那么网络应用为什么不应该呢?
  • 基兰,我认为你是对的。我现在正在阅读有关模型绑定的信息,在我的案例中,它似乎代表了大部分感知的“魔法”——视图中的表单字段如何突然转换为控制器中的强类型类实例。当试图在不了解基本原理的情况下将“魔术”应用于不同的场景时,它当然会出错。认为我现在走在正确的轨道上。
  • 我认为让 Web 应用程序像 WinForms 或桌面应用程序那样真正有状态是不可能的。关于 ASP.NET 状态和其他形式的 AJAX 状态保存的所有内容实际上都是对状态的光荣模仿:-\。当然它可能表现得像一个有状态的应用程序,但是在调试“几乎有状态”的网络应用程序时,你必须对自己诚实并意识到没有状态。这就是我认为“拥抱无国籍的本性”的意思。
  • 这对我来说证实了一些关于 MVC 的事情,这很有帮助。谢谢!好建议。
猜你喜欢
  • 1970-01-01
  • 2019-04-01
  • 1970-01-01
  • 2011-11-21
  • 1970-01-01
  • 2011-02-04
  • 1970-01-01
  • 2017-03-04
  • 1970-01-01
相关资源
最近更新 更多