views:

341

answers:

1

Question:
Is it possible to (automatically) change the base class of auto-generated domain objects created through the Visual Studio 'Add Web Reference' functionality, without manually modifying References.cs?

Background:
When we add a reference to a web service (via the Visual Studio 'Add Web Reference' functionality), a number of classes are auto-generated. Theses represent a proxy object (for example a MyServiceSoapClient) and a number of auto-generated domain objects (say, for example a CustomerInfo).

So, if I do something along the lines of the following:

MyServiceSoapClient client = new MyServiceSoapClient();
CustomerInfo cust = client.GetCustomer("John Smith");

I will get back a CustomerInfo object with various properties etc, all nicely deserialized from whatever XML the server returned.

The Problem Is...
Lets say I change the value of the Name Property in the cust object to "Bob Dylan".
Ideally, I'd like to have a base class called ServiceEntity that will track if changes have been made (by trapping the continently-provided INotifyPropertyChanged.PropertyChanged event in the base class), to provide a 'Dirty' property indicating that the object has changed since it was fetched from the service.

The Solution
Although the below answer is a good one, we took a slightly different approach...
As the sync status only needs to be recorded in a few situations, it made more sense to add sync tracking through a Generic class, so we can use it as and when needed.
Here's an example generic class and interface:

Interface:

public interface ISyncEntity
{
    /// <summary>
    /// Gets or Sets the Entity Sync State
    /// </summary>
    [XmlIgnore]
    [SoapIgnore]
    EntitySyncState SyncState { get; set; }

    /// <summary>
    /// Flag for deletion
    /// </summary>
    void DeleteOnSync();

    /// <summary>
    /// Flag for Creation
    /// </summary>
    void CreateOnSync();
}

Class:

public class SyncEntity<TEntity> : ISyncEntity
{
    /// <summary>
    /// Backing Field for Entity Property
    /// </summary>
    private TEntity _entity;

    /// <summary>
    /// Gets or Sets the Entity in question
    /// </summary>
    public TEntity Entity
    {
        get { return _entity; }
        set { OnEntityChange(value); }
    }

    /// <summary>
    /// Invoked when a Property on the Entity is changing
    /// </summary>
    /// <param name="entity"></param>
    protected void OnEntityChange(TEntity entity)
    {
        // Detach the property change event handler from the previous entity?
        if (_entity is INotifyPropertyChanged)
            (entity as INotifyPropertyChanged).PropertyChanged -= OnPropertyChange;

        // Set backing field
        _entity = entity;

        // Implements INotifyPropertyChanged?
        if (entity is INotifyPropertyChanged)
            (entity as INotifyPropertyChanged).PropertyChanged += OnPropertyChange;

        // Set the Sync State
        SyncState = EntitySyncState.Unchanged;
    }



    /// <summary>
    /// Fired when a property in the entity changes
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    protected void OnPropertyChange(object sender, PropertyChangedEventArgs e)
    {
        // If a delete or create is already pending, don't worry about the update!
        if (SyncState == EntitySyncState.Unchanged)
            SyncState = EntitySyncState.UpdatePending;
    }

    #region Sync Framework Members

    [XmlIgnore]
    [SoapIgnore]
    public EntitySyncState SyncState
    {
        get;
        set;
    }

    public void DeleteOnSync()
    {
        SyncState = EntitySyncState.DeletePending;
    }

    public void CreateOnSync()
    {
        SyncState = EntitySyncState.CreatePending;
    }

    #endregion
}

Extension Method:

public static SyncEntity<TEntity> ToSyncEntity<TEntity>(this TEntity source)
{
    if (source == null)
        throw new ArgumentException("Source cannot be null");

    return new SyncEntity<TEntity>()
    {
        Entity = source
    };
}
+1  A: 

The client proxy classes generated through Visual Studio's Web References feature are built with the .Net Framework's wsdl.exe utility. When generated, the output produces public partial classes. Instead of attempting to modify the auto-generated output, you can provide additional class code in another file that adds the event code you're trying to implement.

Of course, this leaves with implementing similar code for each object. Depending on your source service, you could look into extending SoapHttpClientProtocol (or whichever protocol base class represents your objects) to provide a single implementation for all your inheriting objects. This may not be possible without the use of AOP. As such, your mileage may vary.

jro
That's actually a very good idea. We have however solved the problem by implementing a Generics-based class that reacts to the PropertyChanged event (from the INotifyPropertyChanged interface) and a Generic Extension method to create a SyncObject<T>. This has the added benefit of allowing us to keep our code nice and clean and only go to the expense of monitoring sync data when we explicitly need it.Thanks for your help!
Dan
Generics to the rescue! I like that strategy even better. Effectively wrapping the EF classes can be a challenge.
jro