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