ASP.NET Core学习之路03

本文章是我听B站杨中科的所做的笔记

杨中科B站视频链接:.NET 6教程,.Net Core 2022视频教程,杨中科主讲_哔哩哔哩_bilibili

Identity框架入门

Authentication与Authorization

1、Authentication对访问者的用户身份进行验证,“用户是否登录成功”

2、Authorization验证访问者的用户身份是否有对资源访问的访问权限,“用户是否有权限访问这个地址”。

标识(Identity)框架

1、标识(Identity)框架:采用基于角色的访问控制(Role-Based Access Control,简称RBAC)策略,内置了对用户、角色等表的管理以及相关的接口,支持外部登录、2FA等

2、标识框架使用EF Core对数据库进行操作,因此标识框架支持几乎所有的数据库

Identity框架的使用

1、IdentityUser<TKey>、IdentityRole<TKey>,TKey代表主键的类型。我们一般编写继承自IdentityUser<TKey>、IdentityRole<TKey>等的自定义类,可以增加自定义属性

2、NuGet安装:Microsoft.AspNetCore.Identity.EntityFrameworkCore。

3、创建继承自IdentityDbContext的类

4、可以通过IdDbContext类来操作数据库,不过框架中提供了RoleManager、UserManager等类来简化数据库的操作

5、部分方法的返回值为Task<IdentityResult>类型,查看、讲解IdentityResult类型定义

6、向依赖注入容器中注册标识框架相关的服务

IServiceCollection services = builder.Services;
services.AddDbContext<IdDbContext>(opt => {
    string connStr = builder.Configuration.GetConnectionString("Default");
    opt.UseSqlServer(connStr);});
services.AddDataProtection();
services.AddIdentityCore<User>(options =>{    //注意不是AddIdentity
        options.Password.RequireDigit = false;
        options.Password.RequireLowercase = false;
        options.Password.RequireNonAlphanumeric = false;
        options.Password.RequireUppercase = false;
        options.Password.RequiredLength = 6;
        options.Tokens.PasswordResetTokenProvider = TokenOptions.DefaultEmailProvider;
        options.Tokens.EmailConfirmationTokenProvider = TokenOptions.DefaultEmailProvider; });
var idBuilder = new IdentityBuilder(typeof(User), typeof(Role), services);
idBuilder.AddEntityFrameworkStores<IdDbContext>()
    .AddDefaultTokenProviders().AddRoleManager<RoleManager<Role>>()
    .AddUserManager<UserManager<User>>();

7、执行Add-Migration、Update-Database等命令执行EF Core的数据库迁移

8、通过RoleManager、UserManager等来进行数据操作。比如创建角色、创建用户

bool roleExists = await roleManager.RoleExistsAsync("admin");
if (!roleExists)
{
    Role role = new Role { Name="Admin"};
    var r = await roleManager.CreateAsync(role);
    if (!r.Succeeded) return BadRequest(r.Errors);
}
User user = await this.userManager.FindByNameAsync("yzk");
if (user == null)
{
    user=new User{UserName="yzk",Email="yangzhongke8@gmail.com",EmailConfirmed=true};
    var r = await userManager.CreateAsync(user, "123456");
    if (!r.Succeeded) return BadRequest(r.Errors);
    r = await userManager.AddToRoleAsync(user, "admin");
}

9、检查登录用户信息

string userName = req.UserName;
string password = req.Password;
var user = await userManager.FindByNameAsync(userName);
if (user == null)
    return NotFound($"用户名不存在{userName}");
if (await userManager.IsLockedOutAsync(user))
return BadRequest("LockedOut");
var success = await userManager.CheckPasswordAsync(user, password);
if (success) {
   await userManager.ResetAccessFailedCountAsync(user);
 return  Ok("Success");
}
else await userManager.AccessFailedAsync(user);

Identity框架实现密码的重置

重置密码流程

1、生成重置的Token

2、Token发给客户(邮件、短信),形式:链接、验证码等

3、根据Token完成密码的重置

发送重置密码的请求

var user = await userManager.FindByEmailAsync(email);
string token = await userManager.GeneratePasswordResetTokenAsync(user);
logger.LogInformation($"向邮箱{user.Email}发送Token={token}");

完成重置密码

await userManager.ResetPasswordAsync(user, token, password);

代替Session的JWT是什么

Session的缺点

1、对于分布式集群环境,Session数据保存在服务器内存中就不合适了,应该放到一个中心状态服务器上。ASP.NET Core支持Session采用Redis、Memcacheed.

2、中心状态服务器有性能问题

JWT(Json Web Token)

1、JWT把登录信息(也称作令牌)保存在客户端

2、为了防止客户端的数据造假,保存在客户端的令牌经过了签名处理,而签名的密钥只有服务器端才知道,每次服务器端接收到客户端提交过来的令牌的时候都要检查一下签名。

3、基于JWT如何实现“登录”

JWT的基本使用

生成JWT令牌

1、NuGet:System.IdentityModel.Token.Jwt

2、var claims = new List<Claim>();

claims.Add(new Claim(ClaimTypes.NameIdentifier, "6"));
claims.Add(new Claim(ClaimTypes.Name, "yzk"));
claims.Add(new Claim(ClaimTypes.Role, "User"));
claims.Add(new Claim(ClaimTypes.Role, "Admin"));
claims.Add(new Claim("PassPort", "E90000082"));
string key = "fasdfad&9045dafz222#fadpio@0232";
DateTime expires = DateTime.Now.AddDays(1);
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: expires, signingCredentials: credentials);
string jwt = new JwtSecurityTokenHandler().WriteToken(tokenDescriptor);

解码JWT令牌

string[] segments = jwt.Split('.');
string head = JwtDecode(segments[0]);
string payload = JwtDecode(segments[1]);
Console.WriteLine("---head---");
Console.WriteLine(head);
Console.WriteLine("---payload---");
Console.WriteLine(payload);
string JwtDecode(string s)
{
    s = s.Replace('-', '+').Replace('_', '/');
    switch (s.Length % 4)
    {
        case 2:
            s += "==";
            break;
        case 3:
            s += "=";
            break;
    }
    var bytes = Convert.FromBase64String(s);
    return Encoding.UTF8.GetString(bytes);
}

结论:负载中的内容是明文形式保存的;不要把不能被客户端知道的信息放到JWT中;

用JwtSecurityTokenHandler对JWT解码

string secKey = "fasdfad&9045dafz222#fadpio@0232";
JwtSecurityTokenHandler tokenHandler = new();
TokenValidationParameters valParam = new ();
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secKey));
valParam.IssuerSigningKey = securityKey;
valParam.ValidateIssuer = false;
valParam.ValidateAudience = false;
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwt, 
            valParam,out SecurityToken secToken);
 foreach (var claim in claimsPrincipal.Claims)
 {
     Console.WriteLine($"{claim.Type}={claim.Value}");
 }

随便一个密钥来生成一个用户Id等经过篡改后的JWT令牌,然后分别用上一节和这一节的代码尝试解码

ASP.NET Core对于JWT的封装

步骤

1、配置JWT节点,节点下创建SigningKey、ExpireSeconds两个配置,分别代表JWT的密钥和过期时间(单位:秒)。再创建配置类JWTOptions。包括SigningKey、ExpireSeconds两个属性

2、NuGet:Microsoft.AspNetCore.Authentication.JwtBearer

3、对JWT进行配置

services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
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()
    {
        ValidateIssuer=false, ValidateAudience=false, ValidateLifetime=true, 
        ValidateIssuerSigningKey=true, IssuerSigningKey=secKey
    };
 });

4、Program.cs的app.UseAuthorization()这行代码之前加app.UseAuthentication()

5、Controller类中进行登录:

var user = await userManager.FindByNameAsync(userName);
var success = await userManager.CheckPasswordAsync(user, password);
if (!success)
    return BadRequest("Failed");
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
var roles = await userManager.GetRolesAsync(user);
foreach (string role in roles)
{
    claims.Add(new Claim(ClaimTypes.Role, role));
}
string jwtToken = BuildToken(claims, jwtOptions.Value);

6、在需要登录才能访问的控制器类或者Action方法上添加[Authorize]

public IActionResult Hello()
{
    string id = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;
    string userName = this.User.FindFirst(ClaimTypes.NameIdentifier)!.Value;
    IEnumerable<Claim> roleClaims = this.User.FindAll(ClaimTypes.Role);
    string roleNames = string.Join(',', roleClaims.Select(c => c.Value));
    return Ok($"id={id},userName={userName},roleNames ={roleNames}");
}

7、测试登录和访问。用PostMan自定义报文头:

Authorization的值为”Bearer JWTToken“;Authorization的值中的”Bearer“和JWT令牌之间一定要通过空格分隔。前后不能多出来额外的空格、换行等。

8、JWTToken在Web、APP、小程序等中保存到哪里?

[Authorize]的注意事项

1、ASP.NET Core中身份验证和授权验证的功能由Authentication、Authorization中间件提供:app.UseAuthtication()、app.UseAuthorization()

2、控制器类上标注[Authorize],则所有操作方法都会被进行身份验证和授权验证;对于标注了[Authorize]的控制器中,如果其中某个操作方法不想被验证,可以在操作方法上添加[AllowAnonymous]。如果没有在控制器类上标注[Authorize],那么这个控制器中的所有操作方法都允许被自由地访问;对于没有标注[Authrize]的控制器中,如果其中某个操作方法需要被验证,我们也可以在操作方法上添加[Authorize].

3、ASP.NET Core会按照HTTP协议的规范,从Authorization取出来令牌,并且进行验证、解析,然后把解析结果填充到User属性中,这一切都是ASP.NET Core完成的,不需要开发人员自己编写代码。但是一旦出现401,没有详细的报错信息,很难排查,这是初学者遇到的难题

让Swagger中调试带JWT的请求

对OpenAPI进行配置

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);
});

解决JWT无法提前撤回的难题

JWT的缺点

1、到期前,令牌无法被提前撤回。什么情况下需要撤回?用户被删除了、禁用了;令牌被盗用了;单设备登录。

2、需要JWT撤回的场景用传统Session更合适

3、如果需要在JWT中实现,思路:用Redis保存状态,或者用refresh_token+access_token机制等等

我的思路

在用户表中添加一个整数类型的列JWTVersion,代表最后一次发放出去的令牌的版本号;每次登录、发放令牌的时候,都让JWTVersion的值自增,同时讲JWTVersion的值也放到JWT令牌的负载中;当执行禁用用户、撤回用户的令牌等操作的时候,把这个用户对应的JWTVersion的值自增;当服务器收到客户端提交的JWT令牌后,先把JWT令牌中的JWTVersion值和数据库中JWTVersion的值做一下比较,如果JWT令牌中JWTVersion的值小于数据库中JWTVersion的值,就说明这个JWT令牌过期了

实现

1、为用户实体User类增加一个long类型的属性JWTVersion

2、修改登录并发放令牌的代码,把用户的JWTVersion属性的自增,并且把JWTVersion的值写入到JWT令牌中

3、编写一个操作筛选器,统一实现对所有的控制器的操作方法中JWT令牌的检查操作。把JWTValidationFilter注册到Program.cs中MVC的全局筛选器

优化

每一次客户端和Controller的交互的时候,检查JWTVersion的筛选器都要查询数据库,性能太低,可以用缓存进行优化

ASP.NET Core托管服务的基本使用

托管服务简介

1、场景,代码运行在后台。比如服务器启动的时候在后台预先加载数据到缓存,每天凌晨3点把数据导出到备份数据库,每隔5秒钟在两张表之间同步一次数据。

2、托管服务实现IHostedService接口,一般编写从BackgroundService继承的类 测试:延迟若干秒再度取文件,在延迟,在输出

3、services.AddHostedService<DemoBgService>();

托管服务的异常问题

1、从,NET 6开始,当托管服务中发生未处理异常的时候,程序就会自动停止并退出。可以把HostOptions.BackgroundServiceExceptionBehavior设置为Ignore,程序会忽略异常,而不是停止程序。不过推荐采用默认的设置,因为”异常应该被妥善的处理,而不是被忽略“

2、要在ExecuteAsync方法中把代码用try.....catch包裹起来,当发生异常的时候,记录日志中或发警报等

托管服务中使用DI

1、托管服务是以单例的生命周期注册到依赖注入容器中的,因此不能注入生命周期为范围或者瞬态的服务,比如注入EF Core的上下文的话,程序就会抛出异常

2、可以通过构造方法注入一个IServiceScopeFactory服务,它可以用来创建一个IServiceScope对象,这样我们就可以通过IServiceScope来创建短生命周期的服务了。记得在Dispose中释放IServiceScope

托管服务案例:数据得定时导出

托管服务简介

1、常驻后台得托管服务并不需要特殊的技术,我们只要让ExecuteAsync中的代码一直执行不结束就行了。

2、实现的功能就是每隔五秒钟对数据库中的数据做一些汇总,然后把汇总结果写入一个文本文件

var items = ctx.Users.GroupBy(u => u.CreationTime.Date)
                .Select(e => new { Date = e.Key, Count = e.Count() });

.NET Core内置数据校验的不足

内置数据校验机制

1、.NET Core中内置了对数据校验的支持,在System.ComponentModel.DataAnnotations这个命名空间下,比如[Required]、[EmailAddress]、[RegularExpression]。

2、演示其在ASP.NET Core中请求中过的使用

3、内置的校验机制的问题:校验规则都是和模型类耦合在一起,违反“单一职责原则”;很多常用的校验都是需要编写自定义校验规则,而且写起来麻烦

FluentValidation的基本使用

FluentValidation

1、FluentValidation:用类似于EF Core中Fluent API的方式进行校验规则的配置,也就是我们可以把模型类的校验放到单独的校验类中

2、FluentValidation在ASP.NET Core项目中的用法

1)NuGet:FluentValidation.AspNetCore

2)

builder.Services.AddFluentValidation(fv => {
    Assembly assembly = Assembly.GetExecutingAssembly();
    fv.RegisterValidatorsFromAssembly(assembly);// RegisterValidatorsFromAssemblies
    });

3)编写模型类Login2Request

public record Login2Request(string Email, string Password, string Password2);

4)编写继承自AbstractValidator的数据校验类

public class Login2RequestValidator: AbstractValidator<Login2Request>
{
    public Login2RequestValidator()
    {
        RuleFor(x=>x.Email).NotNull().EmailAddress()
            .Must(v=>v.EndsWith("@qq.com")||v.EndsWith("@163.com"))
            .WithMessage("只支持QQ和163邮箱");
        RuleFor(x => x.Password).NotNull().Length(3, 10)
            .WithMessage("密码长度必须介于3到10之间")
            .Equal(x => x.Password2).WithMessage("两次密码必须一致");
    }
 }

5)用Login2Request做Action方法的参数

FluentValidation中注入服务

FluentValidation+DI

1、可以通过构造方法来向数据校验类中注入服务

2、

RuleFor(x => x.UserName).NotNull()
    .Must(name=>dbCtx.Users.Any(u=>u.UserName== name))
    .WithMessage(c => $"用户名{c.UserName}不存在");
//或
RuleFor(x => x.UserName).NotNull()
    .MustAsync((name,_) => dbCtx.Users.AnyAsync(u => u.UserName == name))
    .WithMessage(c => $"用户名{c.UserName}不存在");

什么是WebSocket、SignaIR

服务器向客户端发送数据

1、需求:Web聊天;站内通知

2、传统HTTP:只能客户端主动发送请求

3、传统方案:长轮询(Long Polling) 缺点是?

WebSocket

1、WebSocket基于TCP协议,支持二进制通信,双工通信

2、性能和并发能力更强

3、WebSocket独立于HTTP协议,不过我们一般仍然把WebSocket服务器端部署到Web服务器上,因为可以借助HTTP协议完成初始的握手(可选),并且共享HTTP服务器的端口(主要)

SignalR

1、ASP.NET Core SignaIR(一下简称SignaIR),是.NET Core平台下对WebSocket的封装

2、Hub(集线器),数据交换中心

SignaIR基本使用

基本SignaIR项目

需要分别编写服务器端Hub和前端代码

1、创建Web API项目,创建一个继承自Hub类

public class ChatRoomHub:Hub
{
    public Task SendPublicMessage(string message)
    {
        string connId = this.Context.ConnectionId;
        string msg = $"{connId} {DateTime.Now}:{message}";
        return Clients.All.SendAsync("ReceivePublicMessage", msg);
    }
}

2、

builder.Services.AddSignalR()
app.MapControllers()调app.MapHub<ChatRoomHub>(“/Hubs/ChatRoomHub”)。默认还要启用CORS。
builder.Services.AddSignalR();
string[] urls = new[] { "http://localhost:3000" };
builder.Services.AddCors(options =>
    options.AddDefaultPolicy(builder => builder.WithOrigins(urls)
        .AllowAnyMethod().AllowAnyHeader().AllowCredentials())
);
var app = builder.Build();
//这里省略其他UseXXX代码
app.UseCors();
 app.UseHttpsRedirection();
 app.UseAuthorization();
 app.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
 app.MapControllers();

3、编写前端项目

npm install @microsoft/signalr

<template>
    <input type="text"  v-model="state.userMessage" v-on:keypress="txtMsgOnkeypress"/>
    <div><ul>
        <li v-for="(msg,index) in state.messages" :key="index">{{msg}}</li>
    </ul></div>
</template>
<script>
    import { reactive, onMounted } from 'vue';
    import * as signalR from '@microsoft/signalr';
    let connection;
    export default {name: 'Login',
        setup() { const state = reactive({ userMessage: "", messages: [] });
            const txtMsgOnkeypress = async function (e) {
                if (e.keyCode != 13) return;
                await connection.invoke("SendPublicMessage", state.userMessage);   state.userMessage = "";  };
            onMounted(async function () {
                connection = new signalR.HubConnectionBuilder()
                    .withUrl('https://localhost:7112/Hubs/ChatRoomHub')
                    .withAutomaticReconnect().build();
                await connection.start();
                connection.on('ReceivePublicMessage', msg => {
                    state.messages.push(msg);
                });
            });
            return { state, txtMsgOnkeypress };
        },
    }
</script>

SignaIR的协议协商

协议协商

1、SignalR支持多种服务器推送方式,WebSocket、Server-Sent Events、长轮询(LongPolling)。默认按顺序尝试

2、F12查看协商过程 看negotiate的请求跟服务器端讨论是使用哪种推送方式

3、WebSocket和HTTP是不同的协议,为什么能用同一个端口

4、在【开发人员工具】的【网络】页签中看WebSocket通信过程

协议协商的问题

1、集群中协议协商的问题:“协商”请求被服务器A处理,而接下来的WebSocket请求却被服务器B处理

2、解决方法:粘性会话和禁用协商

3、“粘性会话”(Sticky Session):把来自同一个客户端的请求都转发给同一台服务器上。缺点:因为共享公网IP等造成请求无法被平均的分配到服务器集群;扩容的自适应性不强

4、“禁用协商”:直接向服务器发出WebSocket请求。WebSocket连接一旦建立之后,在客户端和服务器直接就建立了持续的网络连接通道,在这个WebSocket连接中的后续往返WebSocket通信都是由同一台服务器来处理。缺点:无法降级到“服务发送事件”或“长轮询”,不过不是大问题

禁用协议协商的方式

const options = { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets  };
connection = new signalR.HubConnectionBuilder()
    .withUrl('https://localhost:7047/Hubs/ChatRoomHub', options)
    .withAutomaticReconnect().build();

SignalR的分布式部署

SignalR的分布式问题

1、四个客户端被连接到不同的两个服务器上,由于是不同的两台服务器所以通信服务端不了

2、解决方案:所有服务器连接到同一个信息中间件。

3、官方方案:Redis Backplane

1)NuGet:Microsoft.AspNetCore.SignalR.StackExchangeRedis

2)

builder.Services.AddSignalR().AddStackExchangeRedis("127.0.0.1", options => {
        options.Configuration.ChannelPrefix = "Test1_";
});

SignalR身份认证

身份认证

1、目前SignalR问题:谁都能连。讲JWT方案

2、配置SigningKey、ExpireSeconds。创建配置类JWTOptions

3、NuGet:Microsoft.AspNetCore.Authentication.JwtBearer

var services = builder.Services;
services.Configure<JWTOptions>(builder.Configuration.GetSection("JWT"));
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() {ValidateIssuer = false,
        ValidateAudience = false, ValidateLifetime = true,
        ValidateIssuerSigningKey = true, ssuerSigningKey = secKey};
    x.Events = new JwtBearerEvents
    {
        OnMessageReceived = context =>
        {
            var accessToken = context.Request.Query["access_token"];
            var path = context.HttpContext.Request.Path;
            if (!string.IsNullOrEmpty(accessToken) &&
                (path.StartsWithSegments("/Hubs/ChatRoomHub"))) context.Token = accessToken;
           return Task.CompletedTask;
        }
    };
});

5、app.UseAuthorization()这行代码之前添加app.UseAuthentication()

6、在控制类Test1Controller中增加登录并且创建JWT令牌的操作方法Login。返回JWT

7、在需要登录才能访问的集线器类上或者方法上添加[Authorize]。也支持角色等设置,可以设置Hub或者方法上

8、

<script>
const startConn = async function () {
    const transport = signalR.HttpTransportType.WebSockets;
    const options = { skipNegotiation: true, transport: transport };
    options.accessTokenFactory = () => state.accessToken;
    connection = new signalR.HubConnectionBuilder()
        .withUrl('https://localhost:7047/Hubs/ChatRoomHub', options)
        .withAutomaticReconnect().build();
    await connection.start();
    connection.on('ReceivePublicMessage', msg => {
        state.messages.push(msg);
    });
    alert("登陆成功可以聊天了");
 };
 </script>

SignalR向部分客户端发信息

筛选客户端

1、客户都拿筛选的3个参数:ConnectionId、组和用户Id(它对应ClaimTypes.NameIdntifier的Claim)

2、Hub的Groups属性为IGroupManager属性,可以对组成员进行管理。查看类型的成员

3、Hub的Clients属性为IHubCallerClients类型,可以对连接到当前集线器的客户端进行筛选

4、IClientProcy类型。无法知道具体有哪些客户端调用SendAsync()方法向筛选的客户端发送信息 5、实现聊天室私聊

实现

public async Task<string> SendPrivateMessage(string destUserName,string message)
{
    User? destUser = Users.FindByName(destUserName);
    string destUserId = destUser.Id.ToString();
    string srcUserName = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value;
    string time = DateTime.Now.ToShortTimeString();
    await this.Clients.User(destUserId).SendAsync("ReceivePrivateMessage",
        srcUserName, time, message);
    return "ok";
}
<script>
const ret = await connection.invoke("SendPrivateMessage", destUserName, msg);
​
connection.on('ReceivePrivateMessage', (srcUser,time,msg) => {
    state.messages.push(srcUser+"在"+time+"发来私信:"+msg);
});
</script>

SignalR案例:导入英汉词典

需求

1、英汉词典ECDICT中导入单词到数据库

2、T_WordItems:Id(主键)、Word(单词)、Phonetic(音标)、Definition(英文解释),Translation(中文翻译)

实现

public class ImportDictHub:Hub
{
    private readonly ImportExecutor executor;
    public ImportDictHub(ImportExecutor executor)
    {
        this.executor = executor;
    }
    public Task Import()
    {
        _=executor.ExecuteAsync(this.Context.ConnectionId);
        return Task.CompletedTask;
    }
 }

1、ImportExecutor中注入IHubContext<ImportDictHub>等服务

2、暂时用字符串Split解析CSV,或者用更专业的库

3、用SqlBulkCopy进行分批快速导入:

using SqlBulkCopy bulkCopy = new SqlBulkCopy(conn);
bulkCopy.DestinationTableName = "T_WordItems";
bulkCopy.ColumnMappings.Add("Word", "Word");
...
DataTable dataTable = new DataTable();
dataTable.Columns.Add("Word");
...
var dataRow = dataTable.NewRow();
dataRow["Word"] = word;
dataTable.Rows.Add(dataRow);
...
await bulkCopy.WriteToServerAsync(dataTable);

4、前端:<progress :value=“20" :max=“100"></progress>

ASP.NET Core程序的发布

网站发布

1、不能直接把bin/Debug部署到生产环境的服务器上,性能低。应该创建网站的发布版,用【发布】功能

2、两种部署模式:“框架依赖”和“独立”,两者的优缺点

3、独立模式为什么要选目标操作系统和CPU类型。关于龙芯

网站的运行

1、在Windows(SandBox)和Linux(VMWare Player)里分别运行网站

2、如何在生产服务器中部署.NET Core网站。尽管Kestrel已经强大到足以作为一个独立的Web服务器被使用了,但是一般仍然不会让Kestrel直接面对终端用户的请求。配置域名证书、记录请求日志、Url重写等由反向代理服务器负责

3、多种部署模式:K8S+容器(推荐、难度高);Linux+Nginx;云平台;Windows+IIS

网站安全提醒

1、启用HTTPS

2、如果如果运维人员需要通过远程桌面或者SSH连接到服务器,那么一定要在服务器的防火墙上设置只允许运维人员的IP段访问相关端口

3、严格区分开发环境和生产环境

4、不要相信客户端请求

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ASP.NETASP.NET Core是两个不同的Web应用程序框架。ASP.NET是Microsoft开发的一种Web应用程序框架,而ASP.NET CoreASP.NET的下一代版本。 ASP.NET是基于.NET Framework的,而ASP.NET Core是跨平台的,可以在Windows、Linux和macOS上运行。ASP.NET Core还具有更快的性能、更好的可扩展性和更好的安全性。 ASP.NET Core还提供了一种新的开发模型,即基于中间件的管道模型,这使得开发人员可以更轻松地构建和配置Web应用程序。此外,ASP.NET Core还提供了一种新的依赖注入系统,使得开发人员可以更轻松地管理应用程序中的依赖关系。 总之,ASP.NETASP.NET Core都是用于构建Web应用程序的框架,但它们之间存在一些重要的区别,包括支持的平台、性能、可扩展性和开发模型等方面。 ### 回答2: ASP.NETASP.NET Core都是Microsoft公司开发的Web应用程序框架,两者之间有很多不同之处。这篇文章将讨论它们之间的这些不同点。 1. 跨平台支持: ASP.NET是运行在Windows操作系统上的Web应用程序框架,而ASP.NET Core则是跨平台的。因此,在MacOS和Linux等其他操作系统上也可以使用ASP.NET Core。 2. 依赖的第三方库: ASP.NET依赖于大量的第三方库和框架,这些库可以添加到项目中以增强其功能。但是ASP.NET Core开发人员更多的将自己的应用程序依赖配置在库中,例如,.NET中的NuGet包。 3. 性能: 相比ASP.NETASP.NET Core更快,更高效。其中一个原因是,ASP.NET Core不需要与IIS(Internet Information Services)进行交互,这意味着更少的资源被分配, 4. 打包: ASP.NETASP.NET Core都可以使用NuGet包管理器来进行打包,但是ASP.NET Core可以将其应用程序打包为单个可执行文件,这使得开发和部署更加容易。 5. 依赖的编程语言: ASP.NET Core只能使用C#和F#等可将代码编译为.NET Core的语言,而ASP.NET则可以使用任何可编译为.NET框架的语言,包括C#,VB.NET和C++。 6. JWT的授权: 在ASP.NET Core中,JSON Web Token(JWT)是第一类公民,而在ASP.NET中,它只能使用第三方库进行实现。 7. MVC: 在ASP.NET Core中,MVC(Model-View-Controller)是默认的Web应用程序架构,但是在ASP.NET中,MVC需要安装一个独立的模板。 8. 版本: ASP.NET Core是最新的Web应用程序框架,而ASP.NET是较旧的。因此,ASP.NET Core提供了更多的功能和性能,而ASP.NET则使用固定的框架版本。 总之,虽然两者都是Microsoft公司开发的Web应用程序框架,但是它们之间还是有很多不同之处。因此,选择使用哪个框架取决于项目的要求,例如,是否需要跨平台支持和性能等。 ### 回答3: ASP.NET是一种Web应用程序框架,由Microsoft公司推出,它是Microsoft .NET运行时环境的一部分。ASP.NET提供了丰富的开发工具和框架,包括Web Forms、MVC、Web API等。它通常与IIS(Internet Information Services)一起使用,作为Web服务器上的应用程序。 ASP.NET Core是一个开源的、跨平台的Web应用程序框架,也是由Microsoft公司推出。它是Architecture Unified(一体化架构)领域的一项重要创新。ASP.NET Core.NET平台上的一个新的、轻量级Web框架,可以跨平台运行在Windows、macOS和Linux等操作系统上。它同时支持Web Forms、MVC和Web API等多种编程模型,具有高度灵活性和可扩展性。 下面我们来详细看一下ASP.NETASP.NET Core的区别: 1.跨平台性:ASP.NET只能运行在Windows环境下,而ASP.NET Core可以运行在Windows、Linux和macOS等操作系统上。 2.开源性:ASP.NET是Microsoft公司的闭源产品,而ASP.NET Core是一个开源的多平台Web框架,所有代码都进行了公开。 3.轻量级:ASP.NET Core是一个轻量级的框架,文件大小比ASP.NET小很多,启动速度也更快。而ASP.NET则是重量级的框架,需要较高的硬件配置和更长的启动时间。 4.性能:ASP.NET Core的性能比ASP.NET更好,这是因为它是一个基于模块化设计的框架。模块化设计使得ASP.NET Core可以更容易地进行优化和扩展,而且运行时内存的消耗也更小。 5.配置简单:ASP.NET Core的配置更加简单,可以使用依赖注入模式来配置应用程序。而ASP.NET则需要在Web.config中进行大量的配置。 6.兼容性:ASP.NET Core不支持Web Forms的开发模式,而ASP.NET支持Web Forms、MVC和Web API等多种开发模式。 综上所述,ASP.NETASP.NET Core的最大区别在于跨平台性、开源性、轻量级、性能和配置的简单等方面。ASP.NET Core是一个新的、基于模块化设计的Web框架,具有更高的性能、更好的跨平台性和更简单的配置,未来将会成为ASP.NET的主要发展方向。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值