指定 Web 服务引用自动生成的实体基类
问题:
是否可以(自动)更改通过 Visual Studio“添加 Web 引用”功能创建的自动生成域对象的基类,而无需手动修改 References.cs?
背景:
当我们添加对 Web 服务的引用(通过 Visual Studio 的“添加 Web 引用”功能)时,会自动生成许多类。这些代表一个代理对象(例如 MyServiceSoapClient)和许多自动生成的域对象(例如 CustomerInfo)。
因此,如果我执行以下操作:
MyServiceSoapClient client = new MyServiceSoapClient();
CustomerInfo cust = client.GetCustomer("John Smith");
我将返回一个具有各种属性等的 CustomerInfo 对象,所有属性都从服务器返回的任何 XML 中很好地反序列化。
问题是...
假设我将 cust 对象中的 Name 属性的值更改为“Bob Dylan”。
理想情况下,我希望有一个名为 ServiceEntity 的基类,它将跟踪是否进行了更改(通过捕获基类中大陆提供的 INotifyPropertyChanged.PropertyChanged 事件),以提供一个“脏”属性,指示该对象已自从从服务中获取以来发生了变化。
解决方案
虽然下面的答案是一个很好的答案,但我们采取了略有不同的方法......
由于只需要在少数情况下记录同步状态,因此通过通用类添加同步跟踪更有意义,因此我们可以在需要时使用它。
这是一个通用类和接口的示例:
接口:
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();
}
类:
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
}
扩展方法:
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
};
}
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
};
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
通过 Visual Studio 的 Web 引用功能生成的客户端代理类是使用 .Net Framework 的 wsdl.exe 实用程序。生成时,输出会生成公共部分类。您可以在另一个文件中提供额外的类代码来添加您尝试实现的事件代码,而不是尝试修改自动生成的输出。
当然,这需要为每个对象实现类似的代码。根据您的源服务,您可以考虑扩展 SoapHttpClientProtocol (或代表您的对象的任何协议基类)以提供单一实现你所有的继承对象。如果不使用 AOP。因此,您的里程可能会有所不同。
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.