{"id":4592,"date":"2016-09-30T06:30:10","date_gmt":"2016-09-30T05:30:10","guid":{"rendered":"https:\/\/u-labs.de\/portal\/?p=4592"},"modified":"2016-09-03T14:03:36","modified_gmt":"2016-09-03T13:03:36","slug":"razor-ansichten-in-asp-net-core-mit-dem-neuen-cache-taghelper-zwischenspeichern","status":"publish","type":"post","link":"https:\/\/u-labs.de\/portal\/razor-ansichten-in-asp-net-core-mit-dem-neuen-cache-taghelper-zwischenspeichern\/","title":{"rendered":"Razor-Ansichten in ASP.NET Core mit dem neuen cache TagHelper zwischenspeichern"},"content":{"rendered":"<p>Razor ist eine einfache, flexible, aber zugleich m\u00e4chtige Template-Sprache f\u00fcr ASP.NET und ASP.NET Core Webanwendungen. Allerdings werden Razor-Ansichten erst zur Laufzeit in C# Code kompiliert.\u00a0Dieser wiederum muss bei jeder Anfrage ausgef\u00fchrt werden. Zwar erledigt ASP.NET Core dies sehr performant. Je nach Anwendung macht es dennoch Sinn, die kompilierten und gerenderten Razor-Ansichten zwischenzuspeichern. Insbesondere Teilansichten, die sich nicht ver\u00e4ndern wie beispielsweise ein Footer ohne Inhalte. F\u00fcr diesen Zweck gibt es einen neuen TagHelper, der genau dies erm\u00f6glicht.<\/p>\n<h4 style=\"font-weight: bold\">Warum sollte ich Caching nutzen?<\/h4>\n<p>In einem Satz gesagt: Das Zwischenspeichern reduziert die Ladezeit und Serverlast. Eine Razor-Teilansicht muss kompiliert werden. Bei jeder Anfrage generiert sie ihren Inhalt zudem dynamisch, beides kostet Zeit und Rechenleistung. Dazu kommen m\u00f6glicherweise noch externe Quellen, von denen Daten geladen werden m\u00fcssen &#8211; etwa eine Datenbank. Durch das Zwischenspeichern wird diese Last erheblich reduziert, denn: Wenn die Ansicht einmal im Zwischenspeicher ist, wird nur noch das fertige HTML aus dem RAM geladen. Das geht sehr schnell und verbraucht nur sehr wenige Ressourcen. <\/p>\n<p>Nat\u00fcrlich h\u00e4ngt der effektive Nutzen immer vom jeweiligen Projekt und dessen Aufbau ab. Manche profitieren stark durch die Zwischenspeicherung, andere weniger. Grunds\u00e4tzlich macht es jedoch Sinn, sich mit dem Thema auseinanderzusetzen und Caching m\u00f6glichst bereits bei der Entwicklung zu ber\u00fccksichtigen. <\/p>\n<h4><strong>Wie lassen sich Razor-Ansichten zwischenspeichern?<\/strong><\/h4>\n<p>Hierf\u00fcr wurde der\u00a0<code class=\"\" data-line=\"\">cache<\/code> Tag eingef\u00fchrt. Er ist flexibel und kann direkt HTML\/Razor-Code enthalten. Aber auch Teilansichten lassen sich wie gewohnt \u00fcber <code class=\"\" data-line=\"\">Html.Partial()<\/code> laden, um deren Inhalt zwischenzuspeichern. Am besten l\u00e4sst sich dies demonstrieren, wenn man im <code class=\"\" data-line=\"\">cache<\/code> Tag die aktuelle Uhrzeit ausgibt: <\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;p&gt;\r\n    Seite aufgerufen: @DateTime.Now.ToString()\r\n&lt;\/p&gt;\r\n&lt;cache expires-after=&quot;TimeSpan.FromMinutes(5)&quot;&gt;\r\n    Cache-Zeitpunkt: @DateTime.Now.ToString()\r\n&lt;\/cache&gt;\r\n<\/pre>\n<p>Was bedeutet das? Der obere Teil wird ganz normal bei jedem Seitenaufruf gerendet, und enth\u00e4lt somit den Zeitpunkt des Aufrufens der Seite. Der zweiten Teil im <code class=\"\" data-line=\"\">cache<\/code> wird an dieser Stelle in den Cache gelegt. \u00d6ffnen wir die Seite und aktualisieren sie ein paar Sekunden sp\u00e4ter, wird der Cache-Zeitpunkt unver\u00e4ndert bleiben: <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/u-img.net\/img\/6560Bl.jpg\" class=\"aligncenter\" \/><\/p>\n<p>Erst nach 5 Minuten rendert ASP.NET den Inhalt des Cache-Tags neu, wodurch die Uhrzeit aktualisiert wird. Zu beachten ist, dass der <code class=\"\" data-line=\"\">cache<\/code> Tag rein serverseitig ausgef\u00fchrt wird. Im HTML-Quellcode des Browsers ist er daher nicht zu sehen. <\/p>\n<h4 style=\"font-weight: bold\">Ablaufzeit des Zwischenspeichers bestimmen<\/h4>\n<p>Um den Zwischenspeicher zu leeren, stehen uns verschiedene M\u00f6glichkeiten zur Verf\u00fcgung. Die erste haben wir im obigen Beispiel bereits kennengelernt: Im Parameter <code class=\"\" data-line=\"\">expires-after<\/code> \u00fcbergeben wir die Lebensdauer als Zeitspanne in Form einer <code class=\"\" data-line=\"\">TimeSpan<\/code> Instanz. Es gibt jedoch noch weitere M\u00f6glichkeiten. <\/p>\n<h5 style=\"font-weight:bold\">expires-on: Absoluter Ablaufzeitpunkt<\/h5>\n<p>\u00dcber das <code class=\"\" data-line=\"\">expires-on<\/code> Attribute kann die G\u00fcltigkeit als absolute <code class=\"\" data-line=\"\">DateTime<\/code> Instanz angegeben werden. Sinn macht dies beispielsweise, wenn die Ansicht auf Daten zur\u00fcckgreift, die als Cron\/Aufgabe zu einem bestimmten Zeitpunkt neu geladen werden. <\/p>\n<p>Folgendes Beispiel erneuert den Cache jeweils kurz vor Ende des aktuellen Tages: <\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;cache expires-on=&quot;@DateTime.Today.AddDays(1).AddTicks(-1)&quot;&gt;\r\n    @Html.Partial(&quot;DateTime&quot;)\r\n&lt;\/cache&gt;\r\n<\/pre>\n<h5 style=\"font-weight:bold\">expires-sliding: Alte, nicht verwendete Elemente entfernen<\/h5>\n<p>Einen etwas anderen Ansatz geht <code class=\"\" data-line=\"\">expire-sliding<\/code>: Hier wird das Element aus dem Cache gel\u00f6scht, wenn eine gewisse Zeit lang nicht darauf zugegriffen wurde. Dies ist besonders n\u00fctzlich, wenn viele verschiedene Inhalte zwischengespeichert werden, aber nicht all zu viel Arbeitsspeicher auf dem Server zur Verf\u00fcgung steht. Erwartet wird auch hier eine <code class=\"\" data-line=\"\">TimeSpan<\/code> Instanz: <\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;cache expires-sliding=&quot;TimeSpan.FromMinutes(5)&quot;&gt;\r\n    Cache-Zeitpunkt: @DateTime.Now.ToString()\r\n&lt;\/cache&gt;\r\n<\/pre>\n<p>In diesem Beispiel entfernt ASP.NET den zwischengespeicherten Inhalt, wenn 5 Minuten lang nicht darauf zugegriffen wurde. <\/p>\n<h4 style=\"font-weight:bold\">Komplexere Cache-Schl\u00fcssel<\/h4>\n<p>In allen obigen Demos wird der Zwischenspeicher mit allen Nutzern geteilt. Dies ist sinnvoll f\u00fcr Elemente, die \u00fcberall gleich angezeigt werden wie etwa ein News-Ticker Widget. Nicht selten werden Inhalte aber dynamisch anhand von Nutzerrollen und \u00e4hnlichen Kriterien generiert. Damit in solchen F\u00e4llen keinem Nutzer alte oder falsche Inhalte angezeigt werden, ben\u00f6tigt man verschiedene Cache-Schl\u00fcssel. Beispielsweise k\u00f6nnten diese die Id einer Gruppe beinhalten. F\u00fcr Gruppe 1 wird dann ein anderer Cache-Eintrag erstellt wie f\u00fcr Gruppe 2. <\/p>\n<p>Auch hier ist ASP.NET sehr flexibel und erm\u00f6glicht praktisch jede Kombinationsm\u00f6glichkeit. Zum Einsatz kommen verschiedene Attribute mit dem <code class=\"\" data-line=\"\">vary-by<\/code> Pr\u00e4fix. <\/p>\n<h5 style=\"font-weight: bold\">vary-by: Eigenen Cache-Schl\u00fcssel definieren<\/h5>\n<p>Am flexibelsten ist das <code class=\"\" data-line=\"\">vary-by<\/code> Attribute. Es erlaubt die Nutzung jeder beliebigen C# Variable als Schl\u00fcssel:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n@{ \r\n    var myCacheKey = ...\r\n}\r\n<\/pre>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;cache vary-by=&quot;myCacheKey&quot;&gt;\r\n    Cache-Zeitpunkt: @DateTime.Now.ToString()\r\n&lt;\/cache&gt;\r\n<\/pre>\n<p>F\u00fcr h\u00e4ufig genutzte Schl\u00fcssel existieren allerdings noch weitere Hilfsattribute, die ich im folgenden vorstelle. Erw\u00e4hnenswert ist auch, dass die folgenden Hilfsattribute beliebig Kombiniert werden k\u00f6nnen. ASP.NET setzt den Cache-Schl\u00fcssel dann aus den gegebenen Werten zusammen, beispielsweise Nutzer und Route. <\/p>\n<h5 style=\"font-weight: bold\">vary-by-user: Eigener Cache f\u00fcr jeden Nutzer<\/h5>\n<p>Nutzen wir das Authentifizierungssystem von ASP.NET Core und m\u00f6chten Inhalte separat f\u00fcr jeden angemeldeten Nutzer zwischenspeichern, gen\u00fcgt es, dieses Attribute auf <b>true<\/b> zu setzen. Sinn macht dies beispielsweise bei einer dynamisch generierten Navigation, die den Nutzer mit seinem Namen begr\u00fc\u00dft. <\/p>\n<h5 style=\"font-weight: bold\">vary-by-route: URL-Routen verwenden<\/h5>\n<p>Nutzt den Wert von Route-Parametern als Schl\u00fcssel. Es muss die gleiche Bezeichnung verwendet werden, wie beim definieren der Route. Das kombinieren mehrere Route-Parameter k\u00f6nnen mittels Komma getrennt \u00fcbergeben werden. Folgendes Beispiel erzeugt einen Cache-Schl\u00fcssel aus Controller, Action und Id: <\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\napp.UseMvc(routes =&gt; {\r\n    routes.MapRoute(\r\n        name: &quot;default&quot;,\r\n        template: &quot;{controller=Home}\/{action=Index}\/{id?}&quot;\r\n    );\r\n});\r\n<\/pre>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;cache vary-by-route=&quot;controller,action,id&quot;&gt;\r\n    Cache-Zeitpunkt: @DateTime.Now.ToString()\r\n&lt;\/cache&gt;\r\n<\/pre>\n<h5 style=\"font-weight: bold\">vary-by-query: URL-Parameter, die nicht als Route definiert wurden<\/h5>\n<p>Erz\u00e4nzend zu <code class=\"\" data-line=\"\">vary-by-route<\/code> k\u00f6nnen hier verschiedene URL-Parameter angegeben werden. Im Unterschied zur Route sind sie nicht darin festgelegt, sondern werden daran angehangen. Ein Beispiel, das von der oben definierten Standard-Route ausgeht: <code class=\"\" data-line=\"\">http:\/\/localhost:5000\/home\/index\/?foo=bar<\/code><\/p>\n<p>Der Parameter <em>foo<\/em> kann wie folgt als Schl\u00fcssel f\u00fcr den Zwischenspeicher genutzt werden: <\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;cache vary-by-query=&quot;foo&quot;&gt;\r\n    Cache-Zeitpunkt: @DateTime.Now.ToString()\r\n&lt;\/cache&gt;\r\n<\/pre>\n<h5 style=\"font-weight: bold\">vary-by-cookie: Den Wert eines Cookies als Schl\u00fcssel<\/h5>\n<p>Gleiches Prinzip wie bei den vorherigen Attributen auch, hier muss der Name des Cookies \u00fcbergeben werden. Wie immer bei Daten von Au\u00dfen ist hier nat\u00fcrlich Vorsicht geboten. Es sollten keine Cookies genutzt werden, die sich serverseitig nicht verifizieren lassen. Die Benutzergruppe an dieser Stelle als Schl\u00fcssel zu setzen w\u00e4re beispielsweise keine Idee. So k\u00f6nnte sich der Nutzer durch einfache Manipulation des Cookies Inhalte anzeigen lassen, die nicht f\u00fcr ihn bestimmt sind. <\/p>\n<h5 style=\"font-weight: bold\">vary-by-header: Beliebige HTTP-Header nutzen<\/h5>\n<p>Nutzt den Name des \u00fcbergebenen HTTP-Headers, beispielsweise <em>User-Agent<\/em>. \u00c4hnlich wie bei <code class=\"\" data-line=\"\">vary-by-cookie<\/code> sollte man im Hinterkopf behalten, dass sich dieser Wert leicht vom Nutzer ver\u00e4ndern l\u00e4sst. <\/p>\n<h4 style=\"font-weight: bold\">Priorit\u00e4t des Caches festlegen<\/h5>\n<p>Wie bereits zu Beginn erw\u00e4hnt, wird der Inhalt des Caches im Arbeitsspeicher abgelegt. Insbesondere wenn der Cache pro Nutzer aufgebaut wird, kann er schnell wachsen. Ist der RAM des Servers ersch\u00f6pft, wird ASP.NET aufr\u00e4umen. \u00dcber das <code class=\"\" data-line=\"\">priority<\/code> Attribute k\u00f6nnen die Inhalte gewichtet werden: <\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n@using Microsoft.Extensions.Caching.Memory\r\n&lt;cache vary-by-route=&quot;id&quot; priority=&quot;CacheItemPriority.Low&quot;&gt;\r\n    Cache-Zeitpunkt: @DateTime.Now.ToString()\r\n&lt;\/cache&gt;\r\n<\/pre>\n<p>Das <code class=\"\" data-line=\"\">CacheItemPriority<\/code> Enum bietet die Werte <i>High, Normal, Low<\/i> und <i>NeverRemove<\/i>. Ein Element mit niedriger Priorit\u00e4t wird also vor einem mit normaler oder hoher entfernt. Im Falle von <i>NeverRemove<\/i> wird der Server nach anderen Elementen suchen.<\/p>\n<h4 style=\"font-weight:bold\">M\u00f6gliche Probleme des Zwischenspeichers<\/h4>\n<p>Durch die neue Caching-M\u00f6glichkeit von ASP.NET Core kann die Leistung einfach und effektiv erh\u00f6ht werden. Allerdings sollte man nicht vergessen, dass es sich um einen fl\u00fcchtigen Zwischenspeicher handelt. Sobald der ASP.NET Webserver bzw. der komplette Host-Server neu gestartet wird, geht sein Inhalt verloren. In der Regel passiert dies nicht all zu h\u00e4ufig und ist auch nicht weiter schlimm &#8211; denn schlie\u00dflich wird der Cache bei der n\u00e4chsten Anfrage automatisch wieder neu aufgebaut. <\/p>\n<p>In Umgebungen mit mehreren Servern sind Caches \u00e4hnlich wie Sessions grunds\u00e4tzlich ein Problem: Der Cache wird pro Server erstellt, und ist somit auf anderen Maschinen nicht g\u00fcltig. Landet ein Nutzer bei seiner ersten Anfrage auf Server 1, baut sich dort der Cache auf. Wird er beim zweiten Seitenaufruf an Server 2 geroutet, hat dieser keinen Zugriff auf den Cache von Server 1 und baut ihn somit ebenfalls auf. Am Ende leidet die Effektivit\u00e4t und man hat den gleichen Inhalt X-Mal auf verschiedenen Servern im Zwischenspeicher. <\/p>\n<p>In solchen Umgebungen sollte man auf einen server\u00fcbergreifenden Cache-Provider zur\u00fcckgreifen, wie etwa Redis. Hier hat man einen zentralen Cache-Server, auf den alle Webserver zugreifen k\u00f6nnen. Im obigen Beispiel w\u00fcrde Server 1 den Inhalt in den Redis-Cache legen, und Server 2 greift bei der n\u00e4chsten Anfrage darauf zu, anstatt ihn unn\u00f6tig neu aufzubauen. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>Razor ist eine einfache, flexible, aber zugleich m\u00e4chtige Template-Sprache f\u00fcr ASP.NET und ASP.NET Core Webanwendungen. Allerdings werden Razor-Ansichten erst zur Laufzeit in C# Code kompiliert.\u00a0Dieser wiederum muss bei jeder Anfrage ausgef\u00fchrt werden. Zwar erledigt ASP.NET Core dies sehr performant. Je nach Anwendung macht es dennoch Sinn, die kompilierten und gerenderten Razor-Ansichten zwischenzuspeichern. Insbesondere Teilansichten, die &#8230;<\/p>\n","protected":false},"author":5,"featured_media":4657,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[61],"tags":[522,537],"class_list":["post-4592","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-softwareentwicklung","tag-asp-net-core","tag-asp-net-core-1-0"],"_links":{"self":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/4592","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/comments?post=4592"}],"version-history":[{"count":66,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/4592\/revisions"}],"predecessor-version":[{"id":4659,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/posts\/4592\/revisions\/4659"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media\/4657"}],"wp:attachment":[{"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/media?parent=4592"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/categories?post=4592"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/u-labs.de\/portal\/wp-json\/wp\/v2\/tags?post=4592"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}