MSBuild
A common scenario for generating code from OpenAPI specifications is to do it at build time. This can be achieved using MSBuild tasks. An example of such an approach would be to include a .refitter file in the projects directory and execute the Refitter CLI from pre-build events
<Target Name="Refitter" AfterTargets="PreBuildEvent">
<Exec WorkingDirectory="$(ProjectDir)" Command="refitter --settings-file .refitter --skip-validation" />
</Target>
The snippet above requires that Refitter is installed on the machine as a globally available dotnet tool. This might not be the case if you're running on a build agent from a CI/CD environment. In this case you might want to install Refitter as a local tool using a manifest file, as described in this tutorial
<Target Name="Refitter" AfterTargets="PreBuildEvent">
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool restore" />
<Exec WorkingDirectory="$(ProjectDir)" Command="refitter --settings-file .refitter --skip-validation" />
</Target>
The dotnet build process does will probably not have access to the package repository in which to download Refitter from, this is at least the case with Azure Pipelines and Azure Artifacts. To workaround this, you can provide a separate nuget.config that only uses nuget.org as a <packageSource>.
Something like this:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
You might want to place the nuget.config file in another folder to avoid using it to build the .NET project, then you can specify this when executing dotnet tool restore
<Target Name="Refitter" AfterTargets="PreBuildEvent">
<Exec WorkingDirectory="$(ProjectDir)" Command="dotnet tool restore --configfile refitter/nuget.config" />
<Exec WorkingDirectory="$(ProjectDir)" Command="refitter --settings-file .refitter --skip-validation" />
</Target>
In the example above, the nuget.config file is placed under the refitter folder.
Refitter.MSBuild package
Refitter ships with an MSBuild custom task that is distributed as a NuGet package and includes the Refitter CLI binary. This will simplify generating code from OpenAPI specifications at build time.
To use the package, install Refitter.MSBuild
<ItemGroup>
<PackageReference Include="Refitter.MSBuild" Version="2.3.1" />
</ItemGroup>
The MSBuild package includes a custom .target file which executes the RefitterGenerateTask custom task and looks something like this:
<PropertyGroup>
<RefitterAutoScan Condition="'$(RefitterAutoScan)' == ''">true</RefitterAutoScan>
</PropertyGroup>
<UsingTask TaskName="RefitterGenerateTask"
AssemblyFile="$(MSBuildThisFileDirectory)Refitter.MSBuild.dll"
Condition="Exists('$(MSBuildThisFileDirectory)Refitter.MSBuild.dll')" />
<Target Name="RefitterGenerate">
<RefitterGenerateTask ProjectFileDirectory="$(MSBuildProjectDirectory)"
DisableLogging="$(RefitterNoLogging)"
SkipValidation="$(RefitterSkipValidation)"
IncludePatterns="$(RefitterIncludePatterns)"
JsonLibraryVersion="$(RefitterJsonLibraryVersion)">
<Output TaskParameter="GeneratedFiles" ItemName="RefitterGeneratedFiles" />
</RefitterGenerateTask>
<ItemGroup>
<Compile Include="@(RefitterGeneratedFiles)" />
</ItemGroup>
</Target>
<Target Name="_RefitterGenerateOnBuild"
BeforeTargets="CoreCompile"
DependsOnTargets="RefitterGenerate"
Condition="'$(RefitterAutoScan)' != 'false'" />
The RefitterGenerateTask task scans the project folder for .refitter files and executes them all. RefitterAutoScan defaults to true, so regular builds keep their current behavior. Set <RefitterAutoScan>false</RefitterAutoScan> to disable the automatic build hook while still allowing explicit generation through dotnet build -t:RefitterGenerate.
By default, telemetry collection is enabled, and to opt-out of it you must specify <RefitterNoLogging>true</RefitterNoLogging> in the .csproj <PropertyGroup>.
<PropertyGroup>
<RefitterAutoScan>false</RefitterAutoScan>
</PropertyGroup>
When RefitterAutoScan is false, run dotnet build -t:RefitterGenerate whenever you want MSBuild to regenerate code from .refitter files without re-enabling generation on every normal build. After that explicit generation step, regular dotnet build invocations can reuse the generated .cs files without re-running the Refitter task.
To keep jsonLibraryVersion in sync with the target framework of the consuming project, set RefitterJsonLibraryVersion. It is passed to Refitter as --json-library-version:
<PropertyGroup>
<RefitterJsonLibraryVersion>$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)'))</RefitterJsonLibraryVersion>
</PropertyGroup>
RefitterJsonLibraryVersion must be a version number such as 9.0. If the .refitter file sets a non-default codeGeneratorSettings.jsonLibraryVersion, that value takes precedence over a default (8.0) property value. Generation fails only when both the property and the .refitter file specify non-default values, so use only one source. Because 8.0 is the default, an explicit "jsonLibraryVersion": 8.0 in the .refitter file is treated the same as not setting it, and is overridden by RefitterJsonLibraryVersion. For multi-targeted projects (TargetFrameworks), every target framework writes to the same generated files, so don't derive the value from $(TargetFramework). Set a fixed value that matches the lowest target framework instead, for example <RefitterJsonLibraryVersion>8.0</RefitterJsonLibraryVersion> for net8.0;net9.0. To generate different code per target framework, see Multi-targeted projects.
Multi-targeted projects
In a project with <TargetFrameworks>, MSBuild runs one inner build per target framework, and each inner build runs Refitter. With a single .refitter file, every target framework writes and compiles the same generated files, so the generated code has to work for all of them.
To generate different code per target framework, for example a different jsonLibraryVersion, give each target framework its own .refitter file with its own outputFolder under obj, and select it with a conditional RefitterIncludePatterns:
<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0'">
<RefitterIncludePatterns>petstore.net8.refitter</RefitterIncludePatterns>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
<RefitterIncludePatterns>petstore.net10.refitter</RefitterIncludePatterns>
</PropertyGroup>
petstore.net8.refitter:
{
"openApiPath": "./petstore.json",
"namespace": "MyApp.Petstore",
"outputFolder": "./obj/Refitter/net8.0",
"outputFilename": "Petstore.cs"
}
petstore.net10.refitter:
{
"openApiPath": "./petstore.json",
"namespace": "MyApp.Petstore",
"outputFolder": "./obj/Refitter/net10.0",
"outputFilename": "Petstore.cs",
"codeGeneratorSettings": {
"jsonLibraryVersion": 9.0
}
}
The files only differ in outputFolder and the settings that should change per target framework. Keep outputFolder under obj: the .NET SDK doesn't include files from obj in compilation by default, so each inner build compiles only the file it generated. A folder in the project directory, such as ./Generated, is picked up by every inner build from the second build on and causes duplicate type errors.
For details on how the MSBuild task works internally, see MSBuild Task Internals.