Change class signature, process old xml serialized instances - c#

Let's assume I have got a class like:
public class SomeObject
{
public Guid InternalId { get; set; }
public string Address { get; set; }
}
I store instances of this object into the ASP.NET profile. It get's XML serialized and everything is fine. Now I want to reduce the size of the profile, and I want to replace the long propertynames by something shorter:
public class SomeObject
{
[XmlElement("id")]
public Guid InternalId { get; set; }
[XmlElement("ad")]
public string Address { get; set; }
}
New objects get serialized just fine, and short, and everything. However: the XmlSerializer cannot deserialize the old XML files. Is there any hook I can apply to change a classes signature, but still be able to deserialize old instances.
I have the eventhandler XmlSerializer_UnknownElement, and then I can set the value of the target property myself, however I only have the value of the element as a string, so I should parse it by myself which is quite error-prone.

Two answers, one I know will work, the other I'm not sure.
1) Implement the IXmlSerializable interface in your class. Its very easy to do, and gives you complete control over how the class is serialized and deserialized.
http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx
2) Not sure if this will work, but try adding another XmlElementAttribute tag to your class properties. It compiles, but I'm not sure if it'll work.
public class SomeObject
{
[XmlElement("InternalId")]
[XmlElement("id")]
public Guid InternalId { get; set; }
[XmlElement("Address")]
[XmlElement("ad")]
public string Address { get; set; }
}

Related

Use DataMember during deserialize but nut during serialize?

I'm having trouble understanding serialization of one of my objects.
Scenario:
I'm receiving data from a service which is in a given format. I want to take the data in as is. Due to naming conventions I have to use DataMember properties to match the incoming data to properly named class properties. I use System.Runtime.Serialization for this. Example:
[DataContract]
public class IncomingData
{
[DataMember(Name = "$Filename")]
public string Filename { get; set; }
}
This works fine and the data is mapped to the internal property name.
At some point I have to serialize this object again and I naively thought that it would serialize to the internal property name e.g.
{ "Filename":"C:\temp\lala.txt"}
however that is not true and the original propertyname "$Filename" is used instead. I assume this is because DataMember works both ways.
Is there an elegant way to have this object serialize to the propertynames and ignore DataMember? I tried if using a different serialization library works (JSON.NET) but it also seems to follow DataMember.
Do I have to wrap this object to another to acchieve?
Thanks for any hints!
blu
You could define an interface for keeping these objects in sync...
public interface IData
{
string Filename { get; set; }
}
// deserialize me.
[DataContract]
public class IncomingData : IData
{
[DataMember(Name = "$Filename")]
public string Filename { get; set; }
}
// serialize me.
public class Data : IData
{
public string Filename { get; set; }
}
...or you could use virtual properties and override them with the serialization attributes...
// serialize me.
class Data
{
public virtual string Filename { get; set; }
}
// deserialize me.
[DataContract]
class IncomingData : Data
{
[DataMember(Name = "$Filename")]
public override string Filename { get => base.Filename; set => base.Filename = value; }
}
...both of these methods would require the use of a mapper like AutoMapper to clone the IncomingData into the attribute-free Data class...
mapper.Map<IncomingData, Data>(user);
...so I appreciate this feels less than ideal.

Deserialize object using JSON.NET, but put some properties into a member of the class

I am writing a set of data structures to ingest third-party JSON into (no writing out) using JSON.NET.
I have a case for reading some of the top-level JSON elements into a member object of the object being deserialized into.
My JSON:
{
"Id":1
"Checksum":42
"Name":"adam",
"Hair":true
}
My ideal object structure:
public class EntityHeader
{
int Id { get; set; }
int Checksum { get; set; }
}
public class Entity
{
[HeroicJsonAttribute( "Id", "Checksum" )]
public EntityHeader Header { get; set; }
public string Name { get; set; }
public bool Hair { get; set; }
}
Is there a simple way to achieve this? I will have a number of types which will need this, and I'd hate to have to write a JsonConverter for each.
This question has been asked before, here, but the accepted answer doesn't address the question.
Thanks!
An alternative approach would be to use an EntityHeader field in the Entity class as a backing store for private properties which can be deserialized into:
public class EntityHeader
{
int Id { get; set; }
int Checksum { get; set; }
}
public class Entity
{
private EntityHeader m_Header = new EntityHeader();
public EntityHeader Header { get { return m_Header; } }
[JsonProperty]
private int Id { set { m_Header.Id = value; } }
[JsonProperty]
private int Checksum { set { m_Header.Checksum = value; } }
public string Name { get; set; }
public bool Hair { get; set; }
}
Thus, all the properties in the JSON can be read straight into the Entity object, but consumers of Entity objects have access to a "nicely encapsulated" EntityHeader property.
I haven't tested this, and it may even be kludgey, but it would technically work for me (OP). I am still interested in other answers!
Base on your example you could either; use the adapter pattern:
public class EntityJson
{
int Id { get; set; }
int Checksum { get; set; }
public string Name { get; set; }
public bool Hair { get; set; }
}
// quick/poor example
public class EntityAdapter : IEntity
{
public EntityAdapter(EntityJson model)
{
Header = new Header(); // and populate this objects fields
Name = model.Name; // populate other properties
}
public EntityHeader Header { get; set; }
public string Name { get; set; }
public bool Hair { get; set; }
}
Or abuse the fact that json.net ignores properties not available:
var entity = JsonConvert.Deserialze<Entity>();
var header = JsonConvert.Deserialize<EntityHeader>();
entity.Header = header;
I'm going to go ahead and post this answer which is a little bit too long for a comment, so please take this more as an extended comment than an actual attempt to answer your specific question. And of course, you know your requirements best so this is just my considered opinion :)
With that in mind, my advice is:
Don't do this.
I would instead create a simple DTO class that has a 1-1 relationship to the JSON being received; and I'd put all my validation attributes on the properties of that class.
Once I had deserialised the JSON into this simple DTO, I would then use a mapping layer of some kind (roll your own or use Automapper, etc) to map this DTO into a more meaningful structure such as your Entity class.
My reasoning behind this is because unless your Entity class is itself only a simple DTO (in which case it should be as simple as possible and ideally not be a composite) you are mixing OOP and concerns with data mapping concerns; whilst this in and of itself is not such a bad thing, it only serves to increase the complexity of your code.
Consider for example if your incoming JSON ends up with 30 or 40 properties, and you manage to figure out a way (maybe adapting some of the nice techniques from the other answers) to map it to the Entity class. But what about when something goes wrong - it's going to be much easier to reason about, and therefore debug, a process which you have much more control over; it's also going to be much easier to make special adaptations for odd edge cases where the serialiser behaviour just can't help you out
Granted it's a bit of work to write and maintain these DTOs but not that much - Webtools already does this for you
Reference: At the boundaries, Applications are not Object-Oriented

wcf newbie question: arrays as properties

I have DataContract class which has property of type List<AnotherObject>. AnotherObject is also marked with DataContract. For some reason this property comes from wcf service as null, althought I fill it at the server. Is that by design?
Here you go. Class definitions:
[DataContract]
public class UserInfo
{
[DataMember]
public decimal UserID
{
get;
protected internal set;
}
[DataMember]
public string UserName
{
get;
protected internal set;
}
[DataMember]
public string Pswd
{
get;
protected internal set;
}
[DataMember]
public List<decimal> RoleID
{
get;
protected internal set;
}
List<UserRole> userRolesTable = new List<UserRole>();
[DataMember]
public List<UserRole> UserRoles
{
get
{
return userRolesTable;
}
protected internal set { }
}
}
[DataContract]
public class UserRole
{
[DataMember]
public decimal ROLEID { get; internal set; }
[DataMember]
public string ROLE_CODE { get; internal set; }
[DataMember]
public string ROLE_DESCRIPTION { get; internal set; }
[DataMember]
public decimal FORMID { get; internal set; }
[DataMember]
public string FORMCODE { get; internal set; }
[DataMember]
public string FORMNAME { get; internal set; }
}
UserRoles property comes as null.
Why are you letting the RoleId property be auto-implemented but not UserRoles? The code as-is won't work because you have an empty setter. You should probably just use an auto-property for it:
[DataMember]
public List<UserRole> UserRoles
{
get; set;
}
Or at least provide a meaningful setter. You setter does nothing, hence the de-serializer can't populate the value.
List<UserRole> userRolesTable = new List<UserRole>();
[DataMember]
public List<UserRole> UserRoles
{
get
{
return userRolesTable;
}
protected internal set { }
}
Your setter is empty. Put some
userRolesTable = value;
Another thing, your DataContract properties should have public setters.
Your Setter on the UserRoles property is set to internal. Because the WCF framework will be setting the property, it gives up assigning the value because it is listed as internal.
http://connect.microsoft.com/data/feedback/details/625985/wcf-client-entities-with-internal-setters-and-internalsvisibletoattribute-on-asmbly-fail
You can do what this link suggests, using the InternalsVisibleToAttribute attribute on that property, but I have never used it.
update
What I am trying to say is that I bet the Serialization works fine, the WCF framework is unable to insert the deserialized value into the host code because based upon the data contract, the internal Setter section of the property is inaccessible. use the InternalVisibleTo attribute to inform the WCF serialization framework access to the setter of the client version of your data contract object.
You need to Implement the setter...
protected internal set { userRolesTable = value; }
Basically, its a serialization problem. I had this problem in my code in the past, but it has been a while, so bear with me.
First, we need to find out if the object relations are null before the WCF call, so put a debug before and after.
If the object is being returned as null before the call, you have a few options:
You can explicitly use .Include("AnotherObject") on your DbContext to get the object. I used this by having my Read method take an array of strings which I used to include all the necessary objects. This is more ideal than automatically taking all objects because during serialization, if you take everything, you could fairly easily end up with your entire database being serialized, which introduces performance and security issues, among other things.
Another option is to use a dynamic proxy by adding the keyword virtual in front of your list. The DataContractSerializer, though, has a problem serializing dynamic proxies, so you will need to implement an attribute that uses the ProxyDataContractResolver instead of DataContractResolver. This attribute needs to be applied on all OperationContracts that can pass a dynamic proxy. This will automatically take ALL object references, which is probably bad coding practice, so I do recommend the above method.
public class ApplyDataContractResolverAttribute : Attribute, IOperationBehavior
{
public ApplyDataContractResolverAttribute() { }
public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters) { }
public void ApplyClientBehavior(OperationDescription description, System.ServiceModel.Dispatcher.ClientOperation proxy)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void ApplyDispatchBehavior(OperationDescription description, System.ServiceModel.Dispatcher.DispatchOperation dispatch)
{
DataContractSerializerOperationBehavior dataContractSerializerOperationBehavior = description.Behaviors.Find<DataContractSerializerOperationBehavior>();
dataContractSerializerOperationBehavior.DataContractResolver = new ProxyDataContractResolver();
}
public void Validate(OperationDescription description) { }
}
Edit: Also I think you can have setters in Data Contracts not be public, because I do it and it works fine :). But I would try making your setter public first, then solving the problem, then reverting to a protected setter, just so that you are dealing with as few variables at a time as possible.

Why does the proxy generated code create a new class when a MessageContract is in my WCF Service?

I have created two WCF Services (Shipping & PDFGenerator). They both, along with my ClientApp, share an assembly named Kyle.Common.Contracts. Within this assembly, I have three classes:
namespace Kyle.Common.Contracts
{
[MessageContract]
public class PDFResponse
{
[MessageHeader]
public string fileName { get; set; }
[MessageBodyMember]
public System.IO.Stream fileStream { get; set; }
}
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
[DataContract(Name = "TrackResult", Namespace = "http://kyle")]
public class TrackResult
{
[DataMember]
public int SeqNum { get; set; }
[DataMember]
public int ShipmentID { get; set; }
[DataMember]
public string StoreNum { get; set; }
}
}
My PDFGenerator ServiceContract looks like:
namespace Kyle.WCF.PDFDocs
{
[ServiceContract(Namespace="http://kyle")]
public interface IPDFDocsService
{
[OperationContract]
PDFResponse GeneratePDF(PDFRequest request);
[OperationContract]
void GeneratePDFAsync(Kyle.Common.Contracts.Enums.PDFDocumentNameEnum docType, int? pk, string[] emailAddress);
[OperationContract]
Kyle.Common.Contracts.TrackResult[] Test();
}
}
If I comment out the GeneratePDF stub, the proxy generated by VS2010 realizes that Test returns an array of Kyle.Common.Contracts.TrackResult. However, if I leave GeneratePDF there, the proxy refuses to use Kyle.Common.Contracts.TrackResult, and instead creates a new class, ClientApp.PDFDocServices.TrackResult, and uses that as the return type of Test.
Is there a way to force the proxy generator to use Kyle.Common.Contracts.TrackResult whenever I use a MessageContract? Perhaps there's a better method for using a Stream and File Name as return types?
I just don't want to have to create a Copy method to copy from ClientApp.PDFDocServices.TrackResult to Kyle.Common.Contracts.TrackResult, since they should be the exact same class.
After a lot of extra digging, I realize that it was actually the Enum that "broke" it. It has do with the way DataContractSerializer works vs. XmlSerializer. Long story short, the solution was to turn the Enum into a nullable.
[MessageContract]
public class PDFRequest
{
[MessageHeader]
public Enums.PDFDocumentNameEnum? docType { get; set; }
[MessageHeader]
public int? pk { get; set; }
[MessageHeader]
public string[] emailAddress { get; set; }
[MessageBodyMember]
public Kyle.Common.Contracts.TrackItResult[] trackItResults { get; set; }
}
I ran into the same problem (MessageContract+enums) and your post helped me. Indeed if you explicitly set the enum fields to nullable it works. The issue is that when enums are used, WCF uses the XML serializer which cannot tell null from empty string.
There is a detailed explanation of this behaviour here by one of the actual WCF team members.
In the case of document/literal when using bare messages, WCF falls back to XmlSerializer when handling enum types. ... XmlSerializer treats null as missing by default ... we encounter a schema without nillable="true" ... The detection logic for value types currently only handles primitive value types, it does not check for enums.
In other words WCF does not like enums... But hey, it works, you just need to be aware of it!
You can instruct Visual Studio to re-use classes from referenced assemblies. So if your test project has an assembly reference to the Kyle.Common.Contracts assembly, it should re-use those types defined in there rather than adding new client-side proxy classes.
The switch to enable this is on the Advanced page in the Add Service Reference dialog window - it should be on by default:
Make sure that your project
has an assembly reference to the common data contract assembly
that this setting is really ON when you add the service reference

How to add an attribute to a serialized XML node?

Assume I have a C# class like this:
[XmlRoot("floors")]
public class FloorCollection
{
[XmlElement("floor")]
public Floor[] Floors { get; set; }
}
And I want to serialize it and send to a REST API using WCF. But before sending I need adding an attribute to the floors node in this way: <floors type="array">...</floors>
Any idea?
Just add the type attribute into your collection class:
[XmlRoot("floors")]
public class FloorCollection
{
[XmlAttribute("type")]
public string Type { get; set; }
[XmlElement("floor")]
public Floor[] Floors { get; set; }
}
If you mean adding that without the business code knowing about it, then you'll probably have to use Message Inspectors to modify the message before it is sent.

Categories

Resources