views:

117

answers:

1

I am building a WP7 client app that talk to a web service (SOAP like) using Mvvm-Light.

I have a ViewModel that both implements INotifyPropertyChanged and calls RaisePropertryChanged with the broadcast flag set.

Both my view (XAML) and my model (which does HTTP requests to the web service) subscribe to property changes. XAML, obviously, because of INotifyPropertyChanged, and my model by calling

Messenger.Default.Register<SysObjectCreatedMessage>(this, (action) => SysObjectCreatedHandler(action.SysObject));

This pattern is not going to work, i'm afraid, because of the following:

When I get data back from the web service I set properties on my ViewModel (using DispatcherHelper.CheckBeginInvokeUI). I actually use Reflection and my call looks like this:

GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, e.Response, null));

Here's the problem: The resultant property set caused by that call to SetValue causes my property set to call RaisePropertryChanged causing me to send the data I just got from the server right back to it.

EDIT - Adding more context Per Jon's suggestion

Here's some of my XAML. My GarageDoorOpener class has a GarageDoorOpened property

On the home control server there are a bunch of garage door objects that have a boolean property representing whether they are open or not. I can access these using HTTP POSTs of the form:

http://server/sys/Home/Upstairs/Garage/West Garage Door?f??GarageDoorOpened

The the resultant HTTP body will contain either True or False.

The same model applies to other objects in the home with other types (Strings, Integers, etc...).

For now I'm just focusing on the garage door openers.

The View Model for the garage door looks like this:

public class GarageDoorSensor : SysObject
{
    public static new string SysType = "Garage Door Sensor";
    public const string GarageDoorOpenedPropertyName = "GarageDoorOpened";
    public Boolean _GarageDoorOpened = false;
    [SysProperty]
    public Boolean GarageDoorOpened
    {
        get
        {
            return _GarageDoorOpened;
        }

        set
        {
            if (_GarageDoorOpened == value)
            {
                return;
            }

            var oldValue = _GarageDoorOpened;
            _GarageDoorOpened = value;

            // Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
            RaisePropertyChanged(GarageDoorOpenedPropertyName, oldValue, value, true);
        }
    }
}

The SysObject class this inherits from looks like this (simplified):

public class SysObject : ViewModelBase
{
    public static string SysType = "Object";
    public SysObject()
    {
        Messenger.Default.Send<SysObjectCreatedMessage>(new SysObjectCreatedMessage(this));
        }
    }

    protected override void RaisePropertyChanged<T>(string propertyName, T oldValue, T newValue, bool broadcast)
    {
        // When we are initilizing, do not send updates to the server
        // if (UpdateServerWithChange == true)

        // ****************************
        // ****************************
        // 
        // HERE IS THE PROBLEM
        // 
        // This gets called whenever a property changes (called from set())
        // It both notifies the "server" AND the view
        //
        // I need a pattern for getting the "SendPropertyChangeToServer" out
        // of here so it is only called when properties change based on 
        // UI input.
        // 
        // ****************************
        // ****************************
        SendPropertyChangeToServer(propertyName, newValue.ToString());

        // Check if we are on the UI thread or not
        if (App.Current.RootVisual == null || App.Current.RootVisual.CheckAccess())
        {
            base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast);
        }
        else
        {
            // Invoke on the UI thread
            // Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
            GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() =>
                base.RaisePropertyChanged(propertyName, oldValue, newValue, broadcast));
        }
    }

    private void SendPropertyChangeToServer(String PropertyName, String Value)
    {
          Messenger.Default.Send<SysObjectPropertyChangeMessage>(new SysObjectPropertyChangeMessage(this, PropertyName, Value));
    }

    // Called from PremiseServer when a result has been returned from the server.
    // Uses reflection to set the appropriate property's value 
    public void PropertySetCompleteHandler(HttpResponseCompleteEventArgs e)
    {
        // BUGBUG: this is wonky. there is no guarantee these operations will modal. In fact, they likely
        // will be async because we are calling CheckBeginInvokeUI below to wait on the UI thread.

        Type type = this.GetType();
        PropertyInfo pinfo = type.GetProperty((String)e.context);

        // TODO: Genericize this to parse not string property types
        //
        if (pinfo.PropertyType.Name == "Boolean")
        {
            GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, Boolean.Parse(e.Response), null));
            //pinfo.SetValue(this, Boolean.Parse(e.Response), null);
        }
        else
        {
            GalaSoft.MvvmLight.Threading.DispatcherHelper.CheckBeginInvokeOnUI(() => pinfo.SetValue(this, e.Response, null));
            //pinfo.SetValue(this, e.Response, null);
        }
    }
}

My "model" is called PremiseServer. It wraps Http POSTs and handles causing the server to be "queried" for the latest data every once and a while. I plan on eventually implementing notifications, but for now i am polling. It uses a bit of Reflection to dynamically translate the HTTP results into property sets. At it's core it looks like this (i'm pretty proud of myself for this, although I probably should be ashamed instead).

    protected virtual void OnRequery()
    {
        Debug.WriteLine("OnRequery");
        Type type;

        foreach (SysObject so in sysObjects)
        {
            type = so.GetType();
            PropertyInfo[] pinfos = type.GetProperties();

            foreach (PropertyInfo p in pinfos)
            {
                if (p.IsDefined(typeof(SysProperty),true))
                    SendGetProperty(so.Location, p.Name, so, so.PropertySetCompleteHandler);
            }

        }
    }

    protected delegate void CompletionMethod(HttpResponseCompleteEventArgs e);
    protected void SendGetProperty(string location, string property, SysObject sysobject, CompletionMethod cm)
    {
        String url = GetSysUrl(location.Remove(0, 5));
        Uri uri = new Uri(url + "?f??" + property);
        HttpHelper helper = new HttpHelper(uri, "POST", null, true, property);
        Debug.WriteLine("SendGetProperty: url = <" + uri.ToString() + ">");
        helper.ResponseComplete += new HttpResponseCompleteEventHandler(cm);
        helper.Execute();
    }

Note that OnRequery is not the only place I'll eventually be calling SendGetProperty from; it's just there for initialization scaffolding for now. The idea is that I can have a generic piece of code that gets a "message from the server" and translates it into a SysObject.Property.SetValue() call...

END EDIT

I need a pattern that will let me both bind to my data on the XAML side but also on my model side in a thread safe way.

Suggestions?

Thanks!

+1  A: 

Well, one option is to make your ViewModel responsible for explicitly calling on the model, rather than using the messenger. That way it's easier for the ViewModel to know that it doesn't need to fire off a request for this change.

The alternative is for the model to check the newly set value to see whether or not it corresponds with its own idea of the "current" value. You haven't really told us what's going on here in terms of what the response is or what the server's looking for, but usually I'd expect this to be a case of checking whether an old value is equal to a new value, and ignoring the "change" if so.

If you could show a short but complete example of all of this, it would make it easier to talk about though.

Jon Skeet
Hi Jon! See edits above with more context.
cek
@cek: It still doesn't explain why you want the model to launch a new query just because the ViewModel has changed... it feels like your subscription model is broken, basically. I don't think this is really a threading issue... it's a subscription issue. Think about what should *really* make your model perform another query.
Jon Skeet
I agree with Jon. There's no need to use the Messenger to communicate between your ViewModel and your Model. Your ViewModel can call methods directly on your Model. You should consider having your Model raise events when service calls complete, and have your ViewModel handle those events.
Matt Casto