Search code examples
c#asp.net-web-apiodataasp.net-web-api-odata

ASP.NET MVC Web API and passing oData query


I'm currently executing Web API with oData filter requests as follows:

public IQueryable<OrganizationViewModel> Get(ODataQueryOptions<Organization> oDataQuery)
{
    var query = new FindOrganizationsQuery(oDataQuery);
    var result =_findOrganizationsQueryHandler.Execute(query);
    return result.Organizations.Select(o => new OrganizationViewModel { Id = o.PublicId, Name = o.Name });
}

The handler looks like:

public FindOrganizationsQueryResult Execute(FindOrganizationsQuery request)
{
    var organizations = request.ODataQuery.ApplyTo(_mgpQueryContext.Organizations).Cast<Organization>();            
    return new FindOrganizationsQueryResult(organizations);
}

And the query class looks like:

public class FindOrganizationsQuery
{
    public FindOrganizationsQuery(ODataQueryOptions<Organization> oDataQuery)
    {
        ODataQuery = oDataQuery;
    }
    public ODataQueryOptions<Organization> ODataQuery { get; set; }
}

So if I pass an oData filter with the request, it is handled nicely and this all works fine.

But now, instead of passing the type ODataQueryOptions to the Get operation, I would like to have the FindOrganizationsQuery class, like:

public IQueryable<OrganizationViewModel> FindOrganizations(FindOrganizationsQuery query)
{
    // query is null
}

However, the query parameters is always null. How can I pass the oData filter if the ODataQueryOptions parameters is in another class?


Solution

  • You can write a custom parameter binding attribute for FindOrganizationsQuery the same way we do for ODataQueryOptions and then attribute your FindOrganizationsQuery with that attribute.

    Some sample code below,

    public class CustomQueryBindingAttribute : ParameterBindingAttribute
    {
        public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
        {
            return new CustomQueryBinding(parameter);
        }
    
        internal class CustomQueryBinding : HttpParameterBinding
        {
            public CustomQueryBinding(HttpParameterDescriptor parameter)
                : base(parameter)
            {
            }
    
        internal class CustomQueryBinding : HttpParameterBinding
        {
            public CustomQueryBinding(HttpParameterDescriptor parameter)
                : base(parameter)
            {
            }
    
            public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,
                HttpActionContext actionContext, CancellationToken cancellationToken)
            {
                IEdmModel model = actionContext.Request.GetEdmModel() ?? actionContext.ActionDescriptor.GetEdmModel(typeof(Organization));
                ODataQueryContext queryContext = new ODataQueryContext(model, typeof(Organization));
    
                object customQuery = CreateCustomQuery(queryContext, actionContext.Request);
                SetValue(actionContext, customQuery);
    
                return Task.FromResult(0);
            }
    
            private object CreateCustomQuery(ODataQueryContext queryContext, HttpRequestMessage request)
            {
                Type parameterType = Descriptor.ParameterType;
                // Assuming all custom queries have this public property.
                Type oDataQueryOptionsOfTType = parameterType.GetProperty("ODataQuery").PropertyType;
    
                object odataQueryOptions = Activator.CreateInstance(oDataQueryOptionsOfTType, queryContext, request);
                return Activator.CreateInstance(parameterType, odataQueryOptions);
            }
        }
    }
    

    and the extension method I copied from web API source code as it is not public.

    public static class HttpActionDescriptorExtensions
    {
        internal const string EdmModelKey = "MS_EdmModel";
    
        internal static IEdmModel GetEdmModel(this HttpActionDescriptor actionDescriptor, Type entityClrType)
        {
            // save the EdmModel to the action descriptor
            return actionDescriptor.Properties.GetOrAdd(EdmModelKey + entityClrType.FullName, _ =>
            {
                ODataConventionModelBuilder builder = new ODataConventionModelBuilder(actionDescriptor.Configuration, isQueryCompositionMode: true);
                EntityTypeConfiguration entityTypeConfiguration = builder.AddEntity(entityClrType);
                builder.AddEntitySet(entityClrType.Name, entityTypeConfiguration);
                IEdmModel edmModel = builder.GetEdmModel();
                return edmModel;
            }) as IEdmModel;
        }
    }
    

    I have the complete sample here.