WCF Data Services
WCF Data Services to remove values from payload with EF provider
One of the more frequent questions about WCF Data Services is how to remove a property from the payload. To actually remove the property from the payload would require implementing IDataServiceMetadataProvider and there is a good blog about doing that here. I started down the road about 9 months ago before ditching it – too much copy and paste from the WCF Data Service core into my implementation and null projections plus a list that kept going on. I’m not going to show how to do that instead I’m going to leave the properties in the payload but not map them. This is probably better since the metadata will not change, but some people think differently.
I want to point out that I tried this about 9 month ago and ditched that as well but this great sample code from Derrick VanArnam showed me the light. Can’t say enough about that code without it I wouldn’t have been able to do this. Some of this code is a direct copy of his sample and some is close but with my tweaks. I’m not trying to pass his great work as mine.
First here is my setup. DataModel context that has a list of customers and their invoices.
public partial class DataModel : DbContext { public DataModel() : base("name=DataModel") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { throw new UnintentionalCodeFirstException(); } public IDbSet<Customers> { get; set; } public IDbSet<Invoices> { get; set; } } public partial class Customer { public Customer() { this.Invoices = new HashSet(); } public string CustomerId { get; set; } public string Company { get; set; } public string Contact { get; set; } public string Title { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } public string Phone { get; set; } public Nullable<CreditLimit> { get; set; } public Nullable<Balance> { get; set; } public virtual ICollection<Invoices> { get; set; } } public partial class Invoice { public string InvoiceId { get; set; } public Nullable Invoiced { get; set; } public string CustomerId { get; set; } public string SalesPerson { get; set; } public Nullable<Amount> { get; set; } public Nullable<Discount> { get; set; } public Nullable<Paid> { get; set; } public virtual Customer Customers { getset; } }
Here is my DataService
public class WcfDataService<DataModel> : DataService, IServiceProvider { // This method is called only once to initialize service-wide policies. public static void InitializeService(DataServiceConfiguration config) { config.SetEntitySetAccessRule("*", EntitySetRights.AllRead); config.SetServiceOperationAccessRule("*", ServiceOperationRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3; config.UseVerboseErrors = true; } private readonly DataModel _dataSource; private readonly WCFEFProvider _provider; public WcfDataService() { _dataSource = new DataModel(); _provider = new WCFEFProvider(this, _dataSource); } public object GetService(Type serviceType) { if (serviceType.IsInstanceOfType(_provider)) { return _provider; } return null; } protected override DataModel CreateDataSource() { return _dataSource; } }
The WCFEFProvider is my implementation of the AdventureWorksEFProvider in MS samplecode. Also I called my interface IQueryWrapper instead of IObjectQueryWrapper and I just exposed the IQueryable as that’s all that was needed.
public interface IQueryWrapper { IQueryable Query { get; } } public class WCFEFProvider<T> : EntityFrameworkDataServiceProvider { /// <param name="service">Provider service</param> /// <param name="container">Entity container</param> public WCFEFProvider(object service, T container) : base(new DataServiceProviderArgs(service, container, null, false)) { } /// <summary> /// Override the query root /// </summary> /// <param name="resourceSet"></param> /// <returns></returns> public override IQueryable GetQueryRootForResourceSet(ResourceSet resourceSet) { // Parameterize the expression tree return WrappedQueryProvider.CreateQuery(base.GetQueryRootForResourceSet(resourceSet)); } /// <summary> /// Override the get resource to get underlying ObjectQuery for base.GetResource /// </summary> /// <param name="query"></param> /// <param name="fullTypeName"></param> /// <returns></returns> public override object GetResource(IQueryable query, string fullTypeName) { var queryWrapper = query as IQueryWrapper; if (queryWrapper != null) { query = queryWrapper.Query; } return base.GetResource(query, fullTypeName); } // We will need to create subclasses for EF to not complain but we need to tell WCF what the ResourceType the subclass is public override ResourceType GetResourceType(object target) { var type = target.GetType(); var mainclass = SecurityProjection.SubClasses.Where(kv => kv.Value == type).Select(kv => kv.Key).FirstOrDefault(); if (mainclass != null) { return base.GetResourceType(Activator.CreateInstance(mainclass)); } return base.GetResourceType(target); } }
As a note the GetResource override was what I never figured out 9 months ago and caused me to bail on it. I also rename EFParameterizedQueryProvider to WrappedQueryProvider, EFParameterizedQuery to WrappedIQueryable and made some small tweaks to both.
public class WrappedQueryProvider : IQueryProvider { /// <summary> /// Cache the CreateEFParameterizedQuery generic methodinfo /// </summary> readonly static MethodInfo CreateQueryMethod = typeof(WrappedQueryProvider).GetMethod("CreateWrappedIQueryable", BindingFlags.Instance | BindingFlags.NonPublic); /// <summary> /// The underlying Entity Framework query provider /// </summary> private readonly IQueryProvider _underlyingQueryProvider; public WrappedQueryProvider(IQueryProvider underlyingQueryProvider) { _underlyingQueryProvider = underlyingQueryProvider; } public static IQueryable CreateQuery(IQueryable underlyingQuery) { //Wrap it so we can intercept it var provider = new WrappedQueryProvider(underlyingQuery.Provider); Type elementType = underlyingQuery.Expression.Type.GetQueryElementType(); return (IQueryable)CreateQueryMethod.MakeGenericMethod(elementType).Invoke(provider, new object[] { underlyingQuery.Expression, underlyingQuery }); } public IQueryable CreateQuery(Expression expression) { Type elementType = expression.Type.GetQueryElementType(); return (IQueryable)CreateQueryMethod.MakeGenericMethod(elementType).Invoke(this, new object[] { expression, null }); } public IQueryable<TElement> CreateQuery<TElement>(Expression expression) { return CreateWrappedIQueryable<TElement>(expression, null); } private WrappedIQueryable<TElement> CreateWrappedIQueryable<TElement>(Expression expression, IQueryable queryable) { var objectQuery = queryable as ObjectQuery<TElement> ?? (ObjectQuery<TElement>)_underlyingQueryProvider.CreateQuery<TElement>(expression); return new WrappedIQueryable<TElement>(objectQuery, this); } public object Execute(Expression expression) { //Here we will modify the expressions var securedExpression = new SecurityProjection().CheckSecurity(expression); var parameterdExpression = new ParameterizeExpressionVisitor().Parameterize(securedExpression); if (typeof(IQueryable).IsAssignableFrom(expression.Type)) { return _underlyingQueryProvider.CreateQuery(parameterdExpression); } return _underlyingQueryProvider.Execute(parameterdExpression); } public TResult Execute<TResult>(Expression expression) { return (TResult)Execute(expression); } } public class WrappedIQueryable<T> : IOrderedQueryable<T>, IQueryWrapper { /// <summary> /// The original Entity Framework ObjectQuery /// </summary> private readonly IQueryable _queryable; /// <summary> /// The Entity Framework query provider /// </summary> private readonly IQueryProvider _queryProvider; public WrappedIQueryable(IQueryable<T> objectQuery, IQueryProvider queryProvider) { _queryProvider = queryProvider; _queryable = objectQuery; } public IEnumerator<T> GetEnumerator() { return _queryProvider.Execute<IEnumerable<T>>(Expression).GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } public Type ElementType { get { return typeof (T); } } public Expression Expression { get { return _queryable.Expression; } } public IQueryProvider Provider { get { return _queryProvider; } } IQueryable IQueryWrapper.Query { get { return _queryable; } } }
You will also need the TypeExtension class from MS sample code (I took it as is). And since I have all that I took the EFParameterizedExpressionVisitor and called into that to get the benefit of EF parameterizing my OData calls. All the code above code is setup so we can now intercept the Expression Trees. Now we get into the meat of the issue.
To project I want to do something l like
context.Customers.Select(c=> new Customer() { CustomerId = c.CustomerId, Address = c.Address, City = c.City, Company = c.Company, Contact = c.Contact, Phone = c.Phone, State = c.State });
But Entity Framework will not allow to project to an entity. The solution I’ve found is it will allow you to project to a subclass
public class CustomersSub : Customers { } // can project context.Customers.Select(c=> new CustomersSub () { CustomerId = c.CustomerId, Address = c.Address, City = c.City, Company = c.Company, Contact = c.Contact, Phone = c.Phone, State = c.State });
I also don’t want to create a bunch of empty subclasses by hand or even have T4 template do it. I’m going to use the TypeBuilder to do it at runtime then cache the result.
public class SecurityProjection : ExpressionVisitor { public static readonly ConcurrentDictionary<Type, Type> SubClasses = new ConcurrentDictionary<Type, Type>(); private static readonly ModuleBuilder _moduleBuilder; // These will need to be changed based on your requirements of when properties are removed or not protected static readonly IDictionary<Type, IList<string>> removeProperties = new Dictionary<Type, IList<string>>(); static SecurityProjection() { // Will need these to create subclasses on the fly var assemblyName = new AssemblyName("SecurityProjectionAssembly"); var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); _moduleBuilder = assemblyBuilder.DefineDynamicModule("SecurityProjectionModule"); // always remove the credit limit and balance & sales person removeProperties.Add(typeof(Customer), new List<string>()); removeProperties[typeof(Customer)].Add("CreditLimit"); removeProperties[typeof(Customer)].Add("Balance"); removeProperties.Add(typeof(Invoice), new List<string>()); removeProperties[typeof(Invoice)].Add("SalesPerson"); } public Expression CheckSecurity(Expression expression) { expression = Visit(expression); return expression; } protected override MemberBinding VisitMemberBinding(MemberBinding node) { var memberAssignment = node as MemberAssignment; if (memberAssignment != null) { var memType = memberAssignment.Expression.Type; if (memType != null) { if (removeProperties.ContainsKey(memType)) { // make a subclass for the projection as EF will not allow you project to entity var to = SubClasses.GetOrAdd(memType, CreateSubClass); var projection = Project(memType, to, memberAssignment.Expression); var binder = Expression.Bind(memberAssignment.Member, projection); return binder; } memType = memType.GetQueryElementType(); if (memType !- null && removeProperties.ContainsKey(memType)) { // make a subclass for the projection as EF will not allow you project to entity var to = SubClasses.GetOrAdd(memType, CreateSubClass); // parameter of the expression var source = Expression.Parameter(memType, "source"); var projection = Project(memType, to, source); var func = Expression.Lambda(typeof(Func<,>).MakeGenericType(memType, to), projection, source); var result = Expression.Call(typeof(Enumerable), "Select", new[] { memType, to }, memberAssignment.Expression, func); var binder = Expression.Bind(memberAssignment.Member, result); return binder; } } } return base.VisitMemberBinding(node); } protected override Expression VisitMethodCall(MethodCallExpression node) { var methodType = CheckIQueryable(node.Method.ReturnType); if (methodType != null && removeProperties.ContainsKey(methodType)) { // make a subclass for the projection as EF will not allow you project to entity var to = SubClasses.GetOrAdd(methodType, CreateSubClass); // parameter of the expression var source = Expression.Parameter(methodType, "source"); var projection = Project(methodType, to, source); var func = Expression.Lambda(typeof (Func<,>).MakeGenericType(methodType, to), projection, source); var result = Expression.Call(typeof (Queryable), "Select", new[] {methodType, to}, node, func); return result; } return base.VisitMethodCall(node); } private Type CheckIQueryable(Type type) { return type.GetInterfaces() .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof (IQueryable<>)) .Select(i => i.GetGenericArguments().First()) .FirstOrDefault(); } private Type CreateSubClass(Type type) { var subclass = _moduleBuilder.DefineType(type.Name + "SubClass", type.Attributes, type); var newType = subclass.CreateType(); return newType; } private Expression Project(Type from, Type to, Expression pSource) { var ignoreProps = removeProperties[from]; var bindings = GetProperties(from, false) .Join(GetProperties(to, true), source => new {source.Name, source.PropertyType}, dest => new {dest.Name, dest.PropertyType}, (source, dest) => new {source, dest}) .Where(a => !ignoreProps.Contains(a.source.Name)) .Select(prop => Expression.Bind(prop.dest, Expression.Property(pSource, prop.source))) .ToList(); return Expression.MemberInit(Expression.New(to), bindings); } private IEnumerable<PropertyInfo> GetProperties(Type type, bool write) { var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance).AsEnumerable(); if (write) { properties = properties.Where(p => p.CanWrite); } else { properties = properties.Where(p => p.CanRead); } return properties; } }
Something’s to explain. I created a static IDictionary
It would probably be worth caching the property of each type instead of using reflection each time.
Now when hitting the webservice Invoices will still return the sales person but it will be null and the same for balance and credit limit for the customer. I’ve tested this with projection, expand and it all seems to be working but I can’t say it’s 100% bullet proof. 🙂
Move from WCF Data Services to Web API – Part 4
In Part 2 in the method QueryEntitySet I had a subtle bug.
var process = (Func<IEdmEntityType, IQueryable>) _typeToGeneric.GetOrAdd(new KeyValuePair<IEdmEntityType, Type>(edmType, entityType), pair => { var method = ((MethodInfo) MethodBase.GetMethodFromHandle( _processRequest, GetType().TypeHandle)) .MakeGenericMethod(pair.Value); return Delegate.CreateDelegate( typeof(Func<IEdmEntityType, IQueryable>), this, method); });
In the Delegate.CreateDelegate I’m passing in “this”. That will create a closures around the first controller and will call into that controller object every time. This is not what we want because that controller would be disposed and plus we now have an object that’s just hanging around. I’m going to fix that with ExpressionTrees by passing in the current controller object.
private Func, IEdmEntityType, IQueryable> SwitchToGenericMethod( IEdmEntityType edmEntityType, Type clrType) { return (Func , IEdmEntityType, IQueryable>) _typeToGeneric.GetOrAdd(new KeyValuePair (edmEntityType, clrType), pair => { var method = ((MethodInfo) MethodBase.GetMethodFromHandle( _processRequest, GetType().TypeHandle)) .MakeGenericMethod(pair.Value); var controller = Expression.Parameter( typeof (ODataServicesController ), "oDServiceController"); var edmentityType = Expression.Parameter(typeof (IEdmEntityType), "edmEntityType"); var expr = Expression.Call(controller, method, edmentityType); return Expression.Lambda , IEdmEntityType, IQueryable>>(expr, controller, edmentityType).Compile(); }); }
Now in QueryEntitySet we change the code to be the following
// Switch from system Type to generic type var process = SwitchToGenericMethod(edmType, entityType); var query = process(this, edmType);
Also we need to map all the entities to the CLR type otherwise $expand and $link won’t work.
private void MapIEdmEntitySetsToCLR() { var metaData = GetMetadata(); var entitySets = Container.EntitySets(); foreach (var entitySet in entitySets) { var entityType = GetIEdmTypeToCLRType(entitySet.ElementType); metaData.SetAnnotationValue(entitySet.ElementType, new ClrTypeAnnotation(entityType)); } } private IEdmEntityContainer Container { get { var metadata = GetMetadata(); return metadata.EntityContainers().First(); } }
I removed in the ProcessRequest method this code “model.SetAnnotationValue(edmEntityType, new ClrTypeAnnotation(typeof (TEntity)));” and replaced it with a call into MapIEdmEntitySetsToCLR().
From my testing how I’m determining if we are returning an EntitySet or Entity isn’t correct in the ProcessRequest method. If I ask for something like \api\odata\Customer(100)\SalesOrders the request will be set as a EntitySet and just return all the SalesOrders. Not filtered down to the one customer. So I will need to create, or find if Web API, has something to help me out. I’m doubtful about Web API because they are using controllers and actions to handle this and we want to just generate the ExpressionTrees automatically. I will be looking at the source code for WCF Data Services to see what I can “borrow” from them. I don’t think they will be too upset since they are getting ready to open source it. Because of this it might be a couple of days before I get to Part 5. But I wanted to update everyone along the way of things I found I needed to change. That’s part of the fun of writing the blog while I’m still working out the kinks and not at the end where everything is working 🙂
Move from WCF Data Services to Web API – Part 3
See Part 1 and Part 2. I know in Part 2 I said I was going to look at ExpressionTrees and limit down to a single entity but I wanted to fix the Content Negotiation and add the service document. Both turned out to be simple, once I discovered the Media Type you pass into the ODataMediaTypeFormatter doesn’t do anything.
First I created another method to return back the correct MediaTypeFormatter
private MediaTypeFormatter GetFormatter(Type objType) { var conneg = Configuration.Services.GetContentNegotiator(); var result = conneg.Negotiate(objType, Request, Configuration.Formatters); return result.Formatter; }
Then changed GenerateMetadataResponse to this
protected HttpResponseMessage GenerateMetadataResponse() { var odataMediaTypeFormatter = GetFormatter(typeof (IEdmModel)); var formatter = odataMediaTypeFormatter.GetPerRequestFormatterInstance(typeof (IEdmModel), Request, null); var response = Request.CreateResponse(); response.Content = new ObjectContent(typeof(IEdmModel), GetMetadata(), formatter); return response; }
Now it’s not hard coded to XML but still returns XML. What’s still missing if you just say you want JSON it doesn’t error like WCF Data Services did it will just return XML. I think it should error (add to the list)
This is the bottom part of QueryEntitySet
var response = Request.CreateResponse(); var resultType = typeof(IEnumerable<>).MakeGenericType(query.ElementType); var odataMediaTypeFormatter = GetFormatter(resultType); var formatter = odataMediaTypeFormatter.GetPerRequestFormatterInstance(typeof(IEdmCollectionType), Request, null); response.Content = new ObjectContent(resultType, query, formatter); return response;
Pretty straight forward. I also created the Service Document for the OData. That turned out to be simple.
private HttpResponseMessage GenerateServiceDocument() { var response = Request.CreateResponse(); var odataMediaTypeFormatter = GetFormatter(typeof(ODataWorkspace)); var formatter = odataMediaTypeFormatter.GetPerRequestFormatterInstance(typeof(ODataWorkspace), Request, null); response.Content = new ObjectContent(typeof(ODataWorkspace), GetServiceDocument(), formatter); return response; }
This is ProcessRequest now
protected virtual HttpResponseMessage ProcessRequest() { var pathHandler = Request.GetODataPathHandler(); var metadata = GetMetadata(); var path = pathHandler.Parse(metadata, GetODataPath()); Request.SetODataPath(path); Request.SetODataRouteName("OData"); if (path.Segments.Count == 0) { // Requested service document return GenerateServiceDocument(); } if (path.Segments.Any(s => s.SegmentKind == ODataSegmentKinds.Metadata)) { // Requested metadata return GenerateMetadataResponse(); } var collectionType = path.EdmType as IEdmCollectionType; if (collectionType != null) { // Requested entity collection return QueryEntitySet(collectionType); } return new HttpResponseMessage(HttpStatusCode.NotImplemented); }
Now in Part 4 I’ll get to working on the ExpressionTrees.
Move from WCF Data Services to Web API – Part 2
From Part 1 of getting Metadata up and running we are now going to work on getting querying an entity set working. But first a quick update. From the last time I left getting the metadata in the processing request and I did move that out into it’s own method.
protected HttpResponseMessage GenerateMetadataResponse() { var odataMediaTypeFormatter = new ODataMediaTypeFormatter(new DefaultODataDeserializerProvider(), new DefaultODataSerializerProvider(), new[] { ODataPayloadKind.MetadataDocument }); //ToDo will need to make sure they can accept application/xml var formatter = odataMediaTypeFormatter.GetPerRequestFormatterInstance(typeof(IEdmModel), Request, new MediaTypeHeaderValue( "application/xml")); var response = Request.CreateResponse(); response.Content = new ObjectContent(typeof(IEdmModel), GetMetadata(), formatter); }
Now the first thing we need to do is see if we are dealing with an entity set.
var collectionType = path.EdmType as IEdmCollectionType; if (collectionType != null) { return QueryEntitySet(collectionType); }
If collectionType isn’t null then we will need to check for and retrieve the EntityType.
private IEdmEntityType GetEdmEntityType(IEdmType edmType) { var edmEntityType = edmType as IEdmEntityType; if (edmEntityType == null) { var collectionType = edmType as IEdmCollectionType; if (collectionType != null) { edmEntityType = collectionType.ElementType.AsEntity().EntityDefinition(); } } return edmEntityType; }
If this method returns null then we are not dealing with an entity or entity set. From there we will need to switch from IEdmEntityType to the CLR type. For that I’m going to create another interface and create a default implementation for DbContext.
namespace ODataServices.Interfaces { public interface IEdmEntityToClrConverter { Type AsClrType<TSource>(TSource source, IEdmEntityType edmEntityType); } }
and here is the default implementation. I’m not sold on how I’m looking up the CLR type from metadata of the DbContext and if someone else has a better idea let me know. Or even better answer this stackoverflow question.
namespace ODataServices { public class DbContextEdmEntityToClrConverter : IEdmEntityToClrConverter { private readonly static ConcurrentDictionary<IEdmEntityType, Type> _cachedConversions = new ConcurrentDictionary<IEdmEntityType, Type>(); public Type AsClrType<TSource>(TSource source, IEdmEntityType edmEntityType) { var dbContext = source as DbContext; if (dbContext == null) { return null; } // Can't use GetOrAdd want to trap for null Type result; if (!_cachedConversions.TryGetValue(edmEntityType, out result)) { result = ConvertIEdmEntityTypeToClr(edmEntityType, dbContext); if (result != null) { _cachedConversions.TryAdd(edmEntityType, result); } } return result; } private Type ConvertIEdmEntityTypeToClr(IEdmEntityType edmEntityType, DbContext context) { var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace; var oSpace = metadata.GetItemCollection(DataSpace.OSpace); var typeName = oSpace.GetItems<EntityType>().Select(e => e.FullName).FirstOrDefault(name => { var fullname = name + ":" + edmEntityType.FullName(); MappingBase map; return metadata.TryGetItem(fullname, DataSpace.OCSpace, out map); }); if (typeName != null) { return Type.GetType(typeName, null, GetTypeFromAssembly, false, false); } return null; } private Type GetTypeFromAssembly(Assembly assembly, string nameOfType, bool ignoreCase) { if (assembly == null) { var resolver = new DefaultAssembliesResolver(); return resolver.GetAssemblies() .Select(a => a.GetType(nameOfType, false, ignoreCase)) .FirstOrDefault(t => t != null); } return assembly.GetType(nameOfType, false, ignoreCase); } } }
Noticed I had to use an Assembly Resolver otherwise Type.GetType() would fail. Now that we have a type I’m going to use a bit of reflection and switch us from a System Type to a generic type. I’m going to store a method handle to the method we want to switch to and grab that in the static constructor since it shouldn’t change. Also I’m going to add a couple more interfaces we haven’t talk about yet but will quickly discuss.
static ODataServicesController() { _processRequest = typeof (ODataServicesController<TSource>).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) .First( m => m.Name == "ProcessRequest" && m.IsGenericMethodDefinition) .MethodHandle; // Load up default implementations _serviceLocatorDefaults[typeof (IEdmModelFactory)] = new Lazy<object>(() => new DbContextEdmModel()); _serviceLocatorDefaults[typeof (IEdmEntityToClrConverter)] = new Lazy<object>(() => new DbContextEdmEntityToClrConverter()); _serviceLocatorDefaults[typeof (IQueryRootProvider)] = new Lazy<object>(() => new DbContextQueryRoot()); } private static readonly RuntimeMethodHandle _processRequest;
IQueryRootProvider will be used to return the starting point of the IQueryable. IQueryInterceptor is used to add a where clause. I was on the fence on that interface as it could be implemented in IQueryRootProvider but added it for flexibility and since WCF Data Services has something similar.
namespace ODataServices.Interfaces { public interface IQueryRootProvider { IQueryable<TEntity> QueryRoot<TSource, TEntity>(TSource source) where TEntity : class; } } namespace ODataServices.Interfaces { public interface IQueryInterceptor { Expression<Func<TEntity, bool>> Intercept<TEntity>(); } }
Here is the default implementation of IQueryRootProvider for DbContext
namespace ODataServices { public class DbContextQueryRoot : IQueryRootProvider { public IQueryable<TEntity> QueryRoot<TSource, TEntity>(TSource source) where TEntity : class { var dbContext = source as DbContext; if (dbContext == null) { return null; } return QueryRoot<TEntity>(dbContext); } private IQueryable<TEntity> QueryRoot<TEntity>(DbContext dbContext) where TEntity : class { return dbContext.Set<TEntity>().AsNoTracking(); } }
Pretty simple stuff. For IQueryInterceptor I created a default implementation but it doesn’t rely on DbContext. First I created an attribute called QueryInterceptorAttribute, same name as WCF Data Services.
namespace ODataServices.Attributes { [AttributeUsageAttribute(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public class QueryInterceptorAttribute : Attribute { } } namespace ODataServices { public abstract class QueryInterceptor : IQueryInterceptor { private readonly MethodInfo[] _methodInfos; protected QueryInterceptor() { _methodInfos = GetType().GetMethods() .Where( m => m.GetCustomAttributesData() .Any(a => a.AttributeType == typeof(QueryInterceptorAttribute)) && !m.GetParameters().Any() && m.IsGenericMethod == false).ToArray(); } public Expression<Func<TEntity, bool>> Intercept<TEntity>() { var inteceptor = _methodInfos.FirstOrDefault(m => m.ReturnType == typeof(Expression<Func<TEntity, bool>>)); if (inteceptor != null) { return inteceptor.Invoke(this, new object[0]) as Expression<Func<TEntity, bool>>; } return null; } } }
With the abstract class of QueryInterceptor you can create a class that inherits from it and mark methods with the QueryInterceptor attribute. If the return type of the method is type we are looking for it will auto add the return value to the where clause. If you don’t like this implementation you can just create a different class that implements IQueryInterceptor, that’s the great part. Also word of warning I haven’t tested that code yet 🙂
Now to put it all together
protected HttpResponseMessage QueryEntitySet(IEdmCollectionType edmCollectionType) { var edmType = GetEdmEntityType(edmCollectionType); if (edmType != null) { var entityType = GetIEdmTypeToCLRType(edmType); // Switch from system Type to generic type var process = (Func<IEdmEntityType, IQueryable>) _typeToGeneric.GetOrAdd(new KeyValuePair<IEdmEntityType, Type>(edmType, entityType), pair => { var method = ((MethodInfo) MethodBase.GetMethodFromHandle( _processRequest, GetType().TypeHandle)) .MakeGenericMethod(pair.Value); return Delegate.CreateDelegate( typeof(Func<IEdmEntityType, IQueryable>), this, method); }); var query = process(edmType); var response = Request.CreateResponse(); var odataMediaTypeFormatter = new ODataMediaTypeFormatter(new DefaultODataDeserializerProvider(), new DefaultODataSerializerProvider(), new[] { ODataPayloadKind.Feed }); //ToDo will need to use a content negotiator - hard code to XML for now var formatter = odataMediaTypeFormatter.GetPerRequestFormatterInstance(typeof(IEdmCollectionType), Request, new MediaTypeHeaderValue( "application/xml")); response.Content = new ObjectContent(typeof(IEnumerable<>).MakeGenericType(query.ElementType), query, formatter); return response; } throw new Exception("Entity Set not found!"); } private IQueryable ProcessRequest<TEntity>(IEdmEntityType edmEntityType) where TEntity : class { var queryable = GetQueryRoot<TEntity>(); // Check if there are any interceptor var interception = GetInterceptor<TEntity>(); if (interception != null) { queryable = queryable.Where(interception); } var model = GetMetadata(); // need to tell Web API about the mapping between IEdmEntityType and the CLR Type model.SetAnnotationValue(edmEntityType, new ClrTypeAnnotation(typeof (TEntity))); var queryContext = new ODataQueryContext(model, typeof (TEntity)); var queryOptions = new ODataQueryOptions(queryContext, Request); //ToDo this does the default Web API filtering but would like to get more control over it return queryOptions.ApplyTo(queryable); } private IQueryable<TEntity> GetQueryRoot<TEntity>() where TEntity : class { var queryRoot = ServiceLocator<IQueryRootProvider>(); if (queryRoot != null) { return queryRoot.QueryRoot<TSource, TEntity>(CurrentDataSource); } return null; } private Expression<Func<TEntity, bool>> GetInterceptor<TEntity>() { var interception = ServiceLocator<IQueryInterceptor>(); if (interception != null) { return interception.Intercept<TEntity>(); } return null; }
This is what the ProcessRequest method looks like now and I added to dispose of the Data Source.
protected virtual HttpResponseMessage ProcessRequest() { var pathHandler = Request.GetODataPathHandler(); var metadata = GetMetadata(); var path = pathHandler.Parse(metadata, GetODataPath()); Request.SetODataPath(path); Request.SetODataRouteName("OData"); if (path.Segments.Any(s => s.SegmentKind == ODataSegmentKinds.Metadata)) { return GenerateMetadataResponse(); } var collectionType = path.EdmType as IEdmCollectionType; if (collectionType != null) { return QueryEntitySet(collectionType); } return new HttpResponseMessage(HttpStatusCode.NotImplemented); } protected override void Dispose(bool disposing) { if (disposing) { if (_currentDataSource.IsValueCreated) { var disposer = CurrentDataSource as IDisposable; if (disposer != null) { disposer.Dispose(); } } } base.Dispose(disposing); }
Now we should be able to hit /api/odata/Customers?$orderby=City&$select=City or /api/odata/Customers. Assuming your DbContext has an Customer Entity Set and property called city 🙂 Next I will be trying to get a single entity but that’s for a Part 3 and we’ll need to dive into ExpressionTrees.
Move from WCF Data Services to Web API
If you haven’t heard WCF Data Services is going to be put into maintenance mode. 🙁 While I have always had a love/hate relationship with it, it provided a convenient way to expose data as OData. The new way is to use Web API OData support but that doesn’t quite work for our needs. For one we would have to write a ton of boiler plate code and keep all that code in sync with our data model. From the comment section it was discussed at writing a handler to fill this gap. This is my attempt at doing that. By no means is this done and it’s still a work in progress but I figured I would take any reader along for the journey. Maybe I can get some better ways to solve a problem or better alternatives from readers since this is so new.
Some of my goals
- Not having the handler tied to Entity Framework.
- Be extensible, something the WCF Data Services was lacking.
- Use Web API OData support as much as possible
- For phase 1 just looking at getting query support.
- Not looking at batch support for phase 1.
Enough of that lets get to code. First I created Web API solution called it WebAPIOData. The standard project of WebAPIOData will be where I’m testing/exploring the options from my handler. To this solution I added another projected call ODataServices and made that an empty web project. Then removed the global.asax and web.configs.
First off I couldn’t use the Web API OData routing because I don’t have my IEdmModel at startup time and can’t create my DbContext at startup time. Our application uses Entity Framework code first with MEF to dynamically build the model at runtime, along with user security. That ruled out a static model at startup time, WCF Data Services allowed us to create the data source at runtime.
In the App_Stsart\WebAPiConfig class I added a new HttpRoute.
config.Routes.MapHttpRoute("OData", "api/odata/{*wildcard}", new { controller = "ODService", action = "Get", wildcard = RouteParameter.Optional }, new {httpMethod = new HttpMethodConstraint(new HttpMethod("GET"))}); config.Routes.IgnoreRoute("ODataIgnore", "api/ODService/{*wildcard}");
I’m going to create a controller called ODServiceController that I want to map to api/odata. I also want to stop anyone from going to it directly which is why I configured Web API to ignore it. One of the most important parts is the {*wildcard} since I want all calls to be directed to it and not have MVC trying to parse it out for an action method. Right now it’s restricted to Get and will always call the Get method.
Now in the ODataServices project I created a folder called Controllers and updated to Web API 2 and added references to Entity Framework 6.1 then created an abstract generic controller that inherits from ODataMetadataController called ODataServicesController
One of the first things I’m going to do is setup a Service Locator, I know a lot of people consider this an anti-pattern but when building tools it’s hard to use a real IOC container.
namespace ODataServices.Controllers { public abstract class ODataServicesController<TSource> : ODataMetadataController { protected ODataServicesController() { SetServiceLocator(); } #region ServiceLocator private static IDictionary<Type, Lazy<object>> _serviceLocatorDefaults = new Dictionary<Type, Lazy<object>>(); private Func<Type, object> _serviceLocator; private T ServiceLocator<T>() where T : class { return _serviceLocator(typeof (T)) as T; } private void SetServiceLocator() { // check if datasource or controller implements IServiceProvider; var dataSourceResolver = (typeof (IServiceProvider).IsAssignableFrom(typeof (TSource))); var controllerResolver = (typeof (IServiceProvider).IsAssignableFrom(GetType())); if (dataSourceResolver && controllerResolver) { _serviceLocator = type => { var result = ((IServiceProvider) this).GetService(type); if (result == null) { result = ((IServiceProvider) CurrentDataSource).GetService(type); if (result == null) { result = DependencyResolver.Current.GetService(type); } if (result == null && _serviceLocatorDefaults.ContainsKey(type)) { result = _serviceLocatorDefaults[type].Value; } } return result; }; } else if (dataSourceResolver) { _serviceLocator = type => { var result = ((IServiceProvider) CurrentDataSource).GetService(type); if (result == null) { result = DependencyResolver.Current.GetService(type); } if (result == null && _serviceLocatorDefaults.ContainsKey(type)) { result = _serviceLocatorDefaults[type].Value; } return result; }; } else if (controllerResolver) { _serviceLocator = type => { var result = ((IServiceProvider) this).GetService(type); if (result == null) { result = DependencyResolver.Current.GetService(type); } if (result == null && _serviceLocatorDefaults.ContainsKey(type)) { result = _serviceLocatorDefaults[type].Value; } return result; }; } else { _serviceLocator = type => { var result = DependencyResolver.Current.GetService(type); if (result == null && _serviceLocatorDefaults.ContainsKey(type)) { result = _serviceLocatorDefaults[type].Value; } return result; }; } } #endregion } }
From the code above I’m checking if the controller implements IServiceProvider then check if it can provide the object. If the controller either doesn’t implement the interface or returns null then to check the datasource. If the data source doesn’t implement IServiceProvider or returns null then check the standard Web API Dependency Resolver. If all that fails then we have a dictionary that will contain a mapping of defaults that we will define for the handler. This should give us the extensibility that we need.
Now we are going to create a method for creating the data source. Here I’m going to use the same methodology that WCF Data Services did. We will create a method called CreateDataSource and a property called CurrentDataSource.
protected ODataServicesController() { SetServiceLocator(); // Setup data source to get resolved first time needed. _currentDataSource = new Lazy<TSource>(CreateDataSource, LazyThreadSafetyMode.ExecutionAndPublication); } #region DataSource private readonly Lazy<TSource> _currentDataSource; protected TSource CurrentDataSource { get { return _currentDataSource.Value; } } protected virtual TSource CreateDataSource() { return DependencyResolver.Current.GetService<TSource>(); } #endregion
Here I’m taking advantage of the .Net Lazy class and using that to automatically call CreateDataSource() first time we need it. Then I just write a wrapper property on CurrentDataSource to retrieve the value property of the Lazy backing field. Pretty simple but should flow nicely.
Now on to the meat of the controller. In the ODataServicesController I’m going to create a method called ProcessRequest that returns an HttpResponseMessage. The first thing we need to do is get the ODataPath which the Request property has an extension method just for that. Then we need to get our IEdmModel and parse it out.
protected override void Initialize(HttpControllerContext controllerContext) { base.Initialize(controllerContext); Request.SetEdmModel(BuildEdmModel()); } protected virtual HttpResponseMessage ProcessRequest() { var pathHandler = Request.GetODataPathHandler(); var metadata = GetMetadata(); var path = pathHandler.Parse(metadata, GetODataPath()); Request.SetODataPath(path); return null; } protected virtual string GetODataPath() { var routedata = Request.GetRouteData(); var uriTemplate = new UriTemplate(routedata.Route.RouteTemplate); var baseUri = new Uri(Request.RequestUri.Scheme + "://" + Request.RequestUri.Authority + Request.GetRequestContext().VirtualPathRoot.TrimEnd('/') + "/"); var match = uriTemplate.Match(baseUri, Request.RequestUri); var path = "/" + String.Join("/", match.WildcardPathSegments); return path; } private IEdmModel BuildEdmModel() { var edmModelFactory = ServiceLocator<IEdmModelFactory>(); if (edmModelFactory == null) { return ServiceLocator<IEdmModel>(); } else { return edmModelFactory.EdmModel(CurrentDataSource); } }
In the Initialize method, which MVC calls automatically, we are going to set the Request’s IEdmModel. We get the IEdmModel from an interface called IEdmModelFactory.
namespace ODataServices.Interfaces { public interface IEdmModelFactory { IEdmModel EdmModel<TSource>(TSource source); } }
We are missing error handing because it could return null, I’ll add that in later right now I’m just trying to get it up and running. Since Entity Framework seems pretty popular with people that used WCF Data Services, plus that’s what we use, I’m going to add a default EdmModelFactory for Entity Framework DbContext.
namespace ODataServices { //https://gist.github.com/dariusclay/8673940 //http://stackoverflow.com/questions/22711496/entityframework-model-first-metadata-for-breezejs //https://gist.github.com/raghuramn/5864013 public class DbContextEdmModel : IEdmModelFactory { private const string CsdlFileExtension = ".csdl"; private const string CodeFirstContainer = "CodeFirst"; private const string EntityConnectionMetadataPatternText = @"^(res://\*/(?<name>[^\|]+))(\|res://\*/(?<name>[^\|]+)?)*$"; private const RegexOptions EntityConnectionMetadataRegexOptions = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; private readonly Regex _entityConnectionMetadataPattern = new Regex(EntityConnectionMetadataPatternText, EntityConnectionMetadataRegexOptions); private Stream GetCsdlStreamFromMetadata<TSource>(ObjectContext context) { var metadata = new EntityConnectionStringBuilder(context.Connection.ConnectionString).Metadata; var assembly = Assembly.GetAssembly(typeof(TSource)); var csdlResource = _entityConnectionMetadataPattern.Matches(metadata) .Cast<Match>() .SelectMany(m => m.Groups["name"].Captures.OfType<Capture>()) .Single(c => c.Value.EndsWith(CsdlFileExtension)); return assembly.GetManifestResourceStream(csdlResource.Value); } private IEdmModel NotCodeFirstModel<TSource>(IObjectContextAdapter source) { using (var csdlStream = GetCsdlStreamFromMetadata<TSource>(source.ObjectContext)) { using (var reader = XmlReader.Create(csdlStream)) { IEdmModel model; IEnumerable<EdmError> errors; if (!CsdlReader.TryParse(new[] { reader }, out model, out errors)) { return null; } return model; } } } private IEdmModel CodeFistModel(DbContext context) { using (var stream = new MemoryStream()) { using (var writer = XmlWriter.Create(stream)) { System.Data.Entity.Infrastructure.EdmxWriter.WriteEdmx(context, writer); writer.Close(); stream.Seek(0, SeekOrigin.Begin); using (var reader = XmlReader.Create(stream)) { return EdmxReader.Parse(reader); } } } } public virtual IEdmModel EdmModel<TSource>(TSource source) { var dbContext = source as DbContext; if (dbContext == null) { return null; } var objContext = (IObjectContextAdapter)source; if (objContext.ObjectContext.DefaultContainerName == CodeFirstContainer) { return CodeFistModel(dbContext); } else { return NotCodeFirstModel<TSource>(objContext); } } }
At the top of the code I listed where I got some of these methods and I haven’t tested it with a Code First Entity Framework yet, for testing I just setup a model first since it’s the easiest to get up and running. So I can’t be sure that the container name for code first is CodeFirst, it’s on my list. Also I don’t know if checking the container name is the best approach to know if a DbContext was generated code first or not. This class doesn’t cache the IEdmModel and we will want to add that. But I feel that is the classes responsibility and not the controllers. That’s way the method EdmModel<TSource> is virtual. We can inherit from this class and add a wrapper around our caching logic and call into base when we need a new IEdmModel – again about giving the developer the flexibility to determine when they want to cache or not.
Now to add the default implementation of IEdmModelFactory to the ODataServiceController.
static ODataServicesController() { // Load up default implementations _serviceLocatorDefaults[typeof(IEdmModelFactory)] = new Lazy<object>(() => new DbContextEdmModel()); }
Let’s first work on getting $metadata returned back
Update the ProcessRequest method.
protected virtual HttpResponseMessage ProcessRequest() { var pathHandler = Request.GetODataPathHandler(); var metadata = GetMetadata(); var path = pathHandler.Parse(metadata, GetODataPath()); Request.SetODataPath(path); Request.SetODataRouteName("OData"); if (path.Segments.Any(s => s.SegmentKind == ODataSegmentKinds.Metadata)) { var odataMediaTypeFormatter = new ODataMediaTypeFormatter(new DefaultODataDeserializerProvider(), new DefaultODataSerializerProvider(), new[] {ODataPayloadKind.MetadataDocument}); var formatter = odataMediaTypeFormatter.GetPerRequestFormatterInstance(typeof (IEdmModel), Request, new MediaTypeHeaderValue( "application/xml")); var response = Request.CreateResponse(); response.Content = new ObjectContent(typeof (IEdmModel), metadata, formatter); return response; } return new HttpResponseMessage(HttpStatusCode.NotImplemented); }
now in the ODServiceController pass the Get request to the ProcessRequest method.
public class ODServiceController : ODataServicesController<SampleDataEntities> { public HttpResponseMessage Get() { return ProcessRequest(); } }
With this done you should be able run in debug mode and go to localhost:yourport/api/odata/$metadata and get the xml document back.
Part 2 I’ll be working on query an EntitySet and Entity.