ASP.NET MVC Web API

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 🙂

Tags: , ,

Friday, April 18th, 2014 OData No Comments

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.

Tags: , ,

Wednesday, April 16th, 2014 OData Comments Off on Move from WCF Data Services to Web API – Part 3

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.

Tags: , ,

Tuesday, April 15th, 2014 OData Comments Off on Move from WCF Data Services to Web API – Part 2

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.

 

Tags: , ,

Monday, April 14th, 2014 OData No Comments