I am currently working on a Backend API that acts as a middleman to a Front API and a DocumentDB database (the structure is shown in the above image).
I had the following class which is used to send data to both the Front API and to the DocumentDB. Some of the fields needed to be hidden when returning a response back to the Front API, since they weren’t useful for the user and for security concerns. First I tried using [JsonIgnore] and [IgnoreDataMember] (as seen below), which successfully excluded the required fields from the response, but using them was also excluding the same fields when sending the data to DocumentDB, so it was a no-go.
[code language=”csharp”]
public string Name { get; set; }
public string Url { get; set; }
public string Parent { get; set; }
[JsonIgnore]
public string ObjectType => "Gateway";
[JsonIgnore]
public string Version { get; set; } = "1.0";
[JsonIgnore]
public string id { get; set; } = Guid.NewGuid().ToString();
[JsonIgnore]
public string Tenant { get; set; }
[/code]
An option would have been to create a new class with the same fields and use one for response to the Front API and the other to the DocumentDB, but they need to be kept in sync when there are changes to one class or another. Apart from that, I don’t like duplication of code. I found another option but the property names to be excluded had to be hard-coded before sending the response, which I disagree with. I wanted something customised for my needs, one which I can control, and easy to configure for each property to hide.
Enter custom attributes! A new Attribute class was created targeting class properties and methods (in case any methods need to be hidden as well). This attribute doesn’t have any functionality, it will just be used to decorate properties and methods.
[code language=”csharp”]
namespace Project.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public class ExcludeFromResponse : Attribute
{
}
}
[/code]
The ExcludeFromResponse attribute was then added to the class properties that need to be excluded from the WebApi response as follows:
[code language=”csharp”]
public string Name { get; set; }
public string Url { get; set; }
public string Parent { get; set; }
[ExcludeFromResponse]
public string ObjectType => "Gateway";
[ExcludeFromResponse]
public string Version { get; set; } = "1.0";
[ExcludeFromResponse]
public string id { get; set; } = Guid.NewGuid().ToString();
[ExcludeFromResponse]
public string Tenant { get; set; }
[/code]
Next, a custom ContractResolver was created – JsonExcludeFromResponseContractResolver – inheriting from the DefaultContractResolver. This will be used to modify and re-create JSON properties as needed for the reponse.
In the code below, the properties to be included in the response are selected and stored in the ‘includedPropList’, by selecting the properties not having the ExcludeFromResponse attribute. Next was to re-create the property list to be sent by having the included properties only, and is then stored in the ‘properties’ variable. This will return a JSON with all the keys and values from the included properties only.
[code language=”csharp”]
namespace Project.Resolvers
{
public class JsonExcludeFromResponseContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var includedPropList = type.GetProperties()
.Where(prop => !Attribute.IsDefined(prop, typeof(ExcludeFromResponse)))
.Select(x => x.Name)
.ToList();
var properties = base.CreateProperties(type, memberSerialization)
.Where(p => includedPropList.Contains(p.PropertyName))
.ToList();
return properties;
}
}
}
[/code]
Now it’s time to return the response. In the Controller, a new JsonMediaTypeFormatter is created, using the new ‘JsonExludeFromResponseContractResolver’ as it’s contract resolver, so the media type formatter formats the data as we need it. is called as in the below code without the need to add the properties to remove manually:
[code language=”csharp”]
// Remove properties that have ExcludeFromResponse attribute
var jsonMediaTypeFormatter = new JsonMediaTypeFormatter { SerializerSettings = new JsonSerializerSettings { ContractResolver = new JsonExcludeFromResponseContractResolver() } };
return Content(HttpStatusCode.OK, objectToReturn, jsonMediaTypeFormatter, new MediaTypeHeaderValue("application/json"));
[/code]
Finally, to test that the exclusions are working, you can do the following integration test:
[code language=”csharp”]
// check that excluded props are not returned in json
var json = JObject.Parse(await response.Content.ReadAsStringAsync());
Assert.Null(json["ObjectType"]);
Assert.Null(json["Tenant"]);
[/code]
If you have any custom objects included in the class, and want to assert a child field, you can assert as follows:
[code language=”csharp”]
Assert.Null(json["Activation"]["PreviousStatus"]);
[/code]
And the tests should pass! Let me know in the comments if something didn’t work or for any suggestions on how to improve it. Thank you.
No Responses