Cached InMemory Database mit FileDependency

Auf der WebSite Studios.ppedv.de werden unter anderem Video Aufzeichnungen der ppedv Konferenzen gehostet. Aktuell sind es rund 500 Videos, die eine recht flexible Metabeschreibung benötigen, um Autoren, Content, verbundene Videos und mehr zu speichern. Aus diesem Grund haben wir uns für ein XML Format pro Video entschieden. Es ist flexibel erweiterbar, kann also zusätzliche Elemente aufnehmen, solange man ohne Schema arbeitet.

Dem aspnetcore Paradigma folgend wird eine “Service” Klasse angelegt um die Daten zu lesen und im Speicher zu halten.

   1:   public class ContentData
   2:      {
   3:          public ContentData()
   4:          {
   5:              ContentListe = new List<Content>();
   6:          }
   7:           private List<Content> _contentliste;
   8:          public List<Content> ContentListe

 

Später wird im Getter der eigentliche Lesezugriff auf die XML Dateien implementiert.

Statt einer web.config wird in ASP.NET core (hier Version 3.0) in der startup.cs die Konfiguration codiert (ich mag dieses Konzept bis heute nicht).

   1:    services.AddSingleton<ContentData>();

Auf diese Art und Weise wird nur eine Instanz von ContentData im Application Pool (IIS) erzeugt. In einer Razor Page kann dann per Parameter Injection dieses Objekt referenziert werden.

   1:  public void OnGet([FromServices] ContentData contentData)

Letztlich werden die Daten aus dem Verzeichnis geladen, wenn das Objekt benutzt wird und so der Set Teil der Property ausgeführt wird. Die Daten liegen im Videos Verzeichnis, strukturiert in Unterverzeichnissen und in .content Dateien.

   1:        get {
   2:                  if (_contentliste.Count==0)
   3:                  {
   4:                      try
   5:                      {
   6:                          var Directories = new DirectoryInfo(
   7:                                          AppDomain.CurrentDomain.GetData("WWWBaseDirectory").ToString() +
   8:                                              "\\wwwroot\\videos");
   9:                          var files = Directories.GetFiles("*.content", SearchOption.AllDirectories);
  10:                          var filteredfiles = files.Where(x => x.Name.Contains("AllContent") == false).OrderByDescending(xx => xx.CreationTime); 
  11:                          var i = 0;
  12:                          foreach (var f in filteredfiles)
  13:                          {
  14:                              XmlSerializer mySerializer = new XmlSerializer(typeof(Content));
  15:                              Content ci;
  16:                              using (var myFileStream = new FileStream(f.FullName, FileMode.Open))
  17:                              {
  18:                                  try
  19:                                  {
  20:                                      ci = (Content)mySerializer.Deserialize(myFileStream);
  21:                                      if (ci != null)
  22:                                      {
  23:                                          ContentListe.Add(ci);
  24:                                      }
  25:                                      else
  26:                                      {
  27:                                          // ugly Things happen 
  28:                                          System.Diagnostics.Debug.WriteLine(">>>>>>>>>>>" + f.FullName);
  29:                                      }
  30:                                  }
  31:                                  catch (Exception ex)
  32:                                  {
  33:                                      System.Diagnostics.Debug.WriteLine(">>>>>>>>>>>" + i.ToString() + "|" + f.FullName);
  34:                                      i++;
  35:                                  }
  36:                              }
  37:                          }
  38:                      }
  39:                      catch (Exception)
  40:                      {
  41:   
  42:                      }
  43:                  }
  44:                  return _contentliste; }
  45:        

Das Verzeichnis für die Website (ala ServerMappath) rette ich mir in der Startup.cs

   1:      public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
   2:          {
   3:              AppDomain.CurrentDomain.SetData("WWWBaseDirectory", env.ContentRootPath);

 

FileSystemwatcher BackGround Prozess

Die dotnet Core Webanwendung wird im IIS gehostet. So muss in dieser Konfiguration jedes Mal die Website neu gestartet werden, um ein Reload der Video Daten zu erzwingen um die neu hinzugefügten Videos anzuzeigen. Das kann man in der IIS Managment Console z.B. auch mit dem zugeordneten Apppool tun. Im nächsten Schritt wird das Verzeichnis auf Dateiänderungen überwacht und wenn diese eintritt die Daten neu geladen. Da dies relativ selten passiert (alle paar Tage) kann man das adhoc tun, ohne die Nutzbarkeit der Website relevant einzuschränken. Dazu eine “Service” Klasse mit dem passenden Interface implementieren. Das FileSystemWatcher Objekt dient zur Überwachung des Dateisystems und kennt eine Reihe von Events für neu oder Änderung einer .content Datei. In der wird die Liste der XML basierten Daten neu eingelesen.

   1:     public class VideosWatcher : BackgroundService
   2:      {
   3:          private FileSystemWatcher _fsw;
   4:          private ContentData _VideoContent;
   5:          public VideosWatcher(ContentData cd)
   6:          {
   7:              _VideoContent = cd;
   8:          }
   9:          protected override Task ExecuteAsync(CancellationToken stoppingToken)
  10:          {
  11:              var path = Path.Combine(AppDomain.CurrentDomain.GetData("WWWBaseDirectory").ToString(),    
  12:                  "wwwroot\\videos"); ;
  13:              _fsw = new FileSystemWatcher(path, "*.content");
  14:              _fsw.Created += _fsw_Created;
  15:              _fsw.Changed += _fsw_Changed;
  16:              _fsw.Renamed += _fsw_Renamed;
  17:              _fsw.Error += _fsw_Error;
  18:              _fsw.EnableRaisingEvents = true;
  19:              _fsw.IncludeSubdirectories = true;
  20:              return Task.CompletedTask;
  21:          }
  22:          private void _fsw_Error(object sender, ErrorEventArgs e)
  23:          {
  24:              throw new NotImplementedException();
  25:          }
  26:          private void _fsw_Renamed(object sender, RenamedEventArgs e)
  27:          {
  28:              throw new NotImplementedException();
  29:          }
  30:          private void _fsw_Changed(object sender, FileSystemEventArgs e)
  31:          {
  32:              _VideoContent = new ContentData();
  33:          }
  34:          private void _fsw_Created(object sender, FileSystemEventArgs e)
  35:          {
  36:               _VideoContent = new ContentData();
  37:          }
  38:      }

Die Hintergrund Aufgabe wir wie üblich in startup.cs registriert.

   1:  services.AddHostedService<VideosWatcher>();

In meinen C# Coding Versuchen bin ich über die Tatsache gestolpert, das der Background Service Code sehr früh aufgerufen wird. Also bevor die ASP.NET Core Anwendung fertig gehostet ist.

Kommentare sind geschlossen