一、创建 ASP.NET Core WebApi项目
二、添加
三、
-----------------------------------------------------------
一、创建项目,WideWorldImporters.API,选项按照下列图操作
二、引用需要的Nuget包
- Microsoft.EntityFrameworkCore.SqlServer
Swashbuckle.AspNetCore
Swashbuckle.AspNetCore包允许为Web API启用帮助页。
试运行一下项目
OK, 没任何错误。?
添加一个文件夹Models,在里面添加4个.cs文件,
Entities.cs //实体,为了简单些把多个实体放在这一个文件里,实际项目里可以一个文件一个类
Extensions.cs //扩展类,Requests.cs //请求类,Responses.cs //响应类,4个文件的完整代码如下:
Entities.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading.Tasks; 5 using Microsoft.EntityFrameworkCore; 6 using Microsoft.EntityFrameworkCore.Metadata.Builders; 7 8 namespace WideWorldImporters.API.Models 9 { 10 public partial class StockItem 11 { 12 public StockItem() 13 { 14 } 15 16 public StockItem(int? stockItemID) 17 { 18 StockItemID = stockItemID; 19 } 20 21 public int? StockItemID { get; set; } 22 23 public string StockItemName { get; set; } 24 25 public int? SupplierID { get; set; } 26 27 public int? ColorID { get; set; } 28 29 public int? UnitPackageID { get; set; } 30 31 public int? OuterPackageID { get; set; } 32 33 public string Brand { get; set; } 34 35 public string Size { get; set; } 36 37 public int? LeadTimeDays { get; set; } 38 39 public int? QuantityPerOuter { get; set; } 40 41 public bool? IsChillerStock { get; set; } 42 43 public string Barcode { get; set; } 44 45 public decimal? TaxRate { get; set; } 46 47 public decimal? UnitPrice { get; set; } 48 49 public decimal? RecommendedRetailPrice { get; set; } 50 51 public decimal? TypicalWeightPerUnit { get; set; } 52 53 public string MarketingComments { get; set; } 54 55 public string InternalComments { get; set; } 56 57 public string CustomFields { get; set; } 58 59 public string Tags { get; set; } 60 61 public string SearchDetails { get; set; } 62 63 public int? LastEditedBy { get; set; } 64 65 public DateTime? ValidFrom { get; set; } 66 67 public DateTime? ValidTo { get; set; } 68 } 69 70 public class StockItemsConfiguration : IEntityTypeConfiguration71 { 72 public void Configure(EntityTypeBuilder builder) 73 { 74 // Set configuration for entity 75 builder.ToTable("StockItems", "Warehouse"); 76 77 // Set key for entity 78 builder.HasKey(p => p.StockItemID); 79 80 // Set configuration for columns 81 82 builder.Property(p => p.StockItemName).HasColumnType("nvarchar(200)").IsRequired(); 83 builder.Property(p => p.SupplierID).HasColumnType("int").IsRequired(); 84 builder.Property(p => p.ColorID).HasColumnType("int"); 85 builder.Property(p => p.UnitPackageID).HasColumnType("int").IsRequired(); 86 builder.Property(p => p.OuterPackageID).HasColumnType("int").IsRequired(); 87 builder.Property(p => p.Brand).HasColumnType("nvarchar(100)"); 88 builder.Property(p => p.Size).HasColumnType("nvarchar(40)"); 89 builder.Property(p => p.LeadTimeDays).HasColumnType("int").IsRequired(); 90 builder.Property(p => p.QuantityPerOuter).HasColumnType("int").IsRequired(); 91 builder.Property(p => p.IsChillerStock).HasColumnType("bit").IsRequired(); 92 builder.Property(p => p.Barcode).HasColumnType("nvarchar(100)"); 93 builder.Property(p => p.TaxRate).HasColumnType("decimal(18, 3)").IsRequired(); 94 builder.Property(p => p.UnitPrice).HasColumnType("decimal(18, 2)").IsRequired(); 95 builder.Property(p => p.RecommendedRetailPrice).HasColumnType("decimal(18, 2)"); 96 builder.Property(p => p.TypicalWeightPerUnit).HasColumnType("decimal(18, 3)").IsRequired(); 97 builder.Property(p => p.MarketingComments).HasColumnType("nvarchar(max)"); 98 builder.Property(p => p.InternalComments).HasColumnType("nvarchar(max)"); 99 builder.Property(p => p.CustomFields).HasColumnType("nvarchar(max)");100 builder.Property(p => p.LastEditedBy).HasColumnType("int").IsRequired();101 102 // Computed columns103 104 builder105 .Property(p => p.StockItemID)106 .HasColumnType("int")107 .IsRequired()108 .HasComputedColumnSql("NEXT VALUE FOR [Sequences].[StockItemID]");109 110 builder111 .Property(p => p.Tags)112 .HasColumnType("nvarchar(max)")113 .HasComputedColumnSql("json_query([CustomFields],N'$.Tags')");114 115 builder116 .Property(p => p.SearchDetails)117 .HasColumnType("nvarchar(max)")118 .IsRequired()119 .HasComputedColumnSql("concat([StockItemName],N' ',[MarketingComments])");120 121 // Columns with generated value on add or update122 123 builder124 .Property(p => p.ValidFrom)125 .HasColumnType("datetime2")126 .IsRequired()127 .ValueGeneratedOnAddOrUpdate();128 129 builder130 .Property(p => p.ValidTo)131 .HasColumnType("datetime2")132 .IsRequired()133 .ValueGeneratedOnAddOrUpdate();134 }135 }136 137 public class WideWorldImportersDbContext : DbContext138 {139 public WideWorldImportersDbContext(DbContextOptions options)140 : base(options)141 {142 }143 144 protected override void OnModelCreating(ModelBuilder modelBuilder)145 {146 // Apply configurations for entity147 148 modelBuilder149 .ApplyConfiguration(new StockItemsConfiguration());150 151 base.OnModelCreating(modelBuilder);152 }153 154 public DbSet StockItems { get; set; }155 }156 }
Extensions.cs
using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.EntityFrameworkCore;namespace WideWorldImporters.API.Models{ public static class WideWorldImportersDbContextExtensions { public static IQueryableGetStockItems(this WideWorldImportersDbContext dbContext, int pageSize = 10, int pageNumber = 1, int? lastEditedBy = null, int? colorID = null, int? outerPackageID = null, int? supplierID = null, int? unitPackageID = null) { // Get query from DbSet var query = dbContext.StockItems.AsQueryable(); // Filter by: 'LastEditedBy' if (lastEditedBy.HasValue) query = query.Where(item => item.LastEditedBy == lastEditedBy); // Filter by: 'ColorID' if (colorID.HasValue) query = query.Where(item => item.ColorID == colorID); // Filter by: 'OuterPackageID' if (outerPackageID.HasValue) query = query.Where(item => item.OuterPackageID == outerPackageID); // Filter by: 'SupplierID' if (supplierID.HasValue) query = query.Where(item => item.SupplierID == supplierID); // Filter by: 'UnitPackageID' if (unitPackageID.HasValue) query = query.Where(item => item.UnitPackageID == unitPackageID); return query; } public static async Task GetStockItemsAsync(this WideWorldImportersDbContext dbContext, StockItem entity) => await dbContext.StockItems.FirstOrDefaultAsync(item => item.StockItemID == entity.StockItemID); public static async Task GetStockItemsByStockItemNameAsync(this WideWorldImportersDbContext dbContext, StockItem entity) => await dbContext.StockItems.FirstOrDefaultAsync(item => item.StockItemName == entity.StockItemName); } public static class IQueryableExtensions { public static IQueryable Paging (this IQueryable query, int pageSize = 0, int pageNumber = 0) where TModel : class => pageSize > 0 && pageNumber > 0 ? query.Skip((pageNumber - 1) * pageSize).Take(pageSize) : query; }}
Requests.cs
using System;using System.Collections.Generic;using System.ComponentModel.DataAnnotations;using System.Linq;using System.Threading.Tasks;namespace WideWorldImporters.API.Models{ public class PostStockItemsRequest { [Key] public int? StockItemID { get; set; } [Required] [StringLength(200)] public string StockItemName { get; set; } [Required] public int? SupplierID { get; set; } public int? ColorID { get; set; } [Required] public int? UnitPackageID { get; set; } [Required] public int? OuterPackageID { get; set; } [StringLength(100)] public string Brand { get; set; } [StringLength(40)] public string Size { get; set; } [Required] public int? LeadTimeDays { get; set; } [Required] public int? QuantityPerOuter { get; set; } [Required] public bool? IsChillerStock { get; set; } [StringLength(100)] public string Barcode { get; set; } [Required] public decimal? TaxRate { get; set; } [Required] public decimal? UnitPrice { get; set; } public decimal? RecommendedRetailPrice { get; set; } [Required] public decimal? TypicalWeightPerUnit { get; set; } public string MarketingComments { get; set; } public string InternalComments { get; set; } public string CustomFields { get; set; } public string Tags { get; set; } [Required] public string SearchDetails { get; set; } [Required] public int? LastEditedBy { get; set; } public DateTime? ValidFrom { get; set; } public DateTime? ValidTo { get; set; } } public class PutStockItemsRequest { [Required] [StringLength(200)] public string StockItemName { get; set; } [Required] public int? SupplierID { get; set; } public int? ColorID { get; set; } [Required] public decimal? UnitPrice { get; set; } } public static class Extensions { public static StockItem ToEntity(this PostStockItemsRequest request) => new StockItem { StockItemID = request.StockItemID, StockItemName = request.StockItemName, SupplierID = request.SupplierID, ColorID = request.ColorID, UnitPackageID = request.UnitPackageID, OuterPackageID = request.OuterPackageID, Brand = request.Brand, Size = request.Size, LeadTimeDays = request.LeadTimeDays, QuantityPerOuter = request.QuantityPerOuter, IsChillerStock = request.IsChillerStock, Barcode = request.Barcode, TaxRate = request.TaxRate, UnitPrice = request.UnitPrice, RecommendedRetailPrice = request.RecommendedRetailPrice, TypicalWeightPerUnit = request.TypicalWeightPerUnit, MarketingComments = request.MarketingComments, InternalComments = request.InternalComments, CustomFields = request.CustomFields, Tags = request.Tags, SearchDetails = request.SearchDetails, LastEditedBy = request.LastEditedBy, ValidFrom = request.ValidFrom, ValidTo = request.ValidTo }; }}
Responses.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Net; 5 using System.Threading.Tasks; 6 using Microsoft.AspNetCore.Mvc; 7 8 namespace WideWorldImporters.API.Models 9 { 10 public interface IResponse 11 { 12 string Message { get; set; } 13 14 bool DidError { get; set; } 15 16 string ErrorMessage { get; set; } 17 } 18 19 public interface ISingleResponse: IResponse 20 { 21 TModel Model { get; set; } 22 } 23 24 public interface IListResponse : IResponse 25 { 26 IEnumerable Model { get; set; } 27 } 28 29 public interface IPagedResponse : IListResponse 30 { 31 int ItemsCount { get; set; } 32 33 double PageCount { get; } 34 } 35 36 public class Response : IResponse 37 { 38 public string Message { get; set; } 39 40 public bool DidError { get; set; } 41 42 public string ErrorMessage { get; set; } 43 } 44 45 public class SingleResponse : ISingleResponse 46 { 47 public string Message { get; set; } 48 49 public bool DidError { get; set; } 50 51 public string ErrorMessage { get; set; } 52 53 public TModel Model { get; set; } 54 } 55 56 public class ListResponse : IListResponse 57 { 58 public string Message { get; set; } 59 60 public bool DidError { get; set; } 61 62 public string ErrorMessage { get; set; } 63 64 public IEnumerable Model { get; set; } 65 } 66 67 public class PagedResponse : IPagedResponse 68 { 69 public string Message { get; set; } 70 71 public bool DidError { get; set; } 72 73 public string ErrorMessage { get; set; } 74 75 public IEnumerable Model { get; set; } 76 77 public int PageSize { get; set; } 78 79 public int PageNumber { get; set; } 80 81 public int ItemsCount { get; set; } 82 83 public double PageCount 84 => ItemsCount < PageSize ? 1 : (int)(((double)ItemsCount / PageSize) + 1); 85 } 86 87 public static class ResponseExtensions 88 { 89 public static IActionResult ToHttpResponse(this IResponse response) 90 { 91 var status = response.DidError ? HttpStatusCode.InternalServerError : HttpStatusCode.OK; 92 93 return new ObjectResult(response) 94 { 95 StatusCode = (int)status 96 }; 97 } 98 99 public static IActionResult ToHttpResponse (this ISingleResponse response)100 {101 var status = HttpStatusCode.OK;102 103 if (response.DidError)104 status = HttpStatusCode.InternalServerError;105 else if (response.Model == null)106 status = HttpStatusCode.NotFound;107 108 return new ObjectResult(response)109 {110 StatusCode = (int)status111 };112 }113 114 public static IActionResult ToHttpResponse (this IListResponse response)115 {116 var status = HttpStatusCode.OK;117 118 if (response.DidError)119 status = HttpStatusCode.InternalServerError;120 else if (response.Model == null)121 status = HttpStatusCode.NoContent;122 123 return new ObjectResult(response)124 {125 StatusCode = (int)status126 };127 }128 }129 }
接下来开始添加Api控制器 WarehouseController
右击项目文件夹Controllers
WarehouseController.cs
using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using Microsoft.AspNetCore.Http;using Microsoft.AspNetCore.Mvc;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.Logging;using WideWorldImporters.API.Models;namespace WideWorldImporters.API.Controllers{ [Route("api/v1/[controller]")] [ApiController] public class WarehouseController : ControllerBase { protected readonly ILogger _logger; protected readonly WideWorldImportersDbContext _dbContext; public WarehouseController(ILoggerlogger, WideWorldImportersDbContext dbContext) { _logger = logger; _dbContext = dbContext; } /// /// 获取库存项列表 /// /// 页大小 /// 页号 /// 最后编辑者 /// 颜色ID /// 外包装ID /// 供应商ID /// ///public async Task GetStockItemsAsync(int pageSize = 10, int pageNumber = 1, int? lastEditedBy = null, int? colorId = null, int? outerPackageId = null, int? supplierId = null, int? unitPackageId = null ) { _logger?.LogDebug($"'{nameof(GetStockItemsAsync)}' 被调用了"); var response = new PagedResponse ();//创建分页对象 try { var query = _dbContext.GetStockItems(); response.PageSize = pageSize; response.PageNumber = pageNumber; response.ItemsCount = await query.CountAsync();//获取总记录数 response.Model = await query.Paging(pageSize, pageNumber).ToListAsync(); response.Message = $"第 {pageNumber} 共 {response.PageCount}, 产品总数量: {response.ItemsCount}"; _logger?.LogInformation("已成功检索库存项!"); } catch (Exception e) { response.DidError = true; response.ErrorMessage = "有一个内部错误,请联系技术支持!"; _logger?.LogCritical($"There was an error on '{nameof(GetStockItemsAsync)}' invocation: {e}"); } return response.ToHttpResponse(); } /// /// 获取单记录 /// /// ///[HttpGet("StockItem/{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] [ProducesResponseType(500)] public async Task GetStockItemAsync(int id) { _logger?.LogDebug($"'{nameof(GetStockItemAsync)}' 被执行了!"); var response = new SingleResponse (); try { response.Model = await _dbContext.GetStockItemsAsync(new StockItem(id)); } catch (Exception e) { response.DidError = true; response.ErrorMessage = "有错误,请联系技术支持!"; _logger?.LogCritical($"There was an error on '{nameof(GetStockItemAsync)}' invocation: {e}"); } return response.ToHttpResponse(); } // POST // api/v1/Warehouse/StockItem/ /// /// Creates a new stock item /// /// Request model ///A response with new stock item ///Returns the stock items list ///A response as creation of stock item ///For bad request ///If there was an internal server error [HttpPost("StockItem")] [ProducesResponseType(200)] [ProducesResponseType(201)] [ProducesResponseType(400)] [ProducesResponseType(500)] public async TaskPostStockItemAsync([FromBody]PostStockItemsRequest request) { _logger?.LogDebug("'{0}' has been invoked", nameof(PostStockItemAsync)); var response = new SingleResponse (); try { var existingEntity = await _dbContext.GetStockItemsByStockItemNameAsync(new StockItem { StockItemName = request.StockItemName }); if (existingEntity != null) ModelState.AddModelError("StockItemName", "Stock item name already exists"); if (!ModelState.IsValid) return BadRequest(); // Create entity from request model var entity = request.ToEntity(); // Add entity to repository _dbContext.Add(entity); // Save entity in database await _dbContext.SaveChangesAsync(); // Set the entity to response model response.Model = entity; } catch (Exception ex) { response.DidError = true; response.ErrorMessage = "There was an internal error, please contact to technical support."; _logger?.LogCritical("There was an error on '{0}' invocation: {1}", nameof(PostStockItemAsync), ex); } return response.ToHttpResponse(); } // PUT // api/v1/Warehouse/StockItem/5 /// /// Updates an existing stock item /// /// Stock item ID /// Request model ///A response as update stock item result ///If stock item was updated successfully ///For bad request ///If stock item is not exists ///If there was an internal server error [HttpPut("StockItem/{id}")] [ProducesResponseType(200)] [ProducesResponseType(400)] [ProducesResponseType(404)] [ProducesResponseType(500)] public async TaskPutStockItemAsync(int id, [FromBody]PutStockItemsRequest request) { _logger?.LogDebug("'{0}' has been invoked", nameof(PutStockItemAsync)); var response = new Response(); try { // Get stock item by id var entity = await _dbContext.GetStockItemsAsync(new StockItem(id)); // Validate if entity exists if (entity == null) return NotFound(); // Set changes to entity entity.StockItemName = request.StockItemName; entity.SupplierId = request.SupplierID; entity.ColorId = request.ColorID; entity.UnitPrice = request.UnitPrice; // Update entity in repository _dbContext.Update(entity); // Save entity in database await _dbContext.SaveChangesAsync(); } catch (Exception ex) { response.DidError = true; response.ErrorMessage = "There was an internal error, please contact to technical support."; _logger?.LogCritical("There was an error on '{0}' invocation: {1}", nameof(PutStockItemAsync), ex); } return response.ToHttpResponse(); } // DELETE // api/v1/Warehouse/StockItem/5 /// /// Deletes an existing stock item /// /// Stock item ID ///A response as delete stock item result ///If stock item was deleted successfully ///If stock item is not exists ///If there was an internal server error [HttpDelete("StockItem/{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] [ProducesResponseType(500)] public async TaskDeleteStockItemAsync(int id) { _logger?.LogDebug("'{0}' has been invoked", nameof(DeleteStockItemAsync)); var response = new Response(); try { // Get stock item by id var entity = await _dbContext.GetStockItemsAsync(new StockItem(id)); // Validate if entity exists if (entity == null) return NotFound(); // Remove entity from repository _dbContext.Remove(entity); // Delete entity in database await _dbContext.SaveChangesAsync(); } catch (Exception ex) { response.DidError = true; response.ErrorMessage = "There was an internal error, please contact to technical support."; _logger?.LogCritical("There was an error on '{0}' invocation: {1}", nameof(DeleteStockItemAsync), ex); } return response.ToHttpResponse(); } }}
接下来设置依赖注入,ASP.NET Core自带依赖注入,不需要第三方框架
修改根目录下Startup 类,
using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Reflection;using System.Threading.Tasks;using Microsoft.AspNetCore.Builder;using Microsoft.AspNetCore.Hosting;using Microsoft.AspNetCore.Mvc;using Microsoft.EntityFrameworkCore;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using Microsoft.Extensions.Options;using Swashbuckle.AspNetCore.Swagger;using WideWorldImporters.API.Controllers;using WideWorldImporters.API.Models;namespace WideWorldImporters.API{ public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); //注入数据库上下文对象 services.AddDbContext(options => { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));//从配置文件中获取数据库连接字符串 }); //--------------上面是框架系统的访问,下面是应用程序的注入-------------------- services.AddScoped >();//只用这里写了这一句,apiController里的注入才有效 //注入丝袜哥Swagger services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new Info {Title = "WideWorldImporters API", Version = "v1"}); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "WideWorldImporters API V1"); }); app.UseMvc(); } }}
在appsettings.json文件中添加数据库连接字符串
{ "ConnectionStrings": { "DefaultConnection": "Server=(localdb);Database=WideWorldImportersDb;Trusted_Connection=True;MultipleActiveResultSets=true" }, "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*"}
设置项目属性的swagger生成的文件,丝袜哥就是靠这个文件来生成UI的。
设置完,记得保存!
代码写完了,数据库还没有,需要利用EF Core迁移并生成数据库
在命令行输入迁移命令: add-migration AddEntity
生成了迁移文件
然后更新到数据库,输入:update-database
数据库创建完毕。
运行项目试试!!!!
访问地址:
http://localhost:49956/api/v1/Warehouse/StockItem
好像有问题,明天继续吧.................................郁闷了