Claude Skill

dotnet-project-setup

Create or reorganize .NET solutions with clean project boundaries, repeatable SDK settings, and a maintainable baseline for libraries, apps, tests, CI, and local development.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-project-setup-bfa4ebd.zip · 8 KB
Part of postpartum-genushyacinthus29/dotnet-skills — 80 skills

Install

skills CLI npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-project-setup
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

.NET Project Setup

Trigger On

  • creating a new .NET solution or restructuring an existing one
  • setting up Directory.Build.props, shared package management, or repo-wide defaults
  • defining project layout for apps, libraries, and test projects

Workflow

  1. Start from the app model and deployment target, then choose the smallest correct SDK and target framework set.
  2. Use solution folders and project names that reflect bounded contexts or product areas, not temporary implementation details.
  3. Centralize shared build settings, analyzer rules, nullable context, and package versions where it reduces duplication without hiding important differences.
  4. Create test projects and CI hooks early so new projects do not drift into unverified templates.
  5. Prefer project references and composition over circular dependencies or utility dumping grounds.
  6. Document the local build, test, and run path in repo docs or AGENTS.md when the workflow is not obvious.

Deliver

  • a coherent solution structure
  • shared build defaults that are easy to reason about
  • starter quality and testing hooks for future work

Validate

  • projects have explicit responsibility boundaries
  • shared MSBuild settings do not accidentally override platform-specific needs
  • a new contributor can build and test the repo without guessing

References

  • patterns.md: solution layout conventions, Directory.Build.props, Directory.Build.targets, Central Package Management, global.json, nuget.config, analyzers, multi-targeting, and source link
  • templates.md: dotnet new templates for console apps, class libraries, ASP.NET Core APIs, worker services, Blazor, test projects, .NET Aspire, and gRPC services
Files (dotnet-skills)
  • references
    • patterns.md 11 KB
      # Project Structure Patterns
      
      ## Solution Layout
      
      ### Recommended Directory Structure
      
      ```text
      <repo-root>/
      ├── .config/
      │   └── dotnet-tools.json
      ├── src/
      │   ├── <ProductName>.Core/
      │   ├── <ProductName>.Api/
      │   └── <ProductName>.Web/
      ├── tests/
      │   ├── <ProductName>.Core.Tests/
      │   └── <ProductName>.Api.Tests/
      ├── samples/
      │   └── <ProductName>.Sample/
      ├── docs/
      ├── Directory.Build.props
      ├── Directory.Build.targets
      ├── Directory.Packages.props
      ├── global.json
      ├── nuget.config
      ├── <SolutionName>.sln
      └── README.md
      ```
      
      ### Project Naming Conventions
      
      | Project Type | Pattern | Example |
      |--------------|---------|---------|
      | Core library | `<ProductName>.Core` | `Contoso.Orders.Core` |
      | Domain layer | `<ProductName>.Domain` | `Contoso.Orders.Domain` |
      | Application layer | `<ProductName>.Application` | `Contoso.Orders.Application` |
      | Infrastructure | `<ProductName>.Infrastructure` | `Contoso.Orders.Infrastructure` |
      | Web API | `<ProductName>.Api` | `Contoso.Orders.Api` |
      | Web frontend | `<ProductName>.Web` | `Contoso.Orders.Web` |
      | Worker service | `<ProductName>.Worker` | `Contoso.Orders.Worker` |
      | Unit tests | `<ProjectName>.Tests` | `Contoso.Orders.Core.Tests` |
      | Integration tests | `<ProjectName>.IntegrationTests` | `Contoso.Orders.Api.IntegrationTests` |
      
      ---
      
      ## Directory.Build.props
      
      `Directory.Build.props` is automatically imported by MSBuild for all projects in its directory and subdirectories.
      
      ### Basic Template
      
      ```xml
      <Project>
        <PropertyGroup>
          <TargetFramework>net9.0</TargetFramework>
          <LangVersion>latest</LangVersion>
          <Nullable>enable</Nullable>
          <ImplicitUsings>enable</ImplicitUsings>
          <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
          <WarningsAsErrors />
          <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
          <EnableNETAnalyzers>true</EnableNETAnalyzers>
          <AnalysisLevel>latest-recommended</AnalysisLevel>
        </PropertyGroup>
      
        <!-- Package metadata for libraries -->
        <PropertyGroup>
          <Authors>Your Name or Organization</Authors>
          <Company>Your Company</Company>
          <Copyright>Copyright (c) $(Company) $([System.DateTime]::Now.Year)</Copyright>
          <RepositoryUrl>https://github.com/your-org/your-repo</RepositoryUrl>
          <RepositoryType>git</RepositoryType>
        </PropertyGroup>
      
        <!-- Deterministic builds for CI -->
        <PropertyGroup Condition="'$(CI)' == 'true'">
          <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
          <Deterministic>true</Deterministic>
        </PropertyGroup>
      </Project>
      ```
      
      ### Conditional Properties by Project Type
      
      ```xml
      <Project>
        <!-- Shared defaults -->
        <PropertyGroup>
          <TargetFramework>net9.0</TargetFramework>
          <Nullable>enable</Nullable>
          <ImplicitUsings>enable</ImplicitUsings>
        </PropertyGroup>
      
        <!-- Test project defaults -->
        <PropertyGroup Condition="$(MSBuildProjectName.EndsWith('.Tests'))">
          <IsPackable>false</IsPackable>
          <IsTestProject>true</IsTestProject>
        </PropertyGroup>
      
        <!-- Library defaults -->
        <PropertyGroup Condition="!$(MSBuildProjectName.EndsWith('.Tests')) AND !$(MSBuildProjectName.EndsWith('.Api')) AND !$(MSBuildProjectName.EndsWith('.Web'))">
          <GenerateDocumentationFile>true</GenerateDocumentationFile>
        </PropertyGroup>
      </Project>
      ```
      
      ### Nested Directory.Build.props
      
      Child directories can extend the parent by importing it explicitly:
      
      ```xml
      <!-- tests/Directory.Build.props -->
      <Project>
        <Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
      
        <PropertyGroup>
          <IsPackable>false</IsPackable>
          <IsTestProject>true</IsTestProject>
        </PropertyGroup>
      
        <ItemGroup>
          <PackageReference Include="Microsoft.NET.Test.Sdk" />
          <PackageReference Include="xunit" />
          <PackageReference Include="xunit.runner.visualstudio" />
          <PackageReference Include="coverlet.collector" />
        </ItemGroup>
      </Project>
      ```
      
      ---
      
      ## Directory.Build.targets
      
      Use `Directory.Build.targets` for logic that runs after the project file is fully evaluated.
      
      ```xml
      <Project>
        <!-- Run after project evaluation -->
        <Target Name="PrintBuildInfo" BeforeTargets="Build">
          <Message Importance="High" Text="Building $(MSBuildProjectName) for $(TargetFramework)" />
        </Target>
      
        <!-- Enforce test naming convention -->
        <Target Name="ValidateTestProjectNaming" BeforeTargets="Build" Condition="'$(IsTestProject)' == 'true'">
          <Error Condition="!$(MSBuildProjectName.EndsWith('.Tests')) AND !$(MSBuildProjectName.EndsWith('.IntegrationTests'))"
                 Text="Test projects must end with .Tests or .IntegrationTests" />
        </Target>
      </Project>
      ```
      
      ---
      
      ## Central Package Management (CPM)
      
      Central Package Management consolidates package versions into a single `Directory.Packages.props` file.
      
      ### Enabling CPM
      
      Add to `Directory.Packages.props` at the repository root:
      
      ```xml
      <Project>
        <PropertyGroup>
          <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
          <CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
        </PropertyGroup>
      
        <ItemGroup>
          <!-- Runtime packages -->
          <PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
          <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
          <PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
          <PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
          <PackageVersion Include="System.Text.Json" Version="9.0.0" />
      
          <!-- Entity Framework Core -->
          <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
          <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
          <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
      
          <!-- ASP.NET Core extras -->
          <PackageVersion Include="Swashbuckle.AspNetCore" Version="6.6.2" />
      
          <!-- Testing -->
          <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
          <PackageVersion Include="xunit" Version="2.9.2" />
          <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
          <PackageVersion Include="Moq" Version="4.20.72" />
          <PackageVersion Include="FluentAssertions" Version="6.12.1" />
          <PackageVersion Include="coverlet.collector" Version="6.0.2" />
      
          <!-- Analyzers -->
          <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
          <PackageVersion Include="Roslynator.Analyzers" Version="4.12.6" />
        </ItemGroup>
      </Project>
      ```
      
      ### Project File References with CPM
      
      When CPM is enabled, project files reference packages without versions:
      
      ```xml
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Hosting" />
        <PackageReference Include="Microsoft.Extensions.Logging" />
      </ItemGroup>
      ```
      
      ### Version Overrides
      
      Use `VersionOverride` sparingly when a project requires a different version:
      
      ```xml
      <ItemGroup>
        <PackageReference Include="Newtonsoft.Json" VersionOverride="13.0.3" />
      </ItemGroup>
      ```
      
      ---
      
      ## global.json
      
      Pin the SDK version for reproducible builds:
      
      ```json
      {
        "sdk": {
          "version": "9.0.100",
          "rollForward": "latestFeature",
          "allowPrerelease": false
        }
      }
      ```
      
      ### Roll-Forward Policies
      
      | Value | Behavior |
      |-------|----------|
      | `patch` | Use specified or highest installed patch |
      | `feature` | Use specified or highest installed feature band |
      | `minor` | Use specified or highest installed minor |
      | `major` | Use highest installed SDK |
      | `latestPatch` | Use highest installed patch |
      | `latestFeature` | Use highest installed feature band |
      | `latestMinor` | Use highest installed minor |
      | `latestMajor` | Use highest installed SDK |
      | `disable` | Exact match only |
      
      ---
      
      ## nuget.config
      
      Configure package sources and credentials:
      
      ```xml
      <?xml version="1.0" encoding="utf-8"?>
      <configuration>
        <packageSources>
          <clear />
          <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
          <!-- Private feeds -->
          <add key="github" value="https://nuget.pkg.github.com/your-org/index.json" />
        </packageSources>
      
        <packageSourceMapping>
          <packageSource key="nuget.org">
            <package pattern="*" />
          </packageSource>
          <packageSource key="github">
            <package pattern="YourOrg.*" />
          </packageSource>
        </packageSourceMapping>
      
        <!-- CI credentials via environment variables -->
        <packageSourceCredentials>
          <github>
            <add key="Username" value="%NUGET_USERNAME%" />
            <add key="ClearTextPassword" value="%NUGET_TOKEN%" />
          </github>
        </packageSourceCredentials>
      </configuration>
      ```
      
      ---
      
      ## Analyzers and Code Style
      
      ### .editorconfig Basics
      
      ```ini
      root = true
      
      [*]
      charset = utf-8
      end_of_line = lf
      indent_style = space
      indent_size = 4
      insert_final_newline = true
      trim_trailing_whitespace = true
      
      [*.{cs,vb}]
      dotnet_sort_system_directives_first = true
      dotnet_separate_import_directive_groups = false
      
      [*.cs]
      csharp_style_namespace_declarations = file_scoped:warning
      csharp_style_var_for_built_in_types = true:suggestion
      csharp_style_var_when_type_is_apparent = true:suggestion
      csharp_style_var_elsewhere = true:suggestion
      csharp_prefer_braces = true:warning
      csharp_style_expression_bodied_methods = when_on_single_line:suggestion
      csharp_style_expression_bodied_constructors = false:suggestion
      
      # IDE diagnostics
      dotnet_diagnostic.IDE0005.severity = warning
      dotnet_diagnostic.IDE0055.severity = warning
      
      [*.{json,yml,yaml}]
      indent_size = 2
      ```
      
      ### Analyzer Packages in Directory.Build.props
      
      ```xml
      <ItemGroup>
        <PackageReference Include="StyleCop.Analyzers" PrivateAssets="all" />
        <PackageReference Include="Roslynator.Analyzers" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" PrivateAssets="all" />
      </ItemGroup>
      ```
      
      ---
      
      ## Multi-Targeting
      
      ### Single Project Multi-Targeting
      
      ```xml
      <PropertyGroup>
        <TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
      </PropertyGroup>
      
      <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
        <PackageReference Include="System.Text.Json" />
      </ItemGroup>
      ```
      
      ### Conditional Compilation
      
      ```csharp
      #if NET9_0_OR_GREATER
          // .NET 9+ specific code
          ArgumentNullException.ThrowIfNull(value);
      #else
          // Fallback for older frameworks
          if (value is null)
              throw new ArgumentNullException(nameof(value));
      #endif
      ```
      
      ---
      
      ## Source Link and Deterministic Builds
      
      Enable source link for debugger integration:
      
      ```xml
      <PropertyGroup>
        <PublishRepositoryUrl>true</PublishRepositoryUrl>
        <EmbedUntrackedSources>true</EmbedUntrackedSources>
        <IncludeSymbols>true</IncludeSymbols>
        <SymbolPackageFormat>snupkg</SymbolPackageFormat>
      </PropertyGroup>
      
      <ItemGroup>
        <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="all" />
      </ItemGroup>
      ```
      
      ---
      
      ## Solution Filters
      
      Create `.slnf` files for partial solution loading:
      
      ```json
      {
        "solution": {
          "path": "MyProduct.sln",
          "projects": [
            "src\\MyProduct.Core\\MyProduct.Core.csproj",
            "src\\MyProduct.Api\\MyProduct.Api.csproj",
            "tests\\MyProduct.Core.Tests\\MyProduct.Core.Tests.csproj"
          ]
        }
      }
      ```
      
      Open with: `dotnet sln open MyProduct.Api.slnf`
      
    • templates.md 12.9 KB
      # Common Project Templates
      
      ## Overview
      
      .NET provides project templates via `dotnet new`. This reference covers common templates and their typical use cases.
      
      ---
      
      ## Listing Available Templates
      
      ```bash
      # List all installed templates
      dotnet new list
      
      # Search for templates
      dotnet new search webapi
      
      # Install a template pack
      dotnet new install Microsoft.AspNetCore.SpaTemplates
      ```
      
      ---
      
      ## Console Applications
      
      ### Basic Console App
      
      ```bash
      dotnet new console -n MyApp -o src/MyApp
      ```
      
      **Generated structure:**
      
      ```text
      src/MyApp/
      ├── MyApp.csproj
      └── Program.cs
      ```
      
      **Typical `Program.cs`:**
      
      ```csharp
      // Minimal hosting for console apps with DI
      using Microsoft.Extensions.DependencyInjection;
      using Microsoft.Extensions.Hosting;
      
      var builder = Host.CreateApplicationBuilder(args);
      
      builder.Services.AddSingleton<MyService>();
      
      using var host = builder.Build();
      await host.RunAsync();
      ```
      
      ---
      
      ## Class Libraries
      
      ### Standard Library
      
      ```bash
      dotnet new classlib -n MyProduct.Core -o src/MyProduct.Core
      ```
      
      ### Library with Multi-Targeting
      
      **Modify the generated `.csproj`:**
      
      ```xml
      <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
          <TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks>
          <GenerateDocumentationFile>true</GenerateDocumentationFile>
          <PackageId>MyCompany.MyProduct.Core</PackageId>
          <Description>Core library for MyProduct</Description>
        </PropertyGroup>
      </Project>
      ```
      
      ---
      
      ## ASP.NET Core Web API
      
      ### Minimal API
      
      ```bash
      dotnet new webapi -n MyProduct.Api -o src/MyProduct.Api --use-minimal-apis
      ```
      
      **Typical structure:**
      
      ```text
      src/MyProduct.Api/
      ├── MyProduct.Api.csproj
      ├── Program.cs
      ├── Properties/
      │   └── launchSettings.json
      ├── appsettings.json
      └── appsettings.Development.json
      ```
      
      **Minimal API `Program.cs` example:**
      
      ```csharp
      var builder = WebApplication.CreateBuilder(args);
      
      builder.Services.AddEndpointsApiExplorer();
      builder.Services.AddSwaggerGen();
      
      var app = builder.Build();
      
      if (app.Environment.IsDevelopment())
      {
          app.UseSwagger();
          app.UseSwaggerUI();
      }
      
      app.UseHttpsRedirection();
      
      app.MapGet("/api/health", () => Results.Ok(new { Status = "Healthy" }))
         .WithName("HealthCheck")
         .WithOpenApi();
      
      app.Run();
      ```
      
      ### Controller-Based API
      
      ```bash
      dotnet new webapi -n MyProduct.Api -o src/MyProduct.Api --use-controllers
      ```
      
      **Controller template:**
      
      ```csharp
      using Microsoft.AspNetCore.Mvc;
      
      namespace MyProduct.Api.Controllers;
      
      [ApiController]
      [Route("api/[controller]")]
      public class ItemsController : ControllerBase
      {
          private readonly IItemService _itemService;
      
          public ItemsController(IItemService itemService)
          {
              _itemService = itemService;
          }
      
          [HttpGet]
          public async Task<ActionResult<IEnumerable<ItemDto>>> GetAll(CancellationToken ct)
          {
              var items = await _itemService.GetAllAsync(ct);
              return Ok(items);
          }
      
          [HttpGet("{id:guid}")]
          public async Task<ActionResult<ItemDto>> GetById(Guid id, CancellationToken ct)
          {
              var item = await _itemService.GetByIdAsync(id, ct);
              return item is null ? NotFound() : Ok(item);
          }
      
          [HttpPost]
          public async Task<ActionResult<ItemDto>> Create(CreateItemRequest request, CancellationToken ct)
          {
              var item = await _itemService.CreateAsync(request, ct);
              return CreatedAtAction(nameof(GetById), new { id = item.Id }, item);
          }
      }
      ```
      
      ---
      
      ## Worker Services
      
      ### Background Service
      
      ```bash
      dotnet new worker -n MyProduct.Worker -o src/MyProduct.Worker
      ```
      
      **Worker template:**
      
      ```csharp
      namespace MyProduct.Worker;
      
      public class Worker : BackgroundService
      {
          private readonly ILogger<Worker> _logger;
          private readonly IServiceScopeFactory _scopeFactory;
      
          public Worker(ILogger<Worker> logger, IServiceScopeFactory scopeFactory)
          {
              _logger = logger;
              _scopeFactory = scopeFactory;
          }
      
          protected override async Task ExecuteAsync(CancellationToken stoppingToken)
          {
              while (!stoppingToken.IsCancellationRequested)
              {
                  _logger.LogInformation("Worker running at: {Time}", DateTimeOffset.Now);
      
                  using var scope = _scopeFactory.CreateScope();
                  var service = scope.ServiceProvider.GetRequiredService<IMyService>();
                  await service.ProcessAsync(stoppingToken);
      
                  await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
              }
          }
      }
      ```
      
      ---
      
      ## Web Applications
      
      ### Blazor Server
      
      ```bash
      dotnet new blazor -n MyProduct.Web -o src/MyProduct.Web --interactivity Server
      ```
      
      ### Blazor WebAssembly
      
      ```bash
      dotnet new blazor -n MyProduct.Web -o src/MyProduct.Web --interactivity WebAssembly
      ```
      
      ### Razor Pages
      
      ```bash
      dotnet new webapp -n MyProduct.Web -o src/MyProduct.Web
      ```
      
      ---
      
      ## Test Projects
      
      ### xUnit Test Project
      
      ```bash
      dotnet new xunit -n MyProduct.Core.Tests -o tests/MyProduct.Core.Tests
      ```
      
      **Test project `.csproj`:**
      
      ```xml
      <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
          <TargetFramework>net9.0</TargetFramework>
          <IsPackable>false</IsPackable>
          <IsTestProject>true</IsTestProject>
        </PropertyGroup>
      
        <ItemGroup>
          <PackageReference Include="Microsoft.NET.Test.Sdk" />
          <PackageReference Include="xunit" />
          <PackageReference Include="xunit.runner.visualstudio" />
          <PackageReference Include="coverlet.collector" />
          <PackageReference Include="FluentAssertions" />
          <PackageReference Include="Moq" />
        </ItemGroup>
      
        <ItemGroup>
          <ProjectReference Include="..\..\src\MyProduct.Core\MyProduct.Core.csproj" />
        </ItemGroup>
      </Project>
      ```
      
      **Test class template:**
      
      ```csharp
      using FluentAssertions;
      using Moq;
      using Xunit;
      
      namespace MyProduct.Core.Tests;
      
      public class ItemServiceTests
      {
          private readonly Mock<IItemRepository> _repositoryMock;
          private readonly ItemService _sut;
      
          public ItemServiceTests()
          {
              _repositoryMock = new Mock<IItemRepository>();
              _sut = new ItemService(_repositoryMock.Object);
          }
      
          [Fact]
          public async Task GetByIdAsync_WhenItemExists_ReturnsItem()
          {
              // Arrange
              var itemId = Guid.NewGuid();
              var expected = new Item { Id = itemId, Name = "Test" };
              _repositoryMock
                  .Setup(r => r.GetByIdAsync(itemId, It.IsAny<CancellationToken>()))
                  .ReturnsAsync(expected);
      
              // Act
              var result = await _sut.GetByIdAsync(itemId, CancellationToken.None);
      
              // Assert
              result.Should().BeEquivalentTo(expected);
          }
      
          [Theory]
          [InlineData("")]
          [InlineData("   ")]
          [InlineData(null)]
          public async Task CreateAsync_WhenNameInvalid_ThrowsArgumentException(string? name)
          {
              // Arrange
              var request = new CreateItemRequest { Name = name! };
      
              // Act
              var act = () => _sut.CreateAsync(request, CancellationToken.None);
      
              // Assert
              await act.Should().ThrowAsync<ArgumentException>();
          }
      }
      ```
      
      ### NUnit Test Project
      
      ```bash
      dotnet new nunit -n MyProduct.Core.Tests -o tests/MyProduct.Core.Tests
      ```
      
      ### MSTest Test Project
      
      ```bash
      dotnet new mstest -n MyProduct.Core.Tests -o tests/MyProduct.Core.Tests
      ```
      
      ### Integration Test Project
      
      ```csharp
      // WebApplicationFactory-based integration tests
      using Microsoft.AspNetCore.Mvc.Testing;
      using Microsoft.Extensions.DependencyInjection;
      using Xunit;
      
      namespace MyProduct.Api.IntegrationTests;
      
      public class ItemsEndpointTests : IClassFixture<WebApplicationFactory<Program>>
      {
          private readonly HttpClient _client;
      
          public ItemsEndpointTests(WebApplicationFactory<Program> factory)
          {
              _client = factory
                  .WithWebHostBuilder(builder =>
                  {
                      builder.ConfigureServices(services =>
                      {
                          // Replace services for testing
                          services.AddSingleton<IItemRepository, InMemoryItemRepository>();
                      });
                  })
                  .CreateClient();
          }
      
          [Fact]
          public async Task GetAll_ReturnsOkStatus()
          {
              // Act
              var response = await _client.GetAsync("/api/items");
      
              // Assert
              response.EnsureSuccessStatusCode();
          }
      }
      ```
      
      ---
      
      ## Solution Setup Commands
      
      ### Create Solution and Projects
      
      ```bash
      # Create solution
      dotnet new sln -n MyProduct
      
      # Create projects
      dotnet new classlib -n MyProduct.Core -o src/MyProduct.Core
      dotnet new classlib -n MyProduct.Infrastructure -o src/MyProduct.Infrastructure
      dotnet new webapi -n MyProduct.Api -o src/MyProduct.Api --use-minimal-apis
      dotnet new xunit -n MyProduct.Core.Tests -o tests/MyProduct.Core.Tests
      dotnet new xunit -n MyProduct.Api.IntegrationTests -o tests/MyProduct.Api.IntegrationTests
      
      # Add projects to solution
      dotnet sln add src/MyProduct.Core/MyProduct.Core.csproj
      dotnet sln add src/MyProduct.Infrastructure/MyProduct.Infrastructure.csproj
      dotnet sln add src/MyProduct.Api/MyProduct.Api.csproj
      dotnet sln add tests/MyProduct.Core.Tests/MyProduct.Core.Tests.csproj
      dotnet sln add tests/MyProduct.Api.IntegrationTests/MyProduct.Api.IntegrationTests.csproj
      
      # Add project references
      dotnet add src/MyProduct.Infrastructure/MyProduct.Infrastructure.csproj reference src/MyProduct.Core/MyProduct.Core.csproj
      dotnet add src/MyProduct.Api/MyProduct.Api.csproj reference src/MyProduct.Core/MyProduct.Core.csproj
      dotnet add src/MyProduct.Api/MyProduct.Api.csproj reference src/MyProduct.Infrastructure/MyProduct.Infrastructure.csproj
      dotnet add tests/MyProduct.Core.Tests/MyProduct.Core.Tests.csproj reference src/MyProduct.Core/MyProduct.Core.csproj
      dotnet add tests/MyProduct.Api.IntegrationTests/MyProduct.Api.IntegrationTests.csproj reference src/MyProduct.Api/MyProduct.Api.csproj
      ```
      
      ---
      
      ## .NET Aspire Application
      
      ### Aspire App Host
      
      ```bash
      dotnet new aspire -n MyProduct
      ```
      
      **Creates:**
      
      ```text
      MyProduct/
      ├── MyProduct.AppHost/
      │   ├── MyProduct.AppHost.csproj
      │   └── Program.cs
      ├── MyProduct.ServiceDefaults/
      │   ├── MyProduct.ServiceDefaults.csproj
      │   └── Extensions.cs
      └── MyProduct.sln
      ```
      
      **AppHost `Program.cs`:**
      
      ```csharp
      var builder = DistributedApplication.CreateBuilder(args);
      
      var cache = builder.AddRedis("cache");
      var postgres = builder.AddPostgres("postgres")
          .AddDatabase("ordersdb");
      
      var api = builder.AddProject<Projects.MyProduct_Api>("api")
          .WithReference(cache)
          .WithReference(postgres);
      
      builder.AddProject<Projects.MyProduct_Web>("web")
          .WithReference(api);
      
      builder.Build().Run();
      ```
      
      ### Add Service to Existing Aspire Solution
      
      ```bash
      # Add a new API project
      dotnet new webapi -n MyProduct.OrdersApi -o src/MyProduct.OrdersApi
      dotnet sln add src/MyProduct.OrdersApi/MyProduct.OrdersApi.csproj
      
      # Reference ServiceDefaults
      dotnet add src/MyProduct.OrdersApi/MyProduct.OrdersApi.csproj reference src/MyProduct.ServiceDefaults/MyProduct.ServiceDefaults.csproj
      ```
      
      ---
      
      ## gRPC Services
      
      ```bash
      dotnet new grpc -n MyProduct.GrpcService -o src/MyProduct.GrpcService
      ```
      
      **Proto file template:**
      
      ```protobuf
      syntax = "proto3";
      
      option csharp_namespace = "MyProduct.GrpcService";
      
      package orders;
      
      service OrderService {
        rpc GetOrder (GetOrderRequest) returns (OrderResponse);
        rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
        rpc ListOrders (ListOrdersRequest) returns (stream OrderResponse);
      }
      
      message GetOrderRequest {
        string order_id = 1;
      }
      
      message CreateOrderRequest {
        string customer_id = 1;
        repeated OrderItem items = 2;
      }
      
      message OrderItem {
        string product_id = 1;
        int32 quantity = 2;
      }
      
      message OrderResponse {
        string order_id = 1;
        string customer_id = 2;
        repeated OrderItem items = 3;
        string status = 4;
      }
      
      message ListOrdersRequest {
        string customer_id = 1;
      }
      ```
      
      ---
      
      ## Tool Manifest
      
      ### Create Tool Manifest
      
      ```bash
      dotnet new tool-manifest
      ```
      
      **Creates `.config/dotnet-tools.json`:**
      
      ```json
      {
        "version": 1,
        "isRoot": true,
        "tools": {}
      }
      ```
      
      ### Install Local Tools
      
      ```bash
      dotnet tool install dotnet-ef
      dotnet tool install dotnet-format
      dotnet tool install dotnet-reportgenerator-globaltool
      ```
      
      **Updated manifest:**
      
      ```json
      {
        "version": 1,
        "isRoot": true,
        "tools": {
          "dotnet-ef": {
            "version": "9.0.0",
            "commands": ["dotnet-ef"]
          },
          "dotnet-format": {
            "version": "5.1.250801",
            "commands": ["dotnet-format"]
          },
          "dotnet-reportgenerator-globaltool": {
            "version": "5.3.10",
            "commands": ["reportgenerator"]
          }
        }
      }
      ```
      
      ### Restore Tools
      
      ```bash
      dotnet tool restore
      ```
      
      ---
      
      ## Quick Reference: Common Template Options
      
      | Template | Command | Key Options |
      |----------|---------|-------------|
      | Console | `dotnet new console` | `--use-program-main` |
      | Class Library | `dotnet new classlib` | `--framework` |
      | Web API | `dotnet new webapi` | `--use-controllers`, `--use-minimal-apis`, `--auth` |
      | Blazor | `dotnet new blazor` | `--interactivity`, `--empty` |
      | Worker | `dotnet new worker` | `--framework` |
      | xUnit | `dotnet new xunit` | `--framework` |
      | Solution | `dotnet new sln` | - |
      | gitignore | `dotnet new gitignore` | - |
      | editorconfig | `dotnet new editorconfig` | - |
      | global.json | `dotnet new globaljson` | `--sdk-version`, `--roll-forward` |
      
  • SKILL.md 2.1 KB
    ---
    name: dotnet-project-setup
    version: "1.0.0"
    category: "Core"
    description: "Create or reorganize .NET solutions with clean project boundaries, repeatable SDK settings, and a maintainable baseline for libraries, apps, tests, CI, and local development."
    compatibility: "Best for new repositories or structural refactors of existing .NET solutions."
    ---
    
    # .NET Project Setup
    
    ## Trigger On
    
    - creating a new .NET solution or restructuring an existing one
    - setting up `Directory.Build.props`, shared package management, or repo-wide defaults
    - defining project layout for apps, libraries, and test projects
    
    ## Workflow
    
    1. Start from the app model and deployment target, then choose the smallest correct SDK and target framework set.
    2. Use solution folders and project names that reflect bounded contexts or product areas, not temporary implementation details.
    3. Centralize shared build settings, analyzer rules, nullable context, and package versions where it reduces duplication without hiding important differences.
    4. Create test projects and CI hooks early so new projects do not drift into unverified templates.
    5. Prefer project references and composition over circular dependencies or utility dumping grounds.
    6. Document the local build, test, and run path in repo docs or `AGENTS.md` when the workflow is not obvious.
    
    ## Deliver
    
    - a coherent solution structure
    - shared build defaults that are easy to reason about
    - starter quality and testing hooks for future work
    
    ## Validate
    
    - projects have explicit responsibility boundaries
    - shared MSBuild settings do not accidentally override platform-specific needs
    - a new contributor can build and test the repo without guessing
    
    ## References
    
    - [patterns.md](references/patterns.md): solution layout conventions, `Directory.Build.props`, `Directory.Build.targets`, Central Package Management, `global.json`, `nuget.config`, analyzers, multi-targeting, and source link
    - [templates.md](references/templates.md): `dotnet new` templates for console apps, class libraries, ASP.NET Core APIs, worker services, Blazor, test projects, .NET Aspire, and gRPC services
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related