Reg Edit
Reg Edit

Reputation: 6916

How to control .NET XML serialization/deserialization to exclude an element, when it's a MS class so I can't edit the source to add XMLIgnore etc

To persist objects and restore them on next startup, XML serialization has done the job pretty well for me to date. But I'm currently having trouble with the ScriptingOptions class in the Microsoft.SqlServer.Management.Smo namespace.

Serializing the object produces the XML fragment shown below. But when I try to deserialize it, XmlSerializer throws an exception:

    There is an error in XML document (n,n)

The inner exception is an InvalidOperationException:

    Instance is read-only

The stack trace (included further below) identifies that it's trying to set the EncoderFallback property. This is further confirmed by the fact that deserialization succeeds if I edit the XML to remove the following element:

  <Encoding xsi:type="UnicodeEncoding">
    <EncoderFallback xsi:type="EncoderReplacementFallback" />
    <DecoderFallback xsi:type="DecoderReplacementFallback" />
  </Encoding>

So I want to exclude this Encoding element, during either serialization or deserialization of the object.

I've searched for answers but all the custom serialization/deserialization approaches I found seem to assume access to the class source code, to add XMLIgnore or to implement ISerializable etc.

I suppose I could write a preprocessor that would parse the XML and chop out the offending element prior to deserialization, but surely there must be a more "proper" way than that?

The serialized object:

<ScriptingOptions>
  <FileName />
  <Encoding xsi:type="UnicodeEncoding">
    <EncoderFallback xsi:type="EncoderReplacementFallback" />
    <DecoderFallback xsi:type="DecoderReplacementFallback" />
  </Encoding>
  <DriWithNoCheck>false</DriWithNoCheck>
  <IncludeFullTextCatalogRootPath>false</IncludeFullTextCatalogRootPath>
  <BatchSize>1</BatchSize>
  <ScriptDrops>false</ScriptDrops>
  <TargetServerVersion>Version120</TargetServerVersion>
  <TargetDatabaseEngineType>Standalone</TargetDatabaseEngineType>
  <AnsiFile>false</AnsiFile>
  <AppendToFile>false</AppendToFile>
  <ToFileOnly>false</ToFileOnly>
  <SchemaQualify>true</SchemaQualify>
  <IncludeHeaders>false</IncludeHeaders>
  <IncludeIfNotExists>true</IncludeIfNotExists>
  <WithDependencies>false</WithDependencies>
  <DriPrimaryKey>false</DriPrimaryKey>
  <DriForeignKeys>false</DriForeignKeys>
  <DriUniqueKeys>false</DriUniqueKeys>
  <DriClustered>false</DriClustered>
  <DriNonClustered>false</DriNonClustered>
  <DriChecks>false</DriChecks>
  <DriDefaults>false</DriDefaults>
  <Triggers>false</Triggers>
  <Statistics>false</Statistics>
  <ClusteredIndexes>false</ClusteredIndexes>
  <NonClusteredIndexes>false</NonClusteredIndexes>
  <NoAssemblies>false</NoAssemblies>
  <PrimaryObject>true</PrimaryObject>
  <Default>true</Default>
  <XmlIndexes>false</XmlIndexes>
  <FullTextCatalogs>false</FullTextCatalogs>
  <FullTextIndexes>false</FullTextIndexes>
  <FullTextStopLists>false</FullTextStopLists>
  <Indexes>false</Indexes>
  <DriIndexes>false</DriIndexes>
  <DriAllKeys>false</DriAllKeys>
  <DriAllConstraints>false</DriAllConstraints>
  <DriAll>false</DriAll>
  <Bindings>false</Bindings>
  <NoFileGroup>false</NoFileGroup>
  <NoFileStream>false</NoFileStream>
  <NoFileStreamColumn>false</NoFileStreamColumn>
  <NoCollation>false</NoCollation>
  <ContinueScriptingOnError>false</ContinueScriptingOnError>
  <IncludeDatabaseRoleMemberships>false</IncludeDatabaseRoleMemberships>
  <Permissions>false</Permissions>
  <AllowSystemObjects>true</AllowSystemObjects>
  <NoIdentities>false</NoIdentities>
  <ConvertUserDefinedDataTypesToBaseType>false</ConvertUserDefinedDataTypesToBaseType>
  <TimestampToBinary>false</TimestampToBinary>
  <AnsiPadding>false</AnsiPadding>
  <ExtendedProperties>false</ExtendedProperties>
  <DdlHeaderOnly>false</DdlHeaderOnly>
  <DdlBodyOnly>false</DdlBodyOnly>
  <NoViewColumns>false</NoViewColumns>
  <SchemaQualifyForeignKeysReferences>false</SchemaQualifyForeignKeysReferences>
  <AgentAlertJob>false</AgentAlertJob>
  <AgentJobId>true</AgentJobId>
  <AgentNotify>false</AgentNotify>
  <LoginSid>false</LoginSid>
  <NoCommandTerminator>false</NoCommandTerminator>
  <NoIndexPartitioningSchemes>false</NoIndexPartitioningSchemes>
  <NoTablePartitioningSchemes>false</NoTablePartitioningSchemes>
  <IncludeDatabaseContext>false</IncludeDatabaseContext>
  <NoXmlNamespaces>false</NoXmlNamespaces>
  <DriIncludeSystemNames>false</DriIncludeSystemNames>
  <OptimizerData>false</OptimizerData>
  <NoExecuteAs>false</NoExecuteAs>
  <EnforceScriptingOptions>false</EnforceScriptingOptions>
  <NoMailProfileAccounts>false</NoMailProfileAccounts>
  <NoMailProfilePrincipals>false</NoMailProfilePrincipals>
  <NoVardecimal>true</NoVardecimal>
  <ChangeTracking>false</ChangeTracking>
  <ScriptDataCompression>true</ScriptDataCompression>
  <ScriptSchema>true</ScriptSchema>
  <ScriptData>false</ScriptData>
  <ScriptBatchTerminator>false</ScriptBatchTerminator>
  <ScriptOwner>false</ScriptOwner>
</ScriptingOptions>

The stack trace on attempting to deserialize the above XML:

    StackTrace  "   at System.Text.Encoding.set_EncoderFallback(EncoderFallback value)\r\n   
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderUserSettings.Read13_UnicodeEncoding(Boolean isNullable, Boolean checkType)\r\n   
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderUserSettings.Read7_Encoding(Boolean isNullable, Boolean checkType)\r\n
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderUserSettings.Read10_ScriptingOptions(Boolean isNullable, Boolean checkType)\r\n   
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderUserSettings.Read14_TableScriptingOptions(Boolean isNullable, Boolean checkType)\r\n   
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderUserSettings.Read15_UserSettings(Boolean isNullable, Boolean checkType)\r\n   
    at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderUserSettings.Read16_UserSettings()"  string

Upvotes: 1

Views: 438

Answers (2)

Thomas Levesque
Thomas Levesque

Reputation: 292405

You can use the XmlAttributeOverrides class to specify attributes on classes you don't own:

var overrides = new XmlAttributeOverrides();
overrides.Add(
    typeof(ScriptingOptions),
    "Encoding",
    new XmlAttributes { XmlIgnore = true });
var serializer = new XmlSerializer(typeof(ScriptingOptions), overrides);

But in general, it's often more convenient to use a specific class to handle serialization, as suggested by a-h, because it gives you more flexibility.

Upvotes: 2

a-h
a-h

Reputation: 4284

Sometimes the simplest solutions are the best, but you could create a DTO with just the properties you want, map from source to target with AutoMapper (CreateMap is not Thread Safe btw) and deserialize straight back into the source type from there:

using AutoMapper;
using Microsoft.SqlServer.Management.Smo;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace SerializationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Mapper.CreateMap<ScriptingOptions, ScriptingOptionsDto>();

            var so = new ScriptingOptions();
            var soDto = Mapper.Map<ScriptingOptionsDto>(so); 
            string xml = Serialize(soDto);

            Console.WriteLine(xml); 
            Console.ReadLine(); 

            so = Deserialize(xml);

            Console.WriteLine(so.ToString());
            Console.ReadLine();
        }

        public static string Serialize(ScriptingOptionsDto dto)
        {
            var serializer = new XmlSerializer(dto.GetType()); 
            var ms = new MemoryStream();
            serializer.Serialize(ms, dto); 
            return Encoding.UTF8.GetString(ms.ToArray());
        }

        public static ScriptingOptions Deserialize(string xml)
        {
            var serializer = new XmlSerializer(typeof(ScriptingOptions));
            return serializer.Deserialize(new MemoryStream(UnicodeEncoding.UTF8.GetBytes(xml))) as ScriptingOptions;
        }
    }

    [XmlType("ScriptingOptions")]
    public class ScriptingOptionsDto
    {
        public bool AgentAlertJob { get; set; }
        public bool AgentJobId { get; set; }
        public bool AgentNotify { get; set; }
        public bool AllowSystemObjects { get; set; }
        public bool AnsiFile { get; set; }
        public bool AnsiPadding { get; set; }
        public bool AppendToFile { get; set; }
        public int BatchSize { get; set; }
        public bool Bindings { get; set; }
        public bool ChangeTracking { get; set; }
        public bool ClusteredIndexes { get; set; }
        public bool ContinueScriptingOnError { get; set; }
        public bool ConvertUserDefinedDataTypesToBaseType { get; set; }
        public bool DdlBodyOnly { get; set; }
        public bool DdlHeaderOnly { get; set; }
        public bool Default { get; set; }
        public bool DriAll { get; set; }
        public bool DriAllConstraints { get; set; }
        public bool DriAllKeys { get; set; }
        public bool DriChecks { get; set; }
        public bool DriClustered { get; set; }
        public bool DriDefaults { get; set; }
        public bool DriForeignKeys { get; set; }
        public bool DriIncludeSystemNames { get; set; }
        public bool DriIndexes { get; set; }
        public bool DriNonClustered { get; set; }
        public bool DriPrimaryKey { get; set; }
        public bool DriUniqueKeys { get; set; }
        public bool DriWithNoCheck { get; set; }
        public bool EnforceScriptingOptions { get; set; }
        public bool ExtendedProperties { get; set; }
        public string FileName { get; set; }
        public bool FullTextCatalogs { get; set; }
        public bool FullTextIndexes { get; set; }
        public bool FullTextStopLists { get; set; }
        public bool IncludeDatabaseContext { get; set; }
        public bool IncludeDatabaseRoleMemberships { get; set; }
        public bool IncludeFullTextCatalogRootPath { get; set; }
        public bool IncludeHeaders { get; set; }
        public bool IncludeIfNotExists { get; set; }
        public bool Indexes { get; set; }
        public bool LoginSid { get; set; }
        public bool NoAssemblies { get; set; }
        public bool NoCollation { get; set; }
        public bool NoCommandTerminator { get; set; }
        public bool NoExecuteAs { get; set; }
        public bool NoFileGroup { get; set; }
        public bool NoFileStream { get; set; }
        public bool NoFileStreamColumn { get; set; }
        public bool NoIdentities { get; set; }
        public bool NoIndexPartitioningSchemes { get; set; }
        public bool NoMailProfileAccounts { get; set; }
        public bool NoMailProfilePrincipals { get; set; }
        public bool NonClusteredIndexes { get; set; }
        public bool NoTablePartitioningSchemes { get; set; }
        public bool NoVardecimal { get; set; }
        public bool NoViewColumns { get; set; }
        public bool NoXmlNamespaces { get; set; }
        public bool OptimizerData { get; set; }
        public bool Permissions { get; set; }
        public bool PrimaryObject { get; set; }
        public bool SchemaQualify { get; set; }
        public bool SchemaQualifyForeignKeysReferences { get; set; }
        public bool ScriptBatchTerminator { get; set; }
        public bool ScriptData { get; set; }
        public bool ScriptDataCompression { get; set; }
        public bool ScriptDrops { get; set; }
        public bool ScriptOwner { get; set; }
        public bool ScriptSchema { get; set; }
        public bool Statistics { get; set; }
        public Microsoft.SqlServer.Management.Common.DatabaseEngineType TargetDatabaseEngineType { get; set; }
        public SqlServerVersion TargetServerVersion { get; set; }
        public bool TimestampToBinary { get; set; }
        public bool ToFileOnly { get; set; }
        public bool Triggers { get; set; }
        public bool WithDependencies { get; set; }
        public bool XmlIndexes { get; set; }
    }
}

Upvotes: 2

Related Questions