views:

552

answers:

3

I like the idea of extending on the client side classes that are data contracts of WCF services using partial classes. But I encountered a problem that considerably spoils the party.

Imagine on the server side I have a class:

[DataContract]
public class SolidObject
{
    [DataMember]
    public Point Position { get; set; }
    [DataMember]
    public Size Size { get; set; }
}

On the client side I have a proxy class generated, which is used there in the business logic layer. According to the needs of the business logic I extend it this way:

public partial class SolidObject
{
    public Rect Bounds { get { return new Rect(Position.X - Size.Width / 2, Position.Y - Size.Height / 2, Size.Width, Size.Height); }}
}

Now I want to ensure that anytime either Position or Size changes, then Bounds chage event is invoked. It is easy to do by the code:

PropertyChanged += (sender, e) =>
    {
        if ((e.PropertyName == "Position") || (e.PropertyName == "Size")) PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Bounds"));
    };

The question is where the good place to put this code in is.

If objects weren't created by a service call, I'd put it into the constructor. But WCF services ignore constructors on the client side, see constructor not showing up in my WCF client, serialization problem?.

Now, right after service response my program searches through data contract hierarchy, gets desired objects and adds event handlers. But I don't think it's a right thing.

So I'm interesting in where to it is better to do or, maybe, reasoning that the whole approach should be changed. Any ideas appreciated.

+2  A: 

I'd recommend using a view model rather than a partial class.

public class SolidObjectViewModel : INotifyPropertyChanged
{
    private SolidObject _solidObject;

    public SolidObjectViewModel(SolidObject solidObject)
    {
        _solidObject = solidObject;
        _solidObject.PropertyChanged += (sender, e) =>
        {
            bool positionChanged = e.PropertyName == "Position";
            bool sizeChanged = e.PropertyName == "Size";
            if (positionChanged)
                FirePropertyChanged("Position");
            if (sizeChanged)
                FirePropertyChanged("Size");
            if (positionChanged || sizeChanged)
                FirePropertyChanged("Bounds");
        };
    }

    public Point Position
    {
        get { return _solidObject.Position; }
        set { _solidObject.Position = value; }
    }

    public Size Size
    {
        get { return _solidObject.Size; }
        set { _solidObject.Size = value; }
    }

    public Rect Bounds
    {
        get
        {
            return new Rect(Position.X - Size.Width / 2, Position.Y - Size.Height / 2, Size.Width, Size.Height);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void FirePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
Michael L Perry
+1, but the answer would be better if you said _why_ you suggest the view model.
John Saunders
Michael, thank you for bringing this up, I still don't know the answer. As for your approach, it might work, but not always. I do use ViewModel, but for other purposes. I think view models are for managing UI. However, in this case I need additional properties at the business layer to implement physical model, which is independent from presentation layer.So I upvoted, but did not choose it as the solution.
Dmitry Tashkinov
Perhaps view model is the wrong term for the pattern in this context. The reason that this technique will work and the partial class will not is that this gives you an initialization hook.
Michael L Perry
What I don't like here is that in fact I would have to introduce another layer between model and view model. Maybe it's right, but there must be at least other benefits besides hooking initialization to undertake such a massive action.
Dmitry Tashkinov
Added another answer that does not require the extra layer.
Michael L Perry
A: 

I would say do not use generated class if there is business logic to be implemented.Keep your model clean by having client side implementation of SolidObject. When you are generating proxies you can use option of reuse classes from this dll.

It doesnot make sense for an entity to attach to its own event.The implementation would be..

[DataContract]
public class SolidObject
{
   [DataMember]
   public Point Position
    {
        get { return _position; }
        set 
          { 
           position = value; 
           FirePropertyChanged("Position");
           FirePropertyChanged("Bounds");
           }
    }

    public Size Size
    {  
     //similar to position
    }

    public Rect Bounds { get { return new Rect(Position.X - Size.Width / 2, Position.Y - Size.Height / 2, Size.Width, Size.Height); }}
}
Deepak N
Could you please clarify if the code you suggested should reside on the client or the server side?
Dmitry Tashkinov
This code should reside on Client Side.. Then you can use the option to reuse Types while generating proxies.
Deepak N
Then I wonder what would be on server side. Would those classes have the same names and namespaces and merge with these?
Dmitry Tashkinov
You can have Interface for the DataContract and the have different Implementations on server and client side..
Deepak N
Interesting idea, but I think there would be too much code and probably code duplication in the parts where these implementations overlap. Might make sense in some cases, but in common scenerios I can imagine this solution is heavyweight. Anyway, thank you for participation in discussion.
Dmitry Tashkinov
+1  A: 

You could use the [OnDeserlialized] attribute to insert a method that gets called after deserizlization.

public partial class SolidObject
{
    public Rect Bounds
    {
        get
        {
            return new Rect(Position.X - Size.Width / 2, Position.Y - Size.Height / 2, Size.Width, Size.Height);
        }
    }

    [OnDeserialized]
    public void Initialize(StreamingContext streamingContext)
    {
        PropertyChanged += (sender, e) =>
        {
            if ((e.PropertyName == "Position") || (e.PropertyName == "Size"))
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Bounds"));
        };
    }
}
Michael L Perry
Excellent! That's what I've been looking for. The only remark is that it threw exception until I made this method public.
Dmitry Tashkinov
OK, I edited the answer to include that fix.
Michael L Perry
This is great! Had a similar problem and this has solved it. Thanks!
ScottCher