Introduction
Understanding this article requires understanding some basics regarding WCF Services and various contracts available with the framework.
The MSDN says that a known type is an attribute class defined in the WCF Framework that allows you to specify the types that should be included for consideration during deserialization.
What the preceding statement means is that when communication happens between a WCF service and client by passing parameters and return values, both endpoints share all of the data contracts of the data to be transmitted.
Data Contracts MSDN Definition
A data contract is a formal agreement between a service and a client that abstractly describes the data to be exchanged. That is, to communicate, the client and the service do not need to share the same types, only the same data contracts. A data contract precisely defines, for each parameter or return type, what data is serialized (turned into XML) to be exchanged.
When data arrives at a receiving endpoint, the WCF runtime attempts to de-serialize the data. The type that is instantiated for deserialization is chosen by first inspecting the incoming message to determine the data contract to which the contents of the message conform.
A problem occurs when a service returns a derived type of the data contract instead of the base data contract.
Are you confused? Alright, let us create a scenario where two types of customers exist, one is a normal customer and the other is a classic customer of a customerOrder service.
Here we have a data contract of the customer that will be responsible for the transmission between the client and the service.
Now in one case when we need to return a derived class of a classic customer then WCF de-serialization does not recognize the derived data type and starts giving exceptions.
Sample WCF service
- namespace CustomerOrderService
- {
- [ServiceContract]
- public interface IOrderService
- {
- [OperationContract]
- Customer GetCusomersDetails(int Id);
-
- [OperationContract]
- void AddCustomers();
- }
- }
Data Contract (Base Class: Customer)
- namespace CustomerOrderService
- {
-
- [DataContract]
- public class Customer
- {
- [DataMember]
- public int ID { get; set; }
- [DataMember]
- public string CustomerName { get; set; }
- [DataMember]
- public string Location { get; set; }
- [DataMember]
- public string MobileNo { get; set; }
- public CustomerType customerType { get; set; }
- }
-
- [DataContract]
- public enum CustomerType
- {
- [EnumMember]
- ClaasicCustomers = 1,
- [EnumMember]
- NormalCustomers = 2
- }
- }
Derived Class:
- public class ClassicCustomers : Customer
- {
- public float Discount { get; set; }
- public int RewardPoints { get; set; }
-
- }
-
- public class NormalCustomers : Customer
- {
- public string newOffers { get; set; }
- }
Service Code:
- namespace CustomerOrderService
- {
-
- public class OrderService : IOrderService
- {
- public Customer GetCusomersDetails(int ID)
- {
- Customer cust = Null;
- string Conn = ConfigurationManager.ConnectionStrings["customerConn"].ConnectionString;
-
- using (SqlConnection myconn = new SqlConnection(Conn))
- {
- SqlCommand cmd = new SqlCommand("sp_GetCustomers", myconn);
- cmd.CommandType = CommandType.StoredProcedure;
- SqlParameter param = new SqlParameter();
- param.ParameterName = "@ID";
- param.Value = ID;
- cmd.Parameters.Add(param);
- myconn.Open();
- SqlDataReader reader = cmd.ExecuteReader();
-
- while (reader.Read())
- {
- cust = new ClassicCustomers()
-
- {
- CustomerName = Convert.ToString(reader["CustomerName"]),
- Location = reader["CustomerAddress"].ToString(),
- MobileNo = reader["MobileNo"].ToString(),
- RewardPoints = (int.Parse)(reader["RewardPoints"].ToString())
- };
-
- }
- }
- return cust;
- }
-
- public void AddCustomers(Customer cust)
- {
- throw new NotImplementedException();
- }
- }
- }
ExplanationIn the above example we have a Customer Order service with Base class data contract Customer and two derived classes, normal customer and classic customer. So here our requirement is to get the customer details on the basis of the flag customer type and return derived type.
Here in the above example we are trying to return the Classic customer that is the derived type.
At the client end when the WCF Framework tries to de-serialize the data contract it does not have any knowledge of the derived type and it therefore throws exceptions.
The WCF Framework introduced solution for this problem called the KnownTypeAttribute class.
The KnownTypeAttribute class is recognized by the DataContractSerializer when serializing or deserializing a given type, in other words the known type helps to get a derived class deserialized at the client end.
- [KnownType(typeof(ClassicCustomers))]
- [KnownType(typeof(NormalCustomers))]
-
- [DataContract]
- public class Customer
- {
- [DataMember]
- public int ID { get; set; }
- [DataMember]
- public string CustomerName { get; set; }
- [DataMember]
- public string Location { get; set; }
- [DataMember]
- public string MobileNo { get; set; }
- public CustomerType customerType { get; set; }
- }
The receding is the output from the client using the CustomerOrderService WCF service and the RewardPoints is the property of the derived data contract Classiccustomer meaning that after using the known type attribute the DataContactSerilizer engine of WCF starts recognizing the derived data contact and does allow it to pass to the client giving the desired results.
Ways to Use KnowTypeAttributeThe WCF Framework has provided the control on using the knowtypeattribute when creating the service. The known type attribute can be provided in on of two ways.
- Code Level
- [KnownType(typeof(ClassicCustomers))]
This is the syntax we use with the data contract typeof(Derived Class).
- Known Type Attribute On Base type:
Applying the known type attribute globally to a WCF service means that all the services and operation contracts will use this known type attribute to get de-serialized at the client.
The following is the customer class and the known type is applied at the base level for two serviced classes:
- [KnownType(typeof(ClassicCustomers))]
- [KnownType(typeof(NormalCustomers))]
- [DataContract]
- public class Customer
- {
- [DataMember]
- public int ID { get; set; }
- [DataMember]
- public string CustomerName { get; set; }
- [DataMember]
- public string Location { get; set; }
- [DataMember]
- public string MobileNo { get; set; }
- public CustomerType customerType { get; set; }
- }
- Known Type attribute to Service Contact:
Here control will go to the service level, meaning that applying the known type attribute at the Service level will ensure all the operation contacts will use the known type attribute.
- namespace CustomerOrderService
- {
- [KnownType(typeof(ClassicCustomers))]
- [KnownType(typeof(NormalCustomers))]
- [ServiceContract]
-
- public interface IOrderService
- {
- [OperationContract]
- Customer GetCusomersDetails(int Id);
-
- [OperationContract]
- void AddCustomers(Customer cust);
- }
- }
- Known Type Attribute to Operation contact:
Here we have more granular control on the known type that will be at the operation contact level, meaning that within the service we can specify one operation with a known type and another operation without it.
So one operation contract will use the Known type for de-serialization at the client and another may break if we use a derived type with that.
- namespace CustomerOrderService
- {
- [ServiceContract]
- public interface IOrderService
- {
- [KnownType(typeof(ClassicCustomers))]
- [KnownType(typeof(NormalCustomers))]
- [OperationContract]
- Customer GetCusomersDetails(int Id);
-
- [OperationContract]
- void AddCustomers(Customer cust);
- }
- }
- Configuration Level
In the configuration file we can apply the known type that will be the global way of applying a known type, meaning that all the service and operation contracts will in scope of that known type attribute.
In the configuration file of the WCF service we can apply the known type attribute globally.
- <system.runtime.serialization>
- <dataContractSerializer>
- <declaredTypes>
- <add type="CustomerOrderService.Customer , OrderService , Version=1.0.0.0 , Culture=Neutral , PublicKeyToken=null">
- <knownType type="CustomerOrderService.ClassicCustomers , OrderService , Version=1.0.0.0 , Culture=Neutral , PublicKeyToken=null"/>
- <knownType type="CustomerOrderService.NormalCustomers , OrderService , Version=1.0.0.0 , Culture=Neutral , PublicKeyToken=null"/>
- </add>
- </declaredTypes>
- </dataContractSerializer>
- </system.runtime.serialization>
Conclusion
The Known Type attribute is the solution to the deserialization problem of when a derived type is being used in the service.
Happy Coding and keep learning and sharing.
References
Data Contract Known Types.