User Response Fields for Web Api Filtering

I am creating a new web API and want the user to be able to specify which fields will be returned to them in the URL.

My current thoughts are:

For an example model:

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) 

will return it

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

I came up with a method below to achieve this, but it has some disadvantages. That is, it would not be able to process if Internal was enumerated from ValueInternal or does not support include all or include all except support, or if T and TResult are different types. Does anyone have any suggestions on how I can improve this, or if there is already a way to do this, 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 reading a little more, I think I want to do something like the answer to this LINQ question : Dynamic selection , but still has the same disadvantages as my solution

+6
source share
1 answer

If you use OData support on your ASP.NET web interface, you can use $ select , but if you do not want to use it or your base system is not easy to query with Linq, you can use a custom contract resolver, but in this case you simply Reduce serialization size, not 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 a custom contract resolver is set:

 var jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter; jsonFormatter.SerializerSettings.ContractResolver = new FieldsSelectContractResolver(); 
+2
source

Source: https://habr.com/ru/post/973747/


All Articles