作为构建的一部分生成Xml序列化程序集

69

这段代码会产生一个FileNotFoundException,但最终可以正常运行:

void ReadXml()
{
    XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
    //...
}

以下是异常信息:


首次机会的异常类型为 'System.IO.FileNotFoundException',发生在 mscorlib.dll 中

附加信息:无法加载文件或程序集 'MyAssembly.XmlSerializers, Version=1.4.3190.15950, Culture=neutral, PublicKeyToken=null' 或其依赖项之一。系统无法找到指定的文件。


看起来框架会在找不到序列化程序集时自动生成。我可以使用 sgen.exe 手动生成它,这可以解决异常问题。

如何让 Visual Studio 自动生成 XML 序列化程序集?


更新:设置“生成序列化程序集:开启”似乎没有任何作用。


1
我也决定使用Sgen,它适用于普通类型。但是,它不允许我为这些类型的数组实例化XmlSerializers。如果你遇到了这个问题,请在这里发布:https://dev59.com/b0rSa4cB1Zd3GeqPYKUB - anthony
从.NET 7开始,你会想要查看Alex from Jitbit的回答 - undefined
9个回答

75

正如 Martin 在 他的回答中所解释的那样,仅通过项目属性打开序列化程序集生成并不足够,因为 SGen 任务会在 sgen.exe 命令行中添加 /proxytypes 开关。

微软有一个文档记录的 MSBuild 属性,允许您禁用 /proxytypes 开关,并导致 SGen 任务生成序列化程序集,即使程序集中没有代理类型。

SGenUseProxyTypes

一个布尔值,指示是否应由 SGen.exe 生成代理类型。 SGen 目标使用此属性设置 UseProxyTypes 标志。该属性默认为 true,并且没有UI来更改它。要为非 Web 服务类型生成序列化程序集,请将此属性添加到项目文件并在导入 Microsoft.Common.Targets 或 C#/VB.targets 之前将其设置为 false。

正如文档建议的那样,您必须手动修改项目文件,但是可以将 SGenUseProxyTypes 属性添加到配置中以启用生成。 您的项目文件配置最终可能如下所示:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
    <!-- Snip... -->
    <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
    <SGenUseProxyTypes>false</SGenUseProxyTypes>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
    <!-- Snip... -->
    <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
    <SGenUseProxyTypes>false</SGenUseProxyTypes>
  </PropertyGroup>

8
我最初尝试使用被选答案的方法,但是我发现(至少在MSBuild 4.0中)只需要设置$(SGenUseProxyTypes)属性就可以了,只需要一行代码就能解决问题! - Brian Kretzler
我测试并验证了这在Visual Studio 2008中可行。非常感谢您挖掘出来。 - Pete Magsig
我缺少了这部分,最终能够解决这个问题。非常有帮助的答案! - Michel van Engelen
我尝试了这个,它生成了一个32位的XmlSerializers.dll,即使我的项目设置为x64。是否有一个针对XmlSerializers的“位数”设置? - Paul McCarthy

58
这是我通过修改我的.CSPROJ文件中的MSBUILD脚本来完成它的方法:
首先,以文件而不是项目的方式打开你的.CSPROJ文件。滚动到文件底部,直到你找到这个被注释掉的代码块,在Project标签结束之前:
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->

现在我们只需插入自己的AfterBuild目标来删除任何现有的XmlSerializer并生成我们自己的SGen,如下所示:
<Target Name="AfterBuild" DependsOnTargets="AssignTargetPaths;Compile;ResolveKeySource" Inputs="$(MSBuildAllProjects);@(IntermediateAssembly)" Outputs="$(OutputPath)$(_SGenDllName)">
   <!-- Delete the file because I can't figure out how to force the SGen task. -->
   <Delete
     Files="$(TargetDir)$(TargetName).XmlSerializers.dll"
     ContinueOnError="true" />
   <SGen
     BuildAssemblyName="$(TargetFileName)"
     BuildAssemblyPath="$(OutputPath)"
     References="@(ReferencePath)"
     ShouldGenerateSerializer="true"
     UseProxyTypes="false"
     KeyContainer="$(KeyContainerName)"
     KeyFile="$(KeyOriginatorFile)"
     DelaySign="$(DelaySign)"
     ToolPath="$(TargetFrameworkSDKToolsDirectory)"
     Platform="$(Platform)">
      <Output
       TaskParameter="SerializationAssembly"
       ItemName="SerializationAssembly" />
   </SGen>
</Target>

那对我来说没问题。

谢谢,运行得很好。我把需要序列化的类放到了一个单独的项目中,这样 SGEN 就可以只处理那些类了。 - Craig Shearer
1
在进行特定于平台的构建时,请不要忘记包括 SGen 平台,例如:Platform="$(Platform)"。我遇到了以下错误:WRN: Comparing the assembly name resulted in mismatch of Processor Architecture: Ref x86, Def MSIL。 - Scott
这样可以生成DLL文件,但似乎仍在进行即时汇编生成。创建完DLL后,我需要做些额外的事情吗? - RandomEngy
只需部署它。您可能需要使用调试器来了解是哪种类型导致动态程序集生成。即使您序列化了所有类型,它们肯定由其他类型组成,一些框架类型可能需要程序集生成。 - flipdoubt
这个应该放在定义类的.DLL里还是.EXE项目中呢?我想既然它是“MSBuildAllProjects”,那么它应该放在.EXE中,对吗? - Seph

29
这个问题的其他答案已经提到了“项目属性->生成->生成序列化程序集”的设置,但默认情况下,只有项目中存在“XML Web服务代理类型”时,才会生成程序集。
了解Visual Studio的确切行为的最好方法是检查C:\ WINDOWS \ Microsoft.NET \ Framework \ v2.0.50727 ** Microsoft.Common.targets **文件中的GenerateSerializationAssemblies目标。
您可以从Visual Studio的“输出”窗口中检查此构建任务的结果,并从“显示来自”下拉框中选择“构建”。您应该看到类似于以下内容:
C:\ Program Files \ Microsoft Visual Studio 8 \ SDK \ v2.0 \ bin \ sgen.exe / assembly:D:\ Temp \ LibraryA \ obj \ Debug \ LibraryA.dll / proxytypes / reference:.. / compiler:/delaysign- LibraryA - > D:\ Temp \ LibraryA \ bin \ Debug \ LibraryA.dll
关键点在于/proxytypes开关。您可以阅读XML Serializer Generator Tool(Sgen.exe)的各种开关。
如果您熟悉MSBuild,您可以自定义GenerateSerializationAssemblies目标,使SGen任务具有UseProxyTypes="false"属性而不是true,但是这样做需要承担定制Visual Studio / MSBuild系统的所有相关责任。或者,您可以将构建过程扩展为手动调用SGen而无需使用/proxytypes开关。
如果您阅读SGen的文档,它们非常清楚地表明Microsoft希望限制此功能的使用。考虑到此问题上的噪音量,很明显Microsoft在记录Visual Studio体验方面做得不好。甚至还有一个Connect Feedback项目针对此问题,但回应并不好。

14

创建新的 SGen 任务定义会像大材小用一样。只需设置所需的变量即可使任务按预期工作。不管怎样,Microsoft 文档缺少一些重要信息。

预生成序列化程序集的步骤

(引用自 http://msdn.microsoft.com/zh-cn/library/ff798449.aspx

  1. 在 Visual Studio 2010 中,Solution Explorer 中右键单击要生成序列化程序集的项目,然后单击“卸载项目”。
  2. 在 Solution Explorer 中,右键单击要生成序列化程序集的项目,然后单击“编辑 .csproj”。
  3. 在 .csproj 文件中,在 <TargetFrameworkVersion>v?.?</TargetFrameworkVersion> 元素之后立即添加以下元素:

    <SGenUseProxyTypes>false</SGenUseProxyTypes> <SGenPlatformTarget>$(Platform)</SGenPlatformTarget>

  4. 在 .csproj 文件中的每个平台配置中(例如 <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">),添加以下行:

    <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>

  5. 保存并关闭 .csproj 文件。

  6. 在 Solution Explorer 中,右键单击刚编辑的项目,然后单击“重新加载项目”。

此过程会在输出文件夹中生成一个名为 .xmlSerializers.dll 的附加程序集。您需要将该程序集与解决方案一起部署。


解释

默认情况下,SGen 仅为代理类型生成“Any CPU”的序列化程序集。如果您未在项目文件中设置相应的变量,则会发生这种情况。

SGenPlatformTarget 必须与您的 PlatformTarget 匹配。我倾向于认为这是项目模板中的错误。为什么 sgen 目标平台应与您的项目不同?如果不同,您将收到运行时异常:

0x80131040:找到的程序集清单定义与程序集引用不匹配

您可以通过分析项目文件来找到 msbuild 任务定义:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

根据你的<TargetFrameworkVersion>http://msdn.microsoft.com/en-us/library/bb397428.aspx,MSBuildToolsPath会有所不同。

查看位于

Windows安装路径\Microsoft.NET\Framework\v4.0.30319\Microsoft.CSharp.targets

中TargetFrameworkVersion 4.0的SGen任务定义,以查看像$(SGenPlatformTarget)这样的未记录变量,你可以在项目文件中自由设置它们。

<Target
    Name="GenerateSerializationAssemblies"
    Condition="'$(_SGenGenerateSerializationAssembliesConfig)' == 'On' or ('@(WebReferenceUrl)'!='' and '$(_SGenGenerateSerializationAssembliesConfig)' == 'Auto')"
    DependsOnTargets="AssignTargetPaths;Compile;ResolveKeySource"
    Inputs="$(MSBuildAllProjects);@(IntermediateAssembly)"
    Outputs="$(IntermediateOutputPath)$(_SGenDllName)">

    <SGen
        BuildAssemblyName="$(TargetFileName)"
        BuildAssemblyPath="$(IntermediateOutputPath)"
        References="@(ReferencePath)"
        ShouldGenerateSerializer="$(SGenShouldGenerateSerializer)"
        UseProxyTypes="$(SGenUseProxyTypes)"
        KeyContainer="$(KeyContainerName)"
        KeyFile="$(KeyOriginatorFile)"
        DelaySign="$(DelaySign)"
        ToolPath="$(SGenToolPath)"
        SdkToolsPath="$(TargetFrameworkSDKToolsDirectory)"
        EnvironmentVariables="$(SGenEnvironment)"
        SerializationAssembly="$(IntermediateOutputPath)$(_SGenDllName)"
        Platform="$(SGenPlatformTarget)"
        Types="$(SGenSerializationTypes)">
            <Output TaskParameter="SerializationAssembly" ItemName="SerializationAssembly"/>
    </SGen>
</Target>

1
实际上,将第4点中的添加移动到第3点,将会对所有PropertyGroup条件实现<GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>。 - Michel van Engelen

2

如果有人突然遇到这个问题,而之前一切都正常,请注意:对我来说,问题出在选项菜单(选项->调试)中未选中“启用仅限我的代码(仅托管代码)”复选框(在安装.NET Reflector后自动关闭)。

编辑: 也就是说,在“启用仅限我的代码”关闭时,此异常在发生,如果启用调试助手,则会在抛出此异常时停止。


2

我有点晚来到这个聚会,但我发现之前的答案很难使用。具体来说,每当我尝试查看项目属性时,Visual Studio都会崩溃。我想这是因为它不再理解如何读取csproj文件。 话虽如此...

请将以下内容添加到您的后期生成事件命令行中:

"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\sgen.exe" "$(TargetPath)" /force

这将直接利用 sgen.exe 重新生成 Xml 序列化程序集,每次在 Debug 或 Release 模式下构建项目时都会执行此操作。

2

这对我使用.NET7有效。然而,即使生成的dll存在,它仍会在运行时发出FileNotFoundException。 - rrirower

1

在解决方案的属性中查找。在底部的生成选项卡中,有一个名为“生成序列化程序集”的下拉菜单


这似乎没有产生任何东西。 - Adam Tegen
你必须将它切换为"On"。自动选项似乎没有任何作用。 - Darren Kopp
5
即使设置为“开启”,它也不会产生任何东西。 - sarsnake
3
这只适用于Web服务代理类型 - 否则应使用MSBuild <SGen />任务(或命令工具sgen.exe)。 - ProfyTroll
有一个 MS Connect 问题是开放的,指出该问题由于“设计如此”原因而关闭。http://connect.microsoft.com/VisualStudio/feedback/details/123088/project-does-not-generate-serialization-assembly-even-when-specifically-told-to-do-so - Daniel McQuiston

1
一个与brain backup提供的解决方案略有不同的方案是,直接在需要使用它的地方指定平台目标,像这样:
<!-- Check the platform target value and if present use that for a correct *.XmlSerializer.dll platform setup (default is MSIL)-->
<PropertyGroup Condition=" '$(PlatformTarget)'=='' ">
  <SGenPlatform>$(Platform)</SGenPlatform>
</PropertyGroup>
<PropertyGroup Condition=" '$(PlatformTarget)'!='' ">
  <SGenPlatform>$(PlatformTarget)</SGenPlatform>
</PropertyGroup>

<!-- Delete the file because I can't figure out how to force the SGen task. -->
<Delete Files="$(TargetDir)$(TargetName).XmlSerializers.dll" ContinueOnError="true" />
<SGen
  BuildAssemblyName="$(TargetFileName)"
  BuildAssemblyPath="$(OutputPath)"
  References="@(ReferencePath)"
  ShouldGenerateSerializer="true"
  UseProxyTypes="false"
  KeyContainer="$(KeyContainerName)"
  KeyFile="$(KeyOriginatorFile)"
  DelaySign="$(DelaySign)"
  ToolPath="$(SGenToolPath)"
  SdkToolsPath="$(TargetFrameworkSDKToolsDirectory)"
  EnvironmentVariables="$(SGenEnvironment)"
  Platform="$(SGenPlatform)">
  <Output TaskParameter="SerializationAssembly" ItemName="SerializationAssembly" />
</SGen>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接