在 .NET 上实现清洁架构 1. 创建应用程序核心项目 2. 创建基础设施项目 3. 创建 Web Api 项目

2025-06-11

在 .NET 上实现清洁架构

1.创建Application Core项目

2.创建基础设施项目

3.创建Web Api项目

在本文中,我们将学习 .NET 上的 Clean Architecture 简介。我们将创建 3 个项目(应用程序核心、基础架构和 Web API)。

您可以在此处找到幻灯片

先决条件:

  • 带有 .NET 6 SDK 的 Visual Studio 2022
  • SQL Server 数据库

1.创建Application Core项目

创建一个名为“StoreCleanArchitecture”的空白解决方案,并添加一个名为“src”的解决方案文件夹,在其中使用.NET Standard 2.1创建一个“类库项目”(创建src文件夹目录项目)

图片描述

创建以下文件夹:

图片描述

安装 AutoMapper.Extensions.Microsoft.DependencyInjection。
创建 DependencyInjection 类。



using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

namespace Store.ApplicationCore
{
    public static class DependencyInjection
    {
        public static IServiceCollection AddApplicationCore(this IServiceCollection services)
        {
            services.AddAutoMapper(Assembly.GetExecutingAssembly());

            return services;
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

在实体文件夹中,创建产品类。



using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Store.ApplicationCore.Entities
{
    public class Product
    {
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int Id { get; set; }

        [MaxLength(30)]
        public string Name { get; set; }

        public string Description { get; set; }
        public int Stock { get; set; }
        public double Price { get; set; }
        public DateTime CreatedAt { get; set; }
        public DateTime UpdatedAt { get; set; }
    }
}


Enter fullscreen mode Exit fullscreen mode

在 DTOs 文件夹中,创建 Product 类来指定请求和响应。



using System;
using System.ComponentModel.DataAnnotations;

namespace Store.ApplicationCore.DTOs
{
    public class CreateProductRequest
    {
        [Required]
        [StringLength(30, MinimumLength = 3)]
        public string Name { get; set; }

        [Required]
        public string Description { get; set; }

        [Required]
        [Range(0.01, 1000)]
        public double Price { get; set; }
    }

    public class UpdateProductRequest : CreateProductRequest
    {
        [Required]
        [Range(0, 100)]
        public int Stock { get; set; }
    }

    public class ProductResponse
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public int Stock { get; set; }
        public double Price { get; set; }
    }
}


Enter fullscreen mode Exit fullscreen mode

在 Mappings 文件夹中,创建 GeneralProfile 类。这有助于自动从请求映射到实体,以及从实体映射到响应。



using AutoMapper;
using Store.ApplicationCore.DTOs;
using Store.ApplicationCore.Entities;

namespace Store.ApplicationCore.Mappings
{
    public class GeneralProfile : Profile
    {
        public GeneralProfile()
        {
            CreateMap<CreateProductRequest, Product>();
            CreateMap<Product, ProductResponse>();
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

在 Interfaces 文件夹中,创建 IProductRepository 接口。在这里我们创建 CRUD 的方法。



using Store.ApplicationCore.DTOs;
using System.Collections.Generic;

namespace Store.ApplicationCore.Interfaces
{
    public interface IProductRepository
    {
        List<ProductResponse> GetProducts();

        ProductResponse GetProductById(int productId);

        void DeleteProductById(int productId);

        ProductResponse CreateProduct(CreateProductRequest request);

        ProductResponse UpdateProduct(int productId, UpdateProductRequest request);
    }
}


Enter fullscreen mode Exit fullscreen mode

在 Exceptions 文件夹中,创建 NotFoundException 类。



using System;

namespace Store.ApplicationCore.Exceptions
{
    public class NotFoundException : Exception
    {
    }
}


Enter fullscreen mode Exit fullscreen mode

在 Utils 文件夹中,创建 DateUtil 类。



using System;

namespace Store.ApplicationCore.Utils
{
    public class DateUtil
    {
        public static DateTime GetCurrentDate()
        {
            return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local);
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

2.创建基础设施项目

使用 .NET 6 创建一个名为 Store.Infrastructure 的“类库项目”。
创建以下结构:
图片描述

安装 Microsoft.EntityFrameworkCore.SqlServer。
右键单击 Store.Infrastucture 项目 / 添加 / 项目引用... / 勾选 Store.ApplicationCore / 确定
图片描述

在 Contexts 文件夹中,创建 StoreContext 类。在这里,我们将 Product 实体添加到 DbSets,以便与数据库的 Products 表进行通信。



using Microsoft.EntityFrameworkCore;
using Store.ApplicationCore.Entities;

namespace Store.Infrastructure.Persistence.Contexts
{
    public class StoreContext : DbContext
    {
        public StoreContext(DbContextOptions<StoreContext> options) : base(options)
        {
        }

        public DbSet<Product> Products { get; set; }
    }
}


Enter fullscreen mode Exit fullscreen mode

在 Repositories 文件夹中,创建 ProductRepository 类。



using AutoMapper;
using Store.ApplicationCore.DTOs;
using Store.ApplicationCore.Entities;
using Store.ApplicationCore.Exceptions;
using Store.ApplicationCore.Interfaces;
using Store.ApplicationCore.Utils;
using Store.Infrastructure.Persistence.Contexts;
using System.Collections.Generic;
using System.Linq;

namespace Store.Infrastructure.Persistence.Repositories
{
    public class ProductRepository : IProductRepository
    {
        private readonly StoreContext storeContext;
        private readonly IMapper mapper;

        public ProductRepository(StoreContext storeContext, IMapper mapper)
        {
            this.storeContext = storeContext;
            this.mapper = mapper;
        }

        public ProductResponse CreateProduct(CreateProductRequest request)
        {
            var product = this.mapper.Map<Product>(request);
            product.Stock = 0;
            product.CreatedAt = product.UpdatedAt = DateUtil.GetCurrentDate();

            this.storeContext.Products.Add(product);
            this.storeContext.SaveChanges();

            return this.mapper.Map<ProductResponse>(product);
        }

        public void DeleteProductById(int productId)
        {
            var product = this.storeContext.Products.Find(productId);
            if (product != null)
            {
                this.storeContext.Products.Remove(product);
                this.storeContext.SaveChanges();
            }
            else
            {
                throw new NotFoundException();
            }
        }

        public ProductResponse GetProductById(int productId)
        {
            var product = this.storeContext.Products.Find(productId);
            if (product != null)
            {
                return this.mapper.Map<ProductResponse>(product);
            }

            throw new NotFoundException();
        }

        public List<ProductResponse> GetProducts()
        {
            return this.storeContext.Products.Select(p => this.mapper.Map<ProductResponse>(p)).ToList();
        }

        public ProductResponse UpdateProduct(int productId, UpdateProductRequest request)
        {
            var product = this.storeContext.Products.Find(productId);
            if (product != null)
            {
                product.Name = request.Name;
                product.Description = request.Description;
                product.Price = request.Price;
                product.Stock = request.Stock;
                product.UpdatedAt = DateUtil.GetCurrentDate();

                this.storeContext.Products.Update(product);
                this.storeContext.SaveChanges();

                return this.mapper.Map<ProductResponse>(product);
            }

            throw new NotFoundException();
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

在 DependencyInjection 类中,添加以下内容:



using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Store.ApplicationCore.Interfaces;
using Store.Infrastructure.Persistence.Contexts;
using Store.Infrastructure.Persistence.Repositories;

namespace Store.Infrastructure
{
    public static class DependencyInjection
    {
        public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
        {
            var defaultConnectionString = configuration.GetConnectionString("DefaultConnection");
            services.AddDbContext<StoreContext>(options =>
               options.UseSqlServer(defaultConnectionString));

            services.AddScoped<IProductRepository, ProductRepository>();

            return services;
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

我们在那里配置数据库上下文并将 IProductRepository 作为 Scoped 添加到服务集合中。

3.创建Web Api项目

使用.NET 6创建一个名为Store.WebApi的“Web Api项目”。

图片描述

右键单击 Store.WebApi / 设置为启动项目。
在顶部,单击“调试 / 启动(不调试)”。

图片描述

删除 WeatherForecast 和 WeatherForecastController 文件。

添加对 Store.ApplicationCore 和 Store.Infrastructure 项目的引用。

在 appsettings.json 中添加 SQL Server 连接字符串



{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=DemoStore;Trusted_Connection=True;"
  }
}



Enter fullscreen mode Exit fullscreen mode

在程序类中,添加应用程序核心和基础设施的扩展。



using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Store.ApplicationCore;
using Store.Infrastructure;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddApplicationCore();
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseAuthorization();

app.MapControllers();

app.Run();


Enter fullscreen mode Exit fullscreen mode

打开包管理器控制台,选择 Store.Infrastructure 项目作为默认项目。执行Add-Migration InitialCreate -Context StoreContext
图片描述

在 Store.Infrastructure 项目中,创建了一个包含 2 个文件的 Migrations 文件夹。
图片描述

然后,从包管理器控制台执行Update-Database
从控制器中,添加一个名为 ProductsController 的控制器
图片描述



using Microsoft.AspNetCore.Mvc;
using Store.ApplicationCore.DTOs;
using Store.ApplicationCore.Exceptions;
using Store.ApplicationCore.Interfaces;
using System.Collections.Generic;

namespace Store.WebApi.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProductsController : Controller
    {
        private readonly IProductRepository productRepository;

        public ProductsController(IProductRepository productRepository)
        {
            this.productRepository = productRepository;
        }

        [HttpGet]
        public ActionResult<List<ProductResponse>> GetProducts()
        {
            return Ok(this.productRepository.GetProducts());
        }

        [HttpGet("{id}")]
        public ActionResult GetProductById(int id)
        {
            try
            {
                var product = this.productRepository.GetProductById(id);
                return Ok(product);
            }
            catch (NotFoundException)
            {
                return NotFound();
            }
        }

        [HttpPost]
        public ActionResult Create(CreateProductRequest request)
        {
            var product = this.productRepository.CreateProduct(request);
            return Ok(product);
        }

        [HttpPut("{id}")]
        public ActionResult Update(int id, UpdateProductRequest request)
        {
            try
            {
                var product = this.productRepository.UpdateProduct(id, request);
                return Ok(product);
            }
            catch (NotFoundException)
            {
                return NotFound();
            }
        }

        [HttpDelete("{id}")]
        public ActionResult Delete(int id)
        {
            try
            {
                this.productRepository.DeleteProductById(id);
                return NoContent();
            }
            catch (NotFoundException)
            {
                return NotFound();
            }
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

现在,您可以测试 API。
图片描述

您可以在此处找到源代码

感谢阅读

非常感谢您的阅读,希望您觉得这篇文章有趣,并希望将来能有所收获。如果您有任何疑问或想法需要讨论,我们很乐意与您合作,共同交流。

鏂囩珷鏉ユ簮锛�https://dev.to/cristofima/implement-clean-architecture-on-net-59eo
PREV
带有 React Analytics 的 Material UI 仪表板📊 带有 Cube.js 的后端 带有 Material UI 的前端 带有多个图表的交互式仪表板 带有数据表的多页仪表板 GenAI LIVE!| 2025 年 6 月 4 日
NEXT
下一个大型项目中需要使用的 13 个顶级开源工具🎯