dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.2k stars 4.72k forks source link

Build Issue: Duplicate Attributes Causes XML Deserialization Errors #105511

Open RealDotNetDave opened 3 months ago

RealDotNetDave commented 3 months ago

Version Used: I am using Visual Studio 2022 v17.10.3 with .NET 8.

I have found an issue with XML deserialization of a type that took me a few days to figure out. This is the error I was getting:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.InvalidOperationException: There was an error reflecting type 'DotNetTips.Spargine.Tester.Models.RefTypes.PersonRecord'.
 ---> System.InvalidOperationException: There was an error reflecting property 'AddressesSerilization'.
 ---> System.InvalidOperationException: There was an error reflecting type 'DotNetTips.Spargine.Tester.Models.RefTypes.AddressRecord'.
 ---> System.InvalidOperationException: There was an error reflecting property 'CountyProvince'.
 ---> System.InvalidOperationException: You need to add XmlChoiceIdentifierAttribute to the 'CountyProvince' member.
   at System.Xml.Serialization.XmlReflectionImporter.CheckAmbiguousChoice(XmlAttributes a, Type accessorType, String accessorName)
   at System.Xml.Serialization.XmlReflectionImporter.ImportAccessorMapping(MemberMapping accessor, FieldModel model, XmlAttributes a, String ns, Type choiceIdentifierType, Boolean rpc, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportFieldMapping(StructModel parent, FieldModel model, XmlAttributes a, String ns, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.CreateArrayElementsFromAttributes(ArrayMapping arrayMapping, XmlArrayItemAttributes attributes, Type arrayElementType, String arrayElementNs, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportArrayLikeMapping(ArrayModel model, String ns, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportAccessorMapping(MemberMapping accessor, FieldModel model, XmlAttributes a, String ns, Type choiceIdentifierType, Boolean rpc, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportFieldMapping(StructModel parent, FieldModel model, XmlAttributes a, String ns, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportElement(TypeModel model, XmlRootAttribute root, String defaultNamespace, RecursionLimiter limiter)
   at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(Type type, XmlRootAttribute root, String defaultNamespace)
   at System.Xml.Serialization.XmlSerializer..ctor(Type type, String defaultNamespace)
   at DotNetTips.Spargine.Core.Serialization.XmlSerialization.Serialize(Object obj) in D:\src\GitHub\dotNetTips.Spargine.8\source\dotNetTips.Spargine.8.Core\Serialization\XmlSerialization.cs:line 88
   at DotNetTips.Spargine.Core.BenchmarkTests.Serialization.SerializationBenchmark.Setup()
   at DotNetTips.Spargine.Benchmarking.Benchmark.GlobalSetup() in D:\src\GitHub\dotNetTips.Spargine.8\source\Benchmarking\dotNetTips.Spargine.8.Benchmarking\Benchmark.cs:line 242
   at BenchmarkDotNet.Engines.EngineFactory.CreateReadyToRun(EngineParameters engineParameters)
   at BenchmarkDotNet.Autogenerated.Runnable_0.Run(IHost host, String benchmarkName) in D:\src\GitHub\dotNetTips.Spargine.8\AppBin\net8.0\0c502f6c-a1de-4e7b-93b9-f8e6ea68e36c\0c502f6c-a1de-4e7b-93b9-f8e6ea68e36c.notcs:line 177
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at BenchmarkDotNet.Autogenerated.UniqueProgramName.AfterAssemblyLoadingAttached(String[] args) in D:\src\GitHub\dotNetTips.Spargine.8\AppBin\net8.0\0c502f6c-a1de-4e7b-93b9-f8e6ea68e36c\0c502f6c-a1de-4e7b-93b9-f8e6ea68e36c.notcs:line 57

I discovered that the issue was due to duplicate XML attributes being added to the property, which I might have overlooked while using Copilot. Below is the code for the property that caused the exception:

[DataMember(Name = "countyProvince", IsRequired = false, Order = 5)]
[Display(Name = "County/Province")]
[JsonPropertyName("countryProvince")]
[XmlElement]
[MemberNotNull(nameof(_countyProvince))]
[MaxLength(50, ErrorMessage = "CountyProvince cannot exceed 50 characters.")]
[XmlElement("countyProvince")]
public string CountyProvince

Once I removed the [XmlElement], everything worked as expected. The compiler should have verified that there were no duplicate serialization attributes and produced an error if it detected any. This issue was discovered while I was benchmarking the code. You can find this on the Spargine repository at this URL: https://github.com/RealDotNetDave/dotNetTips.Spargine.8/blob/master/source/dotNetTips.Spargine.8.Tester/Models/RefTypes/Address.cs

davkean commented 3 months ago

XmlElementAttribute is marked as the following:

[System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Parameter | System.AttributeTargets.Property | System.AttributeTargets.ReturnValue, AllowMultiple=true)]
public class XmlElementAttribute : Attribute

That AllowMultiple=true on the attribute means that multiple of these attributes can be applied. If it was marked as AllowMultiple=false then this would error.