I'm writing a small analyzer for C# projects that checks if the XML documentation generation is present and set up correctly. There are two options for to specify XML documentation:
An older DocumentationFile option
A newer GenerateDocumentationFile flag. If the flag is set to true MSBuild should generate a XML documentation file with the project's name in the project's output directory.
Can both of these options be specified explicitly in the project file?
Will the values from these options be combined or one of the options will be ignored?
They both don't need to be set. If DocumentationFile is not empty, then GenerateDocumentationFile will set to true in an MSBuild target.
If you set GenerateDocumentationFile to true, then a default value for DocumentationFile will be set based on the project file name.
Relevant MSBuild targets
<!-- Handle XML documentation file settings -->
<PropertyGroup Condition="'$(GenerateDocumentationFile)' == ''">
<GenerateDocumentationFile Condition="'$(DocumentationFile)' == ''">false</GenerateDocumentationFile>
<GenerateDocumentationFile Condition="'$(DocumentationFile)' != ''">true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(GenerateDocumentationFile)' == 'true' and '$(DocumentationFile)' == ''">
<DocumentationFile Condition="'$(MSBuildProjectExtension)' == '.vbproj'">$(AssemblyName).xml</DocumentationFile>
<DocumentationFile Condition="'$(MSBuildProjectExtension)' != '.vbproj'">$(IntermediateOutputPath)$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(GenerateDocumentationFile)' != 'true'">
<DocumentationFile />
</PropertyGroup>
Related
I am creating a website using ASP.Net Core 5 and EF Core 5.
After I deleted all my migrations in migration folder suddenly Visual Studio starts bombing me with errors as it shown below:
On the top I get this message:
In the Error List I get:
And somehow I can not choose a Target Framework any more:
UPDATE:
Here is the content of the .targets file:
<!--
***********************************************************************************************
Microsoft.Common.targets
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
This file defines the steps in the standard build process for .NET projects. It
contains all the steps that are common among the different .NET languages, such as
Visual Basic, and Visual C#.
Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
In VS 2010 SP1 and VS 2012, both supported for asset compatibility, the MSBuild installed
as part of them did not enforce using the local ToolsVersion (4.0) in all cases, but instead
just used whatever ToolsVersion was in the project file if it existed on the machine, and
only forced 4.0 if that ToolsVersion did not exist.
Moving forward, we do want to enforce a single acting ToolsVersion per version of Visual Studio,
but in order to approximate this behavior on VS 2010 SP1 and VS 2012 as well, we've redirected
the targets: If we're building using 4.X MSBuild (which doesn't define the new reserved
property, MSBuildAssemblyVersion), we'll point right back at the 4.0 targets, which still exist
as part of the .NET Framework. Only if we're using the new MSBuild will we point to the current
targets.
-->
<Choose>
<When Condition="'$(MSBuildAssemblyVersion)' == ''">
<PropertyGroup>
<CommonTargetsPath>$(MSBuildFrameworkToolsPath)\Microsoft.Common.targets</CommonTargetsPath>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<CommonTargetsPath>$(MSBuildToolsPath)\Microsoft.Common.CurrentVersion.targets</CommonTargetsPath>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup Condition="'$(MSBuildAssemblyVersion)' == ''">
<!--
Overrides for the Microsoft.Common.targets extension targets. Used to make sure that only the imports we specify
(hard-coded to 4.0 locations) are used, not the 12.0 locations that would be used by default.
NOTE: This logic is duplicated in Microsoft.VisualBasic.targets and in Microsoft.CSharp.targets because those two files
import Microsoft.Common.targets from the current directory and thus don't get the benefit of the redirections, so for
any changes to this logic in this file, please also edit the other two.
-->
<ImportByWildcardBefore40MicrosoftCommonTargets Condition="'$(ImportByWildcardBefore40MicrosoftCommonTargets)' == ''">$(ImportByWildcardBeforeMicrosoftCommonTargets)</ImportByWildcardBefore40MicrosoftCommonTargets>
<ImportByWildcardBefore40MicrosoftCommonTargets Condition="'$(ImportByWildcardBefore40MicrosoftCommonTargets)' == ''">true</ImportByWildcardBefore40MicrosoftCommonTargets>
<ImportByWildcardAfter40MicrosoftCommonTargets Condition="'$(ImportByWildcardAfter40MicrosoftCommonTargets)' == ''">$(ImportByWildcardAfterMicrosoftCommonTargets)</ImportByWildcardAfter40MicrosoftCommonTargets>
<ImportByWildcardAfter40MicrosoftCommonTargets Condition="'$(ImportByWildcardAfter40MicrosoftCommonTargets)' == ''">true</ImportByWildcardAfter40MicrosoftCommonTargets>
<ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets Condition="'$(ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets)' == ''">$(ImportUserLocationsByWildcardBeforeMicrosoftCommonTargets)</ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets>
<ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets Condition="'$(ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets)' == ''">true</ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets>
<ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets Condition="'$(ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets)' == ''">$(ImportUserLocationsByWildcardAfterMicrosoftCommonTargets)</ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets>
<ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets Condition="'$(ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets)' == ''">true</ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets>
<ImportByWildcardBeforeMicrosoftCommonTargets>false</ImportByWildcardBeforeMicrosoftCommonTargets>
<ImportByWildcardAfterMicrosoftCommonTargets>false</ImportByWildcardAfterMicrosoftCommonTargets>
<ImportUserLocationsByWildcardBeforeMicrosoftCommonTargets>false</ImportUserLocationsByWildcardBeforeMicrosoftCommonTargets>
<ImportUserLocationsByWildcardAfterMicrosoftCommonTargets>false</ImportUserLocationsByWildcardAfterMicrosoftCommonTargets>
<CustomBeforeMicrosoftCommonTargets Condition="'$(CustomBeforeMicrosoftCommonTargets)' == ''">$(MSBuildExtensionsPath)\v4.0\Custom.Before.Microsoft.Common.targets</CustomBeforeMicrosoftCommonTargets>
<CustomAfterMicrosoftCommonTargets Condition="'$(CustomAfterMicrosoftCommonTargets)' == ''">$(MSBuildExtensionsPath)\v4.0\Custom.After.Microsoft.Common.targets</CustomAfterMicrosoftCommonTargets>
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildAssemblyVersion)' == '' and ('$(TargetFrameworkIdentifier)' == '.NETFramework' or '$(TargetFrameworkIdentifier)' == 'Silverlight' or ('$(TargetFrameworkIdentifier)' == '' and ('$(TargetRuntime)' == 'Managed' or '$(TargetRuntime)' == '')))">
<!--
Overrides for the Microsoft.NETFramework.props extension targets. Used to make sure that only the imports we specify
(hard-coded to 4.0 locations) are used, not the 12.0 locations that would be used by default. Required because
Microsoft.Common.targets imports it from the current directory, so we don't get a chance to redirect these in its
own redirection targets.
NOTE: This logic is duplicated in Microsoft.VisualBasic.targets and in Microsoft.CSharp.targets because those two files
import Microsoft.Common.targets from the current directory and thus don't get the benefit of these redirections either,
so for any changes to this logic in this file, please also edit the other two.
-->
<ImportByWildcardBefore40MicrosoftNetFrameworkProps Condition="'$(ImportByWildcardBefore40MicrosoftNetFrameworkProps)' == ''">$(ImportByWildcardBeforeMicrosoftNetFrameworkProps)</ImportByWildcardBefore40MicrosoftNetFrameworkProps>
<ImportByWildcardBefore40MicrosoftNetFrameworkProps Condition="'$(ImportByWildcardBefore40MicrosoftNetFrameworkProps)' == ''">true</ImportByWildcardBefore40MicrosoftNetFrameworkProps>
<ImportByWildcardAfter40MicrosoftNetFrameworkProps Condition="'$(ImportByWildcardAfter40MicrosoftNetFrameworkProps)' == ''">$(ImportByWildcardAfterMicrosoftNetFrameworkProps)</ImportByWildcardAfter40MicrosoftNetFrameworkProps>
<ImportByWildcardAfter40MicrosoftNetFrameworkProps Condition="'$(ImportByWildcardAfter40MicrosoftNetFrameworkProps)' == ''">true</ImportByWildcardAfter40MicrosoftNetFrameworkProps>
<ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps Condition="'$(ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps)' == ''">$(ImportUserLocationsByWildcardBeforeMicrosoftNetFrameworkProps)</ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps>
<ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps Condition="'$(ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps)' == ''">true</ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps>
<ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps Condition="'$(ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps)' == ''">$(ImportUserLocationsByWildcardAfterMicrosoftNetFrameworkProps)</ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps>
<ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps Condition="'$(ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps)' == ''">true</ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps>
<ImportByWildcardBeforeMicrosoftNetFrameworkProps>false</ImportByWildcardBeforeMicrosoftNetFrameworkProps>
<ImportByWildcardAfterMicrosoftNetFrameworkProps>false</ImportByWildcardAfterMicrosoftNetFrameworkProps>
<ImportUserLocationsByWildcardBeforeMicrosoftNetFrameworkProps>false</ImportUserLocationsByWildcardBeforeMicrosoftNetFrameworkProps>
<ImportUserLocationsByWildcardAfterMicrosoftNetFrameworkProps>false</ImportUserLocationsByWildcardAfterMicrosoftNetFrameworkProps>
</PropertyGroup>
<ImportGroup Condition="'$(MSBuildAssemblyVersion)' == ''">
<Import Project="$(MSBuildUserExtensionsPath)\4.0\Microsoft.Common.targets\ImportBefore\*" Condition="'$(ImportUserLocationsByWildcardBefore40MicrosoftCommonTargets)' == 'true' and exists('$(MSBuildUserExtensionsPath)\4.0\Microsoft.Common.targets\ImportBefore')"/>
<Import Project="$(MSBuildExtensionsPath)\4.0\Microsoft.Common.targets\ImportBefore\*" Condition="'$(ImportByWildcardBefore40MicrosoftCommonTargets)' == 'true' and exists('$(MSBuildExtensionsPath)\4.0\Microsoft.Common.targets\ImportBefore')"/>
</ImportGroup>
<ImportGroup Condition="'$(MSBuildAssemblyVersion)' == ''">
<Import Project="$(MSBuildUserExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportBefore\*" Condition="'$(ImportUserLocationsByWildcardBefore40MicrosoftNetFrameworkProps)' == 'true' and exists('$(MSBuildUserExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportBefore')"/>
<Import Project="$(MSBuildExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportBefore\*" Condition="'$(ImportByWildcardBefore40MicrosoftNetFrameworkProps)' == 'true' and exists('$(MSBuildExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportBefore')"/>
</ImportGroup>
<Import Project="$(CommonTargetsPath)" />
<!--
Prepare to import project extensions which usually come from packages. Package management systems will create a file at:
$(MSBuildProjectExtensionsPath)\$(MSBuildProjectFile).<SomethingUnique>.targets
Each package management system should use a unique moniker to avoid collisions. It is a wild-card import so the package
management system can write out multiple files but the order of the import is alphabetic because MSBuild sorts the list.
-->
<PropertyGroup>
<ImportProjectExtensionTargets Condition="'$(ImportProjectExtensionTargets)' == ''">true</ImportProjectExtensionTargets>
</PropertyGroup>
<Import Project="$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.targets" Condition="'$(ImportProjectExtensionTargets)' == 'true' and exists('$(MSBuildProjectExtensionsPath)')" />
<PropertyGroup>
<ImportDirectoryBuildTargets Condition="'$(ImportDirectoryBuildTargets)' == ''">true</ImportDirectoryBuildTargets>
</PropertyGroup>
<!--
Determine the path to the directory build targets file if the user did not disable $(ImportDirectoryBuildTargets) and
they did not already specify an absolute path to use via $(DirectoryBuildTargetsPath)
-->
<PropertyGroup Condition="'$(ImportDirectoryBuildTargets)' == 'true' and '$(DirectoryBuildTargetsPath)' == ''">
<_DirectoryBuildTargetsFile Condition="'$(_DirectoryBuildTargetsFile)' == ''">Directory.Build.targets</_DirectoryBuildTargetsFile>
<_DirectoryBuildTargetsBasePath Condition="'$(_DirectoryBuildTargetsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectoryBuildTargetsFile)'))</_DirectoryBuildTargetsBasePath>
<DirectoryBuildTargetsPath Condition="'$(_DirectoryBuildTargetsBasePath)' != '' and '$(_DirectoryBuildTargetsFile)' != ''">$([MSBuild]::NormalizePath('$(_DirectoryBuildTargetsBasePath)', '$(_DirectoryBuildTargetsFile)'))</DirectoryBuildTargetsPath>
</PropertyGroup>
<Import Project="$(DirectoryBuildTargetsPath)" Condition="'$(ImportDirectoryBuildTargets)' == 'true' and exists('$(DirectoryBuildTargetsPath)')"/>
<ImportGroup Condition="'$(MSBuildAssemblyVersion)' == ''">
<Import Project="$(MSBuildExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportAfter\*" Condition="'$(ImportByWildcardAfter40MicrosoftNetFrameworkProps)' == 'true' and exists('$(MSBuildExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportAfter')"/>
<Import Project="$(MSBuildUserExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportAfter\*" Condition="'$(ImportUserLocationsByWildcardAfter40MicrosoftNetFrameworkProps)' == 'true' and exists('$(MSBuildUserExtensionsPath)\4.0\Microsoft.NETFramework.props\ImportAfter')"/>
</ImportGroup>
<ImportGroup Condition="'$(MSBuildAssemblyVersion)' == ''">
<Import Project="$(MSBuildExtensionsPath)\4.0\Microsoft.Common.targets\ImportAfter\*" Condition="'$(ImportByWildcardAfter40MicrosoftCommonTargets)' == 'true' and exists('$(MSBuildExtensionsPath)\4.0\Microsoft.Common.targets\ImportAfter')"/>
<Import Project="$(MSBuildUserExtensionsPath)\4.0\Microsoft.Common.targets\ImportAfter\*" Condition="'$(ImportUserLocationsByWildcardAfter40MicrosoftCommonTargets)' == 'true' and exists('$(MSBuildUserExtensionsPath)\4.0\Microsoft.Common.targets\ImportAfter')"/>
</ImportGroup>
<!-- Fix up FrameworkPathOverride, which is primarily used to determine the location of mscorlib.dll in the
(relatively uncommon) situation where the reference assemblies, in which it's usually found, are not
installed. -->
<PropertyGroup Condition="'$(MSBuildAssemblyVersion)' == ''">
<FrameworkPathOverride Condition="!Exists('$(FrameworkPathOverride)\mscorlib.dll')">$(MSBuildFrameworkToolsPath)</FrameworkPathOverride>
</PropertyGroup>
</Project>
References are crash because of not belong this framework. There is solved subject about that.
Visual Studio 2010 : Can't change target. Gives TargetFrameworkMoniker Error
I'm trying to achieve the following with MSBuild: my main project (MyProject.csproj) should include a couple Reference items, but the path to one of those References is the value of the SomeProperty property, which is set by a Target. Specifically, the value for SomeProperty is parsed from a file using ReadLinesFromFileTask.
Here is the high-level structure of MyProject.csproj:
<Project>
<Target Name="CreateSomeProperty">
<!-- Tasks that ultimately set $(SomeProperty) by parsing a value with ReadLinesFromFileTask -->
</Target>
<ItemGroup>
<Reference Include="$(SomeProperty)" />
<!-- Other Reference items -->
</ItemGroup>
</Project>
Unfortunately, this setup is not working. I see those little yellow triangles under the Dependencies node of MyProject in the VS Solution Explorer, since the project is looking for a DLL at a path with missing characters. Similarly, when I build the project, I get a bunch of The type or namespace name could not be found errors, even though I still see the output from a Message Task inside my Target. Presumably, the CreatePathProperty Target is running during the execution phase, after the Reference items have already failed to load during the evaluation phase.
Is there a way to make a setup like this work? I've tried setting BeforeTargets="Build" in the Target element, and setting InitialTargets="CreateSomeProperty" in the Project element, but nothing seems to work. Any help would be much appreciated!
Can an MSBuild Item use a Property set by a Target?
Yes, I'm sure it's possible if you're in .net framework project with old csproj format and what you want is a supported scenario in VS2017(Only did the test in VS2017).
Tips:
Normally msbuild reads the Properties and Items before it executes your custom target. So we should use something like BeforeTargets="BeforeResolveReferences" to make sure the correct order in this scenario is custom target runs=>create the property=>msbuild reads the info about references and the property.
Otherwise the order(wrong order when BeforeTargets="Build" or what) should be: Msbuild reads the info about references(now the property is not defined yet)=>the target runs and creates the property.
Solution:
Add this script to the bottom of your xx.csproj.
<!-- Make sure it executes before msbuild reads the ItemGroup above and loads the references -->
<Target Name="MyTest" BeforeTargets="BeforeResolveReferences">
<ItemGroup>
<!-- Define a TestFile to represent the file I read -->
<TestFile Include="D:\test.txt" />
</ItemGroup>
<!-- Pass the file to read to the ReadLinesFromFile task -->
<ReadLinesFromFile File="#(TestFile)">
<!--Create the Property that represents the normal hintpath(SomePath\DLLName.dll)-->
<Output TaskParameter="Lines" PropertyName="HintPathFromFile" />
</ReadLinesFromFile>
<!-- Output the HintPath in output log to check if the path is right -->
<Message Text="$(HintPathFromFile)" Importance="high" />
<ItemGroup>
<Reference Include="TestReference">
<!--It actually equals:<HintPath>D:\AssemblyFolder\TestReference.dll</HintPath>-->
<HintPath>$(HintPathFromFile)</HintPath>
</Reference>
</ItemGroup>
</Target>
In addition:
I did the test with test.txt file whose content is:
I'm not sure about the actual content(and format) of your file, but if you only have path like D:\AssemblyFolder\ in that file, you should pass the D:\AssemblyFolder\+YourAssemblyName.dll to <HintPath> metadata. Cause the default reference format with hintpath looks like this:
<Reference Include="ClassLibrary1">
<HintPath>path\ClassLibrary1.dll</HintPath>
</Reference>
Pay attention to the path format! Hope my answer helps :)
In msbuild, static items can't depend on dynamic properties by design.
Accepted answer provides a work-around: use dynamic items, which, naturally, can depend on dynamic properties. But dynamic items don't show up in the IDE.
When dealing with files, more IDE-friendly approach is to create the item statically and update it when the target runs:
<!-- Static item definition -->
<ItemGroup>
<SomeItem Include="item_file" />
</ItemGroup>
<Target Name="...">
<!-- When the target runs, find the static item using Condition and change its properties -->
<ItemGroup>
<SomeItem Condition="'%(SomeItem.Identity)' == 'item_file'">
<SomeProperty>New Value</SomeProperty>
</SomeItem>
</ItemGroup>
</Target>
There's also an open msbuild issue (#889) to support improved syntax for this:
<Target Name="...">
<ItemGroup>
<!-- Update has the same syntax as Include -->
<SomeItem Update="item_file">
<SomeProperty>New Value</SomeProperty>
</SomeItem>
</ItemGroup>
</Target>
I have read in the documentation that if I want to compile using AoT (ahead of time) I have to add this settings in the .csproj file of the application.
<PropertyGroup>
<PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>
But I have another property group, is this:
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
So my doubt if I have to create a new prperty group or I can set the setting in the existing propery gorup, for example below UseWPF.
Thanks.
It does not matter if you have one or multiple PropertyGroup elements. Normally you would only create more than one if you either want to separate elements explicitly or you want them only used depending on (e. g.) the current configuration. In that case you would annotate them like this:
<PropertyGroup Condition="'$(Configuration)' == 'DEBUG'" >
<OutputPath>$(OutputPath)\debug</OutputPath>
</PropertyGroup>
I have these build configurations:
These platform configurations:
And these compiler conditionals:
NET40
NET45
My solution is a huge API that consists in 20 solutions, some of those solutions consumes Async keywords and other beneffits that are available only from .NetFx 4.5.
That part of the code I have it in a conditional in this way:
#If NET45 then
Sub Async
...
End Sub
#Else
Sub
...
End Sub
#End If
Then, what I'm trying to do is clear, the .NetFx 4.5 build configurations should compile the block of the NET45 conditional, and the .NetFx 4.0 build configurations should compile the block of the #Else part.
The problem I found is that if I change the application target framework in the project settings, the change persist in all the other build configurations, and I would like to avoid that persistance.
So how I can do this?.
Note:
I marked this question with the C# tag because is a general Visual Studio environment question, but I will clarify that my solutions are written in Vb.Net, because I know there are some big differences between the C# project settings and also their compiler params so maybe a C# advanced answer could not help me.
My suggestion is to get rid of conditional statements in code by moving platform/target/etc sencitive code in partial files. Then I would go to project file and would make the icluded files sensitive on particular condition using all the fuctionality ms-build provides
Example:
Create brand new VB Console App in Visual Studio
add three class files ClassDotNetFeatures40.vb, ClassDotNetFeatures45.vb, GenericClass.vb
Add the following code
in GenericClass.vb
Partial Public Class GenericClass
Public Sub Hello()
Console.Write("Hello ")
End Sub
End Class
in ClassDotNetFeatures40.vb
Partial Public Class GenericClass
Public Sub Word()
Console.Write("4.0 Word!")
End Sub
End Class
in
ClassDotNetFeatures45.vb
Public Class GenericClass
Public Sub Word()
Console.Write("4.5 Word!")
End Sub
End Class
Put the following code in Module1.vb
Sub Main()
Dim o = New GenericClass()
o.Hello()
o.Word()
End Sub
Save all
Right click on your solution and press Unload Project
Right click on the project file and press Edit Project
Find the following lines:
<Compile Include="ClassDotNetFeatures40.vb" />
<Compile Include="ClassDotNetFeatures45.vb" />
and replace them with
<Compile Condition="'$(Configuration)' == 'Debug'" Include="ClassDotNetFeatures40.vb" />
<Compile Condition="'$(Configuration)' == 'Release'" Include="ClassDotNetFeatures45.vb" />
press save
right click on project file and press Reload
now when you run the project undo debug you will get:
Hello 4.0 Word!
undo release you willl get:
Hello 4.5 Word!
You will need to change project files manually (I've played with csproj - hopefully vbproj works in the same way).
All project configurations properties described in the sections like this one:
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
...
</PropertyGroup>
Please notice Condition statement - that describes that this particular property set specified for Debug, AnyCPU configuration.
What you need to do is to move TargetFrameworkVersion property from general top level to configuration-specific levels, something like this:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<!-- general properties here - removing framework related... -->
<!--<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>-->
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<!-- Use 4.0 for Debug -->
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<!-- other properties here... -->
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<!-- Use 4.5 for Release -->
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<!-- other properties here... -->
</PropertyGroup>
Please notice that VS.Net GUI does NOT support this, and will not display correct values in the project Properties window; though it will use these values for build.
Depending on complexity of your solution, you might found some other artifacts, as VS.Net will not reload project properly, but at least that should work with build from console.
Additionally, you might need to use similar conditional "hacks" to reference correct libraries.
I have some code which makes use of Extension Methods, but compiles under .NET 2.0 using the compiler in VS2008. To facilitate this, I had to declare ExtensionAttribute:
/// <summary>
/// ExtensionAttribute is required to define extension methods under .NET 2.0
/// </summary>
public sealed class ExtensionAttribute : Attribute
{
}
However, I'd now like the library in which that class is contained to also be compilable under .NET 3.0, 3.5 and 4.0 - without the 'ExtensionAttribute is defined in multiple places' warning.
Is there any compile time directive I can use to only include the ExtensionAttribute when the framework version being targetted is .NET 2?
The linked SO question with 'create N different configurations' is certainly one option, but when I had a need for this I just added conditional DefineConstants elements, so in my Debug|x86 (for instance) after the existing DefineConstants for DEBUG;TRACE, I added these 2, checking the value in TFV that was set in the first PropertyGroup of the csproj file.
<DefineConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">RUNNING_ON_4</DefineConstants>
<DefineConstants Condition=" '$(TargetFrameworkVersion)' != 'v4.0' ">NOT_RUNNING_ON_4</DefineConstants>
You don't need both, obviously, but it's just there to give examples of both eq and ne behavior - #else and #elif work fine too :)
class Program
{
static void Main(string[] args)
{
#if RUNNING_ON_4
Console.WriteLine("RUNNING_ON_4 was set");
#endif
#if NOT_RUNNING_ON_4
Console.WriteLine("NOT_RUNNING_ON_4 was set");
#endif
}
}
I could then switch between targeting 3.5 and 4.0 and it would do the right thing.
I have a few suggestions for improving on the answers given so far:
Use Version.CompareTo(). Testing for equality will not work for later framework versions, yet to be named. E.g.
<CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">
will not match v4.5 or v4.5.1, which typically you do want.
Use an import file so that these additional properties only need to be defined once. I recommend keeping the imports file under source control, so that changes are propagated along with the project files, without extra effort.
Add the import element at the end of your project file, so that it is independent of any configuration specific property groups. This also has the benefit of requiring a single additional line in your project file.
Here is the import file (VersionSpecificSymbols.Common.prop)
<!--
******************************************************************
Defines the Compile time symbols Microsoft forgot
Modelled from https://msdn.microsoft.com/en-us/library/ms171464.aspx
*********************************************************************
-->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5.1')))) >= 0">$(DefineConstants);NETFX_451</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5')))) >= 0">$(DefineConstants);NETFX_45</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0')))) >= 0">$(DefineConstants);NETFX_40</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5')))) >= 0">$(DefineConstants);NETFX_35</DefineConstants>
<DefineConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0')))) >= 0">$(DefineConstants);NETFX_30</DefineConstants>
</PropertyGroup>
</Project>
Add Import Element to Project File
Reference it from your .csproj file by adding at the end, before the tag.
…
<Import Project="VersionSpecificSymbols.Common.prop" />
</Project>
You will need to fix up the path to point to the common/shared folder where you put this file.
To Use Compile Time Symbols
namespace VersionSpecificCodeHowTo
{
using System;
internal class Program
{
private static void Main(string[] args)
{
#if NETFX_451
Console.WriteLine("NET_451 was set");
#endif
#if NETFX_45
Console.WriteLine("NET_45 was set");
#endif
#if NETFX_40
Console.WriteLine("NET_40 was set");
#endif
#if NETFX_35
Console.WriteLine("NETFX_35 was set");
#endif
#if NETFX_30
Console.WriteLine("NETFX_30 was set");
#endif
#if NETFX_20
Console.WriteLine("NETFX_20 was set");
#else
The Version specific symbols were not set correctly!
#endif
#if DEBUG
Console.WriteLine("DEBUG was set");
#endif
#if MySymbol
Console.WriteLine("MySymbol was set");
#endif
Console.ReadKey();
}
}
}
A Common “Real Life” Example
Implementing Join(string delimiter, IEnumerable strings) Prior to .NET 4.0
// string Join(this IEnumerable<string> strings, string delimiter)
// was not introduced until 4.0. So provide our own.
#if ! NETFX_40 && NETFX_35
public static string Join( string delimiter, IEnumerable<string> strings)
{
return string.Join(delimiter, strings.ToArray());
}
#endif
References
Property Functions
MSBuild Property Evaluation
Can I make a preprocessor directive dependent on the .NET framework version?
Conditional compilation depending on the framework version in C#
Property groups are overwrite only so this would knock out your settings for DEBUG, TRACE, or any others. - See MSBuild Property Evaluation
Also if the DefineConstants property is set from the command line anything you do to it inside the project file is irrelevant as that setting becomes global readonly. This means your changes to that value fail silently.
Example maintaining existing defined constants:
<CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v2.0' ">V2</CustomConstants>
<CustomConstants Condition=" '$(TargetFrameworkVersion)' == 'v4.0' ">V4</CustomConstants>
<DefineConstants Condition=" '$(DefineConstants)' != '' And '$(CustomConstants)' != '' ">$(DefineConstants);</DefineConstants>
<DefineConstants>$(DefineConstants)$(CustomConstants)</DefineConstants>
This section MUST come after any other defined constants since those are unlikely to be set up in an additive manner
I only defined those 2 because that's mostly what I'm interested in on my project, ymmv.
See Also: Common MsBuild Project Properties
Pre-defined symbols for target frameworks are now built into the version of MSBuild that is used by the dotnet tool and by VS 2017 onwards. See https://learn.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-a-target-framework for the full list.
#if NET47
Console.WriteLine("Running on .Net 4.7");
#elif NETCOREAPP2_0
Console.WriteLine("Running on .Net Core 2.0");
#endif
I would like to contribute with an updated answer which solves some issues.
If you set DefineConstants instead of CustomConstants you'll end up, in the Conditional Compilation Symbols Debug command line, after some framework version switch, with duplicated conditional constants (i.e.: NETFX_451;NETFX_45;NETFX_40;NETFX_35;NETFX_30;NETFX_20;NETFX_35;NETFX_30;NETFX_20;).
This is the VersionSpecificSymbols.Common.prop which solves any issue.
<!--
*********************************************************************
Defines the Compile time symbols Microsoft forgot
Modelled from https://msdn.microsoft.com/en-us/library/ms171464.aspx
*********************************************************************
Author: Lorenzo Ruggeri (lrnz.ruggeri#gmail.com)
-->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition=" $(TargetFrameworkVersion) == 'v2.0' ">
<PropertyGroup>
<CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
</PropertyGroup>
</When>
<When Condition=" $(TargetFrameworkVersion) == 'v3.0' ">
<PropertyGroup>
<CustomConstants >$(CustomConstants);NETFX_30</CustomConstants>
<CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
</PropertyGroup>
</When>
<When Condition=" $(TargetFrameworkVersion) == 'v3.5' ">
<PropertyGroup>
<CustomConstants >$(CustomConstants);NETFX_35</CustomConstants>
<CustomConstants >$(CustomConstants);NETFX_30</CustomConstants>
<CustomConstants >$(CustomConstants);NETFX_20</CustomConstants>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5.1')))) >= 0">$(CustomConstants);NETFX_451</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.5')))) >= 0">$(CustomConstants);NETFX_45</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('4.0')))) >= 0">$(CustomConstants);NETFX_40</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.5')))) >= 0">$(CustomConstants);NETFX_35</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('3.0')))) >= 0">$(CustomConstants);NETFX_30</CustomConstants>
<CustomConstants Condition="$([System.Version]::Parse('$(TargetFrameworkVersion.Substring(1))').CompareTo($([System.Version]::Parse('2.0')))) >= 0">$(CustomConstants);NETFX_20</CustomConstants>
</PropertyGroup>
</Otherwise>
</Choose>
<PropertyGroup>
<DefineConstants>$(DefineConstants);$(CustomConstants)</DefineConstants>
</PropertyGroup>
</Project>
Use reflection to determine if the class exists. If it does, then dynamically create and use it, otherwise use the .Net2 workaround class which can be defined, but not used for all other .net versions.
Here is code I used for an AggregateException which is .Net 4 and greater only:
var aggregatException = Type.GetType("System.AggregateException");
if (aggregatException != null) // .Net 4 or greater
{
throw ((Exception)Activator.CreateInstance(aggregatException, ps.Streams.Error.Select(err => err.Exception)));
}
// Else all other non .Net 4 or less versions
throw ps.Streams.Error.FirstOrDefault()?.Exception
?? new Exception("Powershell Exception Encountered."); // Sanity check operation, should not hit.