Cacheunterstützung für WCF-Web-HTTP-Dienste

Mit .NET Framework 4.6.1 können Sie den Mechanismus zum deklarativen Zwischenspeichern verwenden, der in ASP.NET bereits in Ihren WCF-Web-HTTP-Diensten verfügbar ist. Auf diese Weise können Sie Antworten der WCF-Web-HTTP-Dienstvorgänge zwischenspeichern. Wenn ein Benutzer HTTP GET an den Dienst sendet, der zum Zwischenspeichern konfiguriert ist, sendet ASP.NET die zwischengespeicherte Antwort zurück, und die Dienstmethode wird nicht aufgerufen. Wenn der Cache abgelaufen ist, wird beim nächsten Senden eines HTTP GET durch einen Benutzer die Dienstmethode aufgerufen und die Antwort erneut zwischengespeichert. Weitere Informationen zur ASP.NET-Zwischenspeicherung finden Sie unter Übersicht über die ASP.NET-Zwischenspeicherung.

Grundlegendes Zwischenspeichern von Web-HTTP-Diensten

Zum Aktivieren der WEB-HTTP-Zwischenspeicherung müssen Sie zuerst die ASP.NET-Kompatibilität aktivieren, indem Sie AspNetCompatibilityRequirementsAttribute auf die Diensteinstellung RequirementsMode und Allowed oder Required anwenden.

Mit .NET Framework 4 wird ein neues Attribut namens AspNetCacheProfileAttribute eingeführt, mit dem Sie einen Cacheprofilenamen angeben können. Dieses Attribut wird auf einen Dienstvorgang angewendet. Im folgenden Beispiel wird AspNetCompatibilityRequirementsAttribute auf einen Dienst angewendet, um ASP.NET-Kompatibilität zu erzielen, und der GetCustomer-Vorgang wird für die Zwischenspeicherung konfiguriert. Das AspNetCacheProfileAttribute-Attribut gibt ein Cacheprofil an, das die zu verwendenden Cacheeinstellungen enthält.

[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service
{
    [WebGet(UriTemplate = "{id}")]
    [AspNetCacheProfile("CacheFor60Seconds")]
    public Customer GetCustomer(string id)
    {
        // ...
    }
}

Aktivieren Sie außerdem den ASP.NET-Kompatibilitätsmodus in der Datei „Web.config“, wie im folgenden Beispiel gezeigt.

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>

Warnung

Wenn der ASP.NET-Kompatibilitätsmodus nicht aktiviert ist und AspNetCacheProfileAttribute verwendet wird, wird eine Ausnahme ausgelöst.

Der von AspNetCacheProfileAttribute angegebene Cacheprofilname identifiziert ein Cacheprofil, das der Konfigurationsdatei "Web.config" hinzugefügt wird. Das Cacheprofil wird in einem <outputCacheSetting>-Element definiert, wie im folgenden Konfigurationsbeispiel veranschaulicht.

<!-- ...  -->
<system.web>  
   <caching>  
      <outputCacheSettings>  
         <outputCacheProfiles>  
            <add name="CacheFor60Seconds" duration="60" varyByParam="none" sqlDependency="MyTestDatabase:MyTable"/>  
         </outputCacheProfiles>  
      </outputCacheSettings>  
   </caching>  
   <!-- ... -->  
</system.web>  

Dies ist das gleiche Konfigurationselement, das für ASP.NET-Anwendungen verfügbar ist. Weitere Informationen zu ASP.NET-Cacheprofilen finden Sie unter OutputCacheProfile. Die wichtigsten Attribute im Cacheprofil für Web-HTTP-Dienste sind cacheDuration und varyByParam. Beide Attribute sind erforderlich. cacheDuration legt den Zeitraum in Sekunden fest, für den eine Antwort zwischengespeichert werden soll. Mit varyByParam können Sie einen Abfragezeichenfolgenparameter angeben, der zum Zwischenspeichern von Antworten verwendet wird. Alle Anforderungen, die mit unterschiedlichen Abfragezeichenfolgenparameter-Werten gestellt werden, werden separat zwischengespeichert. Nachdem beispielsweise eine erste Anforderung an http://MyServer/MyHttpService/MyOperation?param=10 gesendet wurde, wird für alle nachfolgenden Anforderungen mit demselben URI die im Cache gespeicherte Antwort zurückgegeben (sofern die Aufbewahrungsdauer im Cache noch nicht verstrichen ist). Antworten für eine ähnliche Anforderung, die gleich ist, jedoch über einen anderen Wert als Abfragezeichenfolgenparameter verfügt, werden getrennt zwischengespeichert. Falls Sie dieses separate Zwischenspeicherverhalten nicht wünschen, legen Sie varyByParam auf "none" fest.

SQL-Cacheabhängigkeit

Antworten auf Web-HTTP-Dienste können auch mit einer SQL-Cacheabhängigkeit zwischengespeichert werden. Wenn der WCF-Web-HTTP-Dienst von in einer SQL-Datenbank gespeicherten Daten abhängig ist, sollten Sie die Antwort des Diensts zwischenspeichern und die zwischengespeicherte Antwort für ungültig erklären, wenn sich die Daten in der SQL-Datenbanktabelle ändern. Dieses Verhalten wird vollständig in der Datei "Web.config" konfiguriert. Definieren Sie zunächst eine Verbindungszeichenfolge im Element <connectionStrings>.

<connectionStrings>
  <add name="connectString"
       connectionString="Data Source=MyService;Initial Catalog=MyTestDatabase;Integrated Security=True"
       providerName="System.Data.SqlClient" />
</connectionStrings>

Dann müssen Sie die SQL-Cacheabhängigkeit innerhalb eines <caching>-Elements im <system.web>-Element aktivieren, wie im folgenden Konfigurationsbeispiel gezeigt.

<system.web>
  <caching>
    <sqlCacheDependency enabled="true" pollTime="1000">
      <databases>
        <add name="MyTestDatabase" connectionStringName="connectString" />
      </databases>
    </sqlCacheDependency>
    <!-- ... -->
  </caching>
  <!-- ... -->
</system.web>

Hier wird die SQL-Cacheabhängigkeit aktiviert und ein Abrufzeitraum von 1000 Millisekunden festgelegt. Wenn der Abrufzeitraum verstrichen ist, wird die Datenbanktabelle jeweils auf Updates geprüft. Wenn Änderungen erkannt werden, wird der Inhalt des Caches entfernt, und beim nächsten Aufrufen des Dienstvorgangs wird eine neue Antwort zwischengespeichert. Fügen Sie innerhalb des <sqlCacheDependency>-Elements die Datenbanken hinzu, und verweisen Sie auf die Verbindungszeichenfolgen im <databases>-Element, wie im folgenden Beispiel veranschaulicht.

<system.web>
  <caching>
    <sqlCacheDependency enabled="true" pollTime="1000">
      <databases>
        <add name="MyTestDatabase" connectionStringName="connectString" />
      </databases>  
    </sqlCacheDependency>  
    <!-- ... -->  
  </caching>  
  <!-- ... -->  
</system.web>  

Als Nächstes müssen Sie die Ausgabecacheeinstellungen innerhalb des <caching>-Elements konfigurieren, wie im folgenden Beispiel gezeigt.

<system.web>
  <caching>  
    <!-- ...  -->
    <outputCacheSettings>
      <outputCacheProfiles>
        <add name="CacheFor60Seconds" duration="60" varyByParam="none" sqlDependency="MyTestDatabase:MyTable" />
      </outputCacheProfiles>
    </outputCacheSettings>
  </caching>
  <!-- ... -->
</system.web>

Hier wird die Cachedauer auf 60 Sekunden festgelegt, varyByParam wird auf „none“ und sqlDependency auf eine durch Semikolon getrennte Liste mit Datenbankname/Tabelle-Paaren festgelegt, die jeweils durch Doppelpunkte getrennt sind. Falls Daten in MyTable geändert werden, wird die zwischengespeicherte Antwort für den Dienstvorgang entfernt. Wenn der Vorgang dann aufgerufen wird, wird eine neue Antwort generiert (durch das Aufrufen des Dienstvorgangs), zwischengespeichert und für den Client zurückgegeben.

Wichtig

Damit ASP.NET auf eine SQL-Datenbank zugreifen kann, müssen Sie das ASP.NET SQL Server-Registrierungstool verwenden. Außerdem müssen Sie für das entsprechende Benutzerkonto den Zugriff auf die Datenbank und die Tabelle zulassen. Weitere Informationen finden Sie unter Zugreifen auf SQL Server aus einer Webanwendung.

Bedingtes HTTP GET-basiertes Zwischenspeichern

In Web-HTTP-Szenarien wird bedingtes HTTP GET häufig von Diensten verwendet, um eine intelligente HTTP-Zwischenspeicherung zu implementieren. Siehe die Beschreibung unter HTTP-Spezifikation. Dazu muss der Dienst den Wert des ETag-Headers in der HTTP-Antwort festlegen. Außerdem muss er den If-None-Match-Header in der HTTP-Anforderung überprüfen, um zu ermitteln, ob ein angegebener ETag mit dem aktuellen ETag übereinstimmt.

Für GET- und HEAD-Anforderungen verwendet CheckConditionalRetrieve einen ETag-Wert und überprüft diesen anhand des If-None-Match-Headers der Anforderung. Wenn der Header vorhanden ist und eine Übereinstimmung vorliegt, wird eine WebFaultException mit dem HTTP-Statuscode 304 (Nicht geändert) ausgelöst, und der Antwort mit dem übereinstimmenden ETag wird ein ETag-Header hinzugefügt.

Eine Überladung der CheckConditionalRetrieve-Methode verwendet ein Datum der letzten Änderung und vergleicht es mit dem If-Modified-Since-Header der Anforderung. Wenn der Header vorhanden ist und die Ressource seitdem nicht geändert wurde, wird eine WebFaultException mit einem HTTP-Statuscode 304 (Nicht geändert) ausgelöst.

Für PUT-, POST- und DELETE-Anforderungen verwendet CheckConditionalUpdate den aktuellen ETag-Wert einer Ressource. Wenn der aktuelle ETag-Wert NULL lautet, überprüft die Methode, ob der If-None-Match-Header den Wert „*“ aufweist. Falls der aktuelle ETag-Wert kein Standardwert ist, vergleicht die Methode den aktuellen ETag-Wert mit dem If-Match-Header der Anforderung. In beiden Fällen löst die Methode eine WebFaultException mit dem HTTP-Statuscode 412 (Vorbedingungsfehler) aus, wenn der erwartete Header in der Anforderung nicht vorhanden ist oder der Wert die Bedingungsprüfung nicht besteht, und legt den ETag-Header der Antwort auf den aktuellen ETag-Wert fest.

Sowohl die CheckConditional-Methode als auch die SetETag-Methode stellt sicher, dass der für den Antwortheader festgelegte ETag-Wert ein gültiges ETag gemäß HTTP-Spezifikation ist. Dies schließt das Setzen des ETag-Werts in doppelte Anführungszeichen ein, falls diese nicht bereits vorhanden sind, sowie das ordnungsgemäße Versehen von internen doppelten Anführungszeichen mit Escapezeichen. Der Vergleich von schwachen ETags wird nicht unterstützt.

Das folgende Beispiel veranschaulicht die Verwendung dieser Methoden.

[WebGet(UriTemplate = "{id}"), Description("Returns the specified customer from customers collection. Returns NotFound if there is no such customer. Supports conditional GET.")]
public Customer GetCustomer(string id)
{
    lock (writeLock)
    {
        // return NotFound if there is no item with the specified id.
        object itemEtag = customerEtags[id];
        if (itemEtag == null)
        {
            throw new WebFaultException(HttpStatusCode.NotFound);
        }
  
        // return NotModified if the client did a conditional GET and the customer item has not changed
        // since when the client last retrieved it
        WebOperationContext.Current.IncomingRequest.CheckConditionalRetrieve((long)itemEtag);
        Customer result = this.customers[id] as Customer;

        // set the customer etag before returning the result
        WebOperationContext.Current.OutgoingResponse.SetETag((long)itemEtag);
        return result;
    }
}

Sicherheitsüberlegungen

Für Anforderungen, die eine Autorisierung erfordern, sollten die Antworten nicht zwischengespeichert werden, da die Autorisierung nicht ausgeführt wird, wenn die Antwort aus dem Cache bereitgestellt wird. Die Zwischenspeicherung dieser Antworten würde eine ernste Sicherheitslücke darstellen. Anforderungen, die eine Autorisierung erfordern, stellen gewöhnlich benutzerspezifische Daten bereit. Aus diesem Grund bietet serverseitiges Zwischenspeichern auch keine Vorteile. In solchen Situationen ist clientseitiges Zwischenspeichern oder der Verzicht auf Zwischenspeichern besser geeignet.