容器環境下將NuGet包XML文件新增到Swagger
在.NET Core專案開發過程中,為了實現程式碼複用,我們將可以重複使用的部分拆分成一個個小的NuGet包。這些NuGet包可以在其他系統中複用,這樣我們只需要實現系統特定的程式碼,其餘部分的就可以重用了,包括功能、文件等。使用過程中,功能複用沒有遇到任何問題,但是文件複用卻遇到了問題。我們使用SwashBuckle生成Swagger定義和Swagger UI。Swashbuckle需要XML文件,才能顯示控制器和模型的文件說明。不幸的是,Swagger中不能正常顯示NuGet包的模型文件說明。
我們可以用在專案檔案.csproj
的PropertyGroup
中新增<GenerateDocumentationFile>true</GenerateDocumentationFile>
的方式,將XML文件新增到NuGet包中,然後將XML文件手動拷貝到專案裡,作為內容輸出。Swagger中就能正常顯示NuGet包的模型文件說明了。但是這樣做,如果NuGet包版本更新,就需要重新手動拷貝XML文件,感覺不太優雅。是否有更優雅的方式呢?
Google到幾篇文章,方案大同小異,不知道可不可行。試試吧!期待可以成功!
實戰
我們涉及到兩個專案:
ICH.NetCore2.Test.WebApi
:ASP.NET Core WebApi 主專案ICH.Common
:可重用的公共元件NuGet包
我們按照以下幾個步驟分佈實施 。
1、設定.csproj檔案構建NuGet包時包含XML文件
ICH.Common
的專案檔案.csproj
的PropertyGroup
中新增<GenerateDocumentationFile>true</GenerateDocumentationFile>
,作用是設定.csproj檔案構建NuGet包時包含XML文件。
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
2、定義從NuGet包資料夾的根目錄到XML文件所在位置的相對路徑
ICH.NetCore2.Test.WebApi
的專案檔案.csproj
的PackageReference
中新增<CopyToOutputDirectory>lib\netstandard2.0\*.xml</CopyToOutputDirectory>
,作用是定義從NuGet包資料夾的根目錄到XML文件所在位置的相對路徑。
<ItemGroup>
<PackageReference Include="ICH.Common" Version="$(ICHCoreFXVersion)">
<CopyToOutputDirectory>lib\netstandard2.0\*.xml</CopyToOutputDirectory>
</PackageReference>
</ItemGroup>
- 在.NET Core中,我們的NuGet包位於:
%USERPROFILE%\.nuget\packages\{PackageName}\{PackageVersion}
- XML文件檔案位於:
%USERPROFILE%\.nuget\packages\{PackageName}\{PackageVersion}\lib\netcoreapp2.1\{PackageName}.xml
3、設定構建後從NuGet包中複製XML文件
ICH.NetCore2.Test.WebApi
的專案檔案.csproj
新增<Target Name="AfterTargetsBuild" AfterTargets="Build">
,作用是設定構建後從NuGet包中複製XML文件。
<Target Name="AfterTargetsBuild" AfterTargets="Build">
<ItemGroup>
<PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="$(NugetPackageRoot)\%(PackageReference.Identity)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)" />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)" />
</Target>
4、設定釋出後從NuGet包中複製XML文件
與上一步類似,ICH.NetCore2.Test.WebApi
的專案檔案.csproj
新增<Target Name="AfterTargetsPublish" AfterTargets="Publish">
,作用是設定釋出後從NuGet包中複製XML文件。
<Target Name="AfterTargetsPublish" AfterTargets="Publish">
<ItemGroup>
<PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="$(NugetPackageRoot)\%(PackageReference.Identity)\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)" />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(PublishDir)" />
</Target>
5、新增XML文件到Swagger定義中
ICH.NetCore2.Test.WebApi
的專案Startup
設定options.IncludeXmlComments(xmlFile, true)
,作用是新增XML文件到Swagger定義中。
public static void ConfigureSwagger(this IServiceCollection)
{
services.AddSwaggerGen(c =>
{
string[] xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles) options.IncludeXmlComments(xmlFile, true);
});
}
6、測試
現在是見證奇蹟的時候了。
首先本地構建一下試試,心情忐忑!
可以看到XML文件從NuGet包中複製到輸出目錄了,奈斯!
然後本地釋出一下試試,心情依然忐忑!
可以看到XML文件從NuGet包中複製到輸出目錄了, 外瑞奈斯!
最後再看看Swagger效果,Swagger中也能正常顯示NuGet包的模型文件說明。
7、遇坑
就這麼簡單?有一個聲音告訴我,圖樣圖森破!
我們生產環境是採用DockerFile的CICD方式自動釋出到K8S叢集,釋出之後,在生產環境Swagger中並不能正常顯示NuGet包的模型文件說明。
Why?Why?Why?另一個聲音告訴我,不要慌,冷靜思考!
我們的Dockerfile
內容如下:
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY . .
WORKDIR /src/src/ICH.NetCore2.Test.WebApi
RUN dotnet publish ICH.NetCore2.Test.WebApi.csproj -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=build /app .
RUN ls
RUN rm -rf appsettings.Development.json
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone
ENTRYPOINT ["dotnet", "ICH.NetCore2.Test.WebApi.dll"]
看了一下CICD日誌,發現輸出目錄並沒有ICH.Common.xml
檔案。
這就存在兩種可能:
- 要麼快取的NuGet包中不存在XML文件
- 要麼快取的NuGet包中存在XML文件,主專案釋出時卻沒有從NuGet包中複製XML文件。
我們一個個來測試。先看看快取的NuGet包中到底存不存在XML文件。參考本地NuGet包路徑,在Dockerfile
中新增RUN ls /root/.nuget/packages/ich.common/3.0.0-ci.63367-beta/lib/netstandard2.0
,列印NuGet包中的檔案。
注意:Windows和Mac/Linux的global‑packages位置不一致
具體可以參考微軟官方文件
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY . .
WORKDIR /src/src/ICH.NetCore2.Test.WebApi
RUN dotnet publish ICH.NetCore2.Test.WebApi.csproj -c Release -o /app
RUN ls /root/.nuget/packages/ich.common/3.0.0-ci.63367-beta/lib/netstandard2.0 #列印NuGet包中的檔案
FROM base AS final
WORKDIR /app
COPY --from=build /app .
RUN ls
RUN rm -rf appsettings.Development.json
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone
ENTRYPOINT ["dotnet", "ICH.NetCore2.Test.WebApi.dll"]
再試試!
驚,為什麼快取的NuGet包中不存在XML文件呢?不應該啊,本地測試不是好好的嗎?難道跟平臺有關係?本地是Windows,生產環境是Docker。
Google到微軟官方文件,關注NuGet CLI environment variables
中有一個環境變數NUGET_XMLDOC_MODE
。
NUGET_XMLDOC_MODE
Determines how assemblies XML documentation file extraction should be handled.
Supported modes are skip (do not extract XML documentation files), compress (store XML doc files as a zip archive) or none (default, treat XML doc files as regular files).
定義如何處理程式集XML文件檔案提取, 不管怎麼樣,試試吧!
在Dockerfile
中新增ENV NUGET_XMLDOC_MODE none
,設定環境變數NUGET_XMLDOC_MODE
值為none
,也就是
NuGet將XML文件檔案視為常規檔案。
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY . .
WORKDIR /src/src/ICH.NetCore2.Test.WebApi
ENV NUGET_XMLDOC_MODE none # 設定環境變數NUGET_XMLDOC_MODE
RUN dotnet publish ICH.NetCore2.Test.WebApi.csproj -c Release -o /app
RUN ls /root/.nuget/packages/ich.common/3.0.0-ci.63367-beta/lib/netstandard2.0 #列印NuGet包中的檔案
FROM base AS final
WORKDIR /app
COPY --from=build /app .
RUN ls
RUN rm -rf appsettings.Development.json
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone
ENTRYPOINT ["dotnet", "ICH.NetCore2.Test.WebApi.dll"]
再試試!
奈斯,可以看到ICH.Common.xml
檔案靜靜的躺在那!
再看看輸出目錄,居然...居然...沒有ICH.Common.xml
檔案。
靜靜思考,會不會是第4步設定釋出後從NuGet包中複製XML文件
的路徑問題。
修改ICH.NetCore2.Test.WebApi
的專案檔案.csproj
。
<Target Name="AfterTargetsPublish" AfterTargets="Publish">
<ItemGroup>
<PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="/root/.nuget/packages/ich.common/3.0.0-ci.63367-beta/lib/netstandard2.0/*.xml" />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(PublishDir)" />
</Target>
再試試!輸出目錄可以看到ICH.Common.xml
檔案了。
果然是路徑問題,也就是說$(NugetPackageRoot)\%(PackageReference.Identity)\%(PackageReference.Version)
不等於/root/.nuget/packages/ich.common/3.0.0-ci.63367-beta
。
為什麼,難道是大小寫的問題?
修改ICH.NetCore2.Test.WebApi
的專案檔案.csproj
,將PackageReference.Identity
轉為小寫。
<Target Name="AfterTargetsPublish" AfterTargets="Publish">
<ItemGroup>
<PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="$(NugetPackageRoot)\$([MSBuild]::Escape('%(PackageReference.Identity)').ToLower())\%(PackageReference.Version)\lib\%(PackageReference.CopyToOutputDirectory)\*.xml" />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(PublishDir)" />
</Target>
再試試!輸出目錄可以看到ICH.Common.xml
檔案了。
最後再看看Swagger效果,Swagger中也能正常顯示NuGet包的模型文件說明。完美!
總結
填坑的過程是曲折的,收穫是頗豐的,結局是圓滿的。
最後貼出完整的ICH.NetCore2.Test.WebApi
的專案檔案.csproj
和Dockerfile
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ICH.Common" Version="$(ICHCoreFXVersion)">
<CopyToOutputDirectory>lib\netstandard2.0\*.xml</CopyToOutputDirectory>
</PackageReference>
</ItemGroup>
<Target Name="AfterTargetsBuild" AfterTargets="Build">
<ItemGroup>
<PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="$(NugetPackageRoot)\$([MSBuild]::Escape('%(PackageReference.Identity)').ToLower())\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory) />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(OutDir)" />
</Target>
<Target Name="AfterTargetsPublish" AfterTargets="Publish">
<ItemGroup>
<PackageReferenceFiles
Condition="%(PackageReference.CopyToOutputDirectory) != ''"
Include="$(NugetPackageRoot)\$([MSBuild]::Escape('%(PackageReference.Identity)').ToLower())\%(PackageReference.Version)\%(PackageReference.CopyToOutputDirectory)" />
</ItemGroup>
<Copy SourceFiles="@(PackageReferenceFiles)" DestinationFolder="$(PublishDir)" />
</Target>
</Project>
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/dotnet:2.1-sdk AS build
WORKDIR /src
COPY . .
WORKDIR /src/src/ICH.NetCore2.Test.WebApi
ENV NUGET_XMLDOC_MODE none # 設定環境變數NUGET_XMLDOC_MODE
RUN dotnet publish ICH.NetCore2.Test.WebApi.csproj -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=build /app .
RUN ls
RUN rm -rf appsettings.Development.json
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone
ENTRYPOINT ["dotnet", "ICH.NetCore2.Test.WebApi.dll"]
最後
感謝幾篇文章的作者給與我的啟發,同時也希望這篇文章可以幫助大家解決類似的問題。