訓練
模組
建立新的 .NET 專案並使用套件相依性 - Training
建立 .NET 專案,並了解如何在專案中新增套件及管理套件相依性。 使用 .NET Core CLI 與 NuGet 登錄,透過 Visual Studio Code 將程式庫與工具新增至您的 C# 應用程式。
在本教學課程中,您將在 C# 的 MSBuild 中建立自定義工作來處理程式代碼產生,然後在組建中使用工作。 此範例示範如何使用 MSBuild 來處理全新和重建作業。 此範例也會示範如何支援累加建置,因此只有在輸入檔變更時才會產生程序代碼。 示範的技術適用於各種程式代碼產生案例。 這些步驟也會示範如何使用 NuGet 來封裝散發工作,而本教學課程包含使用 BinLog 查看器來改善疑難解答體驗的選擇性步驟。
您應該瞭解 MSBuild 概念,例如工作、目標和屬性。 請參閱 msBuild 概念 。
這些範例需要隨 Visual Studio 一起安裝的 MSBuild,但也可以個別安裝。 請參閱 下載不含 Visual Studio 的 MSBuild。
此範例會採用包含要設定值的輸入文字檔,並使用建立這些值的程式代碼建立 C# 程式代碼檔案。 雖然這是簡單的範例,但相同的基本技術可以套用至更複雜的程式代碼產生案例。
在本教學課程中,您將建立名為 AppSettingStronglyTyped 的 MSBuild 自定義工作。 工作會讀取一組文字檔,並且每個檔案中的行都具有以下格式:
propertyName:type:defaultValue
程式代碼會產生具有所有常數的 C# 類別。 問題應該停止組建,併為使用者提供足夠的資訊來診斷問題。
本教學課程的完整範例程式代碼位於 自定義工作 - GitHub 上 .NET 範例存放庫中的程式代碼產生。
建立 .NET Standard 類別庫。 架構應該是 .NET Standard 2.0。
請注意完整 MSBuild(Visual Studio 所使用的 MSBuild)和可攜式 MSBuild 之間的差異,這是 .NET Core 命令行中配套的 MSBuild。
如果您想要在 .NET Framework 與任何其他 .NET 實作之間共用程序代碼,例如 .NET Core,您的連結庫應該以 .NET Standard 2.0 為目標,而且您想要在 .NET Framework 上執行的 Visual Studio 內執行。 .NET Framework 不支援 .NET Standard 2.1。
編譯自定義工作時,您應該參考符合您預期支援的Visual Studio和/或 .NET SDK 的最低版本 MSBuild API (Microsoft.Build.*
) 版本。 例如,若要支援 Visual Studio 2019 上的使用者,您應該針對 MSBuild 16.11 建置。
第一個步驟是建立 MSBuild 自定義工作。 有關如何 撰寫 MSBuild 自定義工作的資訊, 可能有助於瞭解下列步驟。 MSBuild 自定義任務是實作 ITask 介面的類別。
新增 Microsoft.Build.Utilities.Core NuGet 套件的參考,然後建立衍生自 Microsoft.Build.Utilities.Task 的名為 AppSettingStronglyTyped 的類別。
新增三個屬性。 這些屬性會定義使用者在客戶端專案中使用工作時所設定的工作參數:
//The name of the class which is going to be generated
[Required]
public string SettingClassName { get; set; }
//The name of the namespace where the class is going to be generated
[Required]
public string SettingNamespaceName { get; set; }
//List of files which we need to read with the defined format: 'propertyName:type:defaultValue' per line
[Required]
public ITaskItem[] SettingFiles { get; set; }
工作會處理 SettingFiles,並產生類別 SettingNamespaceName.SettingClassName
。 產生的類別會根據文本文件的內容,有一組常數。
工作輸出應該是提供所產生程式代碼檔名的字串:
// The filename where the class was generated
[Output]
public string ClassNameFile { get; set; }
當您建立自訂工作時,會繼承自 Microsoft.Build.Utilities.Task。 若要實作此任務,應覆寫 Execute() 方法。 如果工作成功,則 Execute
方法會傳回 true
,否則會傳回 false
。
Task
會實作 Microsoft.Build.Framework.ITask,並提供某些 ITask
成員的預設實作,此外還提供一些記錄功能。 請務必將狀態輸出至記錄檔,以診斷故障並排除問題,特別是如果發生問題,而且工作必須返回錯誤結果(false
)。 發生錯誤時,類別會呼叫 TaskLoggingHelper.LogError來發出錯誤訊號。
public override bool Execute()
{
//Read the input files and return a IDictionary<string, object> with the properties to be created.
//Any format error it will return false and log an error
var (success, settings) = ReadProjectSettingFiles();
if (!success)
{
return !Log.HasLoggedErrors;
}
//Create the class based on the Dictionary
success = CreateSettingClass(settings);
return !Log.HasLoggedErrors;
}
工作 API 允許傳回 false,表示失敗,但不會向使用者說明問題發生在哪裡。 最好傳回 !Log.HasLoggedErrors
而不是布林值,並在發生錯誤時記錄下來。
記錄錯誤時的最佳做法是在記錄錯誤時提供詳細數據,例如行號和不同的錯誤碼。 下列程式代碼會剖析文字輸入檔,並使用 TaskLoggingHelper.LogError 方法搭配產生錯誤的文字檔中的行號。
private (bool, IDictionary<string, object>) ReadProjectSettingFiles()
{
var values = new Dictionary<string, object>();
foreach (var item in SettingFiles)
{
int lineNumber = 0;
var settingFile = item.GetMetadata("FullPath");
foreach (string line in File.ReadLines(settingFile))
{
lineNumber++;
var lineParse = line.Split(':');
if (lineParse.Length != 3)
{
Log.LogError(subcategory: null,
errorCode: "APPS0001",
helpKeyword: null,
file: settingFile,
lineNumber: lineNumber,
columnNumber: 0,
endLineNumber: 0,
endColumnNumber: 0,
message: "Incorrect line format. Valid format prop:type:defaultvalue");
return (false, null);
}
var value = GetValue(lineParse[1], lineParse[2]);
if (!value.Item1)
{
return (value.Item1, null);
}
values[lineParse[0]] = value.Item2;
}
}
return (true, values);
}
使用上一個程式代碼所示的技術,文字輸入檔語法中的錯誤會顯示為建置錯誤,並提供實用的診斷資訊:
Microsoft (R) Build Engine version 17.2.0 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 2/16/2022 10:23:24 AM.
Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" on node 1 (default targets).
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
Done Building Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default targets) -- FAILED.
Build FAILED.
"S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default target) (1) ->
(generateSettingClass target) ->
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
0 Warning(s)
1 Error(s)
當您在工作中攔截例外狀況時,請使用 TaskLoggingHelper.LogErrorFromException 方法。 這可改善錯誤輸出,例如,藉由取得擲回例外狀況的呼叫堆疊。
catch (Exception ex)
{
// This logging helper method is designed to capture and display information
// from arbitrary exceptions in a standard way.
Log.LogErrorFromException(ex, showStackTrace: true);
return false;
}
此處不會顯示使用這些輸入來建置所產生程式碼檔案文字的其他方法實作。請參閱範例存放庫中的 AppSettingStronglyTyped.cs。
範例程式代碼會在建置程式期間產生 C# 程式代碼。 此工作就像任何其他 C# 類別,因此當您完成本教學課程時,您可以自定義它,並新增您自己的案例所需的任何功能。
在本節中,您將建立使用工作的標準 .NET Core 控制台應用程式。
重要
請務必避免在將要使用的同一個 MSBuild 程式中建立 MSBuild 自訂工作項。 新項目應該位於完整的不同 Visual Studio 解決方案中,或新專案使用從標準輸出預先產生並重新放置的 dll。
在新的 Visual Studio 方案中建立 .NET 控制台專案 MSBuildConsoleExample。
一般分發工作的方式是透過 NuGet 套件,但在開發與偵錯過程中,您可以直接將 .props
和 .targets
的所有資訊包含在應用程式的專案檔中,然後在將工作分發給其他人時轉向使用 NuGet 格式。
修改專案檔以使用程式碼生成任務。 本節中的程式代碼清單會顯示參考工作之後修改的專案檔、設定工作的輸入參數,以及撰寫處理全新和重建作業的目標,以便如預期般移除產生的程式碼檔案。
工作是使用 UsingTask 元素 (MSBuild)註冊。
UsingTask
項目會註冊工作;它會告訴 MSBuild 工作的名稱,以及如何尋找並執行包含工作類別的元件。 組件路徑相對於專案檔案。
PropertyGroup
包含對應任務中所定義屬性的屬性定義。 這些屬性是使用屬性來設定,而工作名稱會當做項目名稱使用。
TaskName
是從元件參考的工作名稱。 此屬性應該一律使用完整指定的命名空間。
AssemblyFile
是組件的檔案路徑。
若要執行任務,請將任務新增至目標 GenerateSetting
。
目標 ForceGenerateOnRebuild
藉由刪除產生的檔案來處理清除和重建作業。 它會將 AfterTargets
屬性設定為 CoreClean
,以在 CoreClean
目標之後執行。
<Project Sdk="Microsoft.NET.Sdk">
<UsingTask TaskName="AppSettingStronglyTyped.AppSettingStronglyTyped" AssemblyFile="..\..\AppSettingStronglyTyped\AppSettingStronglyTyped\bin\Debug\netstandard2.0\AppSettingStronglyTyped.dll"/>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootFolder>$(MSBuildProjectDirectory)</RootFolder>
<SettingClass>MySetting</SettingClass>
<SettingNamespace>MSBuildConsoleExample</SettingNamespace>
<SettingExtensionFile>mysettings</SettingExtensionFile>
</PropertyGroup>
<ItemGroup>
<SettingFiles Include="$(RootFolder)\*.mysettings" />
</ItemGroup>
<Target Name="GenerateSetting" BeforeTargets="CoreCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs">
<AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)">
<Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" />
</AppSettingStronglyTyped>
<ItemGroup>
<Compile Remove="$(SettingClassFileName)" />
<Compile Include="$(SettingClassFileName)" />
</ItemGroup>
</Target>
<Target Name="ForceReGenerateOnRebuild" AfterTargets="CoreClean">
<Delete Files="$(RootFolder)\$(SettingClass).generated.cs" />
</Target>
</Project>
注意
此程式碼不是覆寫 CoreClean
類的目標,而是採用另一種方法將目標 (BeforeTarget 和 AfterTarget)排序。 SDK 樣式專案在專案檔的最後一行之後隱含匯入目標;這表示除非您手動指定匯入,否則您無法覆寫預設目標。 請參閱 覆寫預先定義的目標。
Inputs
和 Outputs
屬性可藉由提供累加組建的信息,協助 MSBuild 更有效率。 輸入的日期會與輸出進行比較,以查看是否需要執行目標,或是否可以重複使用上一個組建的輸出。
建立輸入文字檔,並定義要探索的擴展名。 使用預設延伸模組,在根目錄上建立 MyValues.mysettings
,其中包含下列內容:
Greeting:string:Hello World!
再次建置,並生成並建置檔案。 檢查 MySetting.generated.cs 檔案的項目資料夾。
MySetting 類別 位於錯誤的命名空間中,因此現在請進行變更以使用我們的應用程式命名空間。 開啟項目檔並新增下列程式代碼:
<PropertyGroup>
<SettingNamespace>MSBuildConsoleExample</SettingNamespace>
</PropertyGroup>
再次重建,並觀察 類別位於 MSBuildConsoleExample
命名空間中。 如此一來,您可以重新定義產生的類別名稱(SettingClass
)、要作為輸入的文字檔(SettingExtensionFile
),以及它們的位置(RootFolder
),以符合您的需求。
開啟 Program.cs
並變更硬編碼的「Hello World!!」 至使用者定義常數:
static void Main(string[] args)
{
Console.WriteLine(MySetting.Greeting);
}
執行程式;它會從產生的類別列印問候語。
您可以使用命令列命令進行編譯。 瀏覽至項目資料夾。 您將使用 -bl
(二進位記錄檔) 選項來產生二進位記錄檔。 二進位記錄檔將有有用的資訊,以瞭解建置程式期間發生什麼情況。
# Using dotnet MSBuild (run core environment)
dotnet build -bl
# or full MSBuild (run on net framework environment; this is used by Visual Studio)
msbuild -bl
這兩個命令都會產生記錄檔 msbuild.binlog
,其可以使用 MSBuild 二進位和結構化記錄檔檢視器 開啟。 選項 /t:rebuild
表示執行重建目標。 它會強制重新產生產生的程式代碼檔案。
祝賀! 您已建置可產生程序代碼的工作,並在組建中使用。
如果您只需要在幾個專案或單一方案中使用自定義任務,以原始組件形式使用任務可能已經足夠,但將任務以用於其他地方或與他人共用的最佳方式是將其作為 NuGet 套件。
MSBuild 工作套件與程式庫 NuGet 套件有某些關鍵差異:
lib/<target framework>
資料夾,因為這會導致 NuGet 將元件包含在取用工作的任何套件中.deps.json
檔案,以一致的方式協助 MSBuild 載入工作的相依性(特別是原生相依性)。若要完成所有這些目標,您必須對上述和超越您可能熟悉的標準專案檔進行一些變更。
建立 NuGet 套件是將自定義工作散發給其他人的建議方式。
若要準備產生 NuGet 套件,請對專案檔進行一些變更,以指定描述封裝的詳細數據。 您建立的初始項目檔類似下列程式代碼:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
</ItemGroup>
</Project>
若要產生 NuGet 套件,請新增下列程式代碼來設定封裝的屬性。 您可以在 Pack 檔案中看到支援 MSBuild 屬性的完整清單,:
<PropertyGroup>
...
<IsPackable>true</IsPackable>
<Version>1.0.0</Version>
<Title>AppSettingStronglyTyped</Title>
<Authors>Your author name</Authors>
<Description>Generates a strongly typed setting class base on a text file.</Description>
<PackageTags>MyTags</PackageTags>
<Copyright>Copyright ©Contoso 2022</Copyright>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
...
</PropertyGroup>
需要 copyLocalLockFileAssemblies 屬性,以確保相依性會複製到輸出目錄。
MSBuild 工作的相依性必須包含在封裝中。它們無法表示為一般套件參考。 套件不會向外部用戶公開任何一般相依性。 這會採取兩個步驟來完成:將您的元件標示為私用,並實際將其內嵌在產生的套件中。 在此範例中,我們假設您的工作依賴於 Microsoft.Extensions.DependencyInjection
的運作,因此請在版本 6.0.0
中將 PackageReference
添加到 Microsoft.Extensions.DependencyInjection
。
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0" />
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0" />
</ItemGroup>
現在,使用 PrivateAssets="all"
屬性來標記此 Task 專案的每個相依性,PackageReference
和 ProjectReference
。 這將指示 NuGet 完全不要顯露這些相依性給消費專案。 您可以在 NuGet 文件 中深入瞭解控制相依性資產。
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
/>
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0"
PrivateAssets="all"
/>
</ItemGroup>
您也必須將相依性的運行時間資產內嵌至工作套件。 這有兩個部分:一個是將我們的相依性新增至 BuildOutputInPackage
ItemGroup 的 MSBuild 目標,另一個是控制這些 BuildOutputInPackage
項目的版面配置的幾個屬性。 您可以在 NuGet 文件 中深入瞭解這個過程。
<PropertyGroup>
...
<!-- This target will run when MSBuild is collecting the files to be packaged, and we'll implement it below. This property controls the dependency list for this packaging process, so by adding our custom property we hook ourselves into the process in a supported way. -->
<TargetsForTfmSpecificBuildOutput>
$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
</TargetsForTfmSpecificBuildOutput>
<!-- This property tells MSBuild where the root folder of the package's build assets should be. Because we are not a library package, we should not pack to 'lib'. Instead, we choose 'tasks' by convention. -->
<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
<!-- NuGet does validation that libraries in a package are exposed as dependencies, but we _explicitly_ do not want that behavior for MSBuild tasks. They are isolated by design. Therefore we ignore this specific warning. -->
<NoWarn>NU5100</NoWarn>
<!-- Suppress NuGet warning NU5128. -->
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
...
</PropertyGroup>
...
<!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output. -->
<Target
Name="CopyProjectReferencesToPackage"
DependsOnTargets="ResolveReferences">
<ItemGroup>
<!-- The TargetPath is the path inside the package that the source file will be placed. This is already precomputed in the ReferenceCopyLocalPaths items' DestinationSubPath, so reuse it here. -->
<BuildOutputInPackage
Include="@(ReferenceCopyLocalPaths)"
TargetPath="%(ReferenceCopyLocalPaths.DestinationSubPath)" />
</ItemGroup>
</Target>
如上所述,此相依性將由 MSBuild 本身在運行時間提供,因此我們不需要將它組合到套件中。 若要這樣做,請將 ExcludeAssets="Runtime"
屬性新增至它的 PackageReference
...
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
ExcludeAssets="Runtime"
/>
...
MSBuild 可以使用 deps.json
檔案,以確保載入正確的相依性版本。 您必須新增一些 MSBuild 屬性來生成檔案,因為它預設不會為函式庫生成。 然後,新增目標以將它包含在套件輸出中,類似於您針對套件相依性所做的作業。
<PropertyGroup>
...
<!-- Tell the SDK to generate a deps.json file -->
<GenerateDependencyFile>true</GenerateDependencyFile>
...
</PropertyGroup>
...
<!-- This target adds the generated deps.json file to our package output -->
<Target
Name="AddBuildDependencyFileToBuiltProjectOutputGroupOutput"
BeforeTargets="BuiltProjectOutputGroup"
Condition=" '$(GenerateDependencyFile)' == 'true'">
<ItemGroup>
<BuiltProjectOutputGroupOutput
Include="$(ProjectDepsFilePath)"
TargetPath="$(ProjectDepsFileName)"
FinalOutputPath="$(ProjectDepsFilePath)" />
</ItemGroup>
</Target>
如需本節的背景,請閱讀 屬性和目標,以及如何 在 NuGet 套件中包含屬性和目標。
在某些情況下,您可能會想要在取用套件的專案中新增自定義建置目標或屬性,例如在建置期間執行自定義工具或程式。 您可以藉由將檔案放在專案中 build
資料夾中的表單 <package_id>.targets
或 <package_id>.props
中來執行此動作。
專案根 組建 資料夾中的檔案會被視為適用於所有目標架構。
在本節中,您會在 .props
和 .targets
檔案中綁定任務實作,這些檔案會包含在我們的 NuGet 套件中,並從引用專案自動載入。
在工作的項目檔中,AppSettingStronglyTyped.csproj,新增下列程式代碼:
<ItemGroup>
<!-- these lines pack the build props/targets files to the `build` folder in the generated package.
by convention, the .NET SDK will look for build\<Package Id>.props and build\<Package Id>.targets
for automatic inclusion in the build. -->
<Content Include="build\AppSettingStronglyTyped.props" PackagePath="build\" />
<Content Include="build\AppSettingStronglyTyped.targets" PackagePath="build\" />
</ItemGroup>
建立 組建 資料夾,並在該資料夾中新增兩個文字檔:AppSettingStronglyTyped.props
和 AppSettingStronglyTyped.targets。
AppSettingStronglyTyped.props
是在 Microsoft.Common.props中提早匯入,因此稍後定義的屬性無法被使用。 因此,請避免參考尚未定義的物件屬性;它們的結果會是空值。
從 NuGet 套件匯入 .targets
檔案之後,Directory.Build.targets 會從 Microsoft.Common.targets 匯入。 因此,它可以覆寫大部分建置邏輯中定義的屬性和目標,或針對所有專案設定屬性,而不論個別專案設定的內容為何。 請參閱 匯入訂單。
AppSettingStronglyTyped.props 包含工作,並使用預設值定義一些屬性:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--defining properties interesting for my task-->
<PropertyGroup>
<!--The folder where the custom task will be present. It points to inside the nuget package. -->
<_AppSettingsStronglyTyped_TaskFolder>$(MSBuildThisFileDirectory)..\tasks\netstandard2.0</_AppSettingsStronglyTyped_TaskFolder>
<!--Reference to the assembly which contains the MSBuild Task-->
<CustomTasksAssembly>$(_AppSettingsStronglyTyped_TaskFolder)\$(MSBuildThisFileName).dll</CustomTasksAssembly>
</PropertyGroup>
<!--Register our custom task-->
<UsingTask TaskName="$(MSBuildThisFileName).AppSettingStronglyTyped" AssemblyFile="$(CustomTasksAssembly)"/>
<!--Task parameters default values, this can be overridden-->
<PropertyGroup>
<RootFolder Condition="'$(RootFolder)' == ''">$(MSBuildProjectDirectory)</RootFolder>
<SettingClass Condition="'$(SettingClass)' == ''">MySetting</SettingClass>
<SettingNamespace Condition="'$(SettingNamespace)' == ''">example</SettingNamespace>
<SettingExtensionFile Condition="'$(SettingExtensionFile)' == ''">mysettings</SettingExtensionFile>
</PropertyGroup>
</Project>
安裝套件時,會自動包含 AppSettingStronglyTyped.props
檔案。 然後,用戶端有可用的工作和一些預設值。 不過,它從未使用過。 若要讓此程式碼運作,請在 AppSettingStronglyTyped.targets
檔案中定義一些目標,這也會在安裝套件時自動包含:
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--Defining all the text files input parameters-->
<ItemGroup>
<SettingFiles Include="$(RootFolder)\*.$(SettingExtensionFile)" />
</ItemGroup>
<!--A target that generates code, which is executed before the compilation-->
<Target Name="BeforeCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs">
<!--Calling our custom task-->
<AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)">
<Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" />
</AppSettingStronglyTyped>
<!--Our generated file is included to be compiled-->
<ItemGroup>
<Compile Remove="$(SettingClassFileName)" />
<Compile Include="$(SettingClassFileName)" />
</ItemGroup>
</Target>
<!--The generated file is deleted after a general clean. It will force the regeneration on rebuild-->
<Target Name="AfterClean">
<Delete Files="$(RootFolder)\$(SettingClass).generated.cs" />
</Target>
</Project>
第一步是建立一個 ItemGroup,它代表要讀取的文字檔案(可能不只一個),並將作為我們某些任務的參數。 我們尋找的位置和延伸模組有預設值,但您可以覆寫在用戶端 MSBuild 專案檔中定義屬性的值。
然後定義兩個 MSBuild 目標,。 我們 延伸 MSBuild 程式,並覆寫預先定義的目標:
BeforeCompile
:目標是呼叫自定義工作來產生 類別,並包含要編譯的類別。 在核心編譯完成之前,此目標中的工作會被插入。 輸入和輸出欄位與 累加建置相關。 如果所有輸出項目都已是 up-to-date,MSBuild 會略過目標。 此目標的增量建構可以顯著提升建構效能。 如果專案的輸出檔與輸入檔或檔案的存留期相同或更新,則專案會視為 up-to日期。
AfterClean
:目標是在執行常規清除後刪除已生成的類別檔案。 在這個目標中的任務會在執行核心清除功能之後插入。 它會強制在重建目標執行時重複程式代碼產生步驟。
若要產生 NuGet 套件,您可以使用 Visual Studio(以滑鼠右鍵按兩下方案總管 項目節點,然後選取 [套件]。 您也可以使用命令列來執行此動作。 瀏覽至工作項目檔 AppSettingStronglyTyped.csproj
所在的資料夾,然後執行下列命令:
// -o is to define the output; the following command chooses the current folder.
dotnet pack -o .
祝賀! 您已產生名為 \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg的 NuGet 套件。
套件具有擴展名 .nupkg
,而且是壓縮的 zip 檔案。 您可以使用 zip 工具加以開啟。
.target
和 .props
檔案位於 build
資料夾中。
.dll
檔案位於 lib\netstandard2.0\
資料夾中。
AppSettingStronglyTyped.nuspec
檔案位於根層級。
您應考慮同時支援 Full
(.NET Framework)和 Core
(包含 .NET 5 及更高版本)的 MSBuild 發行版,以支援最廣泛的使用者群體。
對於 『normal』 .NET SDK 專案,多重目標表示在專案檔中設定多個 TargetFrameworks。 當您這樣做時,系統會針對 TargetFrameworkMonikers 觸發組建,而整體結果可以封裝為單一成品。
這不是 MSBuild 的完整故事。 MSBuild 有兩個主要運送車輛:Visual Studio 和 .NET SDK。 這些是非常不同的運行時間環境;一個在 .NET Framework 運行時間上執行,另一個會在 CoreCLR 上執行。 這表示,雖然您的程式代碼可以以 netstandard2.0 為目標,但您的工作邏輯可能會根據目前使用中的 MSBuild 運行時間類型而有所不同。 實際上,由於 .NET 5.0 和更高版本中有許多新的 API,因此將您的 MSBuild 任務原始碼設置為多重目標,以及將 MSBuild 目標邏輯設置為針對多個 MSBuild 執行時期類型,是有意義的。
若要為多個 TargetFrameworkMonikers(TFM)設置目標:
將您的項目檔變更為使用 net472
和 net6.0
TFM(後者可能會根據您想要鎖定的 SDK 層級而變更)。 您可能想要以 netcoreapp3.1
為目標,直到 .NET Core 3.1 停止支援。 當您這樣做時,封裝資料夾結構會從 tasks/
變更為 tasks/<TFM>/
。
<TargetFrameworks>net472;net6.0</TargetFrameworks>
更新您的 .targets
檔案,以使用正確的 TFM 加載您的任務。 所需的 TFM 會根據您上述選擇的 .NET TFM 而變更,但針對以 net472
和 net6.0
為目標的專案,您會有如下的屬性:
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</AppSettingStronglyTyped_TFM>
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</AppSettingStronglyTyped_TFM>
此程式代碼會使用 MSBuildRuntimeType
屬性作為活躍主控環境的代理。 設定此屬性之後,您可以在 UsingTask
中使用它來載入正確的 AssemblyFile
:
<UsingTask
AssemblyFile="$(MSBuildThisFileDirectory)../tasks/$(AppSettingStronglyTyped_TFM)/AppSettingStronglyTyped.dll"
TaskName="AppSettingStrongTyped.AppSettingStronglyTyped" />
許多工作都牽涉到呼叫可執行檔。 在某些情況下,您可以使用 Exec 工作,但如果 Exec 工作的限制是個問題,您也可以建立自定義工作。 下列教學課程會逐步解說這兩個選項,其中包含更真實的程式代碼產生案例:建立自定義工作來產生 REST API 的用戶端程序代碼。
在建置 中使用程式代碼產生
或者,瞭解如何測試自定義工作。
訓練
模組
建立新的 .NET 專案並使用套件相依性 - Training
建立 .NET 專案,並了解如何在專案中新增套件及管理套件相依性。 使用 .NET Core CLI 與 NuGet 登錄,透過 Visual Studio Code 將程式庫與工具新增至您的 C# 應用程式。
文件
使用 MSBuild 的程式碼撰寫您自己的工作 - MSBuild
探索如何建立自己的任務,以提供在 MSBuild 專案建置過程中執行的程式碼。
使用 Visual Studio 進行單元測試 MSBuild 自定義工作 - MSBuild
在散發之前,先使用 Visual Studio 中的單元測試功能來測試 MSBuild 自訂工作,以確保程式碼的正確性。
了解 MSBuild 隨附的工作,這些工作提供建置流程期間所執行的程式碼。