Move from WCF Data Services to Web API – Part 5

It’s been awhile since Part 4 and the project is now up on GitHub and it seems to be pretty stable. Hasn’t balked at a OData Query I sent at it yet. Since it’s based off Web API 2.1 it only supports V3 of the OData specification.

I changed ProcessRequest to return IHttpActionResult since that’s the Web API way.

protected virtual IHttpActionResult ProcessRequest()
{
    var pathHandler = Request.GetODataPathHandler();
    var metadata = GetMetadata();
    var path = pathHandler.Parse(metadata, GetODataPath());
    if (path != null)
    {
        Request.SetODataPath(path);
        Request.SetODataRouteName(ODataRoute);
        if (path.Segments.Count == 0)
        {
            // Requested service document
            return GenerateServiceDocument();
        }

        if (path.Segments.Any(s => s.SegmentKind == ODataSegmentKinds.Metadata))
        {
            // Requested metadata
            return GenerateMetadataResponse();
        }

        // not great that these are strings
        if (path.Segments[0].SegmentKind == "entityset")
        {
            MapIEdmEntitySetsToCLR();
            return Buildup(path);
        }
    }
    throw new WebAPIDataServiceException("Can not process OData path", new ODataUnrecognizedPathException());
}

In Buildup that’s where the magic happens. We are going to loop though the path segments to get the keys, navigation and property access then stop once we are past that and let the Web API take over. The path.EdmType is the finial Edm Type we should be returning, which since we only really care if it’s a collection or not we just have to check if it’s a collection. Also I made a weak typed IQueryable FirstOrDefault method.

private IHttpActionResult Buildup(ODataPath path)
{
    var segment = path.Segments[0];
    var edmType = segment.GetEdmType(null);
    var edmCollectionType = edmType as IEdmCollectionType;
    if (edmType == null || edmCollectionType == null ||
        !_edmTypeKindToInterface.ContainsKey(path.EdmType.TypeKind))
    {
        throw new WebAPIDataServiceException("Can not resolve OData Path", new ODataUnrecognizedPathException());
    }
    var query = QueryEntitySet(edmCollectionType);
    for (var i = 1; i < path.Segments.Count; i++)
    {
        edmType = segment.GetEdmType(edmType);
        segment = path.Segments[i];
        // wish this would be an enum.  Don't know if I have all the possibilities set here
        switch (segment.SegmentKind)
        {
            case "key":
                {
                    edmCollectionType = edmType as IEdmCollectionType;
                    var segmentKey = segment as KeyValuePathSegment;
                    query = FilterOnKeys(query, edmCollectionType, segmentKey);
                    break;
                }
            case "navigation":
                {
                    var navigationPath = segment as NavigationPathSegment;
                    query = DrillIntoNavigationProperty(query, navigationPath);
                    break;
                }
            case "property":
                {
                    var propertyPath = segment as PropertyAccessPathSegment;
                    query = ProjectProperty(query, propertyPath);
                    break;
                }
            default:
                {
                    // break out of the loop
                    i = path.Segments.Count + 1;
                    break;
                }
        }
    }
    var contentType = _edmTypeKindToInterface[path.EdmType.TypeKind];
    if (contentType == typeof (IEdmCollectionType))
    {
        // setup to have web api odata take over
        var edmEntityType = GetIEdmTypeToCLRType(GetEdmEntityType(path.EdmType));
        var model = GetMetadata();
        var queryContext = new ODataQueryContext(model, edmEntityType);
        var queryOptions = new ODataQueryOptions(queryContext, Request);
        query = ServiceLocator()
            .Apply(query, queryOptions, new ODataQuerySettings(ODataQuerySettings));
        return new ODataHttpActionResult(this, query, contentType,
                                         typeof (IEnumerable<>).MakeGenericType(query.ElementType));
    }
    return new ODataHttpActionResult(this, FirstOrDefault(query), contentType,
                                     query.ElementType);
}

// have just system type use IEnumerable to create the first one
private static object FirstOrDefault(IQueryable queryable)
{
    var enumerator = queryable.GetEnumerator();
    {
        if (enumerator.MoveNext())
        {
            return enumerator.Current;
        }
    }
    var type = queryable.ElementType;
    return type.IsValueType ? Activator.CreateInstance(type) : null;
}

The FilterOnKeys, DrillIntoNavigationProperty, ProjectProperty is where I build the expression trees and not much to see there, if you are interested you can check out the github repository. I created the IApplyQueryOptions so I could have a default implementation for the Web API but I already have plans to hijack it to allow more control. Also this is the end point of the query before it gets serialized and will allow for changes without have to implement IQueryprovider like we had to do with WCF DataServices

Please feel free to grab the code and test it out. I plan on adding OData Service Operations, the Distinct operator and batching, but that’s for next time.

Monday, May 12th, 2014 OData

Leave a Reply

*