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; }
}
Related
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
My solution has a WebAPI project (.net core 3.1, Microsoft.AspNetCore.Mvc) and a (.Net Standard 2.1) class library that defines the data structures.
My Controller takes a post with a single parameter that deserializes mostly correctly
public class apiRequest
{
public RequestData TheData { get; set; }
public Options Options { get; set; }
public apiRequest() { }
}
The RequestData and child objects are defined i a .Net Standard 2.1 class library and added via a nuget package
public class RequestData : IRequestData
{
public int Datum{ get; set; }
...
public List<ComplexItem> ComplexItems { get; set; }
...
}
public class ComplexItem: ItemBase, IComplexItem
{
public ComplexItem() : base() { }
public ComplexItem(Pricing defaultPricing) : base(defaultPricing) { }
[JsonConstructor]
public ComplexItem(Pricing defaultPricing, Pricing selectedPricing) : base(defaultPricing, selectedPricing) { }
}
The problem I am running into is with the defaultPricing is always null when it gets to the controller
public class ItemBase : IItemBase
{
public ItemBase () { }
public ItemBase (Pricing defaultPricing)
{
DefaultPricing = defaultPricing;
}
[JsonConstructor]
public ItemBase (Pricing defaultPricing, Pricing selectedPricing)
{
DefaultPricing = defaultPricing;
SelectedPricing = selectedPricing;
}
#region Pricing
[JsonProperty]
protected Pricing DefaultPricing { get; set; }
public Pricing SelectedPricing { get; set; }
[JsonIgnore]
protected Pricing CurrentPricing
{
get { return SelectedPricing ?? DefaultPricing; }
set { SelectedPricing = value; }
}
[JsonIgnore]
public decimal Cost { get => CurrentPricing?.Cost ?? 0; }
[JsonIgnore]
public decimal Price { get => CurrentPricing?.Price ?? 0; }
#endregion
}
I've tried using [DataContract] and [DataMember] attributes, JsonObject, JsonConstructor, JsonProperty attributes and [Serializable] attribute. (Is there a current best practice on what to use?)
If I read the Json from a file and use Newtonsoft.Json.JsonConvert.DeserializeObject it deserializes correctly with the Json attributes added, but still null in the controller.
It also deserializes in the API properly if I make it public, so it doesn't seem like a problem in the Pricing class itself
After posting I found this Question about making Newtonsoft the default and using MikeBeaton's accepted solution there with Microsoft.AspNetCore.Mvc.NewtonsoftJson package worked so I'll put this as one potential answer for anyone else with this issue. Would still like to know if there is a more correct solution available.
System.Text.Json Serializes Public Properties
As the documentation implies (emphasis mine):
By default, all (read: only) public properties are serialized. You can specify properties to exclude.
I would guess that this was the design chosen because serializing an object is allowing that object to cross barriers of scope and the public scope is the only one that can reliably be assumed.
If you think about it, it makes sense. Lets say, you define a protected property and serialize the object. Then a client picks it up and deserializates that text representation into a public property. What you have designed to be an implementation detail of/to derived types is now accessible outside the scope defined by the modifier.
Apart from simply pointing you to your own answer where Newtonsoft allows this protected property to be serialized, I would suggest you look more intently at your design and why those properties are protected in the first place. It makes sense within the context of your API implementation, but the client can't (shouldn't) be assumed to follow your same inheritance structure (or support inheritance at all). It seems like you might want to define a true DTO to act as the "shape" of your API response and find the right place to transition from your internal types using protected scope to control access and the DTO that can cross the border of the API.
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'm writing a web service and I am attempting to add a response to a Get request. The difficulty is, I have multiple types that need to be returned. So initially I have a base class of:
public abstract class AbstractSource
{
public string name { get; set; }
}
followed by two derivatives:
public class DatabaseSource : AbstractSource
{
}
and
public class WebSource : AbstractSource
{
}
These classes will eventually have more of their own specific elements. In my controller class I have the following test code:
public class DataSourcesController : ApiController
{
AbstractSource[] sources = new AbstractSource[]
{
new WebSource { name="WebPath"},
new DatabaseSource{name="DB Source"}
};
public IEnumerable<AbstractSource> GetAllDataSources()
{
return sources;
}
}
Now when I run this I get a serializationException. Is it even possible to return mulitple types like this?
Sounded like you are trying to use get the data in XML.
The XML Serializer (e.g. the DataContractSerializer) doesn't know how to deserialize AbstractSource into either DatabaseSource or WebSource, and so you will need to snap the [KnownType(...)] attributes on your AbstractSource class:
using System.Runtime.Serialization;
[KnownType(typeof(DatabaseSource))]
[KnownType(typeof(WebSource))]
public abstract class AbstractSource
{
public string name { get; set; }
}
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>.