After a major refactoring of our client/service system written in C# with WCF services, we have moved all the service interface contracts to a new namespace, i.e from something like this:
namespace Old.Framework.Name
{
[DataContract]
public class ServiceEntriesResult
{
[DataMember]
public string SomeData { get; set; }
}
[ServiceContract]
public interface IMyService
{
[OperationContract]
ServiceResult GetServiceData();
}
}
To this:
namespace New.Framework.Name
{
[DataContract]
public class ServiceEntriesResult
{
[DataMember]
public string SomeData { get; set; }
}
[ServiceContract]
public interface IMyService
{
[OperationContract]
ServiceResult GetServiceData();
}
}
The only difference is the namespace, but it affects all parameter and return data structures as well as the service interface.
Now, we would very much like to keep new clients compatible with old services. At the moment, if I try to read from an old service with my new client, I get a null result. I can see that the correct service call is triggered on the server side, so it obviously manages to map something right, but the return data object is null.
Any ideas on how to solve this? Is this at all possible?
Looking at
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/data-contract-names
And
DataContractSerializer compatibility after namespace changed
It appears that the default namespace is generated as:
http://schemas.datacontract.org/2004/07/My.Namespace
So in my case, i need to add the following to my DataContract attribute:
namespace New.Framework.Name
{
[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/Old.Framework.Name")]
public class ServiceEntriesResult
{
[DataMember]
public string SomeData { get; set; }
}
[ServiceContract]
public interface IMyService
{
[OperationContract]
ServiceResult GetServiceData();
}
}
This seems to work nicely in my case, so I just need to go through all my DataContracts and add this namespace
Related
I'm trying to serialize enum values which potentially do not exist yet.
I have an existing project which has several enums in our datacontract for simplicity reason I display one like so:
public partial class TestDTO : ITestDTO
{
public DeleteMe DeleteMeEnum { get; set; }
}
[DataContract]
public enum DeleteMe
{
[EnumMember]
Deleted = 0,
}
Our application has a hidden internal wcf layer which our public web api accesses. A sample Service contract looks like so:
[ServiceContract]
public interface ITestService
{
[OperationContract]
TestDTO GetTestDTO();
}
public class TestService : ITestService
{
public TestDTO GetTestDTO()
{
return new TestDTO() { DeleteMeEnum = (DeleteMe)2 };
}
}
When I call this method from WebApi obviously I get the classic error:
Enum value '2' is invalid for type 'DeleteMe' and cannot be serialized. Ensure that the necessary enum values are present and are marked with EnumMemberAttribute attribute if the type has DataContractAttribute attribute.
I can't go and change all of the enums now because we have a massive project, and replacing them would be too much, Also replacing all of our Service Contracts with a new Attibute would be too much.
Does anyone know of a way I can fix this globally, such as replacing the default XMLSerializer with a custom XMLSerializer?
There isn't a nice way to handle this once your application is released. However if you plan for the situation ahead of time, it can be handled.
So for the example above, you can do this:
public partial class TestDTO : ITestDTO
{
[DataMember(Name = "DeleteMeEnum")]
private string DeleteMeEnumString
{
get { return DeleteMeEnum.ToString(); }
set {
DeleteMe _enum;
if (!Enum.TryParse(value, out _enum))
{
_enum = <default value>;
}
DeleteMeEnum = _enum;
}
}
[IgnoreDataMember]
public DeleteMe DeleteMeEnum { get; set; }
}
I have a class which is placed in a class library:
public class UserProfile
{
public int UserId { get; set; }
public string UserName { get; set; }
}
Then I have a repository class:
public class Repository
{
public List<UserProfile> GetUsers()
{
using (var context = new DBContext())
{
List<UserProfile> list = context.UserProfiles.ToList();
return list;
}
}
}
business logic class:
public class BusinessLogic
{
public List<UserProfile> GetUserProfiles()
{
Repository repo = new Repository();
List<UserProfile> list = repo.GetUsers().ToList();
return list;
}
}
and finaly WCF:
public interface IService1
{
[OperationContract]
List<UserProfile> GetUserProfiles();
}
public class Service1 : IService1
{
public List<UserProfile> GetUserProfiles()
{
BusinessLogic.BusinessLogic bl = new BusinessLogic.BusinessLogic();
List<UserProfile> list = bl.GetUserProfiles().ToList();
return list;
}
}
Whenever I try to get user profiles from wcf, it returns empty list.
However, if I skip wcf and get List<UserProfile> straight from businesslogic, it works perfectly fine.
I tried to debug. Results: when inside wcf it gets list from businesslogic, it's already empty. But as I said earlier, business logic works perfectly fine (returns necessary information).
There were similar posts but none of them did help me.
How can I make my WCF return a list filled with necessary information?
P.S. I do not want to add a copy of my class UserProfile into wcf with [DataContract] flag
Your object must either be serializable or decorated with that DataContract attribute. Your return type from WCF must also be decorated with the DataContract attribute, and the member containing your List must be marked with the DataMember attribute. This is required by WCF's DataContractSerializer in order to properly serialize the data and return it to the consumer. Converting a class for transmission over the wire requires serialization. There isn't a practical way to get around this with WCF.
Your list is empty because your UserProfile class cannot be serialized.
EDIT:
I just saw you are simply returning a list, which is already serializable, so if you just make your UserProfile class serializable or decorate it with the appropriate DataContract/DataMember classes, it will start working fine.
I have a custom datatype I put in a class Library SharedTypes
namespace SharedTypes
{
public class District
{
public long Id { get; set; }
public string Name { get; set; }
}
}
I then have a WCF server with this service
using System.ServiceModel;
using SharedTypes;
namespace WCF.WCFInterfaces
{
[ServiceContract]
public interface IWcfService
{
[OperationContract]
District GetDistrict(long id);
[OperationContract]
void CreateDistrict(District district);
[OperationContract]
List<District> GetDistricts();
}
}
On the client side I have a Interface (So I inject the implementation)
using SharedTypes;
namespace WcfInterfaces
{
public interface IDistrictManager
{
void CreateDistrict(District district);
District GetDistrict(long id);
List<District> GetDistricts();
}
}
I finally have the implementation the client should use
public class DistrictManager : IDistrictManager
{
private readonly WcfServiceClient _salesService;
public DistrictManager()
{
_salesService = new WcfServiceClient();
}
public void CreateDistrict(District district)
{
_salesService.CreateDistrictAsync(district);
}
public District GetDistrict(long id)
{
return _salesService.GetDistrict(id);
}
public List<District> GetDistricts()
{
var list = _salesService.GetDistricts();
return list.ToList();
}
}
But here the problem arises, this implementation expects to use a version of District it gets from the service reference
WcfClientLibrary.SalesService.District
Instead of
SharedTypes.District
They are the same, but VS dont know that
So I get errors that the interface is not properly implemented because I have 2 different types of the District class.
How can I get the Service reference to use the SharedTypes.District instead? Or is it my way of implementing it that is way off?
Right click your service reference in client project and check "Reuse Types in Referenced Assemblies".
Be sure that you have added SharedTypes.District to your client service reference project.
When adding your WCF reference on the client side. Click on the advanced options.
There is a setting that you can specify to tell it to re-use types from specified assembly(s).
You'll be able to specify the assembly(s).
In my solution I have a create a WCF service which takes message contract class as parameter.
[ServiceContract]
public interface IServiceClass
{
[OperationContract]
Employee AddEmployee(Employee employee);
}
public class ServiceClass : IServiceClass
{
public Employee AddEmployee(Employee employee)
{
//do something
}
}
[MessageContract]
public class Employee
{
[MessageHeader]
public string EmployeeNumber { get; set; }
[MessageBodyMember]
public string FirstName { get; set; }
[MessageBodyMember]
public string LastName { get; set; }
[MessageBodyMember]
public DateTime DOB { get; set; }
}
Then I am adding this service to my client application using Add service reference option. The service added successfully.
Then in my client program I used this method. But the method signature has been changed. It changed as follows.
public void AddEmployee(ref string EmployeeNumber,
ref System.DateTime DOB, ref string FirstName, ref string LastName)
I am using the service in the client as follows.
TcpServiceReference.ServiceClassClient service =
new TcpServiceReference.ServiceClassClient();
NOTE:TcpServiceReference is the service name
I cant understand the reason.
Not exactly sure why WCF does this some times, but if you use the service interface, you'll get the "correct" method signatures:
TcpServiceReference.IServiceClass service =
new TcpServiceReference.ServiceClassClient();
(this is assuming that IServiceClass is the client-side name of the service interface that svcutil (Add Service Reference) has generated for you.)
Have you solved the problem?
Looks like the proxy is not generated successfully, unless you also have a AddEmployee method that returns void (and takes four parameters)?
I would recommend that you re-generate (delete and create) the service reference.
Cheers
--Jocke
I got an Employee class and each employee has a list of applied leaves. Is it possible to have the list AppliedLeave as a [DataMember] in WCF?
[DataContract]
public class Employee
{
[DataMember]
public string UserID { get; set; }
[DataMember]
public int EmployeeNumber { get; set; }
[ForeignKey("EmployeeUserID")]
[DataMember]
public List<Leave> AppliedLeave
{
get { return _appliedLeaves; }
set { _appliedLeaves = value; }
}
private List<Leave> _appliedLeaves = new List<Leave>();
...
}
Is there any other way to do this?
thank you for your consideration of this matter
I extend my Question
This is my Leave Class:
[DataContract]
public class Leave
{
[Key()]
[DataMember]
public Guid LeaveId { get; set; }
[DataMember]
public string LeaveType { get; set; }
[DataMember]
public DateTime StartDate { get; set; }
[DataMember]
public string EmployeeUserID { get; set; }
}
this shows ServiceContract ---->
[ServiceContract]
public interface IEmployeeService
{
[OperationContract]
Employee GetEmployeeByUserId(string userId);
[OperationContract]
void AssignSupervisor(string userId, string supervisorUserId);
[OperationContract]
void DeleteEmployeeByUserId(string userId);
....
}
In Client application,
EmployeeServiceClient employeeService = new EmployeeServiceClient();
Employee employee = employeeService.GetEmployeeByUserId(id);
But when Employee gathered from the service its shows Null for leaves,
Can somebody help me? what have I done wrong here?
Yes, it is possible to return generics from WCF service operations.
But by default they are casted to Array on client side. This can be customized while proxy generation.
WCF: Serialization and Generics
Also you have to decorate the service with all the types to which generics can be resolved, using KnownTypeAttribute.
Known Types and the Generic Resolver
I also found my server side list would always arrive at the client as a null pointer. After browsing around a lot for this problem it strikes me it is nearly always denied at first ("your code should work")
Found the issue.. I had configured my solution using one "WCF Service" project and one "Winforms app" project with a generated service reference. Both interface and implementation of Service1 were in the WCF service project, as expected. But any list member returned null.
When I put my IService1.cs = the interface only = in a separate class library instead, reference the class library on both sides (using) and generate the service reference again, my list does work ! The generated code on the client side looks much simpler.
I did not need any special attributes, change service reference configuration, or interface references for this.
You could use IList<T> instead of List<T>.