How to restrict serialized properties with Json.NET and Web API 2.0 using a custom ContractResolver
I recently were in a situation where I needed to hide parts of my models from unauthorized users.
I tried a few different things that weren’t that pretty or maintainable, but in the end I found a pretty simple solution.
To accompany this post I have made a very basic example project.
Person.cs
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
public DateTime DateOfBirth { get; set; }
public string SocialSecurityNumber { get; set; }
}
PersonController.cs
public class PersonController : ApiController
{
[Route("api/persons")]
public IEnumerable GetPersons()
{
return Database.GetPersons();
}
[Route("api/persons/{ssn}")]
public Person GetPerson(string ssn)
{
return Database.GetPerson(ssn);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// Force JSON responses only (no XML)
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
Formatting = Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
}
};
config.Formatters.Clear();
config.Formatters.Add(formatter);
}
}
JSON response at "/api/persons"
[
{
"name": "John Doe",
"address": "Main Street 1",
"dateOfBirth": "1971-06-10T00:00:00",
"socialSecurityNumber": "654-84-6542"
},
{
"name": "Jane Doe",
"address": "Main Street 1",
"dateOfBirth": "1975-08-13T00:00:00",
"socialSecurityNumber": "342-45-1345"
},
{
"name": "Sherlock Holmes",
"address": "Baker Street 221b",
"dateOfBirth": "1952-02-28T00:00:00",
"socialSecurityNumber": "873-32-5284"
}
]
Let’s say we wan’t to hide the DateOfBirth
and SocialSecurityNumber
for unauthorized users, as those informations are a bit personal.
I tried several different approaches, but none of them were that pretty. For example I tried with two different models for each original model – one for the authorized user and one for the unauthorized – but it lead to a lot of code duplication and I could already imagine the horror of maintaining this later on.
So I kept on looking for a better solution and then it hit me – why not just handle this at the serialization level?
Json.NET supports a simple way to dynamically determine if a member should be serialized by creating a method with the name ShouldSerialize{MemberName}()
as illustrated below.
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
public DateTime DateOfBirth { get; set; }
public string SocialSecurityNumber { get; set; }
public bool ShouldSerializeDateOfBirth()
{
return HttpContext.Current.User.IsInRole("Staff");
}
public bool ShouldSerializeSocialSecurityNumber()
{
return HttpContext.Current.User.IsInRole("Staff");
}
}
That’s one way to do it. But you quickly end up with a lot of code to write this way if you have a lot of properties and/or classes. There had to be a better way – and there is.
In the beginning of this post you can see the WebApiConfig.cs
file wherein we create a new JsonMediaTypeFormatter
with some custom serializer settings. The default ContractResolver
serializes member names as they are written in our code. In C# we use PascalCase for public members while in JavaScript the convention is to use camelCase. That’s why I originally changed the ContractResolver
to a CamelCasePropertyNamesContractResolver
– this one serializses member names using the camelCase convention instead.
By extending this (or the DefaultContractResolver
) we can change how and if members will be serialized. Beforewe do thatwe need to be able to identify which properties to restrict and how they should be restricted. This can be done pretty easily with some attributes. If you haven’t worked with attributes before, fear not, it’s pretty easy.
In my specific case I needed to be able to restrict one or more properties on a class if the current user didn’t have a specific role. For this I created two attributes: JsonRestrictedAttribute
and JsonRestrictedRoleAttribute
. To create a new attribute you just create a new class with a name that ends with Attribute and extend it from System.Attribute
. On the new attributes I’m actually using another attribute to indicate where this new attribute can be applied and if it can be used multiple times on the same class/property. You can see my attributes below.
JsonRestrictedRoleAttribute.cs
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class JsonRestrictedRoleAttribute : Attribute
{
public string RoleName { get; set; }
public JsonRestrictedRoleAttribute(string roleName)
{
RoleName = roleName;
}
public bool IsAuthorized(IPrincipal user)
{
return user.IsInRole(RoleName);
}
}
JsonRestrictedAttribute.cs
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class JsonRestrictedAttribute : Attribute
{ }
The new attributes doesn’t actually do anything themselves besides annotating your classes/properties/methods. The way you use them is through reflection, which we will get to soon.
We start out by extending CamelCasePropertyNamesContractResolver
(if you don’t want the camelCase naming you can just extend DefaultContractResolver
instead). Next we override the CreateProperty()
method and then we want to insert our own predicate in theJsonProperty
‘sShouldSerialize
property. This predicate determines if the property should be serialized.
public class RestrictedContractResolver : CamelCasePropertyNamesContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization serialization)
{
var property = base.CreateProperty(member, serialization);
// Check if the declaring type (the class declaring this property)
// has a JsonRestrictedRole attribute
var restrictedRoleAttribute = member.DeclaringType.GetCustomAttribute<JsonRestrictedRoleAttribute>();
if (restrictedRoleAttribute != null)
{
// Check if the property has a JsonRestricted attribute
var restrictedAttribute = member.GetCustomAttribute<JsonRestrictedAttribute>();
if (restrictedAttribute != null)
{
// Set a new predicate that determines if
// this property should be serialized or not
property.ShouldSerialize =
x => restrictedRoleAttribute.IsAuthorized(HttpContext.Current.User);
}
}
return property;
}
}
.NET Core has moved parts of the Reflection API. You have to add in a
GetTypeInfo()
like thismember.DeclaringType.GetTypeInfo().GetCustomAttribute<JsonRestrictedRoleAttribute>()
Before we just overwrite ShouldSerialize
with our new predicate, we check for our new attributes. First we check the declaring class (the class that contains the current property) if it has our JsonRestrictedRoleAttribute
. Next we check if the property itself has our JsonRestrictedAttribute
. If that is the case, then we set ShouldSerialize
to a new predicate, where we call the IsAuthorized()
method of the JsonRestrictedRoleAttribute
which then takes care of the logic for us.
Be aware that the ShouldSerialize
predicate will be called each time the property is being serialized. If it didn’t this wouldn’t work. The CreateProperty()
method, though, will only be called once and then cached for future use, so the authorization logic that is dependant on current information (like if the current user is logged in or has the right role) needs to be in the ShouldSerialize
predicate.
Below you can see how to decorate the Person
class with our new attributes, what needs to be changed in WebApiConfig.cs
to use our new RestrictedContractResolver and at last how the output then looks like, when the current user does not have the Staff role.
Person.cs
[JsonRestrictedRole("Staff")]
public class Person
{
public string Name { get; set; }
public string Address { get; set; }
[JsonRestricted]
public DateTime DateOfBirth { get; set; }
[JsonRestricted]
public string SocialSecurityNumber { get; set; }
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
// Force JSON responses only (no XML)
var formatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
Formatting = Formatting.Indented,
ContractResolver = new RestrictedContractResolver(),
}
};
config.Formatters.Clear();
config.Formatters.Add(formatter);
}
}
JSON response at "/api/persons"
[
{
"name": "John Doe",
"address": "Main Street 1"
},
{
"name": "Jane Doe",
"address": "Main Street 1"
},
{
"name": "Sherlock Holmes",
"address": "Baker Street 221b"
}
]
That’s it. Not that complicated after all and it is now easy to restrict parts of your models by just adding the JsonRestricted
attribute to the properties and the JsonRestrictedRole
attribute to the class itself.
This can easily be extended or changed for other purposes or with more advanced logic to fit your needs.