ASP缓存新手 - 尝试模拟WinForms功能/正确的协议

时间:2013-02-26 15:22:58

标签: c# asp.net .net vb.net

我目前有一个WinForms应用程序,我正在转换为ASP,并且很快意识到缓存数据的重要性。

在我的WinForms应用程序中,我创建了表单级变量Dictionary(of String, List(of String)),我在Form_Load()事件中使用从SQL Server数据库中提取的数据填充,以将2个链接的组合框关联在一起(和在我的程序中用于各种其他事情)。 WinForms应用程序的优点在于,这个变量在整个程序中对我来说是可用的,因为它是一个表单级变量,并且随着表单一起死亡。

现在,我正在尝试在我的ASP项目中获得相同类型的功能,因此我将其设置为属性并将其与缓存关联,如下所示:

Private ReadOnly Property MyDict As Dictionary(Of String, List(Of String))
    Get
        Dim dict As Dictionary(Of String, List(Of String))
        dict = Cache("MyDict")

        If dict Is Nothing Then

            ... QUERY DB AND POPULATE DICTIONARY ...

            Cache("MyDict") = dict
        End If

        Return dict
    End Get
End Property

我的第一个(也是最重要的)问题是我是否正确地执行此操作,或者我是否只是不理解缓存 - 实际上,这是我对ASP的第一次尝试,它证明相当令人生畏。

我的下一个问题是,如果我这样做,我如何/在哪里声明缓存的生命周期?我注意到,当我重新运行我的程序时,缓存的数据仍然可以从之前的运行中获得...我希望它在页面的生命周期内可用,但是一旦它关闭就不可用(如果可能的话。

第三,任何好的建议/链接/技巧都会非常感激!!

感谢!!!

PS - 我知道我的代码是VB,但是C#的答案也很好,因为这更像.Net概念级而不是语言特定的。感谢。

2 个答案:

答案 0 :(得分:1)

缓存方法有点问题,并且可能过于复杂,无法实现您的目标。关于设置缓存生命周期的问题是为什么这是非最佳方法的一些症状。使用更传统的方法来设置相关的下拉菜单可以省去很多麻烦。

来自WinForms开发的ASP.NET中最难习惯的事情之一就是你正在努力解决的问题 - 服务器端数据在帖子之间“丢失”的事实。

在WinForms应用程序中,您可以将DataTable声明为全局变量,只需单击按钮或更改下拉列表的值,就不会影响DataTable的事实。人口稠密。它就在那里因为桌子“存在”直到表格处理完毕。对于ASP.NET,服务器端对象仅在页面处理期间的回发期间存在,并且需要在回发时重新创建。

抽象的大部分魔法都是通过ViewState完成的,它可以在回发之间保存页面上对象的状态。例如,如果您在代码中查询数据库,将结果保存到DataTable,然后将该DataTable绑定到DataGrid,则ViewState将保存DataGrid的内容。在下一个回发中,DataGrid的内容可用,但绑定到DataGrid的初始DataTable不再存在。

尝试使用缓存来模仿对象的持久生命周期,使ASP.NET应用程序像WinForms应用程序一样,是对该技术的滥用。它本身并不是 糟糕 ,并且 并非不可能 ,它只是使用了错误的方法。它基本上是使用错误的工具来完成工作。

您可以更好地搜索如何执行特定任务的示例,而不是尝试执行此操作。在你的情况下,你想要search for "Cascading Drop-Down List ASP.NET"或类似的东西。有几个选项,但这个选项与任何选项一样好:http://www.aspsnippets.com/Articles/Creating-Cascading-DropDownLists-in-ASP.Net.aspx

另外,如果您还不熟悉它,那么在编写.NET应用程序时理解ASP.NET Page Lifecycle非常重要。 ASP.NET开发中没有任何一个组件可以引发新的ASP.NET开发人员,而不是理解页面生命周期。理解这一点对我们来说将比你能发挥的任何其他东西更有帮助,它与你提出的问题直接相关。

答案 1 :(得分:1)

首先,在Windows窗体中,您有1个进程,其中您拥有1个主表单实例(我不会讨论辅助表单,或者在您创建主要的多个实例时完全可以接受的情况形式)。

在ASP.NET中,您有1个进程,其中存在许多“主”页面实例。 有人可能会说,每个不同的用户都可以访问您的Web应用程序。 这是部分正确的:“主”页面实例的瞬时数量可能大于活动用户的数量(我们谈论的不是MVC的ASP.NET)。

您仍然可以全局访问,就像您以前在WinForms中一样。唯一的区别是:

  1. 在WinForms中,因为一般来说主要表单是唯一的,你可以将它用作东西的全局容器。在ASP.NET的情况下你不能这样做,因为主页不仅有一个全局实例(例如,即使在同一个会话的情况下,来自同一个浏览器,刷新页面很可能会创建一个新的Page实例。要测试:实现该页面的其他隐式公共无参数构造函数并使用断点检查出来)

  2. 通常很危险(特别是如果你不知道你做得很好)要使所有不同的线程处理来自许多浏览器的请求,所有线程都访问一些独特的共享内存。 / p>

  3. 我个人并不完全同意以下想法,但通常情况下,初学者应该使用相当多余的 SELECT 命令轰炸数据库。

    为了能够提供一个不会轰炸数据库的简单解决方案,我会简化您的问题:假设您不需要缓存。您只需要从数据库中读取一堆内容,并同意在重新启动Web应用程序之前再也不会读取它的事实。一旦阅读,该信息应该可以在Web应用程序的所有角落中使用。

    如果是这种情况,那么就会有一个简单的解决方案:

    1. 使用众所周知的“全局应用程序类”(Global.asax)及其“Application_Start”方法,以便在应用程序启动时收到通知(只需将其添加到项目中,就像任何源文件一样,'' ll在Add New Item对话框中找到它

    2. 使用全局HttpApplicationState类,它类似于Dictionary,并支持在ASP.NET应用程序中共享全局信息

    3. 像这样:

      public class Global : System.Web.HttpApplication {
      
          protected void Application_Start(object sender, EventArgs e) {
              // .. read the database here
      
              HttpContext.Current.Application["SOME_KEY"] = "ANY OBJECT";
              // .. etc
          }
      

      通过这种方式,您可以从ASP.NET应用程序的任何位置读取您在全局HttpApplicationState实例中编写的内容,如下所示:

      public partial class WebForm2 : System.Web.UI.Page {
          protected void Page_Load(object sender, EventArgs e) {
      
              object obj = this.Context.Application["SOME_KEY"];
              // ...etc...
          }
      }
      

      关于重新运行您的应用:大多数情况下,Web服务器(尤其是IIS,但也包括ASP.NET Development Server)不会停止。只要您想要“重新运行”应用程序(如果它已停止),它就会启动。但是,当您停止调试(单击Visual Studio中的“停止”按钮)时,您正在执行全部。您从Web服务器的进程中分离出来并保持运行状态。

      当你“重新运行”应用程序时,如果Web服务器已经运行(某些时候ASP.NET开发服务器崩溃,IIS崩溃了但不经常崩溃)你只是“重新附加IDE的调试器”到网络服务器,你发现一切都是一样的。

      还有更多:如果你重建(强制,万一没有必要重建),网络服务器不会停止,它会丢弃你的应用程序(它在一个独立的地方运行) AppDomain)并重新加载新程序集并再次启动它。如果您记录Global.asax“Application_Started”方法,则可以看到所有这些内容。

      修改

      这是一种安全的缓存方式(虽然经过优化可以让许多读者同时访问某些全局数据,但仍然会减慢速度 - 拥有缓存是一种奢侈,我能说什么: ))。

      首先写一个这样的类:

      public sealed class SafeCache<T> {
      
          private readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
          private readonly Func<T> expensiveReader;
      
          private readonly TimeSpan lease;
          private DateTime lastRead;
          private T data;
      
          public SafeCache(TimeSpan lease, Func<T> expensiveReader) {
              this.lease = lease;
              this.expensiveReader = expensiveReader;
              this.data = expensiveReader();
              this.lastRead = DateTime.UtcNow;
          }
      
          public T Data {
              get {
                  this.rwLock.EnterReadLock();
                  try {
      
                      if (DateTime.UtcNow - this.lastRead < this.lease)
                          return this.data;
      
                  } finally {
                      this.rwLock.ExitReadLock();
                  }
      
                  this.rwLock.EnterUpgradeableReadLock();
                  try {
      
                      if (DateTime.UtcNow - this.lastRead < this.lease)
                          return this.data;
                      else {
                          this.rwLock.EnterWriteLock();
                          try {
      
                              this.data = expensiveReader();
                              this.lastRead = DateTime.UtcNow;
      
                              return this.data;
      
                          } finally {
                              this.rwLock.ExitWriteLock();
                          }
                      }
      
                  } finally {
                      this.rwLock.ExitUpgradeableReadLock();
                  }
              }
          }
      
      }
      

      然后使用Global.asax创建它的实例并将其放在HttpApplicationState全局实例中,使用某个任意键:

      public class Global : System.Web.HttpApplication {
      
          protected void Application_Start(object sender, EventArgs e) {
              HttpContext.Current.Application["SOME_KEY"] = new SafeCache<SomeRecord[]> (
                lease: TimeSpan.FromMinutes(10),
                expensiveReader: () => {
                   // .. read the database here
                   // and return a SomeRecord[]
                   // (this code will be executed for the first time by the ctor of SafeCache
                   // and later on, with every invocation of the .Data property getter that discovers 
                   // that 10 minutes have passed since the last refresh)
                }
              );
              // .. etc
          }
      

      你也可以创建一个像Helper这样的小帮手:

      public static class Helper {
          public static SomeRecord[] SomeRecords {
              get { 
                 var currentContext = HttpContext.Current;
                 if (null == currentContext) // return null or throw some clear Exception
      
                 var cache = currentContext.Application["SOME_KEY"] as SafeCache<SomeRecord[]>;
                 return cache.Data;
              }
          }
      }
      

      当然,无论何时需要,都可以使用它:

      public partial class WebForm2 : System.Web.UI.Page {
          protected void Page_Load(object sender, EventArgs e) {
      
              SomeRecord[] records = Helper.SomeRecords;
              // ...etc...
          }
      }
      

      END OF EDIT