从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD
第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 DEBUGservices.AddTransient<IMailService, LocalMailService>(); #elseservices.AddTransient<IMailService, CloudMailService>(); #endifvar 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。
下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:相关文章:

可以打游戏的计算机,还在用笔记本玩游戏?台式机才能给你极致享受
【PConline 游戏爆测】随着笔记本的性能越来越好,玩家对于游戏本的需求也越来越高了,再加上购买游戏笔记本并不需要额外购买显示器,就能享受到高刷新率高色域的屏幕,让玩家对于游戏台式机就更加不感兴趣了。但我想说的是ÿ…

如何把Windows安装的所有打印机列出来
[转]最近在论坛中不少网友问"如何把Windows安装的所有打印机列出来",在下面的程序中我们将把系统中所安装的打印机用列表框列出来,同时为默认打印机设置缺省值。 在下面的程序中我们用到了两个主要的类,把所有的打印机列表出来用…

今晚直播 | 谷歌资深工程师手把手教你使用TensorFlow最新API构建学习模型
目前,深度学习的研究和应用大受追捧,各种开源的深度学习框架层出不穷。TensorFlow 作为目前最受欢迎的深度学习框架,已经在 GitHub 上获得了 112194 个 star,受欢迎程序可见一斑。但如何学习 TensorFlow,以及如何通过 …

澳洲计算机学,2020年澳洲计算机科学专业工作好找吗
就业前景:本专业毕业生就业前景十分良好。在完成学业后可以凭借其良好扎实的专业技能自主创业,或者进一步学习获得硕士或博士学位,也可进入计算机科学领域求职,如对先进计算机进行研发、编程、游戏设计、多媒体设计、网页设计、信…

AngularJS如何在filter中相互调用filter
调用方式如下: app.filter(filter2, function( $filter ) {return function( input) {return $filter(filter1)( input );}});本文转自黄聪博客园博客,原文链接:http://www.cnblogs.com/huangcong/p/6800107.html,如需转载请自行联…

腾讯AI Lab开源业内最大规模多标签图像数据集(附下载地址)
今日(10 月 18 日),腾讯AI Lab宣布正式开源“Tencent ML-Images”项目。该项目由多标签图像数据集 ML-Images,以及业内目前同类深度学习模型中精度最高的深度残差网络 ResNet-101 构成。 该开源项目的主要内容包括: 1、…

two years in cnblogs.com
时间过得太快了,几乎还没什么感觉就在博客园扎寨两年了。回头瞄瞄这两年来的随笔觉得自己留下的都是毛皮,自己一直在调船头,几乎没有在哪一个专业中找到精髓,有点遗憾!在博客园这两年最要感谢的人是dudu,他…

自动驾驶公司Momenta完成超2亿美元融资,估值超10亿美元
参加 2018 AI开发者大会,请点击 ↑↑↑10 月 17 日,自动驾驶公司 Momenta 对外公布完成新一轮融资,本轮融资主要来自行业领先的战略投资者和国资背景的投资者。本轮战略投资者包括腾讯等多家机构,国资背景投资者则包括招商局创投…

shell版俄罗斯方块
#!/bin/bash # Tetris Game # 10.21.2003 xhchen #颜色定义 cRed1 cGreen2 cYellow3 cBlue4 cFuchsia5 cCyan6 cWhite7 colorTable($cRed $cGreen $cYellow $cBlue $cFuchsia $cCyan $cWhite) #位置和大小 iLeft3 iTop2 ((iTrayLeft iLeft 2)) ((iTrayTop iTop 1)) ((iTray…

利用计算机进行机械设计属于什么,计算机技术机械设计应用
【摘要】近几年计算机技术的飞速发展使得它在各个领域中的地位越来越显著,应用越来越广泛,在机械设计过程中也逐渐地引入了计算机技术。在计算机技术中有一种单独的辅助设计技术用来辅助各种设计工作,计算机辅助设计技术使得机械设计更加简单…

linux carry php Soap 扩展
前言今天又出现下问题了需要解决如下:报错信息是没有soap这个扩展!解决问题如图所示:cd php-5.6.2/ext/soap//usr/local/php5/bin/phpize # 进入phpize开始编译安装 ./configure -with-php-config/usr/local/php5/bin/php-config -enable-so…

html使用highcharts绘制饼图,html js highcharts绘制圆饼图表
jquery实现饼状图效果 - 站长素材var chart;$(function () {var totalMoney999var zccw178var sycw821$(document).ready(function() {chart new Highcharts.Chart({chart: {renderTo: pie_chart,plotBackgroundColor: white,//背景颜色plotBorderWidth: 0,plotShadow: false}…

pycharm的安装配置和简单使用
1.pycharm的安装和配置1)下载地址https://www.jetbrains.com/pycharm/download/#sectionwindows各位可以选择Community版本进行安装学习,土豪可以选择Professional版本下载安装包后默认安装2)基本设置file-setting-project-project interpret…

港中大、商汤开源目标检测工具包mmdetection,对比Detectron如何?
近日,香港中文大学-商汤联合实验室开源了基于 PyTorch 的检测库——mmdetection。上个月,商汤和港中大组成的团队在 COCO 比赛的物体检测(Detection)项目中夺得冠军,而 mmdetection 正是基于 COCO 比赛时的 codebase 重…

关注中国的房地产市场
美国房贷呆账持续恶化引发道指大跌 亚洲股市应声猛泻中国的房地产,现在热得不得了,很多人贷款买房,买房子的人多了,房价也就上涨很多,投机商也囤积居奇。更促使房价迅猛上升。当中国房价不断上涨的势头减缓的时候&…

冠军揭晓!京东Alpha开发者大赛Pick谁上了C位
近期,京东Alpha开发者大赛圆满结束。遍布全国的AI开发爱好者,对本次大赛展现出了超乎想象的激情与热爱,开发出了很多优质、好玩的技能作品。家中有叮咚的朋友们也纷纷表示更喜欢撩音箱了,因为男女朋友、赚钱、娱乐游戏、学习……通…

大白话讲解Promise(二)理解Promise规范
上一篇我们讲解了ES6中Promise的用法,但是知道了用法还远远不够,作为一名专业的前端工程师,还必须通晓原理。所以,为了补全我们关于Promise的知识树,有必要理解Promise/A规范,理解了它你才能知道Promise内部…

量子计算机人类意识云,意识上传-人类距离永生还要多久(大脑的工作原理很有可能与量子计算机一致)...
事情的引子是最近的科幻电影《炭变》, 300年后,人类意识上传到数字植入物(称为“堆栈”)中,并且富人还可以不停的升级,并基本上永远活着。虽说这只是部科幻电影,但也会让很多人流口水,毕竟如果有可能实现永…

Alpha混合
Direct3D计算Alpha混合的颜色公式:Color ( SrcRGB * SrcK ) ( DestRGB * DestK )SrcRGB表示源颜色值,即将要绘制的颜色值。SrcK表示源混合系数,通常赋值为D3DBLEND_SRCALPHA,即当前绘制像素的Alpha值DestRGB表示目标颜色值&…

考研计算机专业课怎么复习,2016考研计算机专业课如何复习?
2016考研计算机专业课如何复习??基础复习阶段以指定参考书为主,兼顾笔记,进行专业课的第一轮复习。看书要以理解为主,不必纠缠于细节,并在不懂的知识点处做上标记。第一步,选择一本难度适宜、内容全面、与…

Linux 学习手记(1):命令行BASH的基本操作
1. Shell 是什么 Shell(壳)是用户与操作系统底层(通常是内核)之间交互的中介程序,负责将用户指令、操作传递给操作系统底层。 Shell一般分为:图形化Shell(GUI)、命令行Shell…

为了智能驾驶,李彦宏要改造城市道路了!
10 月 18 日,在世界智能网联汽车大会上,百度 CEO 李彦宏提到:当汽车变得越来越智能,道路的基础设施也必须跟着变,必须进行改造。 李彦宏在发言中表示,“很多自动驾驶汽车都是需要依赖极其昂贵的激光雷达来…

LoadRunner Winsock 10053错误的真正原因
最近使用LoadRunner进行Winsock协议的性能测试时,测试的WebServer是JBoss,经常出现10053错误,现象如下:当我用lrs_create_socket创建连接之后,当这个socket连接的请求次数达到100次后,这个连接就不可用了&a…

dsp和通用计算机的区别,dsp芯片是什么_dsp芯片和通用微处理器有什么区别
对于dsp芯片很多人都会比较陌生,它主要运用在信号处理、图像处理、声音语言等多个场所。那么dsp芯片到底是什么呢?它和通用微处理器有什么不同。接下来小编就简单的给大家介绍一下dsp芯片是什么及dsp芯片和通用微处理器有什么区别。一、dsp芯片是什么1、…
百度大脑发布企业服务解决方案,将 AI 技术落实到细分领域
人工智能竞争之势愈演愈烈,AI与场景应用的深度结合将成为各家企业的取胜关键。10月18日,百度大脑行业创新论坛在北京正式拉开帷幕,届时将走进全国6个城市举办7场以企业服务、信息服务和零售等为主题的专题活动,展示人工智能与不同…

图片上传(加水印、缩略图、远程保存)的简单例子
图片上传(加水印、缩略图、远程保存)的简单例子(应用于51aspx.com)该源码下载地址:http://51aspx.com/CV/ImageUpload今天看到xiongeee发的文章使用使用FileUpload控件上传图片并自动生成缩略图、自动生成带文字和图片的水印图 …

wp实例开发精品文章源码推荐
qianqianlianmengwp实例开发精品文章源码推荐 WP8 启动媒体应用 这个示例演示了如何选择正确的msAudioCategory类别的音像(AV)流来配置它作为一个音频播放流。具体地说,这个示例执行以下操作:启动一个媒体应用与“媒体”msA……资源地址: http://www.apkbus.…

98级计算机应用教材,西安外事学院98级计算机应用9806班毕业二十年校友返校
7月28日,计算机应用9806班的校友们赴二十年之约,相聚于西安外事学院工学院。出席此次会议的有工学院党总支书记、副院长任国良、计算机应用9806班当年的老师赵培华、张凤琴、刘可、刘挺以及计算机应用9806班的学生。会议之前,校友们参观了学校…

js:appendChild、insertBefore和insertAfter
web/Win8开发中经常要在js中动态增加一些element,就需要用到下面的一些方法: appendChild: target.appendChild(newChild) newChild作为target的子节点插入最后的一子节点之后 insertBefore: target.insertBefore(newChild,existi…

来呀!AI喊你斗地主——首个搞定斗地主的深度神经网络
参加 2018 AI开发者大会,请点击 ↑↑↑ 作者 | Anonymous authors 译者 | 蔡志兴 编辑 | Jane 出品 | AI科技大本营 【导读】近年来,从围棋到 Dota 团战赛,深度神经网络应用在各种游戏竞赛中不断取得突破。这一次,有人把这种方…