Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

oData $count doesn't work with EntitySetController<TEntity, TKey> in web api 4

I'm useing EntitySetController to create an oData web api controller, and everything works well, except to get the total records count.

THe controller is defined as below:

public class MyODataController : EntitySetController<Entity1, int> where TEntity : class
{
    public override IQueryable<Entity1> Get()
    {
        return EntityDatabase.Get();
    }
}

when I call the count through:

http://localhost:44789/oData/MyOData/$count

I get the error: Invalid action detected. '$count' is not an action that can bind to 'Collection([Entity1 Nullable=False])'.

like image 472
frank Avatar asked May 20 '13 12:05

frank


3 Answers

Unfortunately, Web API doesn't support $count out-of-the box quite yet although it should in a future version. In the meantime, you can still add support by defining these classes:

public class CountODataRoutingConvention : EntitySetRoutingConvention
{
    public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
    {
        if (controllerContext.Request.Method == HttpMethod.Get && odataPath.PathTemplate == "~/entityset/$count")
        {
            if (actionMap.Contains("GetCount"))
            {
                return "GetCount";
            }
        }
        return null;
    }
}

public class CountODataPathHandler : DefaultODataPathHandler
{
    protected override ODataPathSegment ParseAtEntityCollection(IEdmModel model, ODataPathSegment previous, IEdmType previousEdmType, string segment)
    {
        if (segment == "$count")
        {
            return new CountPathSegment();
        }
        return base.ParseAtEntityCollection(model, previous, previousEdmType, segment);
    }
}

public class CountPathSegment : ODataPathSegment
{
    public override string SegmentKind
    {
        get
        {
            return "$count";
        }
    }

    public override IEdmType GetEdmType(IEdmType previousEdmType)
    {
        return EdmCoreModel.Instance.FindDeclaredType("Edm.Int32");
    }

    public override IEdmEntitySet GetEntitySet(IEdmEntitySet previousEntitySet)
    {
        return previousEntitySet;
    }

    public override string ToString()
    {
        return "$count";
    }
}

Registering them in MapODataRoute:

IList<IODataRoutingConvention> routingConventions = ODataRoutingConventions.CreateDefault();
routingConventions.Insert(0, new CountODataRoutingConvention());
config.Routes.MapODataRoute("OData", "odata", GetModel(), new CountODataPathHandler(), routingConventions);

And in your controller, adding this method:

public HttpResponseMessage GetCount(ODataQueryOptions<TEntity> queryOptions)
{
    IQueryable<TEntity> queryResults = queryOptions.ApplyTo(Get()) as IQueryable<TEntity>;
    int count = queryResults.Count();
    HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StringContent(count.ToString(), Encoding.UTF8, "text/plain");
    return response;
}

To avoid having to copy GetCount() to every controller, you could define a base class that derives from EntitySetController that defines GetCount.

like image 141
Youssef Moussaoui Avatar answered Nov 23 '22 18:11

Youssef Moussaoui


If you use the following:

http://localhost:44789/oData/MyOData?$inlinecount=allpages

then the total count will be included in your get return.

like image 32
Gareth Suarez Avatar answered Nov 23 '22 17:11

Gareth Suarez


The latest package Web API 2.2 for OData v4.0 has $count support.

like image 32
user441918 Avatar answered Nov 23 '22 17:11

user441918