Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Web Api user filtering response fields

I am creating a new web API and would like to allow the user to specify what fields get returned to them in the URL.

My current thoughts are:

For a sample model like this:

public class Value
{
    public string ValueId { get; set; }

    public int Number { get; set; }

    public ValueInternal Internal { get; set; }
}
public class ValueInternal
{
    public int Number { get; set; }

    public string Something { get; set; }
}

and a URL like this

http://example.com/api/values/?_fields=Number,Internal(Something)

would return this

[
   {
       "Number": 0,
       "Internal": {
           "Number": 0
       }
   }
]

I have come up with the below method of achieving this, but it has some flaws. I.e. it couldn't handle if Internal was an enumerable of ValueInternal or has no support for include all or include all except, or if T and TResult are different types. Does anyone have any suggestions on how I can improve this or if there already exists a way of doing it that I am missing.

public static Expression<Func<T, TResult>> CreateSelector<T, TResult>() where TResult : new()
    {
        var property = "Number,Internal(Something)";
        return arg => Process<T, TResult>(arg, default(TResult), property);
    }

    private static TResult Process<T, TResult>(T arg, TResult output, string propertyList) where TResult : new()
    {
        if (output == null)
        {
            output = new TResult();
        }
        if (string.IsNullOrEmpty(propertyList))
        {
            return output;
        }
        var properties = Regex.Split(propertyList, @"(?<!,[^(]+\([^)]+),");
        foreach (var property in properties)
        {
            var propertyName = property;
            var propertyInternalsMatch = Regex.Match(property, @"\(.*(?<!,[^(]+\([^)]+)\)");
            var internalPropertyList = propertyInternalsMatch.Value;
            if (!string.IsNullOrEmpty(internalPropertyList))
            {
                propertyName = property.Replace(internalPropertyList, "");
                internalPropertyList = internalPropertyList.Replace("(", "");
                internalPropertyList = internalPropertyList.Replace(")", "");
            }
            var tProperty = arg.GetType().GetProperty(propertyName);
            if(tProperty == null) continue;
            var tResultProperty = output.GetType().GetProperty(propertyName);
            if(tResultProperty == null) continue;

            if (tProperty.PropertyType.IsPrimitive || tProperty.PropertyType.IsValueType || (tProperty.PropertyType == typeof(string)))
            {
                tResultProperty.SetValue(output, tProperty.GetValue(arg));
            }
            else
            {
                var propertyInstance = Activator.CreateInstance(tResultProperty.PropertyType);
                tResultProperty.SetValue(output, Process(tProperty.GetValue(arg), propertyInstance, internalPropertyList));
            }
        }

        return output;
    }

After a bit more reading I think I want to do something like the answer to this question LINQ : Dynamic select but that still has the same flaws my solution had

like image 499
Mike Norgate Avatar asked Aug 13 '14 06:08

Mike Norgate


People also ask

How do I filter response data in react?

Filtering in Reactconst App = () => { const [input] = useState([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); return ( <div> <div>Even: {input. filter(isEven). join(", ")}</div> <div>Odd: {input. filter(isOdd).

How do I customize a response in API?

To add your own custom response method, simply add a file to /api/responses with the same name as the method you would like to create. The file should export a function, which can take any parameters you like.

How do I pass a filter in REST API?

URL parameters is the easiest way to add basic filtering to REST APIs. If you have an /items endpoint which are items for sale, you can filter via the property name such as GET /items?

What are the different types of API responses?

The API supports 3 response types: JSON (Recommended) XML. NVP (Deprecated)


1 Answers

If you use OData support on your ASP .NET Web API you can jus use $select, but if you don't want to use it or your underlying system can't be easy queried using Linq, you can use a custom contract resolver, but in this case you are just reducing the serialization size, not the internal data traffic.

public class FieldsSelectContractResolver : CamelCasePropertyNamesContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);

        property.GetIsSpecified = (t) =>
        {
        var fields = HttpContext.Current.Request["fields"];

        if (fields != null)
        {
            return fields.IndexOf(member.Name, StringComparison.OrdinalIgnoreCase) > -1;
        }

        return true;
        };

        return property;
    }
}

and in WebApiConfig.cs set the custom contract resolver:

var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.ContractResolver = new FieldsSelectContractResolver();
like image 173
giacomelli Avatar answered Oct 15 '22 03:10

giacomelli