问题
I would like to control which properties from my model are serialized to my WebAPI2 JSON response, based on matching a query parameter to an attribute. I mainly want to do this to reduce bandwidth on GETs without causing a proliferation of ViewModel classes. For example:
GET /books/1?format=summary
public class Book
{
[SerializeFormat("summary")]
public int Id { get; set; }
[SerializeFormat("summary")]
public string Title { get; set; }
public string Contents { get; set; }
}
or
[SerializeFormat("summary","Id","Title")]
public class Book
{ ... }
To do this myself, I could derive all of my model classes from a custom base implementing ISerializable. In ISerializable.GetObjectData(), iterate through all properties inspecting the attributes. Not sure about performance on this idea.
Don't want to reinvent this solution, though if it already exists as a package.
回答1:
One possibility would be to introduce a custom attribute JsonConditionalIncludeAttribute that can be applied to properties and fields:
[System.AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = true, Inherited = true)]
public class JsonConditionalIncludeAttribute : System.Attribute
{
public JsonConditionalIncludeAttribute(string filterName)
{
this.FilterName = filterName;
}
public string FilterName { get; private set; }
}
Next, subclass DefaultContractResolver, override CreateProperty, and return null for properties that have at least one [JsonConditionalInclude] applied, none of which match the a filter supplied to the contract resolver:
public class JsonConditionalIncludeContractResolver : DefaultContractResolver
{
public JsonConditionalIncludeContractResolver(string filterName)
{
this.FilterName = filterName;
}
public string FilterName { get; set; }
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
// Properties without JsonConditionalIncludeAttribute applied are serialized unconditionally.
// Properties with JsonConditionalIncludeAttribute are serialized only if one of the attributes
// has a matching filter name.
var attrs = property.AttributeProvider.GetAttributes(typeof(JsonConditionalIncludeAttribute), true);
if (attrs.Count > 0 && !attrs.Cast<JsonConditionalIncludeAttribute>().Any(a => a.FilterName == FilterName))
return null;
return property;
}
}
Finally, when serializing your class to JSON, set JsonSerializerSettings.ContractResolver equal to your custom contract resolver, initializing the FilterName from your web request, for instance:
public class TestClass
{
public string Property1 { get; set; }
[JsonConditionalInclude("summary")]
[JsonConditionalInclude("title")]
public string Property2 { get; set; }
[JsonConditionalInclude("summary")]
public string Property3 { get; set; }
[JsonConditionalInclude("title")]
[JsonConditionalInclude("citation")]
public string Property4 { get; set; }
[JsonConditionalInclude("citation")]
public string Field1;
public static void Test()
{
var test = new TestClass { Property1 = "a", Property2 = "b", Property3 = "c", Property4 = "d", Field1 = "e" };
Test(test, "summary"); // Prints "a", "b" and "c"
Test(test, "title"); // Prints "a", "b" and "d".
Test(test, "citation");// Prints "e", "a" and "d"
Test(test, null); // Prints "e", "a", "b", "c" and "d".
}
public static string Test(TestClass test, string webRequestFormat)
{
var settings = new JsonSerializerSettings { ContractResolver = new JsonConditionalIncludeContractResolver(webRequestFormat) };
var json = JsonConvert.SerializeObject(test, Formatting.Indented, settings);
Debug.WriteLine(json);
return json;
}
}
The contract resolver will apply to all classes being serialized, not just the root class, which looks to be what you want.
来源:https://stackoverflow.com/questions/29713847/conditional-member-serialization-based-on-query-parameter