Skip to content

feat: support building web projects with the dotnet CLI (Core MSBuild)#103

Open
ehasis wants to merge 1 commit into
CZEMacLeod:mainfrom
ehasis:dotnet-build
Open

feat: support building web projects with the dotnet CLI (Core MSBuild)#103
ehasis wants to merge 1 commit into
CZEMacLeod:mainfrom
ehasis:dotnet-build

Conversation

@ehasis

@ehasis ehasis commented Jun 19, 2026

Copy link
Copy Markdown

Support building with dotnet (Core MSBuild) when Visual Studio / Build Tools is installed

Summary

The SDK could only build projects with the full Visual Studio MSBuild. Running dotnet build on a project that uses MSBuild.SDK.SystemWeb failed early with a cryptic error. This PR makes the dotnet CLI tooling work for ASP.NET 4.x web projects whenever Visual Studio or the Visual Studio Build Tools (with the web workload) is installed, while leaving the full-MSBuild / VS path completely unchanged.

All changes are in a single file: src/MSBuild.SDK.SystemWeb/Sdk/Sdk.targets.

Problem

Under dotnet (Core MSBuild), three independent things broke:

  1. Microsoft.WebApplication.targets not found$(MSBuildExtensionsPath32) points at the .NET SDK instead of a VS install, so the import resolved to a non‑existent path:
    error MSB4019: The imported project "...\dotnet\sdk\10.0.301\Microsoft\VisualStudio\v18.0\WebApplications\Microsoft.WebApplication.targets" was not found.
    
  2. MvcBuildViews (Release) — the AspNetCompiler task does not exist in Core MSBuild (error MSB4803), so Release builds failed.
  3. System.Web not resolved — the framework <Reference> contributed by Common.DefaultPackages.targets is added before the Microsoft.NET.Sdk targets and is silently dropped from @(Reference) under Core MSBuild, so System.Web types didn't compile (other framework refs like System.dll resolved fine).

Changes (Sdk.targets)

  1. Locate Microsoft.WebApplication.targets automatically. When the default path doesn't exist (the dotnet case), resolve an installed Visual Studio / Build Tools copy (any edition, incl. BuildTools) and use it.
    • Uses only property functions — the <Import> is processed before items are evaluated, so an item glob can't influence it.
    • Enumerates only shallow, always-readable directory levels (<root>\Microsoft Visual Studio\<year>\<edition>). No recursive search — a recursive GetDirectories(..., AllDirectories) throws on an ACL‑restricted subfolder and would break evaluation for every build.
    • Gated on !Exists(...), so an explicit $(VSToolsPath)/$(WebApplicationsTargetPath) and normal VS / full‑MSBuild builds are untouched and pay no lookup cost.
  2. Actionable error instead of MSB4019. The web-targets import is now conditional; if no VS/Build Tools is found, a clear SYSWEB001 error explains how to fix it (install the web workload, use a Developer prompt, or set VSToolsPath).
  3. MvcBuildViews under Core MSBuild. Precompile views only under full MSBuild; under dotnet emit an informational message (not a warning, so it never trips TreatWarningsAsErrors) instead of failing — dotnet build -c Release still produces output.
  4. Re-declare System.Web after the Microsoft.NET.Sdk import, for Core MSBuild only. This makes the reference survive into @(Reference) under dotnet. Gated on '$(MSBuildRuntimeType)' == 'Core' so full MSBuild keeps using the existing Common.DefaultPackages.targets reference and there is no duplicate (MSB3105).

No changes to Common.DefaultPackages.targets or the RazorLibrary SDK.

Behavior matrix

Scenario dotnet build Full VS MSBuild
Empty web app (Debug / Release) ✅ builds ✅ unchanged (views precompiled)
Web app using System.Web (Debug) ✅ resolves ✅ resolves (no MSB3105)
No VS / Build Tools installed ✅ clear SYSWEB001 error n/a
Explicit -p:VSToolsPath=... ✅ respected (no lookup) ✅ respected

Testing

Validated against the sample web apps on Windows with VS 18 + .NET SDK 10, building via both the dotnet CLI and the full Visual Studio MSBuild.exe:

  • samples/ExampleEmptyWebApplicationdotnet build Debug & Release; full MSBuild Release.
  • samples/ExampleFullWebApplication (VB, uses System.Web) — dotnet build Debug; full MSBuild Release (confirms System.Web resolves and no MSB3105 duplicate).
  • RazorLibrary samples — confirmed no regression under full MSBuild.
  • Error path — building without a resolvable VS install emits SYSWEB001.

Out of scope

The RazorLibrary SDK is not changed here. It has separate, pre-existing incompatibilities with Core MSBuild (e.g. MSB4113 on EnableDefaultItems, MSB4022 on MicrosoftNETBuildTasksAssembly) rooted in its conditional Microsoft.NET.Sdk import structure; making it build under dotnet is a larger change. Its full-MSBuild behavior is unaffected by this PR.

Notes for reviewers

  • System.Web deliberately stays in Common.DefaultPackages.targets (used by full MSBuild and by RazorLibrary) and is only re-declared in the main SDK for Core MSBuild — see the inline comment explaining the duplicate-avoidance gate.
  • The VS lookup intentionally resolves a single VS/Build Tools install of any edition. For machines with several major VS versions side by side, set $(VSToolsPath) (or $(WebApplicationsTargetPath)) explicitly.

…Build)

Make `dotnet build` work for ASP.NET 4.x projects when Visual Studio or the Build Tools (web workload) is installed; the full-MSBuild path is unchanged.

- Locate Microsoft.WebApplication.targets from the installed VS/Build Tools when the default path is missing (fixes MSB4019), with a clear SYSWEB001 error if none is found.
- Skip MvcBuildViews under Core MSBuild (AspNetCompiler is unavailable, MSB4803) instead of failing Release.
- Re-add the System.Web reference after the Microsoft.NET.Sdk import for Core MSBuild, where the earlier reference is dropped from @(Reference).
@ehasis ehasis changed the title feat(sdk): support building web projects with the dotnet CLI (Core MSBuild) feat: support building web projects with the dotnet CLI (Core MSBuild) Jun 19, 2026
@CZEMacLeod

CZEMacLeod commented Jun 19, 2026

Copy link
Copy Markdown
Owner

@ehasis Thanks so much for this PR - there is lots of good stuff in this.
However, I would appreciate it if you could split this into separate PRs for each issue and we can discuss the changes further there.

I like the idea of there being less friction to using this SDK with the dotnet cli, although I think that the 'automagical' fixes for some of the items should be more intentional on behalf of the user.
I do think that adding better error messages and guidance directly within the SDK is a good thing though.

Could you create separate issues and PRs for

  1. System.Web reference - and perhaps include a reference to the main Microsoft.NET.Sdk where it is dropped so that I can understand this. I haven't encountered this myself so perhaps there is something else going on here.
  2. The actionable error if WebApplicationsTargetPath is not resolved/set. I have been hoping that @twsouthwick will get clearance for this to be packaged and released separately by Microsoft (my preference), or to be included directly in this SDK. In that case pulling in the external nuget package would set this property without any issue. In the aspire extensions (mine and microsoft's) and systemweb-adapters projects, an empty targets file is used as many of these targets are not required in all circumstances. Again this is a developer choice and should probably be intentional rather than automatic.
  3. MvcBuildViews error - I like the better error and explanation here - with the possibility of skipping the step entirely. However, I think this should still be a failure message. If a developer is happy with skipping this for release mode, either always or conditionally under the dotnet command, then simply adding the following code to the project or Directory.Build.props file would indicate they understood the issue and were happy with the limitaion.
<PropertyGroup>
    <MvcBuildViews Condition="'$(MSBuildRuntimeType)' == 'Core'">false</MvcBuildViews>
</PropertyGroup>
  1. Auto locating the WebApplicationsTargetPath. I am not entirely against this idea, and your implementation certainly does seem to cover most scenarios. I'm sure there will be an edge case not covered but the error message from 2 above would help. I am not entirely sure if this is meant to cover the CI/CD flow, or local development (especially as you mentioned locating a BuildTools installlation).

For WebApplicationsTargetPath, I feel that using the Visual Studio Developer Command Prompt or VSWhere is a better solution. In CI/CD is it pretty easy to pick up the location of the installed buildtools:

https://github.com/CZEMacLeod/C3D.Extensions.Aspire/blob/42c4f59716203c9c09e928e39982084473412d79/.github/workflows/dotnet-msbuild-vstest.yml#L53-L71

https://github.com/CZEMacLeod/C3D.Extensions.Aspire/blob/main/Set-WebApplicationTargetsPath.ps1

https://github.com/CZEMacLeod/C3D.Extensions.Aspire/blob/42c4f59716203c9c09e928e39982084473412d79/Set-WebApplicationTargetsPath.ps1#L1-L5

https://github.com/openiddict/openiddict-samples/blob/ca02a557747a7b4cd21cf36188ab5c178163ef45/Directory.Build.props#L31

Putting the auto-resolver under a mode flag - e.g. default to no action, option to skip if not found, option to resolve automatically might be a good addtion though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants