MVVM and Asynchronous Data Access

So I have a WPF application using MVVM pattern (Caliburn.Micro). I got the views and view models connected to the web and it is mostly missing data. Data must be restored "on demand" from either WCF service, local storage, or memory / cache - the reason is to enable offline mode and avoid unwanted communication with the server. Another requirement is that the data is fetched asynchronously, so the UI thread is not blocked.

So, I was thinking about creating some kind of "AssetManager" that is used in viewmodels to query data:

_someAssetManager.GetSomeSpecificAsset(assetId, OnGetSomeSpecificAssetCompleted)

      

Note that this is an asynchronous call. However, I am facing several different problems. If the same resource is requested (approximately) at the same time by different view models, how do we ensure that we don't do unnecessary work and that they both get the same objects that we can communicate with?

Not sure if I have the right approach. I've been looking around for a bit at Reactive Framework, but I have no idea how to use it in this scenario. Any suggestions for structure / methods / templates that I can use? This seems to be a fairly common scenario.

+3


source to share


4 answers


Dictionary<int, IObservable<IAsset>> inflightRequests;

public IObservable<IAsset> GetSomeAsset(int id)
{
    // People who ask for an inflight request just get the
    // existing one
    lock(inflightRequests) {
        if inflightRequests.ContainsKey(id) {
            return inflightRequests[id];
        }
    }

    // Create a new IObservable and put in the dictionary
    lock(inflightRequests) { inflightRequests[id] = ret; }

    // Actually do the request and "play it" onto the Subject. 
    var ret = new AsyncSubject<IAsset>();
    GetSomeAssetForReals(id, result => {
        ret.OnNext(id);
        ret.OnCompleted();

        // We're not inflight anymore, remove the item
        lock(inflightRequests) { inflightRequests.Remove(id); }
    })

    return ret;
}

      



+4


source


I have had success with method calls that are passed in a delegate that is called when data is received. You can add up to the requirement to keep everyone with the same data (if a request is currently in progress) by checking a boolean field that determines if the request is being made. I would keep a local collection of delegates that need to be called so that when the data is finally received, the class containing the delegates to call can iterate over them, passing in the newly received data.

Something like this:

public interface IViewModelDataLoader{
    void LoadData(AssignData callback);
}

public delegate void AssignData(IEnumerable<DataObject> results);

      



The class that actually implements this interface can then maintain a current estimate of who should notify when the data is executed (assuming a singlemode model):

public class ViewModelDataLoader : IViewModelDataLoader{
    private IList<AssignData> callbacksToCall;
    private bool isLoading;

    public void LoadData(AssignData callback){
        callbacksToCall.add(callback);
        if (isLoading) { return; }

        // Do some long running code here
        var data = something;
        // Now iterate the list
        foreach(var item in callbacksToCall){
           item(data);
        }
        isLoading = false;
    }
 }

      

+1


source


By using the proxy and events pattern, you can provide both synchronous and asynchronous data. Have your proxy return cached values ​​for synchronous calls, and notify the view models via events when you receive asynchronous data. A proxy can also be designed to keep track of data requests and throttle connections (eg "reference count", requested data / data, received flags, etc.)

+1


source


I would install you AssetManager

like this:

public interface IAssetManager
{
    IObservable<IAsset> GetSomeSpecificAsset(int assetId);
}

      

Internally, you will need to return Subject<IAsset>

which you fill in asynchronously. Do it right and you only have one call for each call GetSomeSpecificAsset

.

+1


source







All Articles