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

Leave a Reply

*