CommunicationException raised when returning an EF4 POCO from WCF service operation
The following code raises a System.ServiceModel.CommunicationException. It's calling a WCF service operation called Login which returns an EF4 POCO:
var client = new AuthServiceReference.AuthServiceClient();
开发者_Python百科 try
{
Console.Write("Trying to logon...");
var session = client.Login("user", "password"); // throws CommunicationException
Console.WriteLine("done!");
Console.WriteLine("Session ID: {0}. Expires {1}",
session.Id, session.UtcExpires.ToLocalTime());
}
finally
{
client.Close();
}
I've been debugging & searching for hours trying to find out why this happens & how to fix it. What I've found so far:
- This is probably a serialization issue
- When I remove the DataMemberAttribute from the Session class's Owner member, the exception disappears, but this means it won't be serialized.
I'd be grateful if anyone could shed some light on this issue.
Below is the code for the service contract & POCO classes:
[ServiceContract]
public interface IAuthService
{
[OperationContract]
Session Login(string username, string passwordHash);
[OperationContract]
void Logout(Guid sessionId);
}
[DataContract]
public class Session
{
[DataMember]
public Guid Id { get; set; }
[DataMember]
public DateTime UtcCreated { get; set; }
[DataMember]
public DateTime UtcExpires { get; set; }
[DataMember] // serializes correctly if commented out
public virtual User Owner { get; set; }
public static Session Create(User owner)
{
return new Session
{
Owner = owner,
Id = Guid.NewGuid(),
UtcCreated = DateTime.UtcNow,
UtcExpires = DateTime.UtcNow.AddDays(1)
};
}
}
[DataContract]
public class User
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string PasswordHash { get; set; }
[DataMember]
public string PasswordSalt { get; set; }
[DataMember]
public bool IsContributor { get; set; }
[DataMember]
public bool IsConfirmed { get; set; }
[DataMember]
public bool IsAdmin { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public virtual ICollection<Post> Posts { get; set; }
[DataMember]
public virtual ICollection<Comment> Comments { get; set; }
}
Turns out this is a known issue when serializing POCO proxies with WCF. There's an MSDN walkthough that explains how to work around it using System.Data.Objects.ProxyDataContractResolver.
Essentially, you create a new class called ApplyDataContractResolverAttribute and apply it to the service methods returning POCOS:
[ServiceContract]
public interface IAuthService
{
[OperationContract]
[ApplyDataContractResolver]
Session Login(string username, string passwordHash);
}
using System;
using System.Data.Objects;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace WcfExampleBlog.Services
{
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
#region IOperationBehavior Members
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
{
}
public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
{
var dataContractSerializerOperationBehavior =
description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver =
new ProxyDataContractResolver();
}
public void Validate(OperationDescription description)
{
// Do validation.
}
#endregion
}
}
I am presuming that the 'User' class is a custom class? If so, you need to add this just below the ServiceContract attribute:
[KnownType(typeof(User))]
You would also need to setup your [DataMember] and [ServiceContract] attributes on the User class as well.
精彩评论