XML Serialization of Unknown Attributes in .NET Core - c#

Over the past day or so I have been upgrading my application to .NET Core, netcoreapp1.0, from net451. Most of the process went very well and everything is ported over minus one issue I am seeing.
I was using System.Xml and System.Xml.Serialization to serialize XML from an API endpoint (which I have absolutely no control over). It was working well, but, due to an issue with the XML, there was always one attribute that came back as an UnknownAttribute. I handled it by wiring to the UknownAttribute event like this:
XmlReader reader = XmlReader.Create(stream);
var serializer = new XmlSerializer(typeof(PersonDetails));
var personDetails = (PersonDetails)serializer.Deserialize(reader);
serializer.UnknownAttribute += new XmlAttributeEventHandler((object sender, XmlAttributeEventArgs e) => {
unknownAttr.Add(e.Attr.Name, e.Attr.Value);
});
// Handle the missing map value if it exists.
string addressValue;
if(unknownAttr.TryGetValue("Address_x0_", out addressValue))
{
personDetails.Address = addressValue;
}
With the upgrade to .NET Core, I now use the System.Xml.XmlSerializer 4.0.11 library. Now, it appears that the UnknownAttribute event is no longer part of the XmlSerializer anymore. I have been unable to find another way to handle unknown attributes and would appreciate a pointer in the right direction.

Rather than using an unknown attribute event, you can add a public XmlAttribute[] XmlAttributes property to your PersonDetails, mark it with [XmlAnyAttribute], and handle the unknown attribute there:
public class PersonDetails
{
[XmlAttribute]
public string Address { get; set; }
[XmlAnyAttribute]
public XmlAttribute[] XmlAttributes
{
get
{
return null;
}
set
{
if (value != null)
{
foreach (var attr in value)
if (attr.Name == "Address_x0_")
Address = attr.Value;
}
}
}
}
As you found, you will need to add https://www.nuget.org/packages/System.Xml.XmlDocument/ to make use of XmlAttribute in .NET Core.

Related

Trying to add subclass to a Fhir.Net Resource and have it serialize?

Because a partner we are trying to setup FHIR communications is using an in-between version of the FHIR schema, they are sending and expecting a Practitioner/PractitionerRoleComponent that has an organization element, instead of an managingOrganization which the FHIR.NET API is expecting.
I've subclassed the Practitioner and PractitionerRoleComponent and got the objects creating fine, so Practitioner now has a custom "THXPractitionerRole" in our case. No errors are encountered, I have all attributes in place in the subclass (see below).
However, when I serialize to XML, the results have no PractitionerRole at all -- it almost seems like the serializer is just totally ignoring it. I'm going to guess that the FHIR.Net Serializers have some sort of check to make sure they are only serializing valid FHIR types? Or is there anything I'm missing from the subclass that might be preventing it from working?
The API I'm talking about is here: https://github.com/ewoutkramer/fhir-net-api/tree/develop
The goal is to be able to have a Practitioner/practitionerRole/organization element in the resulting XML/Json.
[FhirType("Practitioner", IsResource = true)]
[DataContract]
public partial class THXPractitioner : Hl7.Fhir.Model.Practitioner, System.ComponentModel.INotifyPropertyChanged
{
[FhirElement("practitionerRole", Order = 170)]
[Cardinality(Min = 0, Max = -1)]
[DataMember]
public List<THXPractitionerRoleComponent> THXPractitionerRole
{
get { if (_PractitionerRole == null) _PractitionerRole = new List<THXPractitionerRoleComponent>(); return _PractitionerRole; }
set { _PractitionerRole = value; OnPropertyChanged("PractitionerRole"); }
}
private List<THXPractitionerRoleComponent> _PractitionerRole;
[FhirType("PractitionerRoleComponent")]
[DataContract]
public partial class THXPractitionerRoleComponent : Hl7.Fhir.Model.Practitioner.PractitionerRoleComponent, System.ComponentModel.INotifyPropertyChanged, IBackboneElement
{
[NotMapped]
public override string TypeName { get { return "PractitionerRoleComponent"; } }
/// <summary>
/// Organization where the roles are performed
/// </summary>
[FhirElement("organization", Order = 40)]
[References("Organization")]
[DataMember]
public ResourceReference organization
{
get { return _organization; }
set { _organization = value; OnPropertyChanged("organization");}
}
private ResourceReference _organization;
}
Here's where it gets called:
fhirpractitioner.THXPractitionerRole = new List<Model.THXPractitioner.THXPractitionerRoleComponent>()
{
new Model.THXPractitioner.THXPractitionerRoleComponent()
{
Extension = new List<Extension>()
{
new Extension()
{
Url = "[My Url]",
}
},
organization = new ResourceReference()
{
Reference = "asdfasfd"
,Display = "organization"
,DisplayElement= new FhirString("organization")
}
}
};
Thanks.
A colleague of mine ended up finding this "issue" on GitHub for the project:
https://github.com/ewoutkramer/fhir-net-api/issues/337
So, I ended up taking a copy of the library and following the ideas suggested in the issue thread and recompiled. We now have a custom library.
From the issue:
the only way to handle custom resources currently is to create a StructureDefinition for it, add it to the profiles-resources.xml file in the Model/Source directory, and rerun the T4 templates - you'll get your own version of the .NET library, with a POCO for your own resources....
I did this, had to delete the Generated/Practitioner.cs file before running Template-Model.tt via the Run Custom Tool context menu option in Visual Studio. Once that completed, the Practitioner.cs file was generated with our new/custom resource and the Library was able to serialize it into the XML we needed.
Since there is no official FHIR release that has what you need, and therefore no version of the library that you can use, we think your best option would be to fork the source of the library (see https://github.com/ewoutkramer/fhir-net-api). You can then lookup other resources to see the code for their components, and alter the Practitioner to include the PractitionerRoleComponent. Build the solution, and you will be able to use that as your library instead of the official one.

XAML serialization - specifying a property is required

I'm trying to use XAML to serialize/deserialize some custom (non-WPF/UI) information, and would like to enforce that certain properties are required.
XAML deserialization, by default, just creates each object with the default constructor, and then sets any properties it finds in the element's attributes, or in property element syntax. Any properties of the underlying object which are not specified in the XAML being serialized are just left as they are, i.e. whatever value they got after construction.
I'd like to know the best way of specifying that a certain property must be present in the XAML - and if not, have the deserialization fail.
I was expecting an attribute of some kind, but I can't find anything.
There are certain types in WPF which do exhibit this behaviour, but presumably WPF uses its own custom way of enforcing this. For example if you have..
<Setter Property="Height" ></Setter>
..the designer will complain 'The property "Value" is missing'.
I can think of some pretty convoluted ways of doing this:
Have each property setter somehow record it was called, and then have custom code run after deserialization which checks that all 'required' properties were actually set.
Use nullable properties everywhere, and then check after deserialization if any 'required' ones are still null. (Of course this won't work if null is a valid thing to set something to!)
Perhaps there's a way of writing a custom XamlObjectWriter which can check for some [Required] attribute on the object's properties, and fail if these are not found by the XamlReader.
These all sound like a lot more work than I hoped - and I'm sure there's a more obvious way. Does anyone have any other ideas, or experience of (and maybe a solution to) this problem?
I know this is old, but I ran across the question while looking for a way to do this shortly before I figured it out. Maybe it'll help someone else. Fortunately this is easy: implement ISupportInitialize. I'm using WPF but it should work anywhere.
public class Hero : ISupportInitialize
{
public string Who
{ get; set; } = string.Empty;
public string What
{ get; set; } = string.Empty;
public string Where
{ get; set; } = string.Empty;
public void BeginInit()
{
// set a flag here if your property setters
// have dependencies on other properties
}
public void EndInit()
{
if (string.IsNullOrWhiteSpace(Who))
throw new Exception($"The property \"Who\" is missing");
if (string.IsNullOrWhiteSpace(What))
throw new Exception($"The property \"What\" is missing");
// Where is optional...
}
}
Who and What are required, but What is missing on the second entry:
<Window.Resources>
<local:Hero x:Key="Badguy" Who="Vader" What="Sith" Where="Death Star"/>
<local:Hero x:Key="Goodguy" Who="HanSolo" Where="Millenium Falcon"/>
</Window.Resources>
In the VS2017 XAML markup editor:
I was facing a similar problem recently. After not being able to find any straightforward way, I decided to hook into the events of XamlObjectWriter to add custom support for this. It was basically what you suggested in point 3, except it turned out not really that complicated.
Basically it works like this: a dictionary is kept where each deserialized object is mapped to a set of remaining required properties. The BeforePropertiesHandler fills this set for the current object with all its properties with the RequiredAttribute. The XamlSetValueHandler removes the current property from the set. Finally, the AfterPropertiesHandler makes sure that there are no required properties left not set on the current object and throws an exception otherwise.
class RequiredAttribute : Attribute
{
}
public T Deserialize<T>(Stream stream)
{
var requiredProperties = new Dictionary<object, HashSet<MemberInfo>>();
var writerSettings = new XamlObjectWriterSettings
{
BeforePropertiesHandler = (sender, args) =>
{
var thisInstanceRequiredProperties = new HashSet<MemberInfo>();
foreach(var propertyInfo in args.Instance.GetType().GetProperties())
{
if(propertyInfo.GetCustomAttribute<RequiredAttribute>() != null)
{
thisInstanceRequiredProperties.Add(propertyInfo);
}
}
requiredProperties[args.Instance] = thisInstanceRequiredProperties;
},
XamlSetValueHandler = (sender, args) =>
{
if(!requiredProperties.ContainsKey(sender))
{
return;
}
requiredProperties[sender].Remove(args.Member.UnderlyingMember);
},
AfterPropertiesHandler = (sender, args) =>
{
if(!requiredProperties.ContainsKey(args.Instance))
{
return;
}
var propertiesNotSet = requiredProperties[args.Instance];
if(propertiesNotSet.Any())
{
throw new Exception("Required property \"" + propertiesNotSet.First().Name + "\" not set.");
}
requiredProperties.Remove(args.Instance);
}
};
var readerSettings = new XamlXmlReaderSettings
{
LocalAssembly = Assembly.GetExecutingAssembly(),
ProvideLineInfo = true
};
using(var reader = new XamlXmlReader(stream, readerSettings))
using(var writer = new XamlObjectWriter(reader.SchemaContext, writerSettings))
{
XamlServices.Transform(reader, writer);
return (T)writer.Result;
}
}

c# how to use system reserved names as properties [duplicate]

I have never used Web API before, but I need a web service that will accept/return JSON objects and using this seemed like a reasonable thing. It looked pretty simple (if not a bit of overkill for my purposes), but a data structure I need to deal with looks something like:
{
"values":["foo", "bar"],
"default":"bar"
}
And so I went to make a Model object:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
Problem is that default seems to be a protected keyword. There must be some way to get around that, right?
You can use keywords in C# as identifiers by prepending # in front of them.
I would suggest to go different way. Keep your C# object model as much standard as possible (I wouldn't use # sign and C# keywords as property name).
We can separate the serialized (JSON) world and C# objects - just by using the Json.NET features.
One of the simpliest to use is decoration with Attribute:
[JsonProperty(PropertyName = "default")]
public string DefaultValue { get; set; }
In this case we have to reference Newtonsoft.Json in the project. If it must be POCO, we can introduce CustomResolver derrived from DefaultContractResolver and define these conversions there...
But separation of concern in this case is a bit more pure solution, I would say
EDIT: JSON Contract Resolver draft (see comments)
Important NOTE: Newtonsoft.Json is part of the Web API. Not only it is an open source, but even MS team bet on that as a core JSON serializer.
1) Newtonsoft.Json (as a part of the Web.API) is already installed in your solution. So you do not have to downloaded (nuget) separately. It would always be in your packages folder. So, to use the attribute is just adding the reference. It is there...
2) There is a small draft how to do the attribute stuff, while keeping the POCO. As I've tried explain here: POCO's, behavior and Peristance Igorance, to keep POCO (e.g. we do profit from layered Architecture with NHibernate on a data layer), we can replace attributes with a Contract Resolver. Our POCO library does not have to reference anything
We just have to do extend the service layer:
public class MyResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return jProperty;
}
// just adjust in case if Property name is DefaultValue
var isDefaultValueProeprty =
propertyInfo.Name.Equals("DefaultValue");
if(isDefaultValueProeprty)
{
jProperty.PropertyName = "default";
}
return jProperty;
}
...
This way we've provided the same information to serailizer as with the [JsonPropertyAttribute].
Now, we just have to use it. There are many ways (e.g. global) but we can do it for a controller only:
protected override void Initialize(HttpControllerContext context)
{
base.Initialize(context);
var jSettings = context.Configuration.Formatters.JsonFormatter.SerializerSettings;
jSettings.ContractResolver = MyResolver;
}
The class DropDownValues using camel convention:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
You can use prefix # to passby but it is still not following C# coding convention.
The better solution which you can both avoid reserved keyword and still use C# coding convention is using CamelCasePropertyNamesContractResolver:
class DropDownValues {
public string[] Values { get; set; }
public string Default { get; set; }
}
And customize JsonFormatter to avoid convention mismatch between C# and json object as below:
var jsonFormatter = configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

Can not deserialize JSON containing $ref keys

I have the following code trying to deserialize a JSON string and the library gives me this error:
Additional content found in JSON reference object. A JSON reference object should only have a $ref property. Path 'user.obj', line 1, position 34.
Any idea what is wrong? (I understand that it is complaining about the second $ref, but I don't know why.) What is the workaround ?
void Main()
{
var s = "{\"user\": {\"$ref\": \"123456\", \"obj\": {\"$ref\": \"123456\"}}}";
JsonConvert.DeserializeObject<Root>(s).Dump();
}
// Define other methods and classes here
public class Root
{
[JsonProperty("user")]
public User User { get; set; }
}
public class User
{
[JsonPropertyAttribute("$ref")]
public string Ref { get; set; }
[JsonPropertyAttribute("obj")]
public Obj Obj { get; set; }
}
public class Obj
{
[JsonPropertyAttribute("$ref")]
public string Ref { get; set; }
}
Json.Net uses $ref along with $id as metadata to preserve object references in JSON. So when it sees $ref it assumes that property is not part of the actual JSON property set, but an internal identifier referring to a matching $id somewhere else in the JSON. Since your usage of $ref is different than what Json.Net expects to see, it is throwing an error.
UPDATE
In Json.Net version 6.0.4 and later, there is now a setting by which you can instruct the deserializer to treat these metadata properties as normal properties instead of consuming them. All you need to do is set the MetadataPropertyHandling setting to Ignore and then deserialize as usual.
var settings = new JsonSerializerSettings();
settings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
var obj = JsonConvert.DeserializeObject<FormDefinitionList>(json, settings);
Prior to version 6.0.4, a workaround was needed to solve this issue. If you cannot upgrade to the lastest version of Json.Net, see my answer to a similar question for some possible solutions.

How can I use a reserved keyword as an identifier in my JSON model class?

I have never used Web API before, but I need a web service that will accept/return JSON objects and using this seemed like a reasonable thing. It looked pretty simple (if not a bit of overkill for my purposes), but a data structure I need to deal with looks something like:
{
"values":["foo", "bar"],
"default":"bar"
}
And so I went to make a Model object:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
Problem is that default seems to be a protected keyword. There must be some way to get around that, right?
You can use keywords in C# as identifiers by prepending # in front of them.
I would suggest to go different way. Keep your C# object model as much standard as possible (I wouldn't use # sign and C# keywords as property name).
We can separate the serialized (JSON) world and C# objects - just by using the Json.NET features.
One of the simpliest to use is decoration with Attribute:
[JsonProperty(PropertyName = "default")]
public string DefaultValue { get; set; }
In this case we have to reference Newtonsoft.Json in the project. If it must be POCO, we can introduce CustomResolver derrived from DefaultContractResolver and define these conversions there...
But separation of concern in this case is a bit more pure solution, I would say
EDIT: JSON Contract Resolver draft (see comments)
Important NOTE: Newtonsoft.Json is part of the Web API. Not only it is an open source, but even MS team bet on that as a core JSON serializer.
1) Newtonsoft.Json (as a part of the Web.API) is already installed in your solution. So you do not have to downloaded (nuget) separately. It would always be in your packages folder. So, to use the attribute is just adding the reference. It is there...
2) There is a small draft how to do the attribute stuff, while keeping the POCO. As I've tried explain here: POCO's, behavior and Peristance Igorance, to keep POCO (e.g. we do profit from layered Architecture with NHibernate on a data layer), we can replace attributes with a Contract Resolver. Our POCO library does not have to reference anything
We just have to do extend the service layer:
public class MyResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(
MemberInfo member,
MemberSerialization memberSerialization)
{
var jProperty = base.CreateProperty(member, memberSerialization);
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return jProperty;
}
// just adjust in case if Property name is DefaultValue
var isDefaultValueProeprty =
propertyInfo.Name.Equals("DefaultValue");
if(isDefaultValueProeprty)
{
jProperty.PropertyName = "default";
}
return jProperty;
}
...
This way we've provided the same information to serailizer as with the [JsonPropertyAttribute].
Now, we just have to use it. There are many ways (e.g. global) but we can do it for a controller only:
protected override void Initialize(HttpControllerContext context)
{
base.Initialize(context);
var jSettings = context.Configuration.Formatters.JsonFormatter.SerializerSettings;
jSettings.ContractResolver = MyResolver;
}
The class DropDownValues using camel convention:
class DropDownValues {
public string[] values { get; set; }
public string default { get; set; }
}
You can use prefix # to passby but it is still not following C# coding convention.
The better solution which you can both avoid reserved keyword and still use C# coding convention is using CamelCasePropertyNamesContractResolver:
class DropDownValues {
public string[] Values { get; set; }
public string Default { get; set; }
}
And customize JsonFormatter to avoid convention mismatch between C# and json object as below:
var jsonFormatter = configuration.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings = new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

Categories

Resources