ASP.NET MVC - 在控制器之间共享会话状态

6

我对控制反转还不太熟悉(虽然现在正在学习),如果它是解决我的问题的方法,请告诉我,我会回去学习它。

我有一对需要Session变量的控制器,由于Session的工作方式,自然而然地没有发生太多特殊的事情,但这让我想知道在两个独立的控制器之间共享相关对象的最干净的方法是什么。在我的具体场景中,我有一个UploadController和一个ProductController,它们配合使用来上传图像文件。当UploadController上传文件时,上传的数据将存储在Session中。在此之后,我需要在ProductController中访问该Session数据。如果我在两个控制器中都创建一个包含上传信息的Session变量的get/set属性,我就可以访问那些数据,但同时我也将违反所有DRY原则,更不用说创建一个最好是混乱的设计,其中一个对象被两个完全不相关的对象共享和修改。

你有什么建议吗?

确切的上下文:

文件上传视图将文件发布到UploadController.ImageWithpreview(),然后读取发布的文件并将其复制到临时目录。保存文件后,另一个类生成上传图像的缩略图。原始文件和生成的缩略图的路径随后通过JsonResult返回给JavaScript回调函数,该回调函数更新页面上的一些动态内容,在该页面上可以“保存”或“取消”。无论上传的图像是被保存还是被跳过,我都需要从临时目录中移动或删除它和生成的缩略图。为了方便起见,UploadController在Session维护的队列对象中跟踪所有上传文件及其缩略图。

回到视图:在表单中填充了上传的图像的生成缩略图之后,表单将回传到ProductsController,其中选择的文件被确定(目前我将文件名存储在隐藏字段中,我意识到这是一个可怕的漏洞),然后将其从临时目录复制到永久位置。理想情况下,我希望只需访问我在Session中存储的队列即可,以便表单不需要像现在这样包含图像位置。这就是我设想的解决方案,但我将热切地听取任何评论或批评。

2个回答

3
我有几个解决方案。您可以使用一个“SessionState”类,将其映射到请求并获取/设置信息(我是根据记忆做的,因此这不可能编译,并旨在传达观点):
internal class SessionState
{
  string ImageName
  {
    get { return HttpContext.Current.Session["ImageName"]; }
    set { HttpContext.Current.Session["ImageName"] = value; }
  }
}

然后从控制器中,做如下操作:

  var sessionState = new SessionState();
  sessionState.ImageName = "xyz";
  /* Or */
  var imageName = sessionState.ImageName;

或者,您可以创建一个控制器扩展方法:

public static class SessionControllerExtensions
{
  public static string GetImageName(this IController controller)
  {
    return HttpContext.Current.Session["ImageName"];
  }

  public static string SetImageName(this IController controller, string imageName)
  {
    HttpContext.Current.Session["ImageName"] = imageName;
  }
}

然后从控制器开始:

  this.SetImageName("xyz");
  /* or */
  var imageName = this.GetImageName();

这绝对是DRY的。尽管如此,我不是特别喜欢这两种解决方案,因为我更喜欢在会话中存储尽可能少的数据(如果有的话)。但是,如果你想要保留所有这些信息而不必从其他来源加载/分辨出它们,那么这是我能想到的最快(肮脏)的方法。我非常确定有一个更优雅的解决方案,但我并没有关于你正在尝试做什么以及问题领域是什么的所有信息。

请记住,在会话中存储信息时,您将不得不通过序列化脱水/重水化对象,并且您可能并没有从这种方式中获得您认为的性能。

希望这有所帮助。

编辑:回应额外信息 我不确定您正在寻找部署的位置,但实时处理图像是遭受DoS攻击的绝对方法。我向您提出以下建议--假设这是公共面向的,任何人都可以上传图像:

1)允许用户上传图像。该图像进入应用程序或某个服务的后台处理队列。此外,图像的名称进入用户的个人处理队列--可能是数据库中的一个表。有关Web应用程序中后台处理的信息可以在计划托管Web服务器中的作业中找到。

2)处理这些图像,并在处理时显示“处理图形”。您可以在产品页面上有一个ajax请求,检查正在处理的图像并每X秒尝试重新加载它们。

3)当图像正在“处理”时,用户可以选择退出处理,假设他们是上传图像的人。这可在显示图像的产品页面或单独的“用户队列”视图上使用,以允许他们将图像从考虑中删除。

因此,您最终会得到一些领域对象,并且这些对象由队列管理。我强烈支持约定优于配置,因此产品图像的最终目的地应该是预定义的。例如:

images/products/{id}.jpg或者,如果是集合,则为images/products/{id}/{sequence}.jpg。

然后,您不需要在表单中知道目标。对于所有图像都是相同的。

然后,队列需要知道上传了哪个临时图像以及产品ID是什么。队列工作者从队列中弹出项目,对其进行处理,并相应地存储它们。

我知道这听起来比您最初打算的要“结构化”一些,但我认为它更加清洁。


谢谢您的输入。如果有必要的话,我已经在原来的问题中添加了更多信息。 - Nathan Taylor
图像处理是实时的,但存在于网站管理区域内,因此它的总体使用率相当低。在整个应用程序中,最多可能每小时处理几张图片。我考虑的另一种方法是简单地让应用程序的Session_End事件删除当前会话的整个临时目录,但我不确定这是否理想。 - Nathan Taylor
如果您的用户上传了许多正在处理的图像,我认为您无法保证在其会话过期时处理完成。一旦“处理”完成,我会将其删除。工作线程应该在队列有需要处理的项目时保持活动状态,因此可以安全地假设如此。只要您的应用程序池没有“休眠”,您的系统应该可以正常处理。我相信这在我提供链接的文章中已经涵盖了。 - andymeadows
确实。这不仅是一项低频操作,而且每次只能执行一个文件。无论如何,我会看一下你提供的链接。 - Nathan Taylor
你说得非常正确。它已经完全做到了这一点,但是由于图像上传操作的配置方式,它返回一个通用结果,其中包含一对从结构规则生成的文件名。我只是试图保留系统中所有上传的缓冲区,无论它们是“产品”还是“零件”。 - Nathan Taylor
然后一个“最终”的建议是将队列移出会话并使其应用程序特定。您可以将其存储在内存中或数据库中。使每个队列对象存储图像类型(产品或零件)、所有者(用户ID)和临时位置。当图像被处理后,从临时位置中删除它。为整个系统设置单个临时位置。如果我仍在跟随您的话,这应该解决任何其他问题。 - andymeadows

1

上传控制器(UploadController)和产品控制器(ProductController)之间是否完全相等?

因为文件通过上传控制器(UploadController)上传,上传的数据将存储在会话中。当此操作完成后,我需要在产品控制器(ProductController)中访问该会话数据。

根据我的阅读,上传控制器(UploadController)需要对上传数据进行读写访问,而产品控制器(ProductController)仅需要读取。

如果这是真的,那么您可以使用一个不可变包装器来明确上传信息,并让上传控制器(UploadController)将其放入会话中。

会话本身是公共共享公告板,解耦显式关系,但代价是允许任何人获取和存储。您可以允许产品控制器(ProductController)知道有关上传控制器(UploadController)的信息,从而消除通过会话传递上传信息的需要。我认为上传信息对公众来说很有意义,所以使用会话是合理的。

我没有看到任何DRY违规行为,在此我们明确地试图分离责任。


我不确定这更符合DRY问题还是仅仅是组件通信定义不清的问题。Session显然旨在弥合我正在尝试填补的确切差距,但在这种情况下它并不特别干净。 - Nathan Taylor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接