tags:

views:

955

answers:

4

My application uses client side enterprise caching; I would like to avoid writing code for each and every cacheable call and wondered if there is a solution such that WCF client side calls can be cached, even for async calls.

Can this be done with WCF "behaviour" or some other means? Code examples?

A: 

Unfortunately, I think you'll have to roll your own. I don't believe WCF has a client-side caching mechanism built in.

The answer to this question may also help.

Randolpho
I know that; It can be done, I just don't know how to implement IOperationInvoker on a client (not server). Specifically, I don't know how to code ApplyClientBehavior in IOperationBehaviour such that InvokeBegin method in IOperationInvoker is called, instead of the default method.
BlueSky
A: 

If you want caching without having to explicitly implement it on each and every service call, consider the Caching Handler in the Policy Injection application block. You can mark your calls with an attribute, and the policy injection block will handle caching for you.

http://msdn.microsoft.com/en-us/library/cc511757.aspx

dcgartn
I am looking for a client side solution, not a host side solution.
BlueSky
+2  A: 

I did this the other day with Generic Extension methods on the WCF service client (DataServiceClient). It uses Actions and Funcs to pass around the actual ServiceClient calls. The final client usage syntax is a little funky (if you don't like lambdas), but this method does FaultException/Abort wrapping AND caching:

public static class ProxyWrapper
{
    // start with a void wrapper, no parameters
    public static void Wrap(this DataServiceClient _svc, Action operation)
    {
        bool success = false;

        try
        {
            _svc.Open();
            operation.Invoke();
            _svc.Close();

            success = true;
        }
        finally
        {
            if (!success)
                _svc.Abort();
        }
    }

    // next, a void wrapper with one generic parameter
    public static void Wrap<T>(this DataServiceClient _svc, Action<T> operation, T p1)
    {
        bool success = false;

        try
        {
            _svc.Open();
            operation.Invoke(p1);
            _svc.Close();

            success = true;
        }
        finally
        {
            if (!success)
                _svc.Abort();
        }
    }

    // non-void wrappers also work, but take Func instead of Action
    public static TResult Wrap<T, TResult>(this DataServiceClient _svc, Func<T, TResult> operation, T p1)
    {
        TResult result = default(TResult);

        bool success = false;

        try
        {
            _svc.Open();
            result = operation.Invoke(p1);
            _svc.Close();

            success = true;
        }
        finally
        {
            if (!success)
                _svc.Abort();
        }

        return result;
    }
}

On the client side, we have to call them like this:

    internal static DBUser GetUserData(User u)
    {
        DataServiceClient _svc = new DataServiceClient();

        Func<int, DBUser> fun = (x) => _svc.GetUserById(x);

        return _svc.Wrap<int, DBUser>(fun, u.UserId);
    }

See the plan here? Now that we have a generic set of wrappers for WCF calls, we can use the same idea to inject some cacheing. I went "low tech" here, and just started throwing around strings for the cache key name... You could do something more elegant with reflection, no doubt.

    public static TResult Cache<TResult>(this DataServiceClient _svc, string key, Func<TResult> operation)
    {
        TResult result = (TResult)HttpRuntime.Cache.Get(key);

        if (result != null)
            return result;

        bool success = false;

        try
        {
            _svc.Open();
            result = operation.Invoke();
            _svc.Close();

            success = true;
        }
        finally
        {
            if (!success)
                _svc.Abort();
        }

        HttpRuntime.Cache.Insert(key, result);

        return result;
    }

    // uncaching is just as easy
    public static void Uncache<T>(this DataServiceClient _svc, string key, Action<T> operation, T p1)
    {
        bool success = false;

        try
        {
            _svc.Open();
            operation.Invoke(p1);
            _svc.Close();

            success = true;
        }
        finally
        {
            if (!success)
                _svc.Abort();
        }

        HttpRuntime.Cache.Remove(key);
    }

Now just call Cache on your Reads and Uncache on your Create/Update/Deletes:

    // note the parameterless lambda? this was the only tricky part.
    public static IEnumerable<DBUser> GetAllDBUsers()
    {
        DataServiceClient _svc = new DataServiceClient();

        Func<DBUser[]> fun = () => _svc.GetAllUsers();

        return _svc.Cache<DBUser[]>("AllUsers", fun);
    }

I like this method because I didn't have to recode anything server-side, just my WCF proxy calls (which were admittedly a little brittle / smelly to have scattered about everywhere).

Substitute in your own WCF proxy conventions and standard caching procedures, and you're good to go. It's a lot of work to create all the generic wrapper templates at first too, but i only went up to two parameters and it helps all my caching operations share a single function signature (for now). Let me know if this works for you or if you have any improvements.

ifatree
A: 

Similar to the above solution, check out http://www.acorns.com.au/blog/?p=85 (PolicyInjection on WCF Services). You can sepecify the policy to match your service name.

Lee Simpson