第1部分:http://www.cnblogs.com/cgzl/p/7637250.html
第2部分:http://www.cnblogs.com/cgzl/p/7640077.html
第3部分:http://www.cnblogs.com/cgzl/p/7652413.html
第4部分:http://www.cnblogs.com/cgzl/p/7661805.html
Github源碼地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch
這是第一大部分的最后一小部分。要完成CRUD的操作。
Repository Pattern
我們可以直接在Controller訪問DbContext,但是可能會有一些問題:
1.相關的一些代碼到處重復,有可能在程序中很多地方我都會更新Product,那樣的話我可能就會在多個Action里面寫同樣的代碼,而比較好的做法是只在一個地方寫更新Product的代碼。
2.到處寫重復代碼還會導致另外一個問題,那就是容易出錯。
3.還有就是難以測試,如果想對Controller的Action進行單元測試,但是這些Action還包含着持久化相關的邏輯,這就很難的精確的找出到底是邏輯出錯還是持久化部分出錯了。
所以如果能有一種方法可以mock持久化相關的代碼,然后再測試,就會知道錯誤不是發生在持久化部分了,這就可以用Repository Pattern了。
Repository Pattern是一種抽象,它減少了復雜性,目標是使代碼對repository的實現更安全,並且與持久化要無關。
其中持久化無關這點我要明確一下,有時候是指可以隨意切換持久化的技術,但這實際上並不是repository pattern的目的,其真正的目的是可以為repository挑選一個最好的持久化技術。例如:創建一個Product最好的方式可能是使用entity framework,而查詢product最好的方式可能是使用dapper,也有可能會調用外部服務,而對調用repository的消費者來說,它不關心這些具體的實現細節。
首先再建立一個Material entity,然后和Product做成多對一的關系:
namespace CoreBackend.Api.Entities { public class Material { public int Id { get; set; } public int ProductId { get; set; } public string Name { get; set; } public Product Product { get; set; } } public class MaterialConfiguration : IEntityTypeConfiguration<Material> { public void Configure(EntityTypeBuilder<Material> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.Name).IsRequired().HasMaxLength(50); builder.HasOne(x => x.Product).WithMany(x => x.Materials).HasForeignKey(x => x.ProductId) .OnDelete(DeleteBehavior.Cascade); } } }
修改Product.cs:
namespace CoreBackend.Api.Entities { public class Product { public int Id { get; set; } public string Name { get; set; } public float Price { get; set; } public string Description { get; set; } public ICollection<Material> Materials { get; set; } } public class ProductConfiguration : IEntityTypeConfiguration<Product> { public void Configure(EntityTypeBuilder<Product> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.Name).IsRequired().HasMaxLength(50); builder.Property(x => x.Price).HasColumnType("decimal(8,2)"); builder.Property(x => x.Description).HasMaxLength(200); } } }
然后別忘了在Context里面注冊Material的Configuration並添加DbSet屬性:
namespace CoreBackend.Api.Entities { public class MyContext : DbContext { public MyContext(DbContextOptions<MyContext> options) : base(options) { Database.Migrate(); } public DbSet<Product> Products { get; set; } public DbSet<Material> Materials { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new ProductConfiguration()); modelBuilder.ApplyConfiguration(new MaterialConfiguration()); } } }
然后添加一個遷移 Add-Migration AddMaterial:

然后數據庫直接進行遷移操作了,無需再做update-database。

建立一個Repositories文件夾,添加一個IProductRepository:
namespace CoreBackend.Api.Repositories { public interface IProductRepository { IEnumerable<Product> GetProducts(); Product GetProduct(int productId, bool includeMaterials); IEnumerable<Material> GetMaterialsForProduct(int productId); Material GetMaterialForProduct(int productId, int materialId); } }
這個是ProductRepository將要實現的接口,里面定義了一些必要的方法:查詢Products,查詢單個Product,查詢Product的Materials和查詢Product下的一個Material。
其中類似GetProducts()這樣的方法返回類型還是有爭議的,IQueryable<T>還是IEnumerable<T>。
如果返回的是IQueryable,那么調用repository的地方還可以繼續構建IQueryable,例如在真正的查詢執行之前附加一個OrderBy或者Where方法。但是這樣做的話,也意味着你把持久化相關的代碼給泄露出去了,這看起來是違反了repository pattern的目的。
如果是IEnumerable,為了返回各種各樣情況的查詢結果,需要編寫幾十個上百個查詢方法,那也是相當繁瑣的,幾乎是不可能的。
目前看來,兩種返回方式都有人在用,所以根據情況定吧。我們的程序需求比較簡單,所以使用IEnumerable。
然后建立具體的實現類 ProductRepository:
namespace CoreBackend.Api.Repositories { public class ProductRepository : IProductRepository { private readonly MyContext _myContext; public ProductRepository(MyContext myContext) { _myContext = myContext; } public IEnumerable<Product> GetProducts() { return _myContext.Products.OrderBy(x => x.Name).ToList(); } public Product GetProduct(int productId, bool includeMaterials) { if (includeMaterials) { return _myContext.Products .Include(x => x.Materials).FirstOrDefault(x => x.Id == productId); } return _myContext.Products.Find(productId); } public IEnumerable<Material> GetMaterialsForProduct(int productId) { return _myContext.Materials.Where(x => x.ProductId == productId).ToList(); } public Material GetMaterialForProduct(int productId, int materialId) { return _myContext.Materials.FirstOrDefault(x => x.ProductId == productId && x.Id == materialId); } } }
這里面要包含吃就會的邏輯,所以我們需要MyContext(也有可能需要其他的Service)那就在Constructor里面注入一個。重要的是調用的程序不關心這些細節。
這里也是編寫額外的持久化邏輯的地方,比如說查詢之后做個排序之類的。
(具體的Entity Framework Core的方法請查閱EF Core官方文檔:https://docs.microsoft.com/en-us/ef/core/)
GetProducts,查詢所有的產品並按照名稱排序並返回查詢結果。這里注意一定要加上ToList(),它保證了對數據庫的查詢就在此時此刻發生。
GetProduct,查詢單個產品,判斷一下是否需要把產品下面的原料都一起查詢出來,如果需要的話就使用Include這個extension method。查詢條件可以放在FirstOrDefault()方法里面。
GetMaterialsForProduct,查詢某個產品下所有的原料。
GetMaterialForProduct,查詢某個產品下的某種原料。
建立好Repository之后,需要在Startup里面進行注冊:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); #if DEBUG services.AddTransient<IMailService, LocalMailService>(); #else services.AddTransient<IMailService, CloudMailService>(); #endif var connectionString = Configuration["connectionStrings:productionInfoDbConnectionString"]; services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString)); services.AddScoped<IProductRepository, ProductRepository>(); }
針對Repository,最好的生命周期是Scoped(每個請求生成一個實例)。<>里面前邊是它的合約接口,后邊是具體實現。
使用Repository
先為ProductDto添加一個屬性:
namespace CoreBackend.Api.Dtos { public class ProductDto { public ProductDto() { Materials = new List<MaterialDto>(); } public int Id { get; set; } public string Name { get; set; } public float Price { get; set; } public string Description { get; set; } public ICollection<MaterialDto> Materials { get; set; } public int MaterialCount => Materials.Count; } }
就是返回該產品所用的原料個數。
再建立一個ProductWithoutMaterialDto:
namespace CoreBackend.Api.Dtos { public class ProductWithoutMaterialDto { public int Id { get; set; } public string Name { get; set; } public float Price { get; set; } public string Description { get; set; } } }
這個Dto不帶原料相關的導航屬性。
然后修改controller。
現在我們可以使用ProductRepository替代原來的內存數據了,首先在ProductController里面注入ProductRepository:
public class ProductController : Controller { private readonly ILogger<ProductController> _logger; private readonly IMailService _mailService; private readonly IProductRepository _productRepository; public ProductController( ILogger<ProductController> logger, IMailService mailService, IProductRepository productRepository) { _logger = logger; _mailService = mailService; _productRepository = productRepository; }
1.修改GetProducts這個Action:
[HttpGet] public IActionResult GetProducts() { var products = _productRepository.GetProducts(); var results = new List<ProductWithoutMaterialDto>(); foreach (var product in products) { results.Add(new ProductWithoutMaterialDto { Id = product.Id, Name = product.Name, Price = product.Price, Description = product.Description }); } return Ok(results); }
注意,其中的Product類型是DbContext和repository操作的類型,而不是Action應該返回的類型,而且我們的查詢結果是不帶Material的,所以需要把Product的list映射成ProductWithoutMaterialDto的list。
然后試試:
查詢的時候報錯,是因為Product的屬性Price,在fluentapi里面設置的類型是decimal(8, 2),而Price的類型是float,那么我們把所有的Price的類型都改成decimal:
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Description { get; set; } public ICollection<Material> Materials { get; set; } } public class ProductCreation { [Display(Name = "產品名稱")] [Required(ErrorMessage = "{0}是必填項")] [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的長度應該不小於{2}, 不大於{1}")] public string Name { get; set; } [Display(Name = "價格")] [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必須大於{1}")] public decimal Price { get; set; } [Display(Name = "描述")] [MaxLength(100, ErrorMessage = "{0}的長度不可以超過{1}")] public string Description { get; set; } } public class ProductDto { public ProductDto() { Materials = new List<MaterialDto>(); } public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Description { get; set; } public ICollection<MaterialDto> Materials { get; set; } public int MaterialCount => Materials.Count; } public class ProductModification { [Display(Name = "產品名稱")] [Required(ErrorMessage = "{0}是必填項")] [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的長度應該不小於{2}, 不大於{1}")] public string Name { get; set; } [Display(Name = "價格")] [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必須大於{1}")] public decimal Price { get; set; } [Display(Name = "描述")] [MaxLength(100, ErrorMessage = "{0}的長度不可以超過{1}")] public string Description { get; set; } } public class ProductWithoutMaterialDto { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public string Description { get; set; } }
還有SeedData里面和即將廢棄的ProductService:
namespace CoreBackend.Api.Entities { public static class MyContextExtensions { public static void EnsureSeedDataForContext(this MyContext context) { if (context.Products.Any()) { return; } var products = new List<Product> { new Product { Name = "牛奶", Price = new decimal(2.5), Description = "這是牛奶啊" }, new Product { Name = "面包", Price = new decimal(4.5), Description = "這是面包啊" }, new Product { Name = "啤酒", Price = new decimal(7.5), Description = "這是啤酒啊" } }; context.Products.AddRange(products); context.SaveChanges(); } } } namespace CoreBackend.Api.Services { public class ProductService { public static ProductService Current { get; } = new ProductService(); public List<ProductDto> Products { get; } private ProductService() { Products = new List<ProductDto> { new ProductDto { Id = 1, Name = "牛奶", Price = new decimal(2.5), Materials = new List<MaterialDto> { new MaterialDto { Id = 1, Name = "水" }, new MaterialDto { Id = 2, Name = "奶粉" } }, Description = "這是牛奶啊" }, new ProductDto { Id = 2, Name = "面包", Price = new decimal(4.5), Materials = new List<MaterialDto> { new MaterialDto { Id = 3, Name = "面粉" }, new MaterialDto { Id = 4, Name = "糖" } }, Description = "這是面包啊" }, new ProductDto { Id = 3, Name = "啤酒", Price = new decimal(7.5), Materials = new List<MaterialDto> { new MaterialDto { Id = 5, Name = "麥芽" }, new MaterialDto { Id = 6, Name = "地下水" } }, Description = "這是啤酒啊" } }; } } }
然后在運行試試:

結果正確。
然后修改GetProduct:
[Route("{id}", Name = "GetProduct")] public IActionResult GetProduct(int id, bool includeMaterial = false) { var product = _productRepository.GetProduct(id, includeMaterial); if (product == null) { return NotFound(); } if (includeMaterial) { var productWithMaterialResult = new ProductDto { Id = product.Id, Name = product.Name, Price = product.Price, Description = product.Description }; foreach (var material in product.Materials) { productWithMaterialResult.Materials.Add(new MaterialDto { Id = material.Id, Name = material.Name }); } return Ok(productWithMaterialResult); } var onlyProductResult = new ProductDto { Id = product.Id, Name = product.Name, Price = product.Price, Description = product.Description }; return Ok(onlyProductResult); }
首先再添加一個參數includeMaterial表示是否帶着Material表的數據一起查詢出來,該參數有一個默認值是false,就是請求的時候如果不帶這個參數,那么這個參數的值就是false。
通過repository查詢之后把Product和Material分別映射成ProductDto和MaterialDot。
試試,首先不包含Material:

目前數據庫的Material表沒有數據,可以手動添加幾個,也可以把數據庫的Product數據刪了,改一下種子數據那部分代碼:
namespace CoreBackend.Api.Entities { public static class MyContextExtensions { public static void EnsureSeedDataForContext(this MyContext context) { if (context.Products.Any()) { return; } var products = new List<Product> { new Product { Name = "牛奶", Price = new decimal(2.5), Description = "這是牛奶啊", Materials = new List<Material> { new Material { Name = "水" }, new Material { Name = "奶粉" } } }, new Product { Name = "面包", Price = new decimal(4.5), Description = "這是面包啊", Materials = new List<Material> { new Material { Name = "面粉" }, new Material { Name = "糖" } } }, new Product { Name = "啤酒", Price = new decimal(7.5), Description = "這是啤酒啊", Materials = new List<Material> { new Material { Name = "麥芽" }, new Material { Name = "地下水" } } } }; context.Products.AddRange(products); context.SaveChanges(); } } }
然后再試試GetProduct帶有material的查詢:

其中inludeMaterail這個參數需要使用query string的方式,也就是在uri后邊加一個問號,問號后邊跟着參數名,然后是等號,然后是它的值。如果有多個query string的參數,那么每組參數之間用&分開。
然后再修改一下MaterialController:
namespace CoreBackend.Api.Controllers { [Route("api/product")] // 和主Model的Controller前綴一樣 public class MaterialController : Controller { private readonly IProductRepository _productRepository; public MaterialController(IProductRepository productRepository) { _productRepository = productRepository; } [HttpGet("{productId}/materials")] public IActionResult GetMaterials(int productId) { var materials = _productRepository.GetMaterialsForProduct(productId); var results = materials.Select(material => new MaterialDto { Id = material.Id, Name = material.Name }) .ToList(); return Ok(results); } [HttpGet("{productId}/materials/{id}")] public IActionResult GetMaterial(int productId, int id) { var material = _productRepository.GetMaterialForProduct(productId, id); if (material == null) { return NotFound(); } var result = new MaterialDto { Id = material.Id, Name = material.Name }; return Ok(result); } } }
注意GetMaterials方法內,我們往productRepository的GetMaterialsForProduct傳進去一個productId,如果repository返回的是空list可能會有兩種情況:1 product不存在,2 product存在,而它沒有下屬的material。如果是第一種情況,那么應該返回的是404 NotFound,而第二種action應該返回一個空list。所以我們需要一個方法判斷product是否存在,所以打開ProductRepository,添加方法:
public bool ProductExist(int productId) { return _myContext.Products.Any(x => x.Id == productId); }
並在pull up member(右鍵點擊方法代碼--重構里面有)到接口里面:
namespace CoreBackend.Api.Repositories { public interface IProductRepository { IEnumerable<Product> GetProducts(); Product GetProduct(int productId, bool includeMaterials); IEnumerable<Material> GetMaterialsForProduct(int productId); Material GetMaterialForProduct(int productId, int materialId); bool ProductExist(int productId); } }
然后再改一下Controller:
namespace CoreBackend.Api.Controllers { [Route("api/product")] // 和主Model的Controller前綴一樣 public class MaterialController : Controller { private readonly IProductRepository _productRepository; public MaterialController(IProductRepository productRepository) { _productRepository = productRepository; } [HttpGet("{productId}/materials")] public IActionResult GetMaterials(int productId) { var product = _productRepository.ProductExist(productId); if (!product) { return NotFound(); } var materials = _productRepository.GetMaterialsForProduct(productId); var results = materials.Select(material => new MaterialDto { Id = material.Id, Name = material.Name }) .ToList(); return Ok(results); } [HttpGet("{productId}/materials/{id}")] public IActionResult GetMaterial(int productId, int id) { var product = _productRepository.ProductExist(productId); if (!product) { return NotFound(); } var material = _productRepository.GetMaterialForProduct(productId, id); if (material == null) { return NotFound(); } var result = new MaterialDto { Id = material.Id, Name = material.Name }; return Ok(result); } } }
試試:





結果都沒有問題!!!
但是看看上面controller里面的代碼,到處都是映射,這種手寫的映射很容易出錯,如果entity有幾十個屬性,然后在多個地方需要進行映射,那么這么寫實在太糟糕了。
所以需要使用一個映射的庫:
AutoMapper
autoMapper是最主流的.net映射庫,所以我們用它。
通過nuget安裝automapper:

安裝完之后,首先要配置automapper。我們要告訴automapper哪些entity和dto之間有映射關系。這個配置應該只創建一次,並且在startup的時候進行初始化。
在Startup的Configure方法添加:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, MyContext myContext) { loggerFactory.AddNLog(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(); } myContext.EnsureSeedDataForContext(); app.UseStatusCodePages(); AutoMapper.Mapper.Initialize(cfg => { cfg.CreateMap<Product, ProductWithoutMaterialDto>(); }); app.UseMvc(); }
創建映射關系,我們需要使用AutoMapper.Mapper.Initialize方法,其參數是一個Action,這個Action的參數是一個Mapping Configuration。
cfg.CreateMap<Product, ProductWithoutMaterialDto>(),意思就是創建一個從Product到ProductWIthoutMaterialDto的映射關系。
AutoMapper是基於約定的,原對象的屬性值會被映射到目標對象相同屬性名的屬性上。如果屬性不存在,那么就忽略它。
偶爾我們可能需要對AutoMapper的映射進行一些微調,但是對於大多數情況來說,上面這一句話就夠用了。
現在可以在controller里面使用這個映射了。
打開controller首先改一下GetProducts:
[HttpGet] public IActionResult GetProducts() { var products = _productRepository.GetProducts(); var results = Mapper.Map<IEnumerable<ProductWithoutMaterialDto>>(products); return Ok(results); }
使用Mapper.Map進行映射,<T>其中T是目標類型,可以是一個model也可以是一個集合,括號里面的參數是原對象們。
運行試試:

沒問題,結果和之前是一樣的。
然后針對GetProduct,首先再建立一對映射:
AutoMapper.Mapper.Initialize(cfg => { cfg.CreateMap<Product, ProductWithoutMaterialDto>(); cfg.CreateMap<Product, ProductDto>(); });
然后GetProduct:
[Route("{id}", Name = "GetProduct")] public IActionResult GetProduct(int id, bool includeMaterial = false) { var product = _productRepository.GetProduct(id, includeMaterial); if (product == null) { return NotFound(); } if (includeMaterial) { var productWithMaterialResult = Mapper.Map<ProductDto>(product); return Ok(productWithMaterialResult); } var onlyProductResult = Mapper.Map<ProductWithoutMaterialDto>(product); return Ok(onlyProductResult); }
運行,查詢包含Material,報錯:

這是因為ProductDto里面有一個屬性 ICollection<Material> Materials,automapper不知道應該怎么去映射它,所以我們需要再添加一對Material到MaterialDto的映射關系。
AutoMapper.Mapper.Initialize(cfg => { cfg.CreateMap<Product, ProductWithoutMaterialDto>(); cfg.CreateMap<Product, ProductDto>(); cfg.CreateMap<Material, MaterialDto>(); });
運行:

沒問題。
然后把MaterailController里面也改一下:
namespace CoreBackend.Api.Controllers { [Route("api/product")] // 和主Model的Controller前綴一樣 public class MaterialController : Controller { private readonly IProductRepository _productRepository; public MaterialController(IProductRepository productRepository) { _productRepository = productRepository; } [HttpGet("{productId}/materials")] public IActionResult GetMaterials(int productId) { var product = _productRepository.ProductExist(productId); if (!product) { return NotFound(); } var materials = _productRepository.GetMaterialsForProduct(productId); var results = Mapper.Map<IEnumerable<MaterialDto>>(materials); return Ok(results); } [HttpGet("{productId}/materials/{id}")] public IActionResult GetMaterial(int productId, int id) { var product = _productRepository.ProductExist(productId); if (!product) { return NotFound(); } var material = _productRepository.GetMaterialForProduct(productId, id); if (material == null) { return NotFound(); } var result = Mapper.Map<MaterialDto>(material); return Ok(result); } } }
運行一下都應該沒有什么問題。
上面都是查詢的Actions。
下面開始做CUD的映射更改。
添加:
修改ProductRepository,添加以下方法:
public void AddProduct(Product product) { _myContext.Products.Add(product); } public bool Save() { return _myContext.SaveChanges() >= 0; }
AddProduct會把傳進來的product添加到context的內存中(姑且這么說),但是還沒有更新到數據庫。
Save方法里面是把context所追蹤的實體變化(CUD)更新到數據庫。
然后把這兩個方法提取到IProductRepository接口里:
public interface IProductRepository { IEnumerable<Product> GetProducts(); Product GetProduct(int productId, bool includeMaterials); IEnumerable<Material> GetMaterialsForProduct(int productId); Material GetMaterialForProduct(int productId, int materialId); bool ProductExist(int productId); void AddProduct(Product product); bool Save(); }
修改Controller的Post:
[HttpPost] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); } if (product.Name == "產品") { ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var newProduct = Mapper.Map<Product>(product); _productRepository.AddProduct(newProduct); if (!_productRepository.Save()) { return StatusCode(500, "保存產品的時候出錯"); } var dto = Mapper.Map<ProductWithoutMaterialDto>(newProduct); return CreatedAtRoute("GetProduct", new { id = dto.Id }, dto); }
注意別忘了要返回的是Dto。
運行:

沒問題。
Put
cfg.CreateMap<ProductModification, Product>();
[HttpPut("{id}")] public IActionResult Put(int id, [FromBody] ProductModification productModificationDto) { if (productModificationDto == null) { return BadRequest(); } if (productModificationDto.Name == "產品") { ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var product = _productRepository.GetProduct(id); if (product == null) { return NotFound(); } Mapper.Map(productModificationDto, product); if (!_productRepository.Save()) { return StatusCode(500, "保存產品的時候出錯"); } return NoContent(); }
這里我們使用了Mapper.Map的另一個overload的方法,它有兩個參數。這個方法會把第一個對象相應的值賦給第二個對象上。這時候product的state就變成了modified了。
然后保存即可。
試試:

Partial Update
cfg.CreateMap<Product, ProductModification>();
[HttpPatch("{id}")] public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ProductModification> patchDoc) { if (patchDoc == null) { return BadRequest(); } var productEntity = _productRepository.GetProduct(id); if (productEntity == null) { return NotFound(); } var toPatch = Mapper.Map<ProductModification>(productEntity); patchDoc.ApplyTo(toPatch, ModelState); if (!ModelState.IsValid) { return BadRequest(ModelState); } if (toPatch.Name == "產品") { ModelState.AddModelError("Name", "產品的名稱不可以是'產品'二字"); } TryValidateModel(toPatch); if (!ModelState.IsValid) { return BadRequest(ModelState); } Mapper.Map(toPatch, productEntity); if (!_productRepository.Save()) { return StatusCode(500, "更新的時候出錯"); } return NoContent(); }
試試:

沒問題。
Delete
只是替換成repository,不涉及mapping。
在Repository添加一個Delete方法:
public void DeleteProduct(Product product) { _myContext.Products.Remove(product); }
提取到IProductRepository:
void DeleteProduct(Product product);
然后Controller:
[HttpDelete("{id}")] public IActionResult Delete(int id) { var model = _productRepository.GetProduct(id); if (model == null) { return NotFound(); } _productRepository.DeleteProduct(model); if (!_productRepository.Save()) { return StatusCode(500, "刪除的時候出錯"); } _mailService.Send("Product Deleted",$"Id為{id}的產品被刪除了"); return NoContent(); }
運行:

Ok。
第一大部分先寫到這。。。。。。。。。。。。
接下來幾天比較忙,然后我再編寫第二大部分。我會直接弄一個已經重構好的模板,簡單講一下,然后重點是Identity Server 4.
到目前為止可以進行CRUD操作了,接下來需要把項目重構一下,然后再簡單用一下Identity Server4。
