juFo
juFo

Reputation: 18567

MVVM + WCF Async callbacks

I have a WCF service (IMyService) that I wrap into a service (ICentralService), so that I have one central service that I can inject in my ViewModels. This would give me the advantage of changing/adding things at one location just before the WCF service is called.

Now because I need to make an async wcf call, my viewmodel also needs to be async. I'm have a callback from my viewmodel, but my CentralService also has its own callback to call the End... method.

Question: what is the best way to pass my viewmodel-callback to the EndTest method in the central service, so that this EndTest method could notify the callback on the viewmodel?

Or maybe there is a better way? I could directly inject IMyService in my ViewModel but then I don't have a central location (CentralService) where I can manipulate/inject data before sending it to the server via WCF.

Note: I'm on .NET 4.0, can't use "await", I'm also using WCF IAsyncResult Model (server-side).

Code:

[ServiceContract(....)]
public interface IMyService {
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginTest(int x, AsyncCallback, object state);

    int EndTest(IAsyncResult result);
}

public interface ICentralService {
    void WorkItAsync(int x, AsyncCallback callback);
}

public class CentralService : ICentralService 
{
    private IMyService _Srv;

    public CentralService(IMyService srv) 
    {
        _Srv = srv;
    }

    public void WorkItAsync(int x, AsyncCallback callback) 
    {
        // callback is the callback from my viewmodel

        _Srv.BeginTest(x, new AsyncCallback(WorkItCompleted));
    }

    private void WorkItCompleted(IAsyncResult r) 
    {
        // ...
        int result = _Srv.EndTest(r);

        // Need to call callback from viewmodel now to notify it is ready.
    }
}


public class SomeViewModel : INotifyPropertyChanged 
{
    private ICentralService _Central;

    public SomeViewModel(ICentralService central) {
        _Central = central;
    }

  private void A() {
    _Central.WorkItAsync(5, new AsyncCallback(B));
  }

  private void B(object test) {
    // do something with the result
  }
}

UPDATE: I've managed to wrap my IMyService into my ICentralService and pass the result from WCF (IMyService) to my viewmodel via ICentralService.

First attempt/idea, but this did not return my "dataResult" value to my viewmodel:

public void WorkItAsync(int x, AsyncCallback callback) 
{
    var task = Task<int>.Factory.StartNew(() => 
    {
        int dataResult = -1;
        _Srv.BeginTest(x, (ar) => { 
            dataResult  = _Srv.EndTest(ar);
        }, null);

        return dataResult ;
    });

    if (callback != null)
        task.ContinueWith((t) => callback(t));

    return task;
}

Second attempt (works):

public void WorkItAsync(int x, AsyncCallback callback) 
{
    TaskCompletionSource<int> tcs1 = new TaskCompletionSource<int>();
    Task<int> t1 = tcs1.Task;

    Task<int>.Factory.StartNew(() => 
    {
        int dataResult = -1;
        _Srv.BeginTest(x, (ar) => { 
            dataResult = _Srv.EndTest(ar);
            tcs1.SetResult(dataResult);
        }, null);

        return dataResult;
    });

    if (callback != null)
        t1.ContinueWith((t) => callback(t));

    return t1;
}

I'm not sure if this is a good solution using the TaskCompletionSource, but for now it seems to works. (Too bad I have to return a useless -1 dataResult value).

Upvotes: 0

Views: 711

Answers (2)

Ryan
Ryan

Reputation: 8005

The second update looks pretty good, but I think you can use TaskFactory.FromAsync to avoid the TaskCompletionSource for now. Here is a reference page for FromAsync with examples. I haven't tested this, but the method may look something like this:

public interface ICentralService
{
    // Just use .ContinueWith to call a completion method
    Task<int> WorkItAsync(int x);
}

public class CentralService : ICentralService 
{
    private IMyService _Srv;

    public CentralService(IMyService srv) 
    {
        _Srv = srv;
    }

    public Task<int> WorkItAsync(int x) 
    {
        // Callback is handled in ViewModel using ContinueWith
        return Task<int>.Factory.FromAsync(_Src.BeginTest, _Src.EndTest, x);
    }
}

public class SomeViewModel : INotifyPropertyChanged 
{
    private ICentralService _Central;

    public SomeViewModel(ICentralService central)
    {
        _Central = central;
    }

    private void A()
    {
        _Central.WorkItAsync(5)
                .ContinueWith(prevTask =>
                {
                    // Handle or throw exception - change as you see necessary
                    if (prevTask.Exception != null)
                        throw prevTask.Exception;

                    // Do something with the result, call another method, or return it...
                    return prevTask.Result;
                });
    }
}

Upvotes: 2

Holger Thiemann
Holger Thiemann

Reputation: 1082

You could use lambdas:

public void WorkItAsync(int x, AsyncCallback callback) 
{
    // callback is the callback from my viewmodel

    _Srv.BeginTest(x, ar=>{
        int result = _Srv.EndTest(ar);
        callback(ar);
    });
}

ar will be your IAsyncResult if needed. You could even run multiple requests in parallel, because the callback variable will be in local scope for each of the parallel calls.

Upvotes: 1

Related Questions