Introduction
During the development of Web Services for our system, we wanted to design a common service layer, and common data contracts which would cater to requirements
of different role based users. User role distinction was important, because there were some sensitive data points which could be shared only with privileged users.
One way to design this was to make the data translators role specific. The translators could ascertain the user role, and serialize the data accordingly.
The approach was simple but not elegant, and would have introduced lot of additional role checks in various translators.
An alternative was to keep the service layer and translators role agnostic, and use a more declarative approach by using Data Contract Surrogates.
Data Contract Surrogate is an advanced feature which can be used to introduce special behavior during Data Contracts Serialization\Deserialization.
Implementation
Let's start by defining an attribute ConditionalDataMemberAttribute
. This attribute will be used to decorate DataMembers which need to be conditionally serialized.
Usage of this attribute is discussed later.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class ConditionalDataMemberAttribute : Attribute
Next step is to create a custom Service Behavior called ConditionalDataBehavior
.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ConditionalDataBehavior : Attribute, IServiceBehavior
In service behavior ConditionalDataBehavior,
we override the DataContractSurrogate for each operation with our custom implementation
of Data Contract Surrogate ConditionalDataContractSurrogate
.
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ServiceEndpoint ep in serviceHostBase.Description.Endpoints) {
foreach (OperationDescription od in ep.Contract.Operations) {
ApplyDataContractSurrogate(od);
}
}
}
private static void ApplyDataContractSurrogate(OperationDescription description)
{
var dcsOperationBehavior = description.Behaviors.Find<datacontractserializeroperationbehavior>();
if (dcsOperationBehavior != null) {
dcsOperationBehavior.DataContractSurrogate = new ConditionalDataContractSurrogate(dcsOperationBehavior.DataContractSurrogate);
}
}
The class ConditionalDataContractSurrogate
implements the Interface IDataContractSurrogate
.
We add our custom implementation to the method GetObjectToSerialize
. During serialization this method is called
on every object that needs to be serialized. This gives an opportunity to inspect each object before serialization. If the calling context is not authorized
(checked inside IsAuthorized
) to receive the object, then we prevent the actual value from being serialized.
public object GetObjectToSerialize(object obj, Type targetType)
{
if (obj == null) return null;
var type = obj.GetType();
type.GetProperties().ToList()
.ForEach(prop => {
try {
var attr = prop.GetCustomAttributes(typeof(ConditionalDataMemberAttribute), false);
if (attr.Any()) {
var role = ((ConditionalDataMemberAttribute)attr[0]).Role;
if (!IsAuthorized(role)) {
var proptype = prop.PropertyType;
prop.GetSetMethod().Invoke(obj,
new[] { proptype.IsValueType ? Activator.CreateInstance(proptype) : null });
}
}
} catch { }
});
return _baseSerializer != null ? _baseSerializer.GetObjectToSerialize(obj, targetType) : obj;
}
Using the code
Step 1: Decorate your webservice with ConditionalDataBehavior
attribute.
[ServiceBehavior(Namespace = "http://mywebservice.com/v1")]
[ConditionalDataBehavior]
public class MyWebService
Step 2: Decorate the Data Contracts with ConditionalDataMember
attribute.
[DataContract]
public class UserInformation
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
[ConditionalDataMember(Role = "Level2")]
public string Email { get; set; }
[DataMember]
[ConditionalDataMember(Role = "Level2")]
public string Phone { get; set; }
}
Step 3: Add your own implementation of IsAuthroized
function.
private bool IsAuthorized(string role)
{
return true;
}
Example
new UserInformation {
FirstName = "Joe",
LastName = "Doe",
Email = "joe@doe.com",
PhoneNumber = "408000000"
};
<userinfo>
<firstname>Joe</firstname>
<lastname>Doe</lastname>
<email>joe@doe.com</email>
<phone>408000000</phone>
</userinfo>
<userinfo>
<firstname>Joe</firstname>
<lastname>Doe</lastname>
<email></email>
<phone></phone>
</userinfo>
References
Data Contract Surrogate - http://msdn.microsoft.com/en-us/library/ms733064.aspx.