Asp.Net.Core

Asp.net.Core

前言:

Asp的侧重点有2个MVC和WebApi,虽然说WebApi是MVC的一部分都是可以算是分2部分来说。

Asp. MVC

1.1基本概念:

Model(模型)、View(视图)、Controller(控制器) 控制器与视图之间的数据传递就是模型

Controller(控制器)就是我们的实现类,View(视图)就是数据渲染(Render)之后得到的效果页面、模型就是只有属性的普通类

Models/Person.cs
    新的类写法 =>只能说方便
    public record Person(string Name,bool IsVip,DateTime CreatedDateTime)
​
Views/Test/Demo1.cshtml
    @model WebApplication1.Models.Person
    <div>姓名:@Model.Name</div>
    <div>是否Vip:@Model.IsVip</div>
    <div>时间:@Model.CreatedDateTime</div>
    
 Controllers/TestController.cs
 public class TestController : Controller
    {
        public IActionResult Demo1()
        {
            var p1 = new Person("Tome",true,DateTime.Now);
            return View(p1);
        }
    }
   然后跑的时候直接就是Test/Demo1就可以得到页面效果 反正就控制器那个文件夹里面把Controller去掉就是网页访问路径之一 然后Demo1就是访问的视图
  只能说写起来的感觉跟以前写PHP的MCV差不多

Asp.WebApi

1.1直接来个列子看一下基本接口是怎么写的

细节:其实控制器可以不显式的去继承自任何类

就好比下方的代码TestController : ControllerBase 其实 Test是可以不去继承 ControllerBase也是可以执行的,但是不继承的话会有一些方法是无法直接调用,一般来说 继承一下 ControllerBase就可以不用动他了

Controllers.TestController.cs
​
namespace WebApplication1.Controllers
{
    [Route("api/[controller]")] //这个就是接口的访问路径
    [ApiController]
    public class TestController : ControllerBase
    {
        //当要发出get请求的时候需要添加一个Http
        //如果是发出Post delet update 都一样 [HttpPost]
        [HttpGet]  
        //这个就是请求里面所包含的方法
        public Person GetPerson()
        {
            return new Person("帝后云曦",18);
        }
​
        [HttpPost]
          public string[]  SaveNote(SaveNoteRequest req)
        {
            System.IO.File.WriteAllText(req.Title+".text",req.Content);
            return new string[] {"OK",req.Title};
        }
    }
}
​
​
Person.cs
 public record Person(string Name,int Age);

1.2了解一下Rest

Rest是WebApi的一种风格,WbeApi有两种风格一种是(面向过程)RPC、面向REST

Rpc跟Rest对比

Rpc:想到什么干什么,不用考虑那么多,方便 一把搜哈

Rest: 凸显一个专业

Rpc:控制器/操作方法的形式把服务端的代码当成方法去调用(简单直接)
就写前端的时候最常见的:(使用的是querystring)
    /Person/GetAll
    /Person/GetByid?id=8
    /Person/DeleteByid/8;
    
Rest:按照HTTP语义来使用Http协议
    1、Url用于资源的定位: /user/888、/user/8888/orders
                                    //获得用户编号为888的所有东西
    2、Http胃词:就是请求的方式 Get、Post..
    3、幂等: 可以理解成 一个行为不管触发多少次结果都是一样的就是幂等
        //抽象一点来说 lise.Add(3) 这个行为就不是幂等的每次触发这个行为都会新添加3条数据
    4、Get的相应可以被缓存
    5、服务器通过状态码来反映资源的获取结果
        //接口请求触发了之后 如果成功会返回200状态码 如果失败会直接给有关该错误的状态码比如:404

上点代码熟悉一下

 [Route("[controller]/[action]")]
    [ApiController]
    public class Test : ControllerBase
    {
        [HttpGet]
        public Person[] GetAll()
        {
            return new Person[] {
                    new Person(1,"云曦", 16),
                    new Person(2,"月禅",16),
                    new Person(3,"清漪",16),
                    new Person(4,"荒",16) };
        }
​
        [HttpGet]
        public Person? GetById(long id)
        //这里添加?是用来表示可为null的类型
        {
            if (id == 1)
            {
                return new Person(1, "云曦", 16);
            }
            else if( id == 2)
            {
                return new Person(2, "月禅", 16);
            }else if( id == 3)
            {
                return new Person(3, "清漪", 16);
            }else if( id == 4)
            {
                return new Person(4, "荒", 16);
            }
            else
            {
                return null;
            }
        }
​
        [HttpPost]
        public string AddNew(Person p)
        {
            return "OK";
        }
​
        }
    //注意上面的{id}是路由参数并不是querystring
    //就拿Get{id}为例子:运用到浏览器的路径是:Person/6  而不是Person?id=6

Rest的实现

就好比 我想要Person/aaa的路径系统会优先寻找名为aaa的控制器

注意:如果控制器存在一个没有添加HttpGet/Post等的public方法 可以处理这种请求但是Swagger会报错 可以用``

为了实现这样的路径方式:
  
  [Route("[controller]/[action]")]优先匹配action方法的名字
    
      [ApiExplorerSettings(IgnoreApi =true)]
        public void Read()
        {
            Console.WriteLine("你好");
        }
                

1.3WebApi的异步及返回值

Action的异步方法

一般来说如果我们去使用异步的时候不是await...async这样去写吗,然后为了更加的好的去分辨异步方法我们会在方法名后面添加async => ActionAsyncm。但是Action方法可以同步也可以异步,异步的Action方法名一般不需要Async结尾

IActionResult

这个类型是用于我们后端自定义的状态码,正常抛异常不使用这个东西那么会出现一大堆错误乱码,我们使用这一个的话,可以去自定义错误的状态码,更加的直观

​
      
  [HttpGet]
        public IActionResult<int> GetCj2(int id)
        //使用泛型指定就不用写那些什么OK之类的东西了
        {
            if(id == 1)
            {
                //return OK(88);
                return 88;  
            }else if(id == 2)
            {
                return 99;
            }else
            {
                return NotFound("id错误");
            }
        } 这里面的OK NotFound是 ControllerBase里面关于JSON的一种方法

1.4 Asp中依赖注入的使用

首先Aps中的依赖注入 不像是控制程序那样写那么多东西,项目创建的时候在Program.cs就已经给你配置好了

打个比方,我创建了一个Tester.cs的实体类里面包含一个添加的方法,然后我想要在其他地方使用它,我通过依赖注入的方式去把这个方法注入 这样

Tester.cs
public class Tester
    {
        public  int Add(int x,int y)
        {
            return x + y;
        }
    }
    
Program.cs
var builder = WebApplication.CreateBuilder(args);.
​
builder.Services.AddControllers(); // 直接使用这个进行依赖注入
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//上面的这三个Add就已经是帮你配置好使用依赖注入的配置了 
//你想要进行依赖注入就AddScoped<依赖名称> 这样子就已经算是创建好了
​
builder.Services.AddScoped<Tester>();
var app = builder.Build(); 
​
TestController.cs
​
 [Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        private readonly Tester tester;
        //声明了一个只读的字段  tester 他的类型是 Tester.cs里面的配置
        //readonly就是一个私有字段
​
        public TestController(Tester tester)
        {
            this.tester = tester;
        }
        //使用依赖注入的方式把将一个 Tester 对象注入到 TestController 中
        //
​
        [HttpGet]
        public int Add1()
        {
            return tester.Add(1, 3);
        }
    }
​

那么问题来了,有多个项目的时候,如果想要使用其他项目中的东西,那么我不就要都在Asp中创建好几个依赖注入,有没有一种方法是可以让项目自己进行服务的注册

首先安装一下用到配置的包 
Install-Package Zack.Commons
​
安装好之后在每个项目里面创建一个实现了IModuleInitializer接口的类
​

ModelIntics.cs
namespace ClassLibrary1
{
    internal class ModelIntics : IModuleInitializer
    {
        public void Initialize(IServiceCollection services)
        {
            services.AddScoped<Class1>();
        }
    }
}
​
然后在Asp项目中就不需要去创建这个类 只需要在Program.cs中实现
var asms =ReflectionHelper.GetAllReferencedAssemblies();
builder.Services.RunModuleInitializers(asms);
其余的正常使用就可以了

1.5使用EFCore+依赖注入进行一个CURD的数据库操作

先看一下项目目录

image-20240210222921585

首先要先配置好EFCore数据库迁移所需要的东西

BookDbContext.cs
 public class BookDbContext:DbContext
    {
       public BookDbContext(DbContextOptions<BookDbContext> options) : base(options) { }   
      public DbSet<Book> Books { get; set; }       //这个Books就是数据库迁移出来的表名
    }

然后依赖注入一下:
builder.Services.AddDbContext<BookDbContext>(option =>
{
    option.UseSqlServer(builder.Configuration.GetConnectionString("BookConn"));
});

在那个app的json文件里面配置一下数据库的连接字符串
"ConnectionStrings": {
        "BookConn": "Server="";Database=Demo1;Trusted_Connection=True;MultipleActiveResultSets=true"
    },
    
然后直接 add 和 update 
	重点:出现那些什么找不到DbContext的目录删干净一点重新迁移一下就可以了
	
	
直接上接口功能:
 public class BooklController : ControllerBase
    {
        private readonly BookDbContext _db;
        public BooklController(BookDbContext db)
        {
            _db = db;
        }


        [HttpGet]
       public async Task<ActionResult<IEnumerable<Book>>> GetAll()
        {
          var list = _db.Books.ToList(); return Ok(list);
        }

        [HttpPost]
       public async Task<ActionResult<IEnumerable<Book>>> SeletTitle(string Title)
        {
            var selt = _db.Books.Where(x => x.Title == Title).ToList();
            return  Ok(selt);
        }

        [HttpDelete]
        public async Task<ActionResult<IEnumerable<Book>>> DeltOne(string Title)
        {
           var xp = _db.Books.FirstOrDefault(e => e.Title == Title);
            if (xp != null)
            {
                _db.Books.Remove(xp); // 将书籍添加到上下文的删除集合中
                await _db.SaveChangesAsync(); // 提交更改到数据库
                return Ok("删除成功");
            }
            else
            {
                return NotFound("没有找到要删除的书籍");
            }
        }
        
    总结:个人感觉相当于SqlSuger 有一点繁琐,但是还好把

缓存

image-20240204144507826

缓存概念

缓存的命中 、缓存的命中率、缓存的数据不一致

缓存数据的不一致:就可以看一下上面的图,一开始我数据库给出的结果是60 ,但是后面这个id为1给出的结果修改成了62,我们再去调用这个id=1的条件的时候,因为我们缓存中已经保存了结果是60最后出来的结果也是60,但实际数据应该是62 这就是缓存数据的不一致。

1.客户端缓存

首先来看一下cache-control这是一个响应报文头,服务器如果返回cache-control:max-age=60表示服务器只是浏览器端可以缓存这个响应60秒

我们使用的话其实比较简单,只要给需要进行缓存控制的控制器的操作方法添加

    
 [ResponseCache(Duration=20)] //其中Duration="" 这个是代表这个缓存能保存多少秒
 				//缓存20秒之后失效
        [HttpGet]
        public async Task<ActionResult<IEnumerable<YunXi>>> AAA()
        {
            return await db.Queryable<YunXi>().ToListAsync();
        }           
2.内存缓存

把缓存的数据放到应用程序的内存,内存缓存中保存的是一系列的建值,类型跟Dictionary一样

内存缓存的数据时保存在当前运行网站程序的内存中,他和进程是有关系的,不同网站的内存缓存时不会相互干扰,但是网站重启之后,数据就会清空

内存缓存的用法:
	builder.Services.AddMemoryCache(); //先注入一下
	
然后注入到控制器中:
	public class TestController : ControllerBase
    {     
        private readonly IMemoryCache _cache;
        public TestController(ISqlSugarClient db, IMemoryCache cache)
        {
            _cache = cache;
        }
注入完之后开始去写接口:
	  [HttpGet]
        public async Task<ActionResult<IEnumerable<yunxi>>> AAA()
        {
            var cacheKey = "GetAllYunxi";
            //这里首先是定义一个用于内存缓存的键
            if (_cache.TryGetValue(cacheKey, out List<yunxi> yunxiList))
            {
                return yunxiList;
                //然后使用方法TryGetValue 去获取内存缓存中的数据,拿到的话就直接返回缓存中的结果
            }
			
			//如果缓存不存在数据那就从数据库中查询数据
            yunxiList = await _db.Queryable<yunxi>().ToListAsync();

			//这个部分是设置了缓存的的绝对过期值:10分钟。
            var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromMinutes(10));
            //然后使用set方法将查询到的数据添加到内存缓存中
            _cache.Set(cacheKey, yunxiList, cacheEntryOptions);
			
            return yunxiList;
        }

中间件

中间件:MVC框架、响应缓存、身份验证、Swagger之类的都是内置中间件

三个概念:Map、Use、Run 其中:Map是用来定义一个管道可以处理那些请求,Ues和Run是用来定义管道

看一下Program.cs文件中的Use 那个就是直接引用的内置中间件,如果想要自己去自定义:定义一个中间件的类然后 定义好之后use一下就差不多了 to be continue..

Identity标识框架

Identity标识框架是用于角色的访问控制

Authentication:对访问者的用户身份验证 Authorization:对访问者的权限认证

cation是身份验证、zation是权限验证,在中间中添加的时候根据c>z来排序就不会混淆

框架的使用:
	IdentityUser<Tkey>、IdentityRole<Tkey> Tkey代表主键的类型
	但是一般来说,会编写一个类用于去继承IdentityUser和IdentityRole,这样自由度会更高一点
NuGet:
	Microsoft.AspNetCore.ldentity.EntityFrameworkCore
	
	
MyRole.cs
 public class MyRole:IdentityRole<long>{}
 
MyUser.cs
public class MyUser:IdentityUser<long>{}

MyDbContext.cs
   public class MyDbContext:IdentityDbContext<MyUser,MyRole,long>
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

        DbSet<MyRole> Role { get; set; }
		//这两个DbSet可以写也可以不写 就是一个自定义表名的东西而已
        DbSet<MyUser> User { get; set; } //
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ApplyConfigurationsFromAssembly(this.GetType().Assembly);
        }
        
//配置好上面的东西之后进行一下依赖注入


//全是配置的东西,配置好之后直接进行数据库迁移的行了
builder.Services.AddDataProtection();
builder.Services.AddIdentityCore<MyUser>(option =>
{
    option.Password.RequiredLength = 6;
    option.Password.RequireDigit = false;
    option.Password.RequireNonAlphanumeric = false;
    option.Password.RequireUppercase = false;
    option.Password.RequireLowercase = false;
    option.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
    option.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider;
});
IdentityBuilder idBulider = new IdentityBuilder(typeof(MyUser), typeof(MyRole), builder.Services);
idBulider.AddEntityFrameworkStores<MyDbContext>().AddEntityFrameworkStores<MyDbContext>().
    AddDefaultTokenProviders().AddUserManager<UserManager<MyUser>>().AddRoleManager<RoleManager<MyRole>>();


来个登录注册的例子

  public class DemoController : ControllerBase
    {
        private readonly UserManager<MyUser> _userManager;
        private readonly RoleManager<MyRole> _roleManager;

        public DemoController(ILogger<DemoController> logger, UserManager<MyUser> 				userManager, RoleManager<MyRole> roleManager)
        {
      
            _userManager = userManager;
            _roleManager = roleManager;
        }

 public record LoginRequest(string UserName, string Password);
 //首先创建一个我们用来实现登录注册的类
        [HttpPost]
        public async Task<IActionResult> Login(LoginRequest loginRequest)
        {
            string userName = loginRequest.UserName;
            string password = loginRequest.Password; 
            
            //首先通过Find这个方法去查询一下数据库里面是否有我们输入的用户名信息 其实很简单的看看就理解了
            var user = await _userManager.FindByNameAsync(userName);
            if (user == null)
            {
                return NotFound($"用户名{userName}不存在!");
            }
            var islocked = await _userManager.IsLockedOutAsync(user);
            if (islocked)
            {
                return BadRequest("用户已锁定!");
            }
            var success = await _userManager.CheckPasswordAsync(user, password);
            if (success)
            {
                return Ok();
            }
            else
            {
                var r = await _userManager.AccessFailedAsync(user);
                if (!r.Succeeded)
                {
                    return BadRequest("访问失败信息写入错误!");
                }
                else
                {
                    return BadRequest("失败!");
                }
            }
        }

JWT(Json Web Token)

JWT把登录信息(身份令牌)保存在客户端

Asp.net.Core中使用封装的JWT,Net封装了对于JWT的操作,让程序中使用JWT进行鉴权和授权更简单

安装Microsoft.AspNetCore.Authentication.JwtBearer

第一步:首先配置JWT节点,直接在appsettings.json中配置一个JWT节点,并在节点下创建SigningKey、ExpireSeconds两个配置项,分别代表JWT的密钥和过期时间(单位为秒)。

我们再创建一个对应JWT节点的配置类JWTOptions,类中包含SigningKey、ExpireSeconds这两个属性。

 "JWT": {
        "SigningKey": "fasdfad&9045dafz222#fadpio@0232",
        "ExpireSeconds": "86400"
    },
   
   public class JWTOptions
    {
        public string SigningKey { get; set; }
        public int ExpireSeconds { get; set; }
    }

第二步:编写代码对JWT进行配置,把代码内容添加到Program.csbuilder.Build之前

builder.Services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(x =>
{
    var jwtOpt = builder.Configuration.GetSection("JWT").Get<JWTOptions>();
    byte[] keyBytes = Encoding.UTF8.GetBytes(jwtOpt.SigningKey);
    var secKey = new SymmetricSecurityKey(keyBytes);
    x.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = false,
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = secKey
    };
});

第三步,在Program.cs的app.UseAuthorization之前添加app.UseAuthentication

第四步添加登录并且创建JWT的操作方法Login

[Route("api/[controller]/[action]")]
    [ApiController]
    public class ValuesController : ControllerBase
    {
        private readonly IOptionsSnapshot<JWTOptions> jwtOptions;

        public ValuesController(IOptionsSnapshot<JWTOptions> jwtOptions)
        {
            this.jwtOptions = jwtOptions;
        }

        [HttpPost]
        public async Task<ActionResult<string>> login(string  userName, string password)
        {
            if(userName == "Ktton" && password == "123456")
            {
                List<Claim>claims = new List<Claim>();
                claims.Add(new Claim(ClaimTypes.NameIdentifier,"1"));
                claims.Add(new Claim(ClaimTypes.Name, userName));

                string key = jwtOptions.Value.SigningKey; //这里读取的key是我们自定配置所定义的

                DateTime expire =DateTime.Now.AddSeconds(jwtOptions.Value.ExpireSeconds);
                //这个时间也是我们自己定义的那个时间戳
                
                byte[] secBytes = Encoding.UTF8.GetBytes(key);
                var secKey = new SymmetricSecurityKey(secBytes);
                var credentials = new SigningCredentials(
                secKey, SecurityAlgorithms.HmacSha256Signature);
                
                var tokenDescriptor = new JwtSecurityToken(
                claims: claims, expires: expire, signingCredentials: credentials);
                
                string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);
                return Ok(jwt);
            }
            else
            {
                return BadRequest();
            }
        }
        }

第五步:在登录才能访问的控制器类或者Action方法上添加Authorize

这样子去写的话,就是必须要进行Token验证之后才能去访问到这个Test的接口,不然是直接报401的

至于访问,我们添加了一个Authorize值,这个值就是用来存放JWT给出的key值的

[Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class DemoTokenController1 : ControllerBase
    {
        [HttpGet]
        public string Test()
        {
            return "oK";
        }
    }

上面这种情况是需要使用到第三方软件去进行验证的,会比较麻烦,所以下面会讲到一个

Asp.Net Core鉴权授权:在Swaggerr中的Token验证

Swagger中默认没有提供设置自定义HTTP请求报文头的方式,因此对于需要传递Authorization报文头的接口,调试起来很麻烦。我们可以通过对OpenAPI进行配置,从而让Swagger中可以发送Authorization报文头。

我们直接对Program.cs中的builder.Services.AddSwaggerGen();方法进行修改


builder.Services.AddSwaggerGen(c =>
{
    var scheme = new OpenApiSecurityScheme()
    {
        Description = "Authorization header.\r\nExample:‘Bearer 12345abcdef’",Reference = new OpenApiReference
{
    Type = ReferenceType.SecurityScheme,
    Id = "Authorization"
},
    Scheme = "oauth2",
    Name = "Authorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey,
};
    c.AddSecurityDefinition("Authorization", scheme);
    var requirement = new OpenApiSecurityRequirement();
    requirement[scheme] = new List<string>();
    c.AddSecurityRequirement(requirement);
});
然后在Swagger中出现一个Authorize的按钮 然后把登录的Token根据说明写进去之后就可以了
  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值