I have a WCF service which has its Thread.CurrentPrincipal set in the ServiceConfiguration.ClaimsAuthorizationManager.
When I implement the service asynchronously like this:
public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
// Audit log call (uses Thread.CurrentPrincipal)
var task = Task<int>.Factory.StartNew(this.WorkerFunction, state);
return task.ContinueWith(res => callback(task));
}
public string EndMethod1(IAsyncResult ar)
{
// Audit log result (uses Thread.CurrentPrincipal)
return ar.AsyncState as string;
}
private int WorkerFunction(object state)
{
// perform work
}
I find that the Thread.CurrentPrincipal is set to the correct ClaimsPrincipal in the Begin-method and also in the WorkerFunction, but in the End-method it's set to a GenericPrincipal.
I know I can enable ASP.NET compatibility for the service and use HttpContext.Current.User which has the correct principal in all methods, but I'd rather not do this.
Is there a way to force the Thread.CurrentPrincipal to the correct ClaimsPrincipal without turning on ASP.NET compatibility?
Starting with a summary of WCF extension points, you'll see the one that is expressly designed to solve your problem. It is called a CallContextInitializer. Take a look at this article which gives CallContextInitializer sample code.
If you make an ICallContextInitializer extension, you will be given control over both the BeginXXX thread context AND the EndXXX thread context. You are saying that the ClaimsAuthorizationManager has correctly established the user principal in your BeginXXX(...) method. In that case, you then make for yourself a custom ICallContextInitializer which either assigns or records the CurrentPrincipal, depending on whether it is handling your BeginXXX() or your EndXXX(). Something like:
public object BeforeInvoke(System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message request){
object principal = null;
if (request.Properties.TryGetValue("userPrincipal", out principal))
{
//If we got here, it means we're about to call the EndXXX(...) method.
Thread.CurrentPrincipal = (IPrincipal)principal;
}
else
{
//If we got here, it means we're about to call the BeginXXX(...) method.
request.Properties["userPrincipal"] = Thread.CurrentPrincipal;
}
...
}
To clarify further, consider two cases. Suppose you implemented both an ICallContextInitializer and an IParameterInspector. Suppose that these hooks are expected to execute with a synchronous WCF service and with an async WCF service (which is your special case).
Below are the sequence of events and the explanation of what is happening:
Synchronous Case
ICallContextInitializer.BeforeInvoke();
IParemeterInspector.BeforeCall();
//...service executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();
Nothing surprising in the above code. But now look below at what happens with asynchronous service operations...
Asynchronous Case
ICallContextInitializer.BeforeInvoke(); //TryGetValue() fails, so this records the UserPrincipal.
IParameterInspector.BeforeCall();
//...Your BeginXXX() routine now executes...
ICallContextInitializer.AfterInvoke();
//...Now your Task async code executes (or finishes executing)...
ICallContextInitializercut.BeforeInvoke(); //TryGetValue succeeds, so this assigns the UserPrincipal.
//...Your EndXXX() routine now executes...
IParameterInspector.AfterCall();
ICallContextInitializer.AfterInvoke();
As you can see, the CallContextInitializer ensures you have opportunity to initialize values such as your CurrentPrincipal just before the EndXXX() routine runs. It therefore doesn't matter that the EndXXX() routine assuredly is executing on a different thread than did the BeginXXX() routine. And yes, the System.ServiceModel.Channels.Message object which is storing your user principal between Begin/End methods, is preserved and properly transmitted by WCF even though the thread changed.
Overall, this approach allows your EndXXX(IAsyncresult) to execute with the correct IPrincipal, without having to explicitly re-establish the CurrentPrincipal in the EndXXX() routine. And as with any WCF behavior, you can decide if this applies to individual operations, all operations on a contract, or all operations on an endpoint.
Not really the answer to my question, but an alternate approach of implementing the WCF service (in .NET 4.5) that does not exhibit the same issues with Thread.CurrentPrincipal.
public async Task<string> Method1()
{
// Audit log call (uses Thread.CurrentPrincipal)
try
{
return await Task.Factory.StartNew(() => this.WorkerFunction());
}
finally
{
// Audit log result (uses Thread.CurrentPrincipal)
}
}
private string WorkerFunction()
{
// perform work
return string.Empty;
}
The valid approach to this is to create an extension:
public class SLOperationContext : IExtension<OperationContext>
{
private readonly IDictionary<string, object> items;
private static ReaderWriterLockSlim _instanceLock = new ReaderWriterLockSlim();
private SLOperationContext()
{
items = new Dictionary<string, object>();
}
public IDictionary<string, object> Items
{
get { return items; }
}
public static SLOperationContext Current
{
get
{
SLOperationContext context = OperationContext.Current.Extensions.Find<SLOperationContext>();
if (context == null)
{
_instanceLock.EnterWriteLock();
context = new SLOperationContext();
OperationContext.Current.Extensions.Add(context);
_instanceLock.ExitWriteLock();
}
return context;
}
}
public void Attach(OperationContext owner) { }
public void Detach(OperationContext owner) { }
}
Now this extension is used as a container for objects that you want to persist between thread switching as OperationContext.Current will remain the same.
Now you can use this in BeginMethod1 to save current user:
SLOperationContext.Current.Items["Principal"] = OperationContext.Current.ClaimsPrincipal;
And then in EndMethod1 you can get the user by typing:
ClaimsPrincipal principal = SLOperationContext.Current.Items["Principal"];
EDIT (Another approach):
public IAsyncResult BeginMethod1(AsyncCallback callback, object state)
{
var task = Task.Factory.StartNew(this.WorkerFunction, state);
var ec = ExecutionContext.Capture();
return task.ContinueWith(res =>
ExecutionContext.Run(ec, (_) => callback(task), null));
}
Related
I am using InstanceContextMode.Single in the sevice behaviour plus my service also implements the singleton pattern.
Such as:
private static readonly object padlock = new object();
public static MySingletonService Instance
{
get
{
lock (padlock)
{
if (_event == null)
{
_event = new MySingletonService();
}
return _event;
}
}
}
Now, the reason that I did this was I wanted to access the same instance of the service regardless of whether the call comes from a channel or someone simply calls it like a simple class(code snippets given for both scenarios).
var channelFactory = new ChannelFactory<MySingletonService>(myBinding, myEndpoint);
channelFactory.Open();
using (channelFactory)
{
var channelclient = channelFactory.CreateChannel();
channelclient.MethodCall(this);
}
Also,
MySingletonService.Instance.MethodCall(this);
But, I get two differenct objects (verified by checking object hashcode) in both cases and not one object.
Is there any other way to achieve this?
I inherited a Silverlight 5 application. On the server side, it has a DomainContext (service) with a method marked as
[Invoke]
public void DoIt
{
do stuff for 10 seconds here
}
On the client side, it has a ViewModel method containing this:
var q = Context.DoIt(0);
var x=1; var y=2;
q.Completed += (a,b) => DoMore(x,y);
My 2 questions are
1) has DoIt already been activated by the time I attach q.Completed, and
2) does the return type (void) enter into the timing at all?
Now, I know there's another way to call DoIt, namely:
var q = Context.DoIt(0,myCallback);
This leads me to think the two ways of making the call are mutually exclusive.
Although DoIt() is executed on a remote computer, it is best to attach Completed event handler immediately. Otherwise, when the process completes, you might miss out on the callback.
You are correct. The two ways of calling DoIt are mutually exclusive.
If you have complicated logic, you may want to consider using the Bcl Async library. See this blog post.
Using async, your code will look like this:
// Note: you will need the OperationExtensions helper
public async void CallDoItAndDosomething()
{
this.BusyIndicator.IsBusy = true;
await context.DoIt(0).AsTask();
this.BusyIndicator.IsBusy = false;
}
public static class OperationExtensions
{
public static Task<T> AsTask<T>(this T operation)
where T : OperationBase
{
TaskCompletionSource<T> tcs =
new TaskCompletionSource<T>(operation.UserState);
operation.Completed += (sender, e) =>
{
if (operation.HasError && !operation.IsErrorHandled)
{
tcs.TrySetException(operation.Error);
operation.MarkErrorAsHandled();
}
else if (operation.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(operation);
}
};
return tcs.Task;
}
}
I am trying to use BeginExecuteReader method of ADO.Net in an asyn WCF method, but not able to get it.
I have the following contract and service code. I cannot understand how do I fill in the details for callback method in the begin method of service. Any help would be greatly appreciated since I cannot find any examples on the web or any documentation on MSDN for this. Even some link to sample code would help since I am TOTALLY confused with how to do this.
Contract code:
[ServiceContract(Namespace = ServiceConstants.ServiceContractNamespace,
Name = ServiceConstants.ServiceName)]
public interface IAsyncOrderService
{
[OperationContract(AsyncPattern=true)]
IAsyncResult BeginGetProducts(string vendorId, AsyncCallback callback,
object state);
List<Product> EndGetProducts(IAsyncResult result);
}
The service code is:
public IAsyncResult BeginGetProducts(string vendorId, AsyncCallback cb, object s)
{
DocumentsSummaryByProgram summary = null;
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Conn1"].ConnectionString);
SqlCommand sqlCmd = null;
sqlCmd = new SqlCommand("dbo.GetProducts", conn);
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("#vendorId", sqlCmd);
conn.Open();
return sqlCmd.BeginExecuteReader(cb, vendorId);
}
public List<Product> EndGetProducts(IAsyncResult r)
{
List<Product> products = new List<Product>();
SqlCommand cmd = r.AsyncState as SqlCommand;
if (cmd != null)
{
SqlDataReader dr = cmd.EndExecuteReader(r);
while (dr.Read())
{
//do your processing here and populate products collection object
}
}
return products;
}
UPDATE 1 : This seems like an impossible task. Microsoft should have provided examples to show how ADO.Net async methods are called from WCF in async manner, since this would be useful for many apps out there that want to be scalable.
UPDATE 2: I have provided a detailed answer to my question, after I was able to successfully implement async pattern in WCF. Please look at the answer in a separate post below.
You never called opened your SqlConnection
conn.Open();
Also you created two SqlConnection objects:
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["Conn1"].ConnectionString);
and:
sqlCmd = new SqlCommand("dbo.GetProducts", new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["VHA_EDM"].ConnectionString));
Edit
To add an async callback you would do:
var callback = new AsyncCallback(HandleCallback);
sqlCmd.BeginExecuteReader(callback, command);
If you don't have any asynchronous code that you planned on running in between BeginExecuteReader and EndExecuteReader you are better off just using ExecuteReader.
Edit 2
The AsyncCallback delegate has the following signature:
public delegate void AsyncCallback(IAsyncResult ar);
From within that delegate method you can Invoke your EndGetProducts method.
Edit 3
Here is an example of retrieving data using BeginExecuteReader:
public SqlCommand Command { get; set; }
public IAsyncResult BeginGetStuff()
{
var connect = "[enter your connection string here]";
// Note: Your connection string will need to contain:
// Asynchronous Processing=true;
var cn = new SqlConnection(connect);
cn.Open();
var cmd = new SqlCommand("[enter your stored proc name here]", cn);
cmd.CommandType = CommandType.StoredProcedure;
this.Command = cmd;
return cmd.BeginExecuteReader();
}
public List<string> EndGetStuff(IAsyncResult r)
{
var dr = this.Command.EndExecuteReader(r);
var list = new List<string>();
while (dr.Read())
list.Add(dr[0].ToString());
return list;
}
I am providing a separate post to answer my question, since it's quite a long answer. I hope it helps others quickly implement async pattern in their WCF.
The points that I was missing when implementing async pattern in WCF, are as below. Without these, I was either getting a hung WCF problem saying 'Connecting...' or operation was aborted/canceled error message at WCF level. In my solution below, I have not discussed exception handling in async pattern on WCF side in order to keep it simple.
Do not invoke the EndGetProducts method of WCF by your code like calling it by using delagateInstance.Invoke or any other way. In async pattern, all you need to do is call the client-side callback, when your long async operation is complete, which will result in your client-side callback being called which in turn will call the WCF EndGetProduct method ( example: cb(asyncResult1) where cb is the callback delegate instance passed by the client-side code calling this WCF). I was trying to call the EndGetProducts WCF method by using Invoke, which is wrong. Even when client-side is passing nothing for client callback, this should still be done to invoke the End method in WCF.
Do not return the asyncresult you get from ADO.Net async begindatareader method, from BeginGetProducts method, since it needs to be the same AsyncResult that is in the context of client's call to WCF. This means you must include the client-side callback and the client-side state object in the AsyncResult that your BeginGetProducts will return, even when client-side is passing nothing for these. I was returning the AsyncResult of ADO.Net async method begindatareader from BeginGetProducts, which is wrong.
When calling the client-side callback delegate instance from WCF, make sure you pass the AsyncResult that contains client-side context that I have discussed in last bullet. Also, do this when your async operation is complete, which I do in the callback of beginexecutereader after I have created a List object.
One last point to keep in mind is that you must set sufficiently large timeouts at WCF and ADO.Net levels, since your async operation might take quite a long time else you will get timeouts in WCF. For this, set the ADO.Net command timeout to 0 ( infinite timeout) or to an appropriate value, and for WCF you can include configuration like below.
<binding name="legacyBinding" openTimeout="00:10:00" sendTimeout="00:10:00"
receiveTimeout="00:10:00" closeTimeout="00:10:00" maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647" >
Now the code, which might appear lengthy, but my intention is to make it easy for others to implement async pattern in their WCF. It was quite difficult for me.
WCF Contract
[OperationContract(AsyncPattern = true)]
[FaultContract(typeof(string))]
IAsyncResult BeginGetProducts(string vendorId, AsyncCallback cb, object s);
//The End method must return the actual datatype you intend to return from
//your async WCF operation. Also, do not decorate the End method with
//OperationContract or any other attribute
List<Product> EndGetProducts(IAsyncResult r);
WCF Implementation
public IAsyncResult BeginGetProducts( string vendorId, AsyncCallback cb, object s)
{
SqlCommand sqlCmd = null;
sqlCmd = new SqlCommand("dbo.ABC_sp_GetProducts", "Data Source=xyz;Initial Catalog=NorthwindNew;Integrated Security:true;asynchronous processing=true;"));
sqlCmd.CommandType = CommandType.StoredProcedure;
sqlCmd.Parameters.AddWithValue("#vendorId", vendorId);
sqlCmd.CommandTimeout = 0;//async operations can be long operations so set a long timeout
//THIS ASYNRESULT MUST REFLECT THE CLIENT-SIDE STATE OBJECT, AND IT IS WHAT SHOULD FLOW THROUGH TO END METHOD of WCF.
//THE CLIENT CALLBACK (PARAMETER 'cb') SHOULD BE INVOKED USING THIS ASYNCRESULT, ELSE YOUR WCH WILL HANG OR YOUR WCF WILL GET ABORTED AUTOMATICALLY.
AsyncResult<FinalDataForDocumentsSummary> asyncResult1 = new AsyncResult<FinalDataForDocumentsSummary>(false, s);//this is the AsyncResult that should be used for any WCF-related method (not ADO.Net related)
AsyncCallback callback = new AsyncCallback(HandleCallback);//this is callback for ADO.Net async begindatareader method
sqlCmd.Connection.Open();
//AsynResult below is for passing information to ADO.Net asyn callback
AsyncResult<Product> cmdResult = new AsyncResult<Product>(false, new object[] {sqlCmd, cb,s});
sqlCmd.BeginExecuteReader(HandleCallback, cmdResult);
return asyncResult1;//ALWAYS RETURN THE ASYNCRESULT INSTANTIATED FROM CLIENT PARAMETER OF STATE OBJECT. FOR DATAREADER CREATE ANOTHER ASYNCRESULT THAT HAS COMMAND OBJECT INSIDE IT.
}
/// <summary>
/// This is the callback on WCF side for begin data reader method.
/// This is where you retrieve data, and put it into appropriate data objects to be returned to client.
/// Once data has been put into these objects, mark this ASYNC operation as complete and invoke the
/// client callback by using 'cb(asyncResult1)'. Use the same asyncresult that contains the client passed state object.
/// </summary>
/// <param name="result"></param>
public void HandleCallback(IAsyncResult result)
{
List<Product> summaries = new List<Product>();
Product product = null;
//THIS ASYNCRESULT IS ONLY FOR DATAREADER ASYNC METHOD AND NOT TO BE USED WITH WCF, ELSE BE READY FOR WCF FAILING
AsyncResult<Product> asyncResult = result.AsyncState as AsyncResult<Product>;
object[] objects = asyncResult.AsyncState as object[];
SqlCommand cmd = objects[0] as SqlCommand;
AsyncCallback cb = objects[1] as AsyncCallback;
object s = objects[2];
//CREATE THE SAME ASYNCRESULT THAT WE HAD IN BEGIN METHOD THAT USES THE CLIENT PASSED STATE OBJECT
AsyncResult<Product> asyncResult1 = new AsyncResult<Product>(false, s);
SqlDataReader dr = null;
if (cmd != null)
{
try
{
dr = cmd.EndExecuteReader(result);
while (dr.Read())
{
product = new Product(dr.GetInt32(0), dr.GetString(1));
summaries.Add(summary);
}
dr.Close();
cmd.Connection.Close();
//USE THE CORRECT ASYNCRESULT. WE NEED THE ASYNCRESULT THAT WE CREATED IN BEGIN METHOD OF WCF.
asyncResult1.Data = new FinalDataForDocumentsSummary(count, summaries.OrderByDescending(x => x.CountOfOverDue).ToList());
}
finally
{
if (dr != null)
{
dr.Close();
}
if (cmd.Connection != null)
{
cmd.Connection.Close();
cmd.Connection.Dispose();
}
//USE THE CORRECT ASYNCRESULT. WE NEED THE ASYNCRESULT THAT WE CREATED IN BEGIN METHOD OF WCF
asyncResult1.Complete();
//THIS IS REQUIRED ELSE WCF WILL HANG. EVEN WHEN NO CALLBACK IS PASSED BY CLIENT,
//YOU MUST EXECUTE THIS CODE. EXECUTE IT AFTER YOUR OPERATION HAS COMPLETED,
//SINCE THIS IS WHAT CAUSES THE END METHOD IN WCF TO EXECUTE.
//DON'T TRY TO CALL THE WCF END METHOD BY YOUR CODE (like using delegateInstance.Invoke) SINCE THIS WILL HANDLE IT.
cb(asyncResult1);
}
}
}
/// <summary>
/// This method gets automatically called by WCF if you include 'cb(asyncResult1)' in the reader's callback meethod, so don't try to call it by your code.
/// But always use 'cb(asyncResult1)' just after data has been successfully retrieved from database and operation is marked as complete.
/// </summary>
/// <param name="r"></param>
/// <returns></returns>
public List<Product> EndGetProducts(IAsyncResult r)
{
AsyncResult<Product> result = r as AsyncResult<Product>;
// Wait until the AsyncResult object indicates the
// operation is complete, in case the client called the End method just after the Begin method.
if (!result.CompletedSynchronously)
{
System.Threading.WaitHandle waitHandle = result.AsyncWaitHandle;
waitHandle.WaitOne();
}
// Return the database query results in the Data field
return result.Data;
}
Generic class for AsyncResult that is needed in async pattern
using System;
using System.Threading;
class AsyncResult<T> : IAsyncResult
{
private T data;
private object state;
private bool isCompleted = false;
private AutoResetEvent waitHandle;
private bool isSynchronous = false;
public T Data
{
set { data = value; }
get { return data; }
}
public AsyncResult(bool synchronous, object stateData)
{
isSynchronous = synchronous;
state = stateData;
}
public void Complete()
{
isCompleted = true;
((AutoResetEvent)AsyncWaitHandle).Set();
}
public object AsyncState
{
get { return state; }
}
public WaitHandle AsyncWaitHandle
{
get
{
if (waitHandle == null)
waitHandle = new AutoResetEvent(false);
return waitHandle;
}
}
public bool CompletedSynchronously
{
get
{
if (!isCompleted)
return false;
else
return isSynchronous;
}
}
public bool IsCompleted
{
get { return isCompleted; }
}
}
How to call this from client-side:
protected void Page_Load(object sender, EventArgs e)
{
using (ABCService.ServiceClient sc = new ABCService.ServiceClient())
{
// List<ABCService.Product> products = sc.GetDocSummary("Vend1", null, false);//this is synchronous call from client
sc.BeginGetProducts("Vend1",GetProductsCallback, sc);//this is asynchronous call from WCF
}
}
protected void GetProductsCallback(IAsyncResult asyncResult)
{
List<ABCService.Product> products = ((ABCService.ServiceClient)asyncResult.AsyncState).EndGetProducts(asyncResult);//this will call the WCF EndGetProducts method
}
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?
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.
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.
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.
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
If you call a web service from Silverlight like this:
MyServiceClient serviceClient = new MyServiceClient();
void MyMethod()
{
serviceClient.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(serviceClient_GetDataCompleted);
serviceClient.GetDataAsync();
// HOW DO I WAIT/JOIN HERE ON THE ASYNC CALL, RATHER THAN BEING FORCE TO LEAVE THIS METHOD?
}
I would rather wait/join with the asych service thread inside "MyMethod" rather than leaving "MyMethod" after calling "GetDataAsync", what is the best way to do this?
Thanks,
Jeff
No you cannot do this way. You will end up in a deadlock. GetDataCompleted is called by the mainthreed. The same threed thait is waiting in WaitOne.
I have to ask; why? The point is to provide your user with a fluid experience and waiting on a web service call will not necessarily do that. I suppose you want the full block of content to load before the Silverlight control loads. In that case, I would turn to caching the content rather than forcing the client to wait indefinitely.
To do this you would use a ManualResetEvent in your class (class level variable) and then wait on it.
void MyMethod()
{
wait = new ManualResetEvent(false);
// call your service
wait.WaitOne();
// finish working
}
and in your event handler code
void serviceClient_GetDataCompleted(...)
{
// Set values you need from service
wait.Set();
}
You could also use a lambda and closure to get similar behavior:
serviceClient.GetDataCompleted += (s,e) =>
{
// Your code here
};
serviceClient.GetDataAsync();
If you had a base class provide the mechanics of building a WCF channel, it could then be used to build the BeginX / EndX methods for a async call.
public class ServiceFooCoordinator : CoordinatorBase<IServiceFoo>
{
public IAsyncResult BeginMethodFoo ()
{
IAsyncResult ar = null;
IServiceFoo channel = null;
channel = _factory.GetChannel();
Begin( channel, () => ar = channel.BeginMethodFoo( null, channel ) );
return ar;
}
public Bar[] EndMethodFoo ( IAsyncResult ar )
{
IServiceFoo channel = null;
channel = _factory.GetChannel();
return channel.EndMethodFoo( ar );
}
}
Which can then be used in a method:
ServiceFooCoordinator _coordinator;
var asyncResult = _coordinator.BeginMethodFoo();
try
{
var result = _coordinator.EndMethodFoo( asyncResult );
}
catch ( Exception )
{ }
Which gets you your asynchronous call in a sychronous manner.