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
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).
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.
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?
The API supports 3 response types: JSON (Recommended) XML. NVP (Deprecated)
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With