Question

Test.csproj references System.XML and specifies TargetFrameworkVersion v4.0. System.Xml.XmlReaderSettings has gained a DtdProcessing Property, and deprecated ProhibitDtd in the meantime. The requirement is to build from one project, supporting both assemblies.

Building with MSBuild 3.5 (c:\Windows\Microsoft.NET\Framework\v3.5\MSBuild) it ends up referencing C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll (Runtime Version v2.0.50727, Version 2.0.0.0)

Building with MSBuild 4.0 (c:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild) it ends up referencing C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Xml.dll (Runtime Version v4.0.30319, Version 4.0.0.0).

My attempted fix was to define a constant in the .csproj file based on the TargetFrameworkVersion. However, TargetFrameworkVersion is no indicator of which assembly will be linked.

So, this doesn't work:

<PropertyGroup Condition=" '$(TargetFrameworkVersion)' != 'v2.0' And '$(TargetFrameworkVersion)' != 'v3.5' And '$(TargetFrameworkVersion)' != '' ">
  <DefineConstants Condition=" '$(DefineConstants)' != '' ">$(DefineConstants);</DefineConstants>
  <DefineConstants>$(DefineConstants)XMLDTDPROCESSING</DefineConstants>
</PropertyGroup>

With this CSharp code:

namespace TestVSMSBuild
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Xml.XmlReaderSettings readerSettings = new System.Xml.XmlReaderSettings();
#if XMLDTDPROCESSING
            readerSettings.DtdProcessing = System.Xml.DtdProcessing.Ignore;
#else // !XMLDTDPROCESSING
            readerSettings.ProhibitDtd = false;
#endif // XMLDTDPROCESSING
            System.Console.WriteLine("Reader: '{0}'", readerSettings);
        }
    }

Reading C:\Windows\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets the comment for the AssemblySearchPaths property is:

<!--
The SearchPaths property is set to find assemblies in the following order:

    (1) Files from current project - indicated by {CandidateAssemblyFiles}
    (2) $(ReferencePath) - the reference path property, which comes from the .USER file.
    (3) The hintpath from the referenced item itself, indicated by {HintPathFromItem}.
    (4) The directory of MSBuild's "target" runtime from GetFrameworkPath.
        The "target" runtime folder is the folder of the runtime that MSBuild is a part of.
    (5) Registered assembly folders, indicated by {Registry:*,*,*}
    (6) Legacy registered assembly folders, indicated by {AssemblyFolders}
    (7) Look in the application's output folder (like bin\debug)
    (8) Resolve to the GAC.
    (9) Treat the reference's Include as if it were a real file name.
-->
<AssemblySearchPaths Condition=" '$(AssemblySearchPaths)' == '' ">
    {CandidateAssemblyFiles};
    $(ReferencePath);
    {HintPathFromItem};
    {TargetFrameworkDirectory};
    {Registry:$(FrameworkRegistryBase),$(TargetFrameworkVersion),$(AssemblyFoldersSuffix)$(AssemblyFoldersExConditions)};
    {AssemblyFolders};
    {GAC};
    {RawFileName};
    $(OutDir)
</AssemblySearchPaths>

My guess is this has something to do with GetFrameworkPaths which is defined as:

<!--
============================================================
                                    GetFrameworkPaths

Get the paths for the .NET Framework installation directory, and the .NET Framework
SDK installation directory.

These paths are not used directly by this .targets file but are available for pre and
post build steps.
============================================================
-->
<PropertyGroup>
    <Framework35Dir>@(_TargetFramework35DirectoryItem)</Framework35Dir>
    <Framework30Dir>@(_TargetFramework30DirectoryItem)</Framework30Dir>
    <Framework20Dir>@(_TargetFramework20DirectoryItem)</Framework20Dir>
    <FrameworkDir>@(_TargetFramework20DirectoryItem)</FrameworkDir>

    <FrameworkSDKDir>@(_TargetFrameworkSDKDirectoryItem)</FrameworkSDKDir>
    <GetFrameworkPathsDependsOn></GetFrameworkPathsDependsOn>
</PropertyGroup>
<Target
    Name="GetFrameworkPaths"
    DependsOnTargets="$(GetFrameworkPathsDependsOn)">

    <!-- Get the paths to all of the target .NET framework directories. -->
    <GetFrameworkPath>
        <Output Condition=" '$(TargetFrameworkVersion)' == 'v3.5' "                                                                                  TaskParameter="FrameworkVersion35Path"  ItemName="_CombinedTargetFrameworkDirectoriesItem" />
        <Output Condition=" '$(TargetFrameworkVersion)' == 'v3.0' or '$(TargetFrameworkVersion)' == 'v3.5' "                                         TaskParameter="FrameworkVersion30Path"  ItemName="_CombinedTargetFrameworkDirectoriesItem" />
        <Output Condition=" '$(TargetFrameworkVersion)' == 'v2.0' or '$(TargetFrameworkVersion)' == 'v3.0' or '$(TargetFrameworkVersion)' == 'v3.5'" TaskParameter="FrameworkVersion20Path"  ItemName="_CombinedTargetFrameworkDirectoriesItem" />
        <Output TaskParameter="FrameworkVersion35Path"  ItemName="_TargetFramework35DirectoryItem" />
        <Output TaskParameter="FrameworkVersion30Path"  ItemName="_TargetFramework30DirectoryItem" />
        <Output TaskParameter="FrameworkVersion20Path"  ItemName="_TargetFramework20DirectoryItem" />
    </GetFrameworkPath>

    <PropertyGroup>
        <TargetFrameworkDirectory>@(_CombinedTargetFrameworkDirectoriesItem)</TargetFrameworkDirectory>
    </PropertyGroup>

    <!-- Get the path to the target .NET framework SDK directory. -->
    <GetFrameworkSdkPath>
        <Output TaskParameter="FrameworkSdkVersion35Path" PropertyName="TargetFrameworkSDKDirectory"/>
        <Output TaskParameter="FrameworkSdkVersion35Path" ItemName="_TargetFrameworkSDKDirectoryItem"/>
    </GetFrameworkSdkPath>

</Target>

So, is there an easier way to alter the CSharp code based on the System.XML assembly being used ? If this approach is correct, what condition should I use to define my XMLDTDPROCESSING constant ?

Was it helpful?

Solution

Here's what I ended up doing. Resolve the System.XML assembly, and then check specifically for version 2.0.0.0.

Note that assumption that the System.XML assembly path ends in System.XML. There doesn't seem to be a way to tie the ReferencePath back to the corresponding Reference.

<!-- System.XML.XmlReaderSettings changed after version 2.0.0.0.
     Define a constant to recognised that.
 -->
<Target Name="SetupXMLDtdProcessing">
  <GetAssemblyIdentity AssemblyFiles="%(ReferencePath.Identity)"
                       Condition=" '%(ReferencePath.Filename)' == 'System.XML' "
  >
    <Output TaskParameter="Assemblies" ItemName="_SystemXMLAssemblyIdentityItem" />
  </GetAssemblyIdentity>

  <PropertyGroup>
    <_SystemXMLAssemblyIdentity>%(_SystemXMLAssemblyIdentityItem.Identity)</_SystemXMLAssemblyIdentity>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(_SystemXMLAssemblyIdentity)' != 'System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
                           "
  >
    <DefineConstants Condition=" '$(DefineConstants)' != '' ">$(DefineConstants);</DefineConstants>
    <DefineConstants>$(DefineConstants)XMLDTDPROCESSING</DefineConstants>
  </PropertyGroup>
</Target>

<PropertyGroup>
  <CompileDependsOn>
    SetupXMLDtdProcessing;
    $(CompileDependsOn)
  </CompileDependsOn>
</PropertyGroup>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top