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的数据库操作
先看一下项目目录
首先要先配置好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 有一点繁琐,但是还好把
缓存
缓存概念
缓存的命中 、缓存的命中率、缓存的数据不一致
缓存数据的不一致:就可以看一下上面的图,一开始我数据库给出的结果是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.cs
的builder.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根据说明写进去之后就可以了