简体   繁体   中英

Caching in WCF?

I am building a WCF service. I need to store reference data in the cache which I will look up every time I receive input from the method... What is the right way to do this? I would also like to define an expiration policy for the cache that will invalidate it after a certain time interval.

如果您使用的是.NET 4,建议使用MemoryCache

Any caching solution should address two basic problems

1) Storage of cache items and retrieval

2) Cache invalidation

Since Http caching is a well known one I am not going to explain it in detail. You can use asp compatibility attribute alone with some web configuration, where you will get caching by charm.

[AspNetCacheProfile("MyProfile")]
        public Customer GetName(string id)
        {
             // ...
        }

And the web config is like

<system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />    
</system.serviceModel>
<system.web>
   <caching>
      <outputCacheSettings>
         <outputCacheProfiles>
            <add name=" MyProfile" duration="600" varyByParam="none" sqlDependency="MyTestDatabase:MyTable"/>
         </outputCacheProfiles>
      </outputCacheSettings>
   </caching>
</system.web>

But this is not suitable for most scenarios especially when you have large complex object to cache. For example I had a situation where I wanted to cache a system generated image (the output of the operation contract is a system generated image that depends on the input). In such a case, you have to implement your own cache. I have used Microsoft enterprise library caching blocks that addressed all my caching storage requirements. However, you still need to do the plumbing to integrate Microsoft enterprise library caching block with your WCF service. First you have to intercept the WCF communication channel to implement the cache. A detail discussion of how to intercept the WCF communication channel can be found at http://msdn.microsoft.com/en-us/magazine/cc163302.aspx . This is how you do the plumbing for WCF caching

基本的管道架构

Step 0 Let's say you have an operation contract as follows and you want to cache the item return by that method.

[OperationContract]
MyCompositeClass Rotate(int angle)

Step 1 First you have to register your custom cacher in the WCF pipeline. To do that I am going to use an attribute so that I can nicely decorate my WCF call according to aspect orient programming principles.

using System;
using System.ServiceModel.Description;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Reflection;

    [AttributeUsage(AttributeTargets.Method)]
    public class MyCacheRegister : Attribute, IOperationBehavior
    {
        ConstructorInfo _chacherImplementation;
        public ImageCache(Type provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("Provider can't be null");
            }
            else if (provider.IsAssignableFrom(typeof(IOperationInvoker)))
            {
                throw new ArgumentException("The type " + provider.AssemblyQualifiedName + " does not implements the interface " + typeof(IOperationInvoker).AssemblyQualifiedName);
            }
            else
            {
                try
                {
                    Type[] constructorSignatureTypes = new Type[1];
                    constructorSignatureTypes[0] = typeof(IOperationInvoker);
                    _chacherImplementation = provider.GetConstructor(constructorSignatureTypes);

                }
                catch
                {
                    throw new ArgumentException("There is no constructor in " + provider.AssemblyQualifiedName + " that accept " + typeof(IOperationInvoker).AssemblyQualifiedName + " as a parameter");
                }

            }


        }

        public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
        {
            return;
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            return;
        }

        /// <summary>
        /// Decorate the method call with the cacher
        /// </summary>
        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            //decorator pattern, decorate with a  cacher
            object[] constructorParam = new object[1];
            constructorParam[0] = dispatchOperation.Invoker;
            dispatchOperation.Invoker = (IOperationInvoker)_chacherImplementation.Invoke(constructorParam);
        }

        public void Validate(OperationDescription operationDescription)
        {
            return;
        }
    }

Step 2

Then you have to implement the point where cache object will be retrieved.

using System;
using System.ServiceModel.Dispatcher;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Common;
using System.IO;

    class RotateCacher : IOperationInvoker
    {

        private IOperationInvoker _innerOperationInvoker;
        public RotateImageCacher(IOperationInvoker innerInvoker)
        {
            _innerOperationInvoker = innerInvoker;
        }
        public object[] AllocateInputs()
        {
            Object[] result = _innerOperationInvoker.AllocateInputs();
            return result;
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            object result=null;

///TODO: You will have more object in the input if you have more ///parameters in your method

            string angle = inputs[1].ToString();

            ///TODO: create a unique key from the inputs
            string key = angle;

            string provider = System.Configuration.ConfigurationManager.AppSettings["CacheProviderName"];
            ///Important Provider will be DiskCache or MemoryCache for the moment
provider =”DiskCache”;
///TODO: call enterprise library cache manager, You can have your own 
/// custom cache like Hashtable

    ICacheManager manager = CacheFactory.GetCacheManager(provider);

            if (manager.Contains(key))
            {

                result =(MyCompositeClass) manager[key];

            }
            else
            {
                result =(MyCompositeClass) _innerOperationInvoker.Invoke(instance, inputs, out outputs);
                manager.Add(key, result);
            }
            return result;
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            IAsyncResult result = _innerOperationInvoker.InvokeBegin(instance, inputs, callback, state);
            return result;
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult asyncResult)
        {
            object result = _innerOperationInvoker.InvokeEnd(instance, out outputs, asyncResult);
            return result;
        }

        public bool IsSynchronous
        {
            get { return _innerOperationInvoker.IsSynchronous; }
        }
    }

Step 3

Finally add your attribute above your service call

[OperationContract]
[MyCacheRegister(typeof(RotateCacher)]
MyCompositeClass Rotate(int angle)

The configuration of enterprise library caching block is beyond the scope of this answer. You can use following link to learn it. The good thing about enterprise library is that you get ready made ways to extend your caching policy. It has built in ways for cache expiry and storage. You also can write your own cache expiration and storage policies. http://www.codeproject.com/KB/web-cache/CachingApplicationBlock.aspx

One final thing, for you to get your enterprise library caching working you need to add following configuration details. You also need to add relevant dlls to your project reference.

<configSections>
    <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
  </configSections>


  <cachingConfiguration defaultCacheManager="Cache Manager">
    <cacheManagers>
      <add name="MemoryCache" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
        numberToRemoveWhenScavenging="10" backingStoreName="NullBackingStore" />
      <add name="DiskCache" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000"
        numberToRemoveWhenScavenging="10" backingStoreName="IsolatedStorageCacheStore" />
    </cacheManagers>
    <backingStores>
      <add type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        name="NullBackingStore" />
      <add name="IsolatedStorageCacheStore" type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.IsolatedStorageBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
        encryptionProviderName="" partitionName="MyCachePartition" />
    </backingStores>
  </cachingConfiguration>

If you're going to be scaling out to more than one server in a load balanced, stateless system, you'll want to design for use of a distributed cache . The main things to do here are:

  1. Use both a local and distributed cache. Only put session or short lived stuff in the distributed cache, other stuff cache locally.

  2. Set appropriate timeouts for items. This will vary depending on the type of information and how close to the source it needs to be.

  3. Remove stuff from cache when you know it will be incontinent (like updates, deletes, etc).

  4. Take care to design cache keys that are unique. Build a model of the type of information you plan to cache and use that as a template for building keys.

You could take a look at Velocity . This is Microsoft's distributed in-memory caching framework . But this may be a little bit too beta...

Rather than expiring the cache data every so often, you can actually just make sure to invalidate the cache whenever the underlying data you are caching changes.

See this example from info Q http://www.infoq.com/news/2011/04/Attribute-Caching

[Cache.Cacheable("UserTransactionCache")]
public DataTable GetAllTransactionsForUser(int userId)
{
    return new DataProvider().GetAllTransactionsForUser(userId);
}

[Cache.TriggerInvalidation("UserTransactionCache")]
public void DeleteAllTransactionsForUser(int userId)
{
 ...
}

You can use System.Web.Cache (even if you're not in a web context), and that's what I'd do. It's basically a big, in memory hash table with some niceties for expiring contents.

There are many ways you can do this. One fairly easy is to host the System.Web.Cache object yourself and use that to store the reference data. There's a good example of that here: http://kjellsj.blogspot.com/2007/11/wcf-caching-claims-using.html

The WCF REST Starter Kit has caching, here is an article about using it... with sample code.

http://weblogs.asp.net/gsusx/archive/2008/10/29/adding-caching-to-wcf-restful-services-using-the-rest-starter-kit.aspx

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM