This document explains how .NET tooling within Visual Studio can help users download and install specific versions of the .NET SDK by integrating with the .NET host infrastructure and release management systems.
The process consists of three main steps:
- Locate and evaluate global.json status in the workspace using hostfxr native library
- Locate and evaluate available .NET SDK installers using the Microsoft.Deployment.DotNet.Releases library
- Apply global.json constraints to installer information to determine the best SDK for download
The .NET CLI uses the hostfxr native library to resolve SDK versions and global.json files. Visual Studio tooling can leverage the same approach through P/Invoke interop.
Based on the Interop.cs implementation, the following APIs are essential:
1. hostfxr_resolve_sdk2
[DllImport(Constants.HostFxr, CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_resolve_sdk2(
string? exe_dir,
string? working_dir,
hostfxr_resolve_sdk2_flags_t flags,
hostfxr_resolve_sdk2_result_fn result);
This API resolves the SDK version based on:
- Current working directory
- global.json file presence and content
- Rollforward policy specified in global.json
2. hostfxr_get_available_sdks
[DllImport(Constants.HostFxr, CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_get_available_sdks(
string? exe_dir,
hostfxr_get_available_sdks_result_fn result);
This API enumerates all locally installed SDKs.
3. hostfxr_get_dotnet_environment_info
[DllImport(Constants.HostFxr, CharSet = UTF16, ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
internal static extern int hostfxr_get_dotnet_environment_info(
string dotnet_root,
IntPtr reserved,
hostfxr_get_dotnet_environment_info_result_fn result,
IntPtr result_context);
This API provides comprehensive environment information including installed SDKs and frameworks.
public class SdkResolver
{
public struct SdkResolutionResult
{
public string ResolvedSdkDir { get; set; }
public string GlobalJsonPath { get; set; }
public string RequestedVersion { get; set; }
public bool SdkFound { get; set; }
}
public static SdkResolutionResult ResolveSdk(string workingDirectory)
{
var result = new SdkResolutionResult();
// Callback to capture resolution results
void ResultCallback(hostfxr_resolve_sdk2_result_key_t key, string value)
{
switch (key)
{
case hostfxr_resolve_sdk2_result_key_t.resolved_sdk_dir:
result.ResolvedSdkDir = value;
result.SdkFound = !string.IsNullOrEmpty(value);
break;
case hostfxr_resolve_sdk2_result_key_t.global_json_path:
result.GlobalJsonPath = value;
break;
case hostfxr_resolve_sdk2_result_key_t.requested_version:
result.RequestedVersion = value;
break;
}
}
// Call hostfxr to resolve SDK
int hr = Interop.Windows.hostfxr_resolve_sdk2(
null, // exe_dir - use default
workingDirectory,
hostfxr_resolve_sdk2_flags_t.disallow_prerelease, // or 0 to allow prerelease
ResultCallback);
return result;
}
}
When a global.json file is found, parse it to extract:
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}
Key properties:
- version: Exact or minimum version required
- rollForward: Policy for version selection (
patch
,feature
,minor
,major
,latestPatch
,latestFeature
,latestMinor
,latestMajor
,disable
) - allowPrerelease: Whether prerelease versions are acceptable
The Microsoft.Deployment.DotNet.Releases NuGet package provides access to official .NET release manifests.
<PackageReference Include="Microsoft.Deployment.DotNet.Releases" Version="1.0.0" />
1. ProductCollection - Get All Products
using Microsoft.Deployment.DotNet.Releases;
// Get all .NET products (versions/channels)
var products = await ProductCollection.GetAsync();
foreach (var product in products)
{
Console.WriteLine($"Product: {product.ProductName} {product.ProductVersion}");
Console.WriteLine($" Latest SDK: {product.LatestSdkVersion}");
Console.WriteLine($" Latest Runtime: {product.LatestRuntimeVersion}");
Console.WriteLine($" Support Phase: {product.SupportPhase}");
Console.WriteLine($" End of Life: {product.EndOfLifeDate}");
}
2. ProductRelease - Get Specific Release Information
// Get releases for a specific product
var releases = await product.GetReleasesAsync();
foreach (var release in releases)
{
Console.WriteLine($"Release: {release.Version}");
// SDKs in this release
foreach (var sdk in release.Sdks)
{
Console.WriteLine($" SDK: {sdk.Version}");
Console.WriteLine($" C# Version: {sdk.CSharpVersion}");
Console.WriteLine($" F# Version: {sdk.FSharpVersion}");
// Download files for this SDK
foreach (var file in sdk.Files)
{
Console.WriteLine($" File: {file.Name}");
Console.WriteLine($" URL: {file.Address}");
Console.WriteLine($" Platform: {file.Platform}");
Console.WriteLine($" Architecture: {file.Architecture}");
Console.WriteLine($" Hash: {file.Hash}");
}
}
}
3. ReleaseFile - Download SDK Installers
// Find the appropriate installer for current platform
var currentPlatform = GetCurrentPlatform(); // "win", "linux", "osx"
var currentArchitecture = GetCurrentArchitecture(); // "x64", "arm64", etc.
var sdkFile = sdk.Files.FirstOrDefault(f =>
f.Platform == currentPlatform &&
f.Architecture == currentArchitecture);
if (sdkFile != null)
{
// Download the installer
string downloadPath = Path.Combine(Path.GetTempPath(), sdkFile.Name);
await sdkFile.DownloadAsync(downloadPath);
// of course do hash verification here of the downloaded file -
// the hash is available in sdkFile.Hash
Console.WriteLine($"Downloaded SDK installer: {downloadPath}");
Console.WriteLine($"Verified hash: {sdkFile.Hash}");
}
public class SdkDiscoveryService
{
public async Task<IEnumerable<AvailableSdk>> GetAvailableSdksAsync()
{
var products = await ProductCollection.GetAsync();
var availableSdks = new List<AvailableSdk>();
foreach (var product in products)
{
var releases = await product.GetReleasesAsync();
foreach (var release in releases)
{
foreach (var sdk in release.Sdks)
{
availableSdks.Add(new AvailableSdk
{
Version = sdk.Version,
Product = product,
Release = release,
SdkComponent = sdk,
IsPrerelease = sdk.Version.IsPrerelease,
IsSupported = product.IsSupported
});
}
}
}
return availableSdks.OrderByDescending(s => s.Version);
}
}
public class AvailableSdk
{
public ReleaseVersion Version { get; set; }
public Product Product { get; set; }
public ProductRelease Release { get; set; }
public SdkReleaseComponent SdkComponent { get; set; }
public bool IsPrerelease { get; set; }
public bool IsSupported { get; set; }
}
Step 1 gives you a user-requested Sdk version + rollforward policy, and Step 2 gives you a list of available SDKs. The next step is to apply the constraints from global.json to select the best SDK.
The combination of version + optional rollforward policy sort-of create a SemVer Version Range. Create that range and then filter the available SDKs based on that range.