Conditional WCF DataContract Serialization (Using DataContractSurrogate)

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 SurrogatesData 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;
          //Is the user authorized
          if (!IsAuthorized(role)) {
            var proptype = prop.PropertyType;
            //Set the property value to its default value
            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)
{
  //Implement your own Authorization check
  // currentUser.Roles.HasDesiredRole
  return true;
}

Example

//Object Returned by Translator
new UserInformation {
    FirstName = "Joe",
    LastName = "Doe",
    Email = "joe@doe.com",
    PhoneNumber = "408000000"
  };
<!-- Data values received by Level 2 user -->
<userinfo>
    <firstname>Joe</firstname>
    <lastname>Doe</lastname>
    <email>joe@doe.com</email>
    <phone>408000000</phone>
</userinfo>
<!-- Data values received by Level 3 user -->
<userinfo>
    <firstname>Joe</firstname>
    <lastname>Doe</lastname>
    <email></email>
    <phone></phone>
</userinfo>

Download Source Code

Download

References

Data Contract Surrogate – http://msdn.microsoft.com/en-us/library/ms733064.aspx.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s