在前面的文章里直接定义了 WcfService 继承于 DataService<T> , 它使用的是:ReflectionServiceProvider 提供程序通过使用反射来推断数据模型。
以下列表显示了反射提供程序如何推断数据模型:
【2.自定义Provider实现的DataService】
如何自定义一个 ServiceProvider 来实现 DataService (具体参考: http://blogs.msdn.com/b/alexj/archive/tags/dsp/)DSPDataService<T> 继承于 DataService<T> 实现 IServiceProvider 接口,而 ProductDataService 继承于它,返回 IDataServiceMetadataProvider, IDataServiceQueryProvider, IDataServiceUpdateProvider 的实例。它是 DataService 的入口。而这3个接口顾名思义分别提供:元数据,查询,更新的功能。
DSPDataService.cs
public abstract class DSPDataService<T> : DataService<T>, IServiceProvider where T : DSPContext { private IDataServiceMetadataProvider _metadata; private IDataServiceQueryProvider _query; private IDataServiceUpdateProvider _update; public DSPDataService() { _metadata = GetMetadataProvider(typeof(T)); _query = GetQueryProvider(_metadata); _update = GetUpdateProvider(_metadata, _query); } public object GetService(Type serviceType) { if (serviceType == typeof(IDataServiceMetadataProvider)) return _metadata; else if (serviceType == typeof(IDataServiceQueryProvider)) return _query; else if (serviceType == typeof(IDataServiceUpdateProvider)) return _update; else return null; } public abstract IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType); public abstract IDataServiceQueryProvider GetQueryProvider(IDataServiceMetadataProvider metadata); public abstract IDataServiceUpdateProvider GetUpdateProvider(IDataServiceMetadataProvider metadata, IDataServiceQueryProvider query); }
DSPContext.cs
public abstract class DSPContext { public abstract IQueryable GetQueryable(ResourceSet set); public abstract object CreateResource(ResourceType resourceType); public abstract void AddResource(ResourceType resourceType, object resource); public abstract void DeleteResource(object resource); public abstract void SaveChanges(); }
可以看到上面两个都是抽象类,具体实现类是:ProductDataService.svc 和 ProductsContext.cs。这儿 ProductsContext.cs 就是自定义数据模型的容器。
ProductDataService.svc.cs
(1) 重写了 DataService.CreateDataSource 返回 ProductsContext 实例
(2) 重写了 DSPDataService.GetMetadataProvider 返回 DSPMetadataProvider 实例
(3) 重写了 DSPDataService.GetQueryProvider 返回 DSPQueryProvider 实例
(4) 重写了 DSPDataService.GetUpdateProvider 返回 DSPUpdateProvider 实例
注意:GetMetadataProvider 部分的代码,这里手动添加了对应的 ResourceType(及包含的 ResourceProperty)
using System; using System.Collections.Generic; using System.Data.Services; using System.Data.Services.Common; using System.Linq; using System.ServiceModel.Web; using System.Web; using System.Data.Services.Providers; namespace WcfCustomerDataProviderDataService { public class ProductDataService : DSPDataService<ProductsContext> { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.All); config.SetServiceOperationAccessRule("*", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } protected override ProductsContext CreateDataSource() { ProductsContext context = new ProductsContext(); context.Products.Add( new Product { ProdKey = 1, Name = "Bovril", Cost = 4.35M, Price = 6.49M }); context.Products.Add( new Product { ProdKey = 2, Name = "Marmite", Cost = 4.97M, Price = 7.21M }); return context; } public override IDataServiceMetadataProvider GetMetadataProvider(Type dataSourceType) { DSPMetadataProvider metadata = new DSPMetadataProvider(); var productType = new ResourceType( typeof(Product), // CLR type backing this Resource ResourceTypeKind.EntityType, // Entity, ComplexType etc null, // BaseType "Namespace", // Namespace "Product", // Name false // Abstract? ); var prodKey = new ResourceProperty( "ProdKey", ResourcePropertyKind.Key | ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(int)) ); var prodName = new ResourceProperty( "Name", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(string)) ); var prodPrice = new ResourceProperty( "Price", ResourcePropertyKind.Primitive, ResourceType.GetPrimitiveResourceType(typeof(Decimal)) ); productType.AddProperty(prodKey); productType.AddProperty(prodName); productType.AddProperty(prodPrice); metadata.AddResourceType(productType); metadata.AddResourceSet(new ResourceSet("Products", productType)); return metadata; } public override IDataServiceQueryProvider GetQueryProvider(IDataServiceMetadataProvider metadata) { return new DSPQueryProvider<ProductsContext>(metadata); } public override IDataServiceUpdateProvider GetUpdateProvider(IDataServiceMetadataProvider metadata, IDataServiceQueryProvider query) { return new DSPUpdateProvider<ProductsContext>(metadata, query); } } }ProductsContext.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Services.Providers; namespace WcfCustomerDataProviderDataService { public class ProductsContext : DSPContext { private List<Product> _products = new List<Product>(); public override IQueryable GetQueryable(ResourceSet set) { if (set.Name == "Products") return Products.AsQueryable(); throw new NotSupportedException(string.Format("{0} not found", set.Name)); } public List<Product> Products { get { return _products; } } public override object CreateResource(ResourceType resourceType) { if (resourceType.InstanceType == typeof(Product)) { return new Product(); } throw new NotSupportedException(string.Format("{0} not found", resourceType.FullName)); } public override void AddResource(ResourceType resourceType, object resource) { if (resourceType.InstanceType == typeof(Product)) { Product p = resource as Product; if (p != null) { Products.Add(p); return; } } throw new NotSupportedException("Type not found"); } public override void DeleteResource(object resource) { if (resource.GetType() == typeof(Product)) { Products.Remove(resource as Product); return; } throw new NotSupportedException("Type not found"); } public override void SaveChanges() { var prodKey = Products.Max(p => p.ProdKey); foreach (var prod in Products.Where(p => p.ProdKey == 0)) prod.ProdKey = ++prodKey; } } }DSPMetadataProvider.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Services.Providers; using System.Reflection; using System.Data.Services; namespace WcfCustomerDataProviderDataService { public class DSPMetadataProvider : IDataServiceMetadataProvider { private Dictionary<string, ResourceType> _resourceTypes = new Dictionary<string, ResourceType>(); private Dictionary<string, ResourceSet> _resourceSets = new Dictionary<string, ResourceSet>(); //private Dictionary<string, ServiceOperation> serviceOperations // = new Dictionary<string, ServiceOperation>(); public DSPMetadataProvider() { } public void AddResourceType(ResourceType type) { type.SetReadOnly(); _resourceTypes.Add(type.FullName, type); } public void AddResourceSet(ResourceSet set) { set.SetReadOnly(); _resourceSets.Add(set.Name, set); } public string ContainerName { get { return "Container"; } } public string ContainerNamespace { get { return "Namespace"; } } public IEnumerable<ResourceType> GetDerivedTypes(ResourceType resourceType) { // We don't support type inheritance yet yield break; } public ResourceAssociationSet GetResourceAssociationSet( ResourceSet resourceSet, ResourceType resourceType, ResourceProperty resourceProperty) { throw new NotImplementedException("No relationships."); } public bool HasDerivedTypes(ResourceType resourceType) { // We don’t support inheritance yet return false; } public IEnumerable<ResourceSet> ResourceSets { get { return this._resourceSets.Values; } } public IEnumerable<ServiceOperation> ServiceOperations { // No service operations yet get { yield break; } } public bool TryResolveResourceSet(string name, out ResourceSet resourceSet) { return _resourceSets.TryGetValue(name, out resourceSet); } public bool TryResolveResourceType(string name, out ResourceType resourceType) { return _resourceTypes.TryGetValue(name, out resourceType); } public bool TryResolveServiceOperation(string name, out ServiceOperation serviceOperation) { // No service operations are supported yet serviceOperation = null; return false; } public IEnumerable<ResourceType> Types { get { return this._resourceTypes.Values; } } } }DSPQueryProvider.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Services.Providers; using System.Reflection; using System.Globalization; namespace WcfCustomerDataProviderDataService { public class DSPQueryProvider<T> : IDataServiceQueryProvider where T : DSPContext { private T _currentDataSource; private IDataServiceMetadataProvider _metadata; public DSPQueryProvider(IDataServiceMetadataProvider metadata) { // TODO: Complete member initialization _metadata = metadata; } public object CurrentDataSource { get { return _currentDataSource; } set { _currentDataSource = value as T; } } public object GetOpenPropertyValue(object target, string propertyName) { throw new NotImplementedException(); } public IEnumerable<KeyValuePair<string, object>> GetOpenPropertyValues(object target) { throw new NotImplementedException(); } public object GetPropertyValue(object target, ResourceProperty resourceProperty) { throw new NotImplementedException(); } public IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet) { return _currentDataSource.GetQueryable(resourceSet); } public ResourceType GetResourceType(object target) { Type type = target.GetType(); return _metadata.Types.Single(t => t.InstanceType == type); } public object InvokeServiceOperation(ServiceOperation serviceOperation, object[] parameters) { return null; } public bool IsNullPropagationRequired { get { throw new NotImplementedException(); } } } }DSPUpdateProvider.cs
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Services.Providers; namespace WcfCustomerDataProviderDataService { public class DSPUpdateProvider<T> : IDataServiceUpdateProvider where T : DSPContext { private IDataServiceMetadataProvider _metadata; private IDataServiceQueryProvider _query; private List<Action> _actions; public DSPUpdateProvider(IDataServiceMetadataProvider metadata, IDataServiceQueryProvider query) { _metadata = metadata; _query = query; _actions = new List<Action>(); } public T GetContext() { return (_query.CurrentDataSource as T); } public void SetConcurrencyValues(object resourceCookie, bool? checkForEquality, IEnumerable<KeyValuePair<string, object>> concurrencyValues) { throw new NotImplementedException(); } public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded) { throw new NotImplementedException(); } public void ClearChanges() { _actions.Clear(); } public object CreateResource(string containerName, string fullTypeName) { ResourceType type = null; if (_metadata.TryResolveResourceType(fullTypeName, out type)) { var context = GetContext(); var resource = context.CreateResource(type); _actions.Add(() => context.AddResource(type, resource)); return resource; } throw new Exception(string.Format("Type {0} not found", fullTypeName)); } public void DeleteResource(object targetResource) { _actions.Add(() => GetContext().DeleteResource(targetResource)); } public object GetResource(IQueryable query, string fullTypeName) { var enumerator = query.GetEnumerator(); if (!enumerator.MoveNext()) throw new Exception("Resource not found"); var resource = enumerator.Current; if (enumerator.MoveNext()) throw new Exception("Resource not uniquely identified"); if (fullTypeName != null) { ResourceType type = null; if (!_metadata.TryResolveResourceType(fullTypeName, out type)) throw new Exception("ResourceType not found"); if (!type.InstanceType.IsAssignableFrom(resource.GetType())) throw new Exception("Unexpected resource type"); } return resource; } public object GetValue(object targetResource, string propertyName) { var value = targetResource .GetType() .GetProperties() .Single(p => p.Name == propertyName) .GetGetMethod() .Invoke(targetResource, new object[] { }); return value; } public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved) { throw new NotImplementedException(); } public object ResetResource(object resource) { _actions.Add(() => ReallyResetResource(resource)); return resource; } public void ReallyResetResource(object resource) { // Create an new 'blank' instance of the resource var clrType = resource.GetType(); ResourceType resourceType = _metadata.Types.Single(t => t.InstanceType == clrType); var resetTemplate = GetContext().CreateResource(resourceType); // Copy non-key property values from the 'blank' resource foreach (var prop in resourceType.Properties.Where(p => (p.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key)) { // Obviously for perf reasons you could might want to // cache the result of these reflection calls. var clrProp = clrType.GetProperties().Single(p => p.Name == prop.Name); var defaultPropValue = clrProp.GetGetMethod().Invoke(resetTemplate, new object[] { }); clrProp.GetSetMethod().Invoke(resource, new object[] { defaultPropValue }); } } public object ResolveResource(object resource) { return resource; } public void SaveChanges() { _actions.ForEach(a => a()); GetContext().SaveChanges(); } public void SetReference(object targetResource, string propertyName, object propertyValue) { throw new NotImplementedException(); } public void SetValue(object targetResource, string propertyName, object propertyValue) { _actions.Add( () => ReallySetValue( targetResource, propertyName, propertyValue) ); } public void ReallySetValue(object targetResource, string propertyName, object propertyValue) { targetResource .GetType() .GetProperties() .Single(p => p.Name == propertyName) .GetSetMethod() .Invoke(targetResource, new[] { propertyValue }); } } }
(2011/7/25 Updated)
遗留问题:通过自定义Provider并不能实现类似使用反射数据提供程序实现的DataService的完整功能,至少比如像 http://localhost:51396/ProductDataService.svc/Products() ?$filter=Name eq 'fx'这样的查询功能都不支持。根据 DataService<T> 源码分析还需要实现比如:TryResolveServiceOperation,InvokeServiceOperation 以及 IDataServicePagingProvider 接口 等等。DataService<T> 水深啊,需要进一步研究。