从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD

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.修改GetProducs这个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:


还有SeedData里面和即将废弃的ProductService:


然后在运行试试:

结果正确。


首先再添加一个参数includeMaterial表示是否带着Material表的数据一起查询出来,该参数有一个默认值是false,就是请求的时候如果不带这个参数,那么这个参数的值就是false。

通过repository查询之后把Product和Material分别映射成ProductDto和MaterialDot。

试试,首先不包含Material:

目前数据库的Material表没有数据,可以手动添加几个,也可以把数据库的Product数据删了,改一下种子数据那部分代码:

 View Code

然后再试试GetProduct带有material的查询:

其中inludeMaterail这个参数需要使用query string的方式,也就是在uri后边加一个问号,问号后边跟着参数名,然后是等号,然后是它的值。如果有多个query string的参数,那么每组参数之间用&分开。

然后再修改一下MaterialController: 


注意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里面的代码,到处都是映射,这种手写的映射很容易出错,如果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里面也改一下:

 View Code

运行一下都应该没有什么问题。

上面都是查询的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 2.0 web api 基础框架 (1)

  • 从头编写 asp.net core 2.0 web api 基础框架 (2)

  • 从头编写 asp.net core 2.0 web api 基础框架 (3)

  • 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置

原文地址: http://www.cnblogs.com/cgzl/p/7675485.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323231.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

做这个网站原因之二

讲讲我最近干了什么事吧以及以后该干啥 其实大部分我学到的东西&#xff0c;都写在博客上了&#xff0c;可以通过博客看到一个人的学习轨迹。除了这些&#xff0c;我也尝试拍些视频去学习一些新的东西。视频剪辑&#xff0c;特效什么的都在尝试。 有个视频我也只是随便尝试尝试…

spring cloud+dotnet core搭建微服务架构:配置中心续(五)

前言 上一章最后讲了&#xff0c;更新配置以后需要重启客户端才能生效&#xff0c;这在实际的场景中是不可取的。由于目前Steeltoe配置的重载只能由客户端发起&#xff0c;没有实现处理程序侦听服务器更改事件&#xff0c;所以还没办法实现彻底实现这一功能。这一章的例子&…

聊聊最近吧

讲讲我最近干了什么事吧以及以后该干啥 其实大部分我学到的东西&#xff0c;都写在博客上了&#xff0c;可以通过博客看到一个人的学习轨迹。除了这些&#xff0c;我也尝试拍些视频去学习一些新的东西。视频剪辑&#xff0c;特效什么的都在尝试。 有个视频我也只是随便尝试尝试…

C# 实现虚拟数字人

随着Ai技术的提升和应用&#xff0c;虚拟数字人被广泛应用到各行各业中。为我们的生活和工作提供了非常多的便利和色彩。 通过设置虚拟数字人的位置大小&#xff0c;可以让数字人可以在电脑屏幕各个位置显示&#xff1a; 虚拟数字人素材&#xff1a; 虚拟数字人(实际有语音&am…

AspectCore.Extension.Reflection : .NET Core反射扩展库

在从零实现AOP的过程中&#xff0c;难免会需要大量反射相关的操作&#xff0c;虽然在.net 4.5/.net core中反射的性能有了大幅的优化&#xff0c;但为了追求极致性能&#xff0c;自己实现了部分反射的替代方案&#xff0c;包括构造器调用、方法调用、字段读写&#xff0c;属性读…

【分享】通过手游赚¥

这本来是个回答&#xff0c;但是在知乎被删了&#xff0c;于是我决定还是在自己网站再发一份&#xff0c;特么知乎店大欺人&#xff0c;我一这么水回答&#xff0c;还被认为是广告营销 首先说明一点&#xff0c;这个完全是自我经历&#xff0c;一种分享吧。觉得假的自然假。 我…

Azure Cosmos DB技术性解读

Azure Cosmos DB是微软公司打造的一项全球分布式、横向分区、多模型数据库服务。该服务允许客户弹性&#xff08;及独立形式&#xff09;跨越任意数量地理服务区对吞吐量与存储进行扩展。Azure Cosmos DB可立足第99百分位比例提升99.99%高可用性水平&#xff0c;提供可预测吞吐…

NOIP2018-普及总结

前言 原本说要去提高的&#xff0c;然后市里瞎搞&#xff0c;就去不了了QVQQVQQVQ。 总结 这次一看感觉题目比较难&#xff0c;所以基本凉凉… 首先这次有很多失误&#xff0c;特别是T2T2T2&#xff0c;其实很容易就分析出要用longlonglong\ \ longlong long的&#xff0c;但…

【博客】csdn搬家到wordpress

在wordpress的插件中搜索cnblogs2wp&#xff0c;安装后&#xff0c;在工具->导入->选博客搬家&#xff0c; 遇到了些问题 总是遇到博客地址不对 https://blog.csdn.net/weixin_43560272 首先修改了后缀 这是我的博客首页地址绝对没错的啊 后缀绝对改了的 总是说地址…

Microsoft加入量子计算的竞争

Microsoft在Ignite大会上宣布了自己的量子计算新平台的预览版&#xff0c;并公开了借助近期粒子物理学方面的进展推出拓扑量子计算机的计划。 Microsoft的量子计算平台预览版将包括一个量子计算模拟器&#xff0c;以及一种集成在Visual Studio中的量子计算编程新语言。据Micros…

Window系统多硬盘设置新引导盘

一、系统启动过程 电脑通电后&#xff0c;首先是启动BIOS程序&#xff0c;BIOS自检完毕后&#xff0c;找到硬盘上的主引导记录MBR&#xff0c;MBR读取DPT&#xff08;分区表&#xff09;&#xff0c;从中找出活动的主分区&#xff0c;然后读取活动主分区的PBR&#xff08;分区引…

【Java】jdk和eclipse下载安装

&#xff08;以前忘了写这方面的安装&#xff09; 附一段测试java环境的代码 public class hello {public static void main(String[] args) {System.out.println("Hello World dsadasdaSasdasd");} }jdk安装&#xff1a; 这里我采用不同方式 直接360软件管家搜索jd…

使用BigQuery分析GitHub上的C#代码

一年多以前&#xff0c;Google 在GitHub中提供了BigQuery用于查询的GitHub上的开源代码&#xff08;open source code on GitHub available for querying&#xff09;&#xff0c;如果这还不够&#xff0c;您可以免费每月运行1TB的查询&#xff01; 所以在这篇文章中&#xff0…

【博客】博客转移

最近一直在搞博客&#xff0c;随着博客基本转移到wordpress&#xff0c;总算是告一段落。 我通过各种博客搬家的方式都没能把博客转移到wordpress上&#xff0c;后来实在没办法&#xff0c;强行Gutenberg编辑器&#xff0c;一个一个复制粘贴到自己博客上面&#xff0c;总算是实…

AspectCore中的IoC容器和依赖注入

IOC模式和依赖注入是近年来非常流行的一种模式&#xff0c;相信大家都不陌生了&#xff0c;在Asp.Net Core中提供了依赖注入作为内置的基础设施&#xff0c;如果仍不熟悉依赖注入的读者&#xff0c;可以看看由我们翻译的Asp.Net Core中文文档中依赖注入的相关章节: ASP.NET Cor…

HttpClient的性能隐患

最近在进行开发过程中&#xff0c;基于都是接口开发&#xff0c;A站接口访问B接口接口来请求数据&#xff0c;而在这个过程中我们使用的是HttpClient这个框架&#xff0c;当然也是微软自己的框架&#xff0c;性能当前没有问题&#xff0c;但如果你直接使用官方的写法&#xff0…

【填坑】博客搬家造成的博客重复问题

原本我的博客数量是差不多八十几篇&#xff0c;昨天晚上一看&#xff0c;怎么变成一百三十多篇了。 惊讶之余有点不可思议&#xff0c;查了才发现原来有几十篇是重复的&#xff0c;后来删掉了一些&#xff0c;但还是有很多 有些也不是重复的&#xff0c;我从知乎发的东西也被搬…

使用acs-engine在Azure中国区部署kubernetes集群详解

1. acs-engine简介 ACS是微软在2015年12月推出的一项基于容器的云端PaaS服务。说简单点&#xff0c;acs-engine就是一个ARM模板生成器&#xff0c;用户只需要配置几个简单的参数来描述容器集群的规格&#xff0c;然后acs-engine将这个容器集群描述文件转化成一组ARM&#xff08…

Lyft的TypeScript实践

来自Lyft的前端工程师Mohsen Azimi介绍了Lyft向TypeScript转型的过程&#xff0c;说明JavaScript类型系统的重要性、为什么Lyft选择TypeScript以及他们的一些实践经验。以下内容翻译自作者的博客&#xff0c;查看原文TypeScript at Lyft。 在我刚刚成为JavaScript开发者的时候&…

【Python】Conda的安装

挖个坑&#xff0c;以后自己慢慢填&#xff1a;下载conda后无法使用 conda优势&#xff1a;conda将几乎所有的工具、第三方包都当做package对待&#xff0c;甚至包括python和conda自身&#xff01;因此&#xff0c;conda打破了包管理与环境管理的约束&#xff0c;能非常方便地…