ASP.NET Core如何不重启获取更改后的配置_windowsliusheng的专栏-CSDN博客

mikel阅读(559)

来源: ASP.NET Core如何不重启获取更改后的配置_windowsliusheng的专栏-CSDN博客

1.appsettings.json配置文件中配置数据

{
“UserName”: “Admin”
}
2.Startup文件Configure方法中添加以下配置(热更新主要使用的ChangeToken.OnChange​方法通知设置的回调方法来达到及时更新)

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

{

IConfigurationRoot configurationRoot;

if (env.IsDevelopment())

{

app.UseDeveloperExceptionPage();

configurationRoot = new ConfigurationBuilder().AddJsonFile(“appsettings.Development.json”, true, true).Build();

}

else

{

configurationRoot = new ConfigurationBuilder().AddJsonFile(“appsettings.json”, true, true).Build();

}

//string environment = Environment.GetEnvironmentVariable(“ASPNETCORE_ENVIRONMENT”);

//获取配置

GetAppSettingsModel(configurationRoot.Get<AppSettingsModel>());

 

//配置文件更新后回调更新方法

ChangeToken.OnChange(() => configurationRoot.GetReloadToken(), () =>

{

GetAppSettingsModel(configurationRoot.Get<AppSettingsModel>());

});

}

 

public void GetAppSettingsModel(AppSettingsModel appSettings)

{

AppSettingsConfig.UserName = appSettings.UserName;

Console.WriteLine($”UserName:{ appSettings.UserName}”);

}

 

//配置文件实体模型

public class AppSettingsModel

{

public string UserName { get; set; }

}

 

//配置文件数据

public class AppSettingsConfig

{

public static string UserName { get; set; }

}

 

注:不同读取方式请参考:https://www.cnblogs.com/libingql/p/11326358.html
————————————————
版权声明:本文为CSDN博主「windowsliusheng」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/windowsliusheng/article/details/107094967

在 ASP.NET Core 中修改配置文件后自动加载新的配置 - 张志敏的技术专栏

mikel阅读(529)

来源: 在 ASP.NET Core 中修改配置文件后自动加载新的配置 – 张志敏的技术专栏

ASP.NET Core 默认的应用程序模板中, 配置文件的处理如下面的代码所示:

config.AddJsonFile(
    path: "appsettings.json",
    optional: true,
    reloadOnChange: true
);
config.AddJsonFile(
    path: $"appsettings.{env.EnvironmentName}.json",
    optional: true,
    reloadOnChange: true
);

appsettings.json 和 appsettings.{env.EnvironmentName}.json 两个配置文件都是可选的, 并且支持当文件被修改时能够重新加载。

可以在 ASP.NET Core 应用中利用这个特性, 实现修改配置文件之后, 不需要重启应用, 自动加载修改过的配置文件, 从而减少系统停机的时间。 实现的步骤如下:

使用配置 API 进行注入

假设要在程序中注入这样一个配置类型:

public class WeatherOption {
    public string City { get; set; }
    public int RefreshInterval { get; set; }
}

在 appsettings.json 中添加的配置如下:

{
  "weather": {
    "city": "GuangZhou",
    "refreshInterval": 120
  }
}

在 Startup.cs 的 ConfigureServices 方法中使用配置 API 进行注入, 代码如下:

public void ConfigureServices(IServiceCollection services) {
    services.Configure<WeatherOption>(Configuration.GetSection("weather"));
    services.AddControllers();
}

这个步骤很关键, 通过这个配置 API 可以把注入内容和配置所在的节点关联起来。 如果有兴趣了解底层实现的话, 可以继续查看这个 OptionsConfigurationServiceCollectionExtensions.cs 。

通过这种方式注册的内容, 都是支持当配置文件被修改时, 自动重新加载的。

在控制器 (Controller) 中加载修改过后的配置

控制器 (Controller) 在 ASP.NET Core 应用的依赖注入容器中注册的生命周期是 Scoped , 即每次请求都会创建新的控制器实例。 这样只需要在控制器的构造函数中注入 IOptionsSnapshot<TOption> 参数即可, 代码如下:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase {

    private WeatherOption option;

    public WeatherForecastController(
        IOptionsSnapshot<WeatherOption> options
    ) {
        this.option = options.Value;
    }

    // GET /weatherforcase/options
    [HttpGet("options")]
    public ActionResult<WeatherOption> GetOption() {
        return options;
    }
}

当然, 如果不希望在控制器中使用这个 IOptionsSnapshot 接口类型(会带来一些对现有代码重构和修改, 还是有一定的风险的), 可以在 ConfigureServices 中添加对 WeatherOption 的注入, 代码如下:

public void ConfigureServices(IServiceCollection services) {
    services.Configure<WeatherOption>(Configuration.GetSection("weather"));
    // 添加对 WeatherOption 的注入, 生命周期为 Scoped , 这样每次请求都可以获取新的配置值。
    services.AddScoped(serviceProvider => {
        var snapshot = serviceProvider.GetService<IOptionsSnapshot<WeatherOption>>();
        return snapshot.Value;
    });
    services.AddControllers();
}

这样在控制器中就不需要注入 IOptionsSnapshot<T> 类型了, 最终控制器的代码如下:

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase {

    private WeatherOption option;

    public WeatherForecastController(
        WeatherOption option
    ) {
        this.option = option;
    }

    // GET /weatherforcase/options
    [HttpGet("options")]
    public ActionResult<WeatherOption> GetOption() {
        return options;
    }
}

这样控制器就无需修改任何代码即可加载修改过后的新配置。

在中间件 (Middleware) 中加载修改过后的配置

中间件 (Middleware) 在 ASP.NET Core 应用的依赖注入容器中注册的生命周期是 Singleton , 即单例的, 只有在当应用启动时, 根据中间件创建处理连时创建一次全局实例, 所以只能通过注入 IOptionsMonitor<T> 来监听配置文件的修改情况, 示例代码如下:

public class TestMiddleware {

    private RequestDelegate next;
    private WeatherOption option;

    public TestMiddleware(
        RequestDelegate next,
        IOptionsMonitor<WeatherOption> monitor
    ) {
        this.next = next;
        option = monitor.CurrentValue;
        // moni config change
        monitor.OnChange(newValue => {
            option = newValue;
        });
    }

    public async Task Invoke(HttpContext context) {
        await context.Response.WriteAsync(JsonSerializer.Serialize(option));
    }

}

当然, 在中间件的 Task Invoke(HttpContext context) 方法中, 直接获取 IOptionsSnapshot<T> 也是可以的, 代码如下:

public async Task Invoke(HttpContext context) {
    var snapshot = context.RequestServices.GetService<IOptionsSnapshot<WeatherOption>>();
    await context.Response.WriteAsync(JsonSerializer.Serialize(snapshot.Value));
}

但是这么做的话, 似乎就偏离了依赖注入的原则了, 因此不推荐这种做法。

Asp.Net Core 3.1学习-依赖注入、服务生命周期(6) - 魏杨杨 - 博客园

mikel阅读(629)

来源: Asp.Net Core 3.1学习-依赖注入、服务生命周期(6) – 魏杨杨 – 博客园

1、前言

面向对象设计(OOD)里有一个重要的思想就是依赖倒置原则(DIP),并由该原则牵引出依赖注入(DI)、控制反转(IOC)及其容器等概念。在学习Core依赖注入、服务生命周期之前,下面让我们先了解下依赖倒置原则(DIP)、依赖注入(DI)、控制反转(IOC)等概念,然后再深入学习Core依赖注入服务。

 

2、依赖倒置原则(Dependency Inversion  Principle, DIP

抽象不应该依赖于细节,细节应当依赖于抽象,高层模块不依赖于低层模块的实现,而低层模块依赖于高层模块定义的接口。一般来讲,就是高层模块定义接口,低层模块负责具体的实现。针对接口编程而不是针对细节编程

3、什么是依赖注入(Denpendency Injection)

3.1、依赖

人与人之间都有依赖(尤其我,就是离不开女人哈哈)何况软件呢?所谓依赖就是:当一个类需要另一个类协作来完成工作的时候就产生了依赖。比如用户登录,我们在控制器中UserController要完成用户登录、注册、修改密码等等事情、其中操作到数据库的(登录)我们用EF来完成,这里我们封装了一个EFLogin,这里的UserController就有一个ILogin的依赖。需要知道的是这里依赖于一个抽象为不是具体的某一个实现,所以给EFLogin定义了一个接口ILogin抽象了EFLogin的行为

3.2、注入

注入体现的是一个IOC(控制反转的的思想)。

复制代码
 public interface IUser
    {
        string BB();
    }
    public class User : IUser
    {
        public string BB()
        {
            return "LP整天只会BB";
        }
    }
    public class ShowInfo
    {
        IUser user = new User();
        public void UserBB()
        {
            user.BB();
        }
    }
复制代码

当我们调用ShowInfo的时候,是通过IUser接口实例化一个User类去实现其方法的这叫控制正传, 但是大湿兄说,我们不应该创建User类,而是让调用者给你传递,于是你通过构造函数让外界把这两个依赖给你。把依赖的创建丢给其它人。自己只负责使用,其它人丢给你依赖的这个过程理解为注入其它人丢给你依赖的这个过程理解为注入。也叫控制反转(IOC)

复制代码
public interface IUser
    {
        string BB();
    }
    public class User : IUser
    {
        public string BB()
        {
            return "LP整天只会BB";
        }
    }
   
    public class ShowInfo2
    {
        private readonly IUser _user;
        public ShowInfo2 (IUser user)
        {
            _user = user;
        }
        public void UserBB()
        {
            _user.BB();
        }
    }
复制代码

 3.3、为什么要使用依赖注入?

使用依赖注入我们可以很好的管理类跟类之间的依赖,在我们设计应用程序的时候遵循这几原则,确保代码的可维护性和扩展性;另外在Core的架构中依赖注入提供了对象创建和生命周期管理的核心能力,各个组件之间的相互协作也是由依赖注入框架来实现的

4、服务生命周期

在ConfigureServices方法中的容器注册每个应用程序的服务,Asp.Core都可以为每个应用程序提供三种服务生命周期:
Transient(暂时):每次请求都会创建一个新的实例。这种生命周期最适合轻量级,无状态服务。
Scoped(作用域):在同一个作用域内只初始化一个实例 ,可以理解为每一个请求只创建一个实例,同一个请求会在一个作用域内。在Scooped的生存周期内,如果容器释放 它也就被释放了
Singleton(单例):整个应用程序生命周期以内只创建一个实例,后续每个请求都使用相同的实例。如果应用程序需要单例行为,建议让服务容器管理服务的生命周期,而不是在自己的类中实现单例模式。

为了演示生命周期和注册选项之间的差异,请考虑以下代码:

 

 

IGuid接口返回一个Guid

public interface IGuid
    {
        Guid GetGuid { get; }
    }

接口IScopedService、ISingletonService、ITransientService、都继承接口IGuid

复制代码
 public interface IScopedService:IGuid
    {
       
    }
 public interface ISingletonService: IGuid
    {
       
    }
  public interface ITransientService: IGuid
    {
       
    }
复制代码
1
GuidShow类继承接口IScopedService、ISingletonService、ITransientService
复制代码
public class GuidShow : IScopedService, ISingletonService, ITransientService
    {
      
        public GuidShow() : this(Guid.NewGuid())
        {
        }
        public GuidShow(Guid id)
        {
            GetGuid = id;
        }
        public Guid GetGuid { get; private set; }

    }
复制代码

在Starup里面注册

复制代码
public void ConfigureServices(IServiceCollection services)
        {
            #region//注册不同生命周期的服务
            services.AddSingleton<ISingletonService, SingletonService>();
            services.AddTransient<ITransientService, TransientService>();
            services.AddScoped<IScopedService, ScopedService>();
            #endregion
            services.AddControllers();
        }
复制代码

 

在WeatherForecastController Api里写一个Api

FromServices就是从容器里面获取我们的对象 每个对象都获取两边来来对比每个生命周期是怎么样的
复制代码
 [ApiController]
    [Route("[controller]/[action]")]
//路由
//API
[HttpGet]
        public string GetService(
            [FromServices] IScopedService scoped1, [FromServices] IScopedService scoped2, 
            [FromServices] ITransientService transient1, [FromServices] ITransientService transient2, 
            [FromServices] ISingletonService singleton, [FromServices] ISingletonService singleton2)
        {
            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine($"作用域1-->{scoped1.GetGuid}");
            Console.WriteLine($"作用域2-->{scoped2.GetGuid}");

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine($"瞬时1-->{transient1.GetGuid}");
            Console.WriteLine($"瞬时2-->{transient2.GetGuid}");

            Console.WriteLine();
            Console.WriteLine();
            Console.WriteLine($"单例1-->{singleton.GetGuid}");
            Console.WriteLine($"单例2-->{singleton2.GetGuid}");

            Console.WriteLine("===========分割线=====================");
            Console.WriteLine();
            Console.WriteLine();

            return "成功";
        }
复制代码

 

修改应用程序启动

 

启动应用程序

 

 

可以看出来单例跟作用域的都是一样的Guid 只有瞬时的不一样  再次刷新浏览器

 

 

单例的没有改变所以

Transient(暂时):每次调用服务的时候都会创建一个新的实例

 Scoped(作用域):一次请求(Action)内对象实例是相同的,但每次请求会产生一个新实例。

Singleton(单例):首次请求初始化同一个实例,后续每次请求都使用同一个实例。相当于在整个应用Application中只实例化一次实例,常见的单例模式。

下面是其他的注册

  #region//工程模式注册 单例作用域、瞬时 都可以用
            services.AddSingleton<ISingletonService>(s=> {
                return new SingletonService();
            });
   #region//尝试注册
            //注册过了就不在注册了
            //using Microsoft.Extensions.DependencyInjection.Extensions;
            services.TryAddScoped<IScopedService, ScopedService>();
            #endregion

#region//移除注册 移除所有IScopedService的注册 不同实现的
 services.RemoveAll<IScopedService>(); #endregion

 

注册泛型 先写一个泛型类

复制代码
 public interface ITypeT<T>
    {
    }
    public class TypeT<T> : ITypeT<T>
    {
        public T GetT { get; }
        public TypeT(T getT)
        {
            this.GetT = getT;
        }
    }
复制代码

 

创建一个api Test

复制代码
[Route("api/[controller]/[action]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        public ITypeT<IScopedService> _typeT;
        public TestController(ITypeT<IScopedService> typeT)
        {
            _typeT = typeT;
        }

        [HttpGet]
        public string TestGet()
        {
            return _typeT.GetHashCode().ToString();
        }
    }
复制代码

 

注册一下 里面具体的参数不用谢 实现的时候只要带入某个具体的类就可以了,第一个参数服务的额类型,第二个参数服务的实现类型

     services.AddScoped(typeof(ITypeT<>),typeof(TypeT<>));

 

地址栏输入https://localhost:5001/api/test/testGet

看断点

 

GetT他得到的是ScopedService

5、依赖注入的方式

5.1、构造函数注入

我们可以在定义的Controller中以构造函数注入的方式注入所需的服务。他的服务是大部分接口都需要的话就用它

public ITypeT<IScopedService> _typeT;
        public TestController(ITypeT<IScopedService> typeT)
        {
            _typeT = typeT;
        }

5.2、FromServices

上面的GetService就是这种方式注入的,这个服务只是在某一个接口下用FromServices

当然还有其他的注入方式就不在研究了。

原文链接:https://www.cnblogs.com/w5942066/p/12808405.html

Asp.Net Core 3.1学习-Web Api 中基于JWT的token验证及Swagger使用 (4) - 魏杨杨 - 博客园

mikel阅读(600)

来源: Asp.Net Core 3.1学习-Web Api 中基于JWT的token验证及Swagger使用 (4) – 魏杨杨 – 博客园

1、初始JWT

1.1、JWT原理

JWT(JSON Web Token)是目前最流行的跨域身份验证解决方案,他的优势就在于服务器不用存token便于分布式开发,给APP提供数据用于前后端分离的项目。登录产生的 token的项目完全可以独立与其他项目。当用户访问登录接口的时候会返回一个token,然后访问其他需要登录的接口都会带上这个token,后台进行验证如果token是有效的我们就认为用户是正常登录的,然后我们可以从token中取出来一些携带的信息进行操作。当然这些携带的信息都可以通过其他额外的字段进行传递,但是用token传递的话,不用其他额外加其他字段了。

JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

1.2、JWT结构

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbklEIjoiYWRtaW4iLCJuYmYiOjE1ODc4OTE2OTMsImV4cCI6MTU4NzkyNzY5MywiaXNzIjoiV1lZIiwiYXVkIjoiRXZlcnlUZXN0T25lIn0.-snenNVHrrKq9obN8FzKe0t99ok6FUm5pHv-P_eYc30

第一部分我们称它为头部(header):声明类型,这里是jwt;声明加密的算法 通常直接使用 HMAC SHA256

{
  'typ': 'JWT',
  'alg': 'HS256'
}

第二部分我们称其为载荷(payload, 类似于飞机上承载的物品):

iss:Token发布者

exp:过期时间 分钟

sub:主题

aud:Token接受者

nbf:在此之前不可用

iat:发布时间

jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

复制代码
{

"sub": "1234567890",

"name": "wyy",

"admin": true

}
复制代码

第三部分是签证(signature):这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

2、生成Token

2.1、建立项目

在VS2019中新建一个Core Api程序 Core选3.1 然后在项目上添加一个Jwt文件夹帮助类,新建接口ITokenHelper,类:TokenHelper继承ITokenHelper,类JWTConfig,类TnToken

JWTConfig:用来保存读取jwt相关配置

复制代码
/// <summary>
    /// 配置token生成信息
    /// </summary>
    public class JWTConfig
    {
        /// <summary>
        /// Token发布者
        /// </summary>
        public string Issuer { get; set; }
        /// <summary>
        /// oken接受者
        /// </summary>
        public string Audience { get; set; }
        /// <summary>
        /// 秘钥
        /// </summary>
        public string IssuerSigningKey { get; set; }
        /// <summary>
        /// 过期时间
        /// </summary>
        public int AccessTokenExpiresMinutes { get; set; }
    }
复制代码

TnToken:存放Token 跟过期时间的类

复制代码
/// <summary>
    /// 存放Token 跟过期时间的类
    /// </summary>
    public class TnToken
    {
        /// <summary>
        /// token
        /// </summary>
        public string TokenStr { get; set; }
        /// <summary>
        /// 过期时间
        /// </summary>
        public DateTime Expires { get; set; }
    }
复制代码

ITokenHelper接口:token工具类的接口,方便使用依赖注入,很简单提供两个常用的方法

复制代码
/// <summary>
    /// token工具类的接口,方便使用依赖注入,很简单提供两个常用的方法
    /// </summary>
    public interface ITokenHelper
    {
        /// <summary>
        /// 根据一个对象通过反射提供负载生成token
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="user"></param>
        /// <returns></returns>
        TnToken CreateToken<T>(T user) where T : class;
        /// <summary>
        /// 根据键值对提供负载生成token
        /// </summary>
        /// <param name="keyValuePairs"></param>
        /// <returns></returns>
        TnToken CreateToken(Dictionary<string, string> keyValuePairs);
    }
复制代码

TokenHelper:实现类

复制代码
/// <summary>
    /// Token生成类
    /// </summary>
    public class TokenHelper : ITokenHelper
    {
        private readonly IOptions<JWTConfig> _options;
        public TokenHelper(IOptions<JWTConfig> options)
        {
            _options = options;
        }

        /// <summary>
        /// 根据一个对象通过反射提供负载生成token
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="user"></param>
        /// <returns></returns>
        public TnToken CreateToken<T>(T user) where T : class
        {
            //携带的负载部分,类似一个键值对
            List<Claim> claims = new List<Claim>();
            //这里我们用反射把model数据提供给它
            foreach (var item in user.GetType().GetProperties())
            {
                object obj = item.GetValue(user);
                string value = "";
                if (obj != null)
                    value = obj.ToString();

                claims.Add(new Claim(item.Name, value));
            }
            //创建token
            return CreateToken(claims);
        }

        /// <summary>
        /// 根据键值对提供负载生成token
        /// </summary>
        /// <param name="keyValuePairs"></param>
        /// <returns></returns>
        public TnToken CreateToken(Dictionary<string, string> keyValuePairs)
        {
            //携带的负载部分,类似一个键值对
            List<Claim> claims = new List<Claim>();
            //这里我们通过键值对把数据提供给它
            foreach (var item in keyValuePairs)
            {
                claims.Add(new Claim(item.Key, item.Value));
            }
            //创建token
            return CreateTokenString(claims);
        }
        /// <summary>
        /// 生成token
        /// </summary>
        /// <param name="claims">List的 Claim对象</param>
        /// <returns></returns>
        private TnToken CreateTokenString(List<Claim> claims)
        {
            var now = DateTime.Now;
            var expires = now.Add(TimeSpan.FromMinutes(_options.Value.AccessTokenExpiresMinutes));
            var token = new JwtSecurityToken(
                issuer: _options.Value.Issuer,//Token发布者
                audience: _options.Value.Audience,//Token接受者
                claims: claims,//携带的负载
                notBefore: now,//当前时间token生成时间
                expires: expires,//过期时间
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.Value.IssuerSigningKey)), SecurityAlgorithms.HmacSha256));
            return new TnToken { TokenStr = new JwtSecurityTokenHandler().WriteToken(token), Expires = expires };
        }

    }
复制代码

 2.2、在Startup中去配置jwt相关:

ConfigureServices中:

复制代码
#region jwt配置
            services.AddTransient<ITokenHelper, TokenHelper>();
            //读取配置文件配置的jwt相关配置
            services.Configure<JWTConfig>(Configuration.GetSection("JWTConfig"));
            //启用JWT
            services.AddAuthentication(Options =>
            {
                Options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                Options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).
            AddJwtBearer();#endregion
复制代码

JwtBearerDefaults.AuthenticationScheme与AddJwtBearer();下载两个依赖即可。或者NuGet安装

appsettings中简单配置一下jwt相关的信息:

 "JWTConfig": {
        "Issuer": "WYY", //Token发布者
        "Audience": "EveryTestOne", //Token接受者
        "IssuerSigningKey": "WYY&YL889455200Sily", //秘钥可以构建服务器认可的token;签名秘钥长度最少16
        "AccessTokenExpiresMinutes": "600" //过期时间 分钟
    },

Configure中去启用验证中间件:

//启用认证中间件 要写在授权UseAuthorization()的前面
app.UseAuthentication();

 2.3、一个简单的登录获取token

在Controllers文件夹里面新建一个api 名字LoginTest

复制代码
 [EnableCors("AllowCors")]
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class LoginTestController : ControllerBase
    {
        private readonly ITokenHelper tokenHelper = null;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="_tokenHelper"></param>
        public LoginTestController(ITokenHelper _tokenHelper)
        {
            tokenHelper = _tokenHelper;
        }
        /// <summary>
        /// 登录测试
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
         [HttpPost]
        public ReturnModel Login([FromBody]UserDto user)
        {
            var ret = new ReturnModel();
            try
            {
                if (string.IsNullOrWhiteSpace(user.LoginID) || string.IsNullOrWhiteSpace(user.Password))
                {
                    ret.Code = 201;
                    ret.Msg = "用户名密码不能为空";
                    return ret;
                }
                //登录操作 我就没写了 || 假设登录成功
                if (1 == 1)
                {
                    Dictionary<string, string> keyValuePairs = new Dictionary<string, string>
                    {
                        { "loginID", user.LoginID }
                    };
                    ret.Code = 200;
                    ret.Msg = "登录成功";
                    ret.TnToken= tokenHelper.CreateToken(keyValuePairs);
                }
            }
            catch(Exception ex)
            {
                ret.Code = 500;
                ret.Msg = "登录失败:"+ex.Message;
            }
            return ret;
        }
    }
复制代码

UserDto接收类

复制代码
/// <summary>
    /// 登录类Dto
    /// </summary>
    public class UserDto
    {
        /// <summary>
        /// 用户名
        /// </summary>
        public string LoginID { get; set; }
        /// <summary>
        /// 密码
        /// </summary>
        public string Password { get; set; }
    }
复制代码

ReturnModel 只是我自己封装的一个统一的接口返回格式标准

复制代码
/// <summary>
    /// 返回类
    /// </summary>
    public class ReturnModel
    {
        /// <summary>
        /// 返回码
        /// </summary>
        public int Code { get; set; }
        /// <summary>
        /// 消息
        /// </summary>
        public string Msg { get; set; }
        /// <summary>
        /// 数据
        /// </summary>
        public object Data { get; set; }
        /// <summary>
        /// Token信息
        /// </summary>
        public TnToken TnToken { get; set; }
    }
复制代码

跨域上篇文章说了这里就不提了

2.4、前端获取token

我是用传统的MVC的一个启动页面

复制代码
<input type="hidden" id="tokenValue" name="tokenValue" value="" />
<br /><br /><br />
<span>Token:</span><div id="txtval"></div><br />
<span>有效期:</span><div id="txtvalTime"></div><br />

<div>
    <input type="button" value="获取Token" onclick="getToken()" /><br /><br /><br />
</div>
<script src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript">
    //获取token
    function getToken() {
        var data = JSON.stringify({ LoginID: "admin", Password: "admin888" });
        $.ajax({
            type: "post",
            url: "https://localhost:44331/api/LoginTest/Login",
            dataType: "json",
            async: true,
            data: data,
            contentType: 'application/json',
            success: function (data) {
                console.log(data);
                $("#txtval").html(data.tnToken.tokenStr);
                $("#txtvalTime").html(new Date(data.tnToken.expires).Format("yyyy-MM-dd hh:mm"));
                $("#tokenValue").val(data.tnToken.tokenStr);

            },
            error: function (data) {
                console.log("错误" + data);
            }
        });
    }
    Date.prototype.Format = function (fmt) { //author: zhengsh 2016-9-5
        var o = {
            "M+": this.getMonth() + 1, //月份
            "d+": this.getDate(), //日
            "h+": this.getHours(), //小时
            "m+": this.getMinutes(), //分
            "s+": this.getSeconds(), //秒
            "q+": Math.floor((this.getMonth() + 3) / 3), //季度
            "S": this.getMilliseconds() //毫秒
        };
        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
        for (var k in o)
            if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        return fmt;
    }
</script>
复制代码

把Api启动起来 MVC也启动起来试试看

在JWT管网解码

3、验证前端传递的token

现在说说怎么来验证前台传递的jwt,其实很简单,最主要的就是验证token的有效性和是否过期。在接口ITokenHelper中添加验证的两个方法 。TokenHelper中实现

ITokenHelper中添加

复制代码
/// <summary>
        /// Token验证
        /// </summary>
        /// <param name="encodeJwt">token</param>
        /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值</param>
        /// <returns></returns>
        bool ValiToken(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad = null);
        /// <summary>
        /// 带返回状态的Token验证
        /// </summary>
        /// <param name="encodeJwt">token</param>
        /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值</param>
        /// <param name="action"></param>
        /// <returns></returns>
        TokenType ValiTokenState(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action);
复制代码

TokenHelper中添加

复制代码
/// <summary>
        /// 验证身份 验证签名的有效性
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param>
        public bool ValiToken(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad = null)
        {
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length < 3)//数据格式都不对直接pass
            {
                return false;
            }
            var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
            //配置文件中取出来的签名秘钥
            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey));
            //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可)
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
            if (!success)
            {
                return success;//签名不正确直接返回
            }

            //其次验证是否在有效期内(也应该必须)
            var now = ToUnixEpochDate(DateTime.UtcNow);
            success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));

            //不需要自定义验证不传或者传递null即可
            if (validatePayLoad == null)
                return true;

            //再其次 进行自定义的验证
            success = success && validatePayLoad(payLoad);

            return success;
        }
        /// <summary>
        /// 时间转换
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        private long ToUnixEpochDate(DateTime date)
        {
            return (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public TokenType ValiTokenState(string encodeJwt, Func<Dictionary<string, string>, bool> validatePayLoad, Action<Dictionary<string, string>> action)
        {
            var jwtArr = encodeJwt.Split('.');
            if (jwtArr.Length < 3)//数据格式都不对直接pass
            {
                return TokenType.Fail;
            }
            var header = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(_options.Value.IssuerSigningKey));
            //验证签名是否正确(把用户传递的签名部分取出来和服务器生成的签名匹配即可)
            if (!string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1]))))))
            {
                return TokenType.Fail;
            }
            //其次验证是否在有效期内(必须验证)
            var now = ToUnixEpochDate(DateTime.UtcNow);
            if (!(now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString())))
            {
                return TokenType.Expired;
            }

            //不需要自定义验证不传或者传递null即可
            if (validatePayLoad == null)
            {
                action(payLoad);
                return TokenType.Ok;
            }
            //再其次 进行自定义的验证
            if (!validatePayLoad(payLoad))
            {
                return TokenType.Fail;
            }
            //可能需要获取jwt摘要里边的数据,封装一下方便使用
            action(payLoad);
            return TokenType.Ok;
        }
复制代码

其中TokenType是返回类型成功失败

public enum TokenType
    {
        Ok,
        Fail,
        Expired
    }

在api LoginTest中新增两个验证的方法

复制代码
/// <summary>
        /// 验证Token
        /// </summary>
        /// <param name="tokenStr">token</param>
        /// <returns></returns>
        [HttpGet]
        public ReturnModel ValiToken(string tokenStr)
        {
            var ret = new ReturnModel
            {
                TnToken = new TnToken()
            };
            bool isvilidate = tokenHelper.ValiToken(tokenStr);
            if(isvilidate)
            {
                ret.Code = 200;
                ret.Msg = "Token验证成功";
                ret.TnToken.TokenStr = tokenStr;
            }
            else
            {
                ret.Code = 500;
                ret.Msg = "Token验证失败";
                ret.TnToken.TokenStr = tokenStr;
            }
            return ret;
        }
        /// <summary>
        /// 验证Token 带返回状态
        /// </summary>
        /// <param name="tokenStr"></param>
        /// <returns></returns>
        [HttpGet]
        public ReturnModel ValiTokenState(string tokenStr)
        {
            var ret = new ReturnModel
            {
                TnToken = new TnToken()
            };
            string loginID = "";
            TokenType tokenType = tokenHelper.ValiTokenState(tokenStr, a => a["iss"] == "WYY" && a["aud"] == "EveryTestOne", action => { loginID = action["loginID"]; });
            if (tokenType == TokenType.Fail)
            {
                ret.Code = 202;
                ret.Msg = "token验证失败";
                return ret;
            }
            if (tokenType == TokenType.Expired)
            {
                ret.Code = 205;
                ret.Msg = "token已经过期";
                return ret;
            }

            //..............其他逻辑
            var data = new List<Dictionary<string, string>>();
            var bb = new Dictionary<string, string>
            {
                { "Wyy", "123456" }
            };
            data.Add(bb);
            ret.Code = 200;
            ret.Msg = "访问成功!";
            ret.Data =data ;
            return ret;
        }
复制代码

上面一个简单的验证和支持自定义验证的就写好了。下面带有状态的是让我们清楚的知道是什么状态请求登录的时候 或者请求数据的时候,是token过期还是说token没有获取到等等。

ValiTokenState第三个参数我还更了一个系统委托,是这样想的,处理可以验证token,还可以顺便取一个想要的数据,当然其实这样把相关逻辑混到一起也增加代码的耦合性,当时可以提高一点效率不用在重新解析一次数据,当然这个数据也可以通前台传递过来,所以怎么用还是看实际情况,这里只是封装一下提供这样一个方法,用的时候也可以用。

其前端请求代码

复制代码
 $.ajax({
            type: "post",
            url: "https://localhost:44331/api/LoginTest/ValiToken?tokenStr="+ $("#tokenValue").val(),
            dataType: "json",
            async: true,
            data: { token: $("#tokenValue").val() },
            contentType: 'application/json',
            success: function (data) {
                console.log(data);             
            },
            error: function (data) {
                console.log("错误" + data);
            }
        });
复制代码

4、Api中过滤器实现通用token验证

项目上新建一个文件夹Filter,在文件夹Filter里新建一个过滤器TokenFilter

复制代码
namespace JWTToken.Filter
{
    public class TokenFilter : Attribute, IActionFilter
    {
        private ITokenHelper tokenHelper;
        public TokenFilter(ITokenHelper _tokenHelper) //通过依赖注入得到数据访问层实例
        {
            tokenHelper = _tokenHelper;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {

        }
        public void OnActionExecuting(ActionExecutingContext context)
        {
            ReturnModel ret = new ReturnModel();
            //获取token
            object tokenobj = context.ActionArguments["token"];//前端地址栏参数传参
       //object tokenobj = context.HttpContext.Request.Headers["token"].ToString();//前端写在header里面获取的

            if (tokenobj == null)
            {
                ret.Code = 201;
                ret.Msg = "token不能为空";
                context.Result = new JsonResult(ret);
                return;
            }

            string token = tokenobj.ToString();

            string userId = "";
            //验证jwt,同时取出来jwt里边的用户ID
            TokenType tokenType = tokenHelper.ValiTokenState(token, a => a["iss"] == "WYY" && a["aud"] == "EveryTestOne", action => { userId = action["userId"]; });
            if (tokenType == TokenType.Fail)
            {
                ret.Code = 202;
                ret.Msg = "token验证失败";
                context.Result = new JsonResult(ret);
                return;
            }
            if (tokenType == TokenType.Expired)
            {
                ret.Code = 205;
                ret.Msg = "token已经过期";
                context.Result = new JsonResult(ret);
            }
            if (!string.IsNullOrEmpty(userId))
            {
                //给控制器传递参数(需要什么参数其实可以做成可以配置的,在过滤器里边加字段即可)
                //context.ActionArguments.Add("userId", Convert.ToInt32(userId));
            }
        }
    }
}
复制代码

context.ActionArguments。这是前段请求的时候地址栏带上的参数 token=xxx;这种类型的,不是请求的参数 不然会报错;

把过滤器在startup中注入一下:

 services.AddScoped<TokenFilter>();

需要验证token的地方,直接加上这个过滤器即可

前台试试 请求上图的GetList

复制代码
<input type="hidden" id="tokenValue" name="tokenValue" value="" />
<br /><br /><br />
<span>Token:</span><div id="txtval"></div><br />
<span>有效期:</span><div id="txtvalTime"></div><br />

<div>
    <input type="button" value="获取Token" onclick="getToken()" /><br /><br /><br />
</div>
<input type="button" value="获取List" onclick="getList()" /><br />
<script src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript">
    //获取token
    function getToken() {
        var data = JSON.stringify({ LoginID: "admin", Password: "admin888" });
        $.ajax({
            type: "post",
            url: "https://localhost:44331/api/LoginTest/Login",
            dataType: "json",
            async: true,
            data: data,
            contentType: 'application/json',
            success: function (data) {
                console.log(data);
                $("#txtval").html(data.tnToken.tokenStr);
                $("#txtvalTime").html(new Date(data.tnToken.expires).Format("yyyy-MM-dd hh:mm"));
                $("#tokenValue").val(data.tnToken.tokenStr);

            },
            error: function (data) {
                console.log("错误" + data);
            }
        });
    }
    //获取list
    function getList() {
        var data = JSON.stringify();
        $.ajax({
            type: "post",
            url: "https://localhost:44331/api/Home/GetList?token="+ $("#tokenValue").val(),
            dataType: "json",
            async: true,
            data: { token: $("#tokenValue").val() },
            contentType: 'application/json',
            success: function (data) {
                console.log(data);
                $("#txtval").html(JSON.stringify(data));
                
             

            },
            error: function (data) {
                console.log("错误" + data);
            }
        });
    }
    Date.prototype.Format = function (fmt) { //author: zhengsh 2016-9-5
        var o = {
            "M+": this.getMonth() + 1, //月份
            "d+": this.getDate(), //日
            "h+": this.getHours(), //小时
            "m+": this.getMinutes(), //分
            "s+": this.getSeconds(), //秒
            "q+": Math.floor((this.getMonth() + 3) / 3), //季度
            "S": this.getMilliseconds() //毫秒
        };
        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
        for (var k in o)
            if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        return fmt;
    }
</script>
复制代码

现获取token 赋值在隐藏框里在请求

5、在Api中使用Swagger

5.1项目中添加Swagger的相关包

 5.2ConfigureServices、Configure 中添加

复制代码
#region Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1",
                    Title = "测试接口文档",
                    Description = "测试接口"
                });
                // 为 Swagger 设置xml文档注释路径
                var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
                c.IncludeXmlComments(xmlPath);
                c.DocInclusionPredicate((docName, description) => true);
                //添加对控制器的标签(描述)
                c.DocumentFilter<ApplyTagDescriptions>();//显示类名
                c.CustomSchemaIds(type => type.FullName);// 可以解决相同类名会报错的问题
                //c.OperationFilter<AuthTokenHeaderParameter>();
            });
            #endregion
复制代码
复制代码
 app.UseSwagger(c =>
            {
                c.RouteTemplate = "swagger/{documentName}/swagger.json";
            });
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web App v1");
                c.RoutePrefix = "doc";//设置根节点访问
                //c.DocExpansion(DocExpansion.None);//折叠
                c.DefaultModelsExpandDepth(-1);//不显示Schemas
            });
复制代码

 5.3、项目属性修改

5.4、添加接口类的注释

看效果

6、总结

JWT个人的理解就是api配置文件的IssuerSigningKey作为秘钥来加密的,客户端登录后获取到token 地址栏请求传到后端 后端通过解码获取到IssuerSigningKey是否跟后台解析出来的一直来匹配。后端可以写在过滤器里面来接收这个token来验证从而限制能不能访问Api。前端可以自己封装一个请求把token传进去的参数就可以避免每次输入Token,前端可以Session?

下了班写的仓促了 哈哈。欢迎补充。

原文链接:https://www.cnblogs.com/w5942066/p/12781397.html

Asp.Net Core 3.1学习-Web Api 文件上传 Ajax请求以及跨域问题(3) - 魏杨杨 - 博客园

mikel阅读(688)

来源: Asp.Net Core 3.1学习-Web Api 文件上传 Ajax请求以及跨域问题(3) – 魏杨杨 – 博客园

1、创建Api项目

我用的是VS2019 Core3.1 。打开Vs2019 创建ASP.NET Core Web应用程序命名CoreWebApi 创建选择API 在Controller文件夹下面添加一个Api控制器 FileUp,修改Api的路由  [Route(“api/[controller]/[action]”)] 这样就可以访问到具体的某一个了  写一个测试 api

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace CoreWebApi.Controllers
{
    [Route("api/[controller]/[action]")]//修改路由
    //[Route("api/[controller]")]//默认路由
    [ApiController]
    public class FileUpController : ControllerBase
    {
        /// <summary>
        /// 测试接口
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        //[HttpGet,Route("Test")]//默认路由
        
        public string Test()
        {
            return "这是测试接口";
        }
    }
}
复制代码

 

写Api的时候一定要加上,请求方式  post、get 其他的暂时我没用到。默认的路由是被我注释的,修改一下 让他的访问时api / 控制器 / 方法名称。不改也可以,测试的接口就用注释的那个特性。然后运行项目,地址栏输入https://localhost:44376/api/FileUp/Test

 

2、上传文件接口

在FileUp里写一个上传文件的接口所有代码都在下,OutPut是一个输出的类,Dto里面是一些参数 应该都有看得懂。里面就是上传文件储存到服务器上 并没有数据库操作,需要的加上去就可以了。

注意:有些会加[Consumes(“application/json”)]//application/json//application/x-www-form-urlencoded 这样来验证媒体类型,这里我弄了好久才想起来我写了这个。因为文件上传怎么说呢,媒体类型肯定不会是Json之类的。我以前的Api就是在基础Api类里加了这个。给我弄了头皮发麻才看到

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

using CoreWebApi.Models;
using Newtonsoft.Json;
using Microsoft.Extensions.Hosting;
using System.IO;

namespace CoreWebApi.Controllers
{
    [Route("api/[controller]/[action]")]//修改路由
    //[Route("api/[controller]")]//默认路由
    [ApiController]
    public class FileUpController : ControllerBase
    {
        public  IHostingEnvironment env;
        public FileUpController(IHostingEnvironment _env)
        {
            env = _env;
        }
        /// <summary>
        /// 测试接口
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        //[HttpGet,Route("Test")]//默认路由
        
        public string Test()
        {
            return "这是测试接口";
        }
        /// <summary>
        /// 文件上传
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<OutPut> FileUp()
        {
            var ret = new OutPut();
            try
            {
                //不能用FromBody
                var dto = JsonConvert.DeserializeObject<ImagesDto>(Request.Form["ImageModelInfo"]);//文件类实体参数
                var files = Request.Form.Files;//接收上传的文件,可能多个 看前台
                if (files.Count > 0)
                {
                    var path = env.ContentRootPath + @"/Uploads/Images/";//绝对路径
                    string dirPath = Path.Combine(path, dto.Type + "/");//绝对径路 储存文件路径的文件夹
                    if (!Directory.Exists(dirPath))//查看文件夹是否存在
                        Directory.CreateDirectory(dirPath);
                    var file = files.Where(x => true).FirstOrDefault();//只取多文件的一个
                    var fileNam = $"{Guid.NewGuid():N}_{file.FileName}";//新文件名
                    string snPath = $"{dirPath + fileNam}";//储存文件路径
                    using var stream = new FileStream(snPath, FileMode.Create);
                    await file.CopyToAsync(stream);
                    //次出还可以进行数据库操作 保存到数据库
                    ret = new OutPut { Code = 200, Msg = "上传成功", Success = true };  
                }
                else//没有图片
                {
                    ret = new OutPut { Code = 400, Msg = "请上传图片", Success = false };
                }
            }
            catch (Exception ex)
            {
                ret = new OutPut { Code = 500, Msg = $"异常:{ex.Message}", Success = false };
            }
            return ret;
        }
    }
}
复制代码

 

Dto与返回的类

复制代码
 /// <summary>
    /// 返回输出类
    /// </summary>
    public class OutPut
    {
        /// <summary>
        /// 状态码
        /// </summary>
        public int Code { get; set; }
        /// <summary>
        /// 消息
        /// </summary>
        public string Msg { get; set; }
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool Success { get; set; }
        /// <summary>
        /// 返回数据
        /// </summary>
        public object Data { get; set; }
    }
/// <summary>
    /// 接收参数Dto
    /// </summary>
    public class ImagesDto
    {
        /// <summary>
        /// ID
        /// </summary>
        public int ID { get; set; }
        /// <summary>
        /// 名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 地址
        /// </summary>
        public string Url { get; set; }
        /// <summary>
        /// 备注
        /// </summary>
        public string Remark { get; set; }
        /// <summary>
        /// 
        /// </summary>
        public int RelationId { get; set; }
        /// <summary>
        /// 类型
        /// </summary>
        public int Type { get; set; }
    }
复制代码

3、前台Ajax调用

我在一个MVC程序里面用了个Ajax调用  下面是前端代码,contentType这个资源的类型我转载着坑里好久了,哈哈。

复制代码
@{
    ViewBag.Title = "测试";
}

<h2>文件上传测试</h2>
<form enctype="multipart/form-data" id="formData">
    <div>
        <br /><br /><br />
        文件:<input type="file" id="filesp" name="filesp" /><br /><br />
        名称:<input type="text" id="fileName" name="fileName" /><br /><br />
        备注:<input type="text" id="txtRemake" name="txtRemake" /><br /><br />
        RelationId:<input type="number" id="txtRelationId" name="txtRelationId" /><br /><br />
        类型:<select id="selType" name="selType">
            <option value="1">动物</option>
            <option value="2">植物</option>
            <option value="3">妹子</option>
            <option value="3">风景</option>
            <option value="4">滑稽</option>
            <option value="100">其他</option>
        </select>
        <br /><br />
        <input type="button" id="btnSave" name="btnSave" value="提交" />
    </div>
</form>

<script src="~/Scripts/jquery-3.3.1.js"></script>
<script type="text/javascript">
    $(document).ready(function () {
        $("#btnSave").click(function () {
            var data = new FormData(document.getElementById("formData"));
            //参数
            var parame = JSON.stringify({ Name: $("#fileName").val(), Remark: $("#txtRemake").val(), Type: $("#selType").val(), RelationId: $("#txtRelationId").val() });
            data.append("ImageModelInfo", parame);
            $.ajax({
                type: "post",
                url: "https://localhost:44376/api/FileUp/FileUp",
                dataType: "json",
                data: data,
                async: true,
                contentType: false,//实体头部用于指示资源的MIME类型 media type 。这里要为false
                processData: false,//processData 默认为true,当设置为true的时候,jquery ajax 提交的时候不会序列化 data,而是直接使用data
                success: function (data) {
                    console.log(data);
                },
                error: function (data) {
                    console.log("错误" + data);
                }
            });
        });
    });
</script>
复制代码

 

运行起来看看 测试一下  ,Api那边也打好断点。打来浏览器调式就是很熟悉的bug就来了(一天看不到出错仿佛心里不踏实)

 

 

很明显的CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。所以这里我们就要允许前端来访问,从而就要在Api增加跨域;

4、跨域设置

在Startup.cs下面的ConfigureServices 中标添加services.AddCors(option => option.AddPolicy(“AllowCors”, bu => bu.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            //跨域
            services.AddCors(option => option.AddPolicy("AllowCors", bu => bu.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
        }

 

AllowAnyOrigin  :允许CORS请求从任何源来访问,这是不安全的
AllowAnyHeader:允许所有的请求头

AllowCredentials :服务端也需要允许证书。
AllowAnyMethod允许跨域策略允许所有的方法:GET/POST/PUT/DELETE 等方法 如果进行限制需要 AllowAnyMethod(“GET”,”POST”) 这样来进行访问方法的限制


在Configure中添加中间件app.UseCors(“AllowCors”);,集体要限制哪些看个人了

复制代码
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();


            app.UseAuthorization();

            //跨域
            app.UseCors("AllowCors");
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

        }
复制代码

 

现在就配置好了在需要跨域的地方加上特性[EnableCors("AllowCors")]就行了 ,引用命名空间using Microsoft.AspNetCore.Cors;
复制代码
    [EnableCors("AllowCors")]
    [Route("api/[controller]/[action]")]//修改路由
    //[Route("api/[controller]")]//默认路由
    [ApiController]
    public class FileUpController : ControllerBase
    {
        public  IHostingEnvironment env;
        public FileUpController(IHostingEnvironment _env)
        {
            env = _env;
        }
        /// <summary>
        /// 测试接口
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        //[HttpGet,Route("Test")]//默认路由
        
        public string Test()
        {
            return "这是测试接口";
        }
复制代码

 

现在再来调用就解决了

 

前台结果看看。文件夹也创建了。

5、总结

如果所有Api都要跨域的话就建立一个基础的Api 让所有的都继承这个Api,就需要打赏跨域的标签就可以了;

文件上传还可以用From直接提交这边用List<IFormFile>接收,也可以像FromBady一样的一个来接收搞,单词 忘了0.0,参数不能FermBody接收,这样文件就没了。

纸上得来终觉浅,觉知这事不写还是不行哈。

原文链接:https://www.cnblogs.com/w5942066/p/12762076.html

Asp.Net Core 3.1学习-使用ASP.NET Core中的RazorPages(2) - 魏杨杨 - 博客园

mikel阅读(566)

来源: Asp.Net Core 3.1学习-使用ASP.NET Core中的RazorPages(2) – 魏杨杨 – 博客园

1、创建一个ASP.NET Core Web应用程序

1.1、打开VS2019 新建项目

1.2、选好项目位置后进入选择界面,选择Web应用程序

 

1.3、进去的页面结构如下

 

 

Pages 文件夹:包含 Razor 页面和支持文件。 每个 Razor 页面都是一对文件:

  • 一个 .cshtml 文件,其中包含使用 Razor 语法的 C# 代码的 HTML 标记 。
  • 一个 .cshtml.cs 文件,其中包含处理页面事件的 C# 代码 。

 

wwwroot 文件夹包含静态文件,如 HTML 文件、JavaScript 文件和 CSS 文件。

appSettings.json包含配置数据,如连接字符串。

Program.cs包含程序的入口点。 

Startup.cs包含配置应用行为的代码。

运行起来如果提示要安装证书的直接点是就可以了,出现WelCome就表示可以了

2、添加模型

2.1、在这里搭建“商品”模型的基架。 确切地说,基架工具将生成页面,用于对“商品”模型执行创建、读取、更新和删除 (CRUD) 操作。

右击项目名称添加文件夹Models,在Models新文件夹里新建一个模型类Shop

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace CoreRazorPages.Models
{
    /// <summary>
    /// 商品类
    /// </summary>
    public class Shop
    {
        
        [Display(Name="ID")]
        public int ID { get; set; }
        [Display(Name = "Guid")]
        public string Guid { get; set; }
        [Display(Name = "名称")]
        public string Name { get; set; }
        [Display(Name = "价格")]
        public decimal Price { get; set; }
        [Display(Name = "添加日期")]
        [DataType(DataType.Date)]
        public DateTime AddTime { get; set; }
    }
}
复制代码

3、添加基架

3.1、右击Pages文件夹添加一个文件夹Shop,然后右击Shop>添加>新搭建基架的项目>使用实体框架生成Razor页面(CRUD)模型类选择Shop,数据库上下文点击右边的+号

3.2、生成项目,如果报错Shop就加上命名空间,这里是因为文件夹Shop名称跟类名Shop一样

项目文件夹多了个Data那是数据库上下文,还有配置文件里面也加了数据库访问的字符串在appsetting.json文件夹里

   3.3、更改数据库链接

打开appsettiong.json文件修改里面的数据库链接字符串,然后点工具>nuget包管理器>程序包管理控制台依次输入Add-Migration InitialCreate、Update-Database,警告不要管他(程序员不怕警告就怕错误 哈哈)

复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
    "ConnectionStrings": {
        //原来生成的Server=(localdb)\\mssqllocaldb;Database=CoreRazorPagesContext-10c906a7-1959-4967-9659-0fcbfe8b7d16;Trusted_Connection=True;MultipleActiveResultSets=true
        "CoreRazorPagesContext": "Data Source=服务器地址;Initial Catalog=数据库名User ID=用户名;Password=密码"
    }
}
复制代码

 

 

看生成的数据库

 

3.4、修改文件夹Shared下的布局_Layout.cchtml添加Shop导航

看起来有点像winform的服务器控件一样哈哈,asp-area是区域名,现在没有 asp-page是指Razor的位置这里是Shop/Index

复制代码
 <header>
        <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
            <div class="container">
                <a class="navbar-brand" asp-area="" asp-page="/Index">CoreRazorPages</a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
                        aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
                    <ul class="navbar-nav flex-grow-1">
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="Shop/Index">商品</a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
                        </li>
                    </ul>
                </div>
            </div>
        </nav>
    </header>
复制代码

  3.5、添加一些数据在Shop中,在Data文件件下面添加一个类 AddDefaultData,然后修改程序入口main 方法。

从依赖关系注入容器获取数据库上下文实例>调用 InitData方法,并将上下文传递给它、方法完成时释放上下文

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

//添加命名空间
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using CoreRazorPages.Models;
namespace CoreRazorPages.Data
{
    public class AddDefaultData
    {
        /// <summary>
        /// 添加数据
        /// </summary>
        /// <param name="serviceProvider"></param>
        public static void InitData(IServiceProvider serviceProvider)
        {
            using (var db = new CoreRazorPagesContext(serviceProvider.GetRequiredService<DbContextOptions<CoreRazorPagesContext>>()))
            {
                //如果有数据就不添加了
                if (db.Shop.Any())
                {
                    return;
                }
                //添加10条初始数据
                for(var i=0;i<10;i++)
                {
                    var model = new Shop
                    {
                        Name = "商品"+i.ToString(),
                        Guid = Guid.NewGuid().ToString("N"),
                        Price = i + 0.66M,
                        AddTime = DateTime.Now
                    };
                    db.Shop.AddRange(model);
                }
                db.SaveChanges();
            }
        }
    }
}
复制代码
复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
//添加命名空间
using Microsoft.Extensions.DependencyInjection;
using CoreRazorPages.Data;

namespace CoreRazorPages
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = CreateHostBuilder(args).Build();
            using (var scope=host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    AddDefaultData.InitData(services);
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "添加初始数据失败");
                }

            }
                host.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}
复制代码

数据库依赖注入在staup.cs里面已经自己生成好了

复制代码
public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
            //数据库连接
            services.AddDbContext<CoreRazorPagesContext>(options =>
                    options.UseSqlServer(Configuration.GetConnectionString("CoreRazorPagesContext")));
        }
复制代码

然后启动 点到商品导航上面数据就有了,也可以一些基本的操作(CRUD)

4、添加搜索

添加名称,guid的搜索,在Pages文件夹下面的Shop文件夹下面的Index.cchtml.sc打开 修改

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using CoreRazorPages.Data;
using CoreRazorPages.Models;

namespace CoreRazorPages.Pages.Shop
{
    public class IndexModel : PageModel
    {
        private readonly CoreRazorPages.Data.CoreRazorPagesContext _context;

        public IndexModel(CoreRazorPages.Data.CoreRazorPagesContext context)
        {
            _context = context;
        }
        /// <summary>
        /// 模型List
        /// </summary>
        public IList<CoreRazorPages.Models.Shop> Shop { get;set; }
        [BindProperty(SupportsGet = true)]
        public string SearchName { get; set; }
        [BindProperty(SupportsGet = true)]
        public string SearchGuid { get; set; }
        public async Task OnGetAsync()
        {
            var model = _context.Shop.Where(x=>x.ID>0);
            if (!string.IsNullOrEmpty(SearchName))
                model = model.Where(x => x.Name.Contains(SearchName));
            if (!string.IsNullOrEmpty(SearchGuid))
                model = model.Where(x => x.Guid.Contains(SearchGuid));
            Shop = await model.ToListAsync();
        }
    }
}
复制代码

在Index.cshtml里面添加

复制代码
<p>
    <a asp-page="Create">Create New</a>
</p>
<form>
    <p>
        名字: <input type="text" asp-for="SearchName" />
        Guid: <input type="text" asp-for="SearchGuid" />
        <input type="submit" value="搜索" />
    </p>
</form>
复制代码

SearchName 、SearchGuid:包含用户在搜索文本框中输入的文本。 SearchString 也有 [BindProperty] 属性。 [BindProperty] 会绑定名称与属性相同的表单值和查询字符串。 在 GET 请求中进行绑定需要 (SupportsGet = true)

5、添加字段,验证

5.1在模型文件Shop.cs添加一个字段Remark

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace CoreRazorPages.Models
{
    /// <summary>
    /// 商品类
    /// </summary>
    public class Shop
    {
        
        [Display(Name="ID")]
        public int ID { get; set; }
        [Display(Name = "Guid")]
        public string Guid { get; set; }
        [Display(Name = "名称")]
        public string Name { get; set; }
        [Display(Name = "价格")]
        public decimal Price { get; set; }
        [Display(Name = "添加日期")]
        [DataType(DataType.Date)]
        public DateTime AddTime { get; set; }
        /// <summary>
        /// 新加字段、验证
        /// </summary>
        [Required]//必填
        [StringLength(10,ErrorMessage ="{0}最大长度10  必填")]//最大长度
        [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]//验证格式
        public string Ramake { get; set; }
    }
}
复制代码

 

 5.2nuget包管理器>程序包管理控制台依次输入Add-Migration Remake、Update-Database,

 

 

 

 5.3、编辑Shop文件夹下面的Index.cshtml、Create.cshtml、Delete.cshtml、Details.cshtml、Edit.cshtml文件添加Remake字段

复制代码
 <td>
                @Html.DisplayFor(modelItem => item.Ramake)
            </td>



 <div class="form-group">
                <label asp-for="Shop.Ramake" class="control-label"></label>
                <input asp-for="Shop.Ramake" class="form-control" />
                <span asp-validation-for="Shop.Ramake" class="text-danger"></span>
            </div>




 <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Shop.Ramake)
        </dd>




<div class="form-group">
                <label asp-for="Shop.Ramake" class="control-label"></label>
                <input asp-for="Shop.Ramake" class="form-control" />
                <span asp-validation-for="Shop.Ramake" class="text-danger"></span>
            </div>
复制代码

 

5.4、运行起来测试看

6、代码总结:

  • Razor 页面派生自 PageModel。 按照约定,PageModel 派生的类称为 <PageName>Model。 此构造函数使用依赖关系注入将 RazorPagesMovieContext 添加到页。 所有已搭建基架的页面都遵循此模式,对页面发出请求时,OnGetAsync 方法向 Razor 页面返回影片列表。 调用 OnGetAsync 或 OnGet 以初始化页面的状态。 在这种情况下,OnGetAsync 将获得影片列表并显示出来。当 OnGet 返回 void 或 OnGetAsync 返回 Task 时,不使用任何返回语句。 当返回类型是 IActionResult 或 Task<IActionResult> 时,必须提供返回语句
  • @page 指令:@page Razor 指令将文件转换为一个 MVC 操作,这意味着它可以处理请求。 @page 必须是页面上的第一个 Razor 指令。 @page 是转换到 Razor 特定标记的一个示例
  • @model 指令指定传递给 Razor 页面的模型类型。 在前面的示例中,@model 行使 PageModel 派生的类可用于 Razor 页面。 在页面上的 @Html.DisplayNameFor 和 @Html.DisplayFor HTML 帮助程序中使用该模型。

现在开始对Razor页面有一定了解,大多数代码都是系统自己生成的,数据库上下文类、依赖注入、我也是对Core还不怎么熟悉不知道怎么学习跟着做就行了,对后面肯定有很大的帮助。

原文地址:https://www.cnblogs.com/w5942066/p/12597712.html

Asp.Net Core 3.1学习-初始.Net Core与VS Code 第一个web程序(1) - 魏杨杨 - 博客园

mikel阅读(598)

来源: Asp.Net Core 3.1学习-初始.Net Core与VS Code 第一个web程序(1) – 魏杨杨 – 博客园

1、.Net Core介绍

.NET Core是.NET Framework的新一代版本, 是微软开发的第一个具有跨平台(Windows、Macosx、Linux) 能力的应用程序开发框架,未来也将会支持FreeBSD与Alpine平台,是微软在一开始发展时就开源的软件平台,它也经常被拿来和现有的开源NET平台Mono比较。
由于.NET Core的开发目标是跨平台的.NET平台,因此.NET Core会包含.NET Framework的类库。与.NET Framework不同的是,.NET Core 采用包化(Packages) 的管理方式,应用程序只需获取需要的组件即可。与.NET Framework大包式安装的做法截然不同,并且各包亦有独立的版本线,不再硬性要求应用程序跟随主线版本。

2、.Net Core跨平台

.Net Core 拥有跨平台能力,并支持多种系统,让我们开大的程序可以在多个系统中运行。.Net支持的操作系统:Windows 客户端、Windows 服务端、Debian、Red Hat Enterpise Linux、 Fedora、 Ubuntu、 Linux Mint、 OpenSUSE、 Oracle Linux、 CentOS、 Mac OSX

3、 .Net Core SDK 下载安装

下载链接  点击就下载了3.0.1.00 也可以去管网下载 https://dotnet.microsoft.com/download

下载下来微软的就是傻瓜式安装,位置就默认了装在C盘了,下一步下一步就好了 哈哈。安装好了 win+R 输入cmd 打开命令提示符输入 dotnet –info 看到下面的信息就证明安装好了

 4、 dotnet命令

一般的命令在命令提示符里面直接输入dotnet -all 查看

 

比如新建一个项目 它提示了是 new 怎么搞呢? 不知道就是在来一次 dotnet new -all 可以看到很多创建的命令 都有提示  表头的意思: 模板、短名称、语言、标记 下面创建一个控制台应用程序

进入文件夹的命令自己搜吧 创建控制台应用程序是dotnet new console -n 名称     后面要接语言的话后面加上 -lang f#这样

 

运行 dotnet run 的时候先要进去到创建项目的文件夹里面  其他命令有兴趣的自己试试   感觉跟git一样 哈哈。

 5、在VS Code中创建.Net Core项目

VS codel下载地址:https://code.visualstudio.com/download      根据版本选择自己的下载 我的是Windows 当然安装过VSCode 的就忽略这一步。安装好了打开安装插件 快捷键(Ctrl+Shift+X)或者点击左上一排的倒数第二个按钮 搜索 C#(包括语法高亮显示、智能感知、定义、查找所有引用等。调试支持。网络核心(CoreCLR)。) 安装一下 ,一般语言都是中文的 可能有的是英文的就还要安装一个简体中文包Chinese (Simplified) 。安装好了重启VS Code。

接下来用dotnet new 创建一个.Net Core web程序  ,输入了命令要稍等一下 ,有点慢 (可能我的机子慢 0.0)。 创建好了 命令进入创建的文件夹WebFirst 输入code . 用VScode快速打开,并加载当前目录。你也可以打开VS Code左上角打开文件夹来完成这一部操作。

 

 

首次打开的时候会提示我们添加VS Code配置文件,选择Yes就好了,等出现.vscode文件夹后就可以开发、调试程序了。按F5或者菜单:调试->启动调试启动项目  出现错误咯

遇到问题不要慌 看看 。这是因为net core2.1默认使用的https,如果使用Kestrel web服务器的话没有安装证书就会报这个错。其实仔细看他的错误提示,其中有一句叫你执行一个命令安装证书的语句: dotnet dev-certs https –trust 

安装就是了  这是启动就浏览器就出现了 Hello Word!好熟悉的感觉  你要相信这是真的 这就是.Net Core 的一个程序。神奇吧。

5.1关于 VS Code C#插件配置介绍

使用VS Code打开.Net Core 项目文件夹后悔根据提示生成VS Code所需要的的配置文件,就是刚才说的C#配置文件。在.vscode文件夹下面有两个文件 :launch.json和tasks.json。tasks.json是用于配置执行那些命令行命令来构建项目,launch.json配置需要使用的调试器的类型。有了这两个文件 VS Code就知道如何构建调式了。

复制代码
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build",
            "command": "dotnet",
            "type": "process",
            "args": [
                "build",
                "${workspaceFolder}/Co.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "publish",
            "command": "dotnet",
            "type": "process",
            "args": [
                "publish",
                "${workspaceFolder}/Co.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "problemMatcher": "$msCompile"
        },
        {
            "label": "watch",
            "command": "dotnet",
            "type": "process",
            "args": [
                "watch",
                "run",
                "${workspaceFolder}/Co.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary"
            ],
            "problemMatcher": "$msCompile"
        }
    ]
}
复制代码

节点介绍:

command节点在这里表示使用dotnet命令;

args 在这里是参数对应路径dotnet.csproj 及build

结合起来就相当于指向了dotnet build 路径\dotnet.csproj

复制代码
{
   // Use IntelliSense to find out which attributes exist for C# debugging
   // Use hover for the description of the existing attributes
   // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
   "version": "0.2.0",
   "configurations": [
        {
            "name": ".NET Core Launch (web)",
            "type": "coreclr",
            "request": "launch",
            "preLaunchTask": "build",
            // If you have changed target frameworks, make sure to update the program path.
            "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/Co.dll",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
            "serverReadyAction": {
                "action": "openExternally",
                "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"                
            },
            "env": {
                "ASPNETCORE_ENVIRONMENT": "Development"
            },
            "sourceFileMap": {
                "/Views": "${workspaceFolder}/Views"
            }
        },
        {
            "name": ".NET Core Attach",
            "type": "coreclr",
            "request": "attach",
            "processId": "${command:pickProcess}"
        }
    ]
}
复制代码

节点介绍:(这里主要是configurations节点下)

program: 这里表示程序build以后生成的dll,默认是bin\Debug\框架\项目名称.dll

args:用样式参数,这个参数可以传递到程序里

cwd:代码的目录

访问的时候是https不信的试试看,要改成http。打开Properties/launchSettings.json文件

复制代码
{
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:22028",
      "sslPort": 44383
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "WebFirst": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "https://localhost:5001;http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}
复制代码

 

iisSettings、profiles.helloweb配置节点都有启动绑定配置,因为VS Code启动项目默认是不通过IIS来host的,iisSettings选项我们忽略就可以了。将applicationUrl修改为http://localhost:5001 重启就看到了是http协议了

5.2项目结构说明:

 

 

 

 

 

.vscode目录:VS Code项目配置目录,相当于.vs、.idea文件夹;

bin:编译输出目录;

obj:编译配置与中间目录,用于存放编译配置与编译中间结果;

Properties:用于存放项目配置;

wwwroot:存放静态文件(JS,css,img等);

WebFirst.csproj:项目描述文件;

Program.cs文件:应用程序入口类文件

Startup.cs文件:ASP.NET Core Web应用启动类文件,用于项目启动前进行相关配置

 6、总结

现在大致已经会在VS Code建一个.Net Core应用程序了,我门的目的不是这么简单,接下来就是在VS 2017 里面开始学习 ASP.NET Core 了(Asp.Net Core ==.Net Core ??),重点介绍一下Program.cs 跟Startup.cs这两个文件,我觉得现在不知道他们是什么意思没事 接下来慢慢学吧只有知道了就好写程序了吧。哪里不对的多多指教^_^

本文链接 https://www.cnblogs.com/w5942066/p/12195984.html

Asp.Net Core 3.1学习- 应用程序的启动过程(5) - 魏杨杨 - 博客园

mikel阅读(652)

来源: Asp.Net Core 3.1学习- 应用程序的启动过程(5) – 魏杨杨 – 博客园

本文主要讲的是ASP.NET Core的启动过程,帮助大家掌握应用程序的关键配置点。

1、创建项目

1.1、用Visual Studio 2019 创建WebApi项目。

 

 

 

 

 

 

这里面可以看到有两个关键的类。 一个Program,一个stsrtup

Program里面有一个Main函数,Main函数里面会调用我们的CreateHosbuilder这个方法,CreateHosbuilder里面返回了一个IHostBuilder,那么IHostBuilder就是我们应用程序启动的核心接口

1.2、我们转到定义看一下IHostBuilder这个接口的定义

 

这个接口主要有6个方法这里面我们需要关注的是:ConfigureAppConfiguration、ConfigureHostConfiguration、和ConfigureServices

为了演示整个应用程序的启动过程我们回到Program,修改CreateHostBuilder 在里面添加一些代码,把方法名打印出来

复制代码
 public class Program
    {
        public static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                //委托入参是:IConfigurationBuilder
                .ConfigureAppConfiguration(builder =>
                {
                    Console.WriteLine("ConfigureAppConfiguration");
                })
                //入参 IServiceCollection
                .ConfigureServices(services =>
                {
                    Console.WriteLine("ConfigureServices");
                })
                //入参IConfigurationBuilder
                .ConfigureHostConfiguration(builder =>
                {
                    Console.WriteLine("ConfigureHostConfiguration");
                })
                //入参 IWebHostBuilder
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    Console.WriteLine("ConfigureWebHostDefaults");
                    webBuilder.UseStartup<Startup>();
                });
    }
复制代码

 

Starup里面的主要有三个方法 构造行数、ConfigureServices、Configure 同样的打印出来

复制代码
public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Console.WriteLine("Startup构造函数");
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

    
        public void ConfigureServices(IServiceCollection services)
        {
            Console.WriteLine("Startup.ConfigureServices");
            
            services.AddControllers();
        }

      
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            Console.WriteLine("Startup.Configure");
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
复制代码

 

启动应用程序看看,顺序我标注在下面了,Starup里面的是按顺序启动的

 

如果应用程序启动起来没有输出控制台的修改一下启动  选择项目名称 不要选择IIS 我在次研究了半天哈哈

 

修改一下里面顺序看看,可以发现执行的顺序与之前略微不同,就是ConfigureService这一行跑到了Startup.ConfigureServices之后。

 

 

2、结论

这些委托注册进去之后都是按一定顺序来执行的

 

整个启动过程分为5个阶段:

2.1、ConfigureWebHostDefaults

这个阶段注册了我们应用程序必要的几个组件,比如说配置的组件、容器的组件

2.2、ConfigureHostConfiguration

它是用来配置我们应用程序启动时必要的配置。比如说我们应用程序启动时所需要的监听的端口、我们需要监听的Url地址这些。在这个过程我们可以嵌入一些我们自己的配置内容注入到我们的配置的框架中去

 

 2.3、ConfigureAppConfiguration

是让我们来嵌入我们自己的配置文件供应用程序来读取,这些配置将来就会在后续的应用程序执行过程中间每个组件读取

2.4、ConfigureServices、ConfigureLogging、Startup、Startup.ConfigureServices

这些的话都是用来往容器里面注入我们的应用的组件

2.5、Startup.Configure

是我们用来注入我们的中间件,处理HttpContext整个请求过程的

3、Startup类非必要

Startup这个类其实不是必要的,在Progarm里面也可以配置,

复制代码
 public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                //入参 IWebHostBuilder
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    Console.WriteLine("ConfigureWebHostDefaults");
                    // webBuilder.UseStartup<Startup>();
                    webBuilder.ConfigureServices(services =>//对等Startup里面的Startup.ConfigureServices
                    {
                        services.AddControllers();
                    })
                    .Configure(app =>// 对等Startup里面的Startup.Configure
                    {
                        //if (env.IsDevelopment())
                        //{
                        //    app.UseDeveloperExceptionPage();
                        //}

                        app.UseHttpsRedirection();

                        app.UseRouting();

                        app.UseAuthorization();

                        app.UseEndpoints(endpoints =>
                        {
                            endpoints.MapControllers();
                        });
                    });
                })
                //入参 IServiceCollection
                .ConfigureServices(services =>
                {
                    Console.WriteLine("ConfigureServices");
                })
                //委托入参是:IConfigurationBuilder
                .ConfigureAppConfiguration(builder =>
                {
                    Console.WriteLine("ConfigureAppConfiguration");
                })
               
                //入参IConfigurationBuilder
                .ConfigureHostConfiguration(builder =>
                {
                    Console.WriteLine("ConfigureHostConfiguration");
                }) ;
复制代码

 

这样应用程序一样的可以启动起来 这只是为了规范建议还是留着哈哈

记得添加命名空间

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

参考视频链接:https://time.geekbang.org/course/intro/272?code=PuP-H4FsFaRyMFC-qRHJndCmpGGNZ64zelNHZOYI1R8%3D

原文地址 https://www.cnblogs.com/w5942066/p/12803556.html

使用 JMeter 进行压力测试 - 晓晨Master - 博客园

mikel阅读(750)

来源: 使用 JMeter 进行压力测试 – 晓晨Master – 博客园

一.前言

压力测试是每一个Web应用程序上线之前都需要做的一个测试,他可以帮助我们发现系统中的瓶颈问题,减少发布到生产环境后出问题的几率;预估系统的承载能力,使我们能根据其做出一些应对措施。所以压力测试是一个非常重要的步骤,下面我带大家来使用一款压力测试工具JMeter。

二.关于JMeter

Apache JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试,它最初被设计用于Web应用测试,但后来扩展到其他测试领域。 它可以用于测试静态和动态资源,例如静态文件、Java 小服务程序、CGI 脚本、Java 对象、数据库、FTP 服务器, 等等。JMeter 可以用于对服务器、网络或对象模拟巨大的负载,来自不同压力类别下测试它们的强度和分析整体性能。另外,JMeter能够对应用程序做功能/回归测试,通过创建带有断言的脚本来验证你的程序返回了你期望的结果。为了最大限度的灵活性,JMeter允许使用正则表达式创建断言。
Apache jmeter 可以用于对静态的和动态的资源(文件,Servlet,Perl脚本,java 对象,数据库和查询,FTP服务器等等)的性能进行测试。它可以用于对服务器、网络或对象模拟繁重的负载来测试它们的强度或分析不同压力类型下的整体性能。你可以使用它做性能的图形分析或在大并发负载测试你的服务器/脚本/对象。

官网:http://jmeter.apache.org/download_jmeter.cgi

这里我选用了 4.0 版本的二进制包:http://mirror.bit.edu.cn/apache//jmeter/binaries/apache-jmeter-4.0.zip

更多内容介绍 https://baike.baidu.com/item/Jmeter/3104456

三.准备工作

因为JMeter是使用JAVA写的,所以使用JMeter之前,先安装JAVA环境,本文就不讲不如安装JAVA环境了。.

JAVA环境变量配置:https://jingyan.baidu.com/article/fd8044fa2c22f15031137a2a.html

解压下载的二进制包,进入bin目录,使用jmeter.bat启动程序。

启动之后会有两个窗口,一个cmd窗口,一个JMeter的 GUI。前面不要忽略CMD窗口的提示信息:

JMeter:

CMD窗口的提示信息

================================================================================
Don't use GUI mode for load testing !, only for Test creation and Test debugging.
For load testing, use NON GUI Mode:
   jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]
& increase Java Heap to meet your test requirements:
   Modify current env variable HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m" in the jmeter batch file
Check : https://jmeter.apache.org/usermanual/best-practices.html
================================================================================

上面的意思就是:不要使用GUI运行压力测试,GUI仅用于压力测试的创建和调试;执行压力测试请不要使用GUI。使用下面的命令来执行测试:

 jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

并且修改JMeter批处理文件的环境变量:HEAP="-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m"

更改语言为中文

官方默认为我们提供了简体中文。通过 【Options】->【Choose Language】变更为简体中文

四.创建测试

1.创建线程组

在“测试计划”上右键 【添加】–>【Threads(Users)】–>【线程组】。

设置线程数和循环次数。我这里设置线程数为500,循环一次。

2.配置元件

在我们刚刚创建的线程组上右键 【添加】–>【配置元件】–>【HTTP请求默认值】。

配置我们需要进行测试的程序协议、地址和端口

当所有的接口测试的访问域名和端口都一样时,可以使用该元件,一旦服务器地址变更,只需要修改请求默认值即可。

3.构造HTTP请求

在“线程组”右键 【添加-】->【samlper】–>【HTTP 请求】设置我们需要测试的API的请求路径和数据。我这里是用的json

4.添加HTTP请求头

在我们刚刚创建的线程组上右键 【添加】–>【配置元件】–>【HTTP信息头管理器】。

因为我要传输的数据为json,所以设置一个 Content-Type:application/json

5.添加断言

在我们刚刚创建的线程组上右键 【添加】–>【断言】–>【响应断言】。

根据响应的数据来判断请求是否正常。我在这里只判断的响应代码是否为200。还可以配置错误信息

6.添加察看结果树

在我们刚刚创建的线程组上右键 【添加】–>【监听器】–>【察看结果树】。

直接添加,然后点击运行按钮就可以看到结果了。

7.添加Summary Report

在我们刚刚创建的线程组上右键 【添加】–>【监听器】–>【Summary Report】。

直接添加,然后点击运行按钮就可以看到结果了。

为了不引起不必要的争论,隐藏了TPS。此数据不具备任何价值,仅仅为文章演示。

8.测试计划创建完成

记得点保存。

五.执行测试计划

前面我们说过,执行测试计划不能用GUI,需要用命令行来执行。

我这里执行的命令为:

jmeter -n -t testplan/RedisLock.jmx -l testplan/result/result.txt -e -o testplan/webreport

说明:

testplan/RedisLock.jmx 为测试计划文件路径
testplan/result/result.txt 为测试结果文件路径
testplan/webreport 为web报告保存路径。

Web报告如下:

六.写在最后

线程数量和循环次数将会影响最终的测试报告,请大家多多测试。

asp.net core 3.x 授权默认流程,

mikel阅读(678)

ASP.NET core 3.x 授权默认流程,

 

一、前言

接上一篇《ASP.NET core 3.x 授权中的概念》,本篇看看ASP.NET core默认授权的流程。从两个方面来看整个授权系统是怎么运行的:启动阶段的配置请求阶段中间件的处理流程

由于asp.net core 3.x目前使用终结点路由,因此授权框架可以用于所有asp.net web项目类型,比如:webapi mvc razorpages…。但本篇只以MVC为例

二、核心概念关系图

 

三、启动阶段的配置

主要体现为3点

 

3.1、注册相关服务和选项配置

在mvc项目Startup.ConfigreServices中services.AddControllersWithViews(); (MvcServiceCollectionExtensions)用来向依赖注入框架注册各种mvc相关服务。其中会调用services.AddAuthorization(选项)扩展方法(PolicyServiceCollectionExtensions)注册授权相关服务,此扩展方法内部还会调用两个扩展方法,这里不再深入。

这里主要需要搞懂2个问题:

3.1.1、授权选项AuthorizationOptions

AddAuthorization扩展方法的参数是Action<AuthorizationOptions>类型的,这是asp.net core中典型的选项模型,将来某个地方需要时,直接注入此选项对象,那时依赖注入容器会使用此委托对这个选项对象赋值。所以我们在启动时可以通过此对象来对授权框架进行配置。

最最重要的是我们可以在这里配置全局授权策略列表,参考上图的右侧中间部分,源码不多,注意注释。

 1 //代表授权系统的全局选项对象,里面最最核心的就是存储着全局授权策略 2 public class AuthorizationOptions 3 { 4 //存储全局授权策略(AuthorizationPolicy),key是策略唯一名,方便将来获取 5 private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase); 6 //授权验证时,将遍历所有授权处理器(Authorization)逐个进行验证,若某个发生错误是否立即终止后续的授权处理器的执行 7 public bool InvokeHandlersAfterFailure { get; set; } = true; 8 //默认授权策略,拒绝匿名访问 9 public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); 10 //若将来授权检查时没有找到合适的授权策略,默认授权策略也是空的情况下会回退使用此策略 11 public AuthorizationPolicy FallbackPolicy { get; set; } 12 //添加全局策略 13 public void AddPolicy(string name, AuthorizationPolicy policy) 14 { 15 if (name == null) 16 { 17 throw new ArgumentNullException(nameof(name)); 18 } 19 20 if (policy == null) 21 { 22 throw new ArgumentNullException(nameof(policy)); 23 } 24 25 PolicyMap[name] = policy; 26 } 27 //添加全局策略,同时可以对此策略进行配置 28 public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy) 29 { 30 if (name == null) 31 { 32 throw new ArgumentNullException(nameof(name)); 33 } 34 35 if (configurePolicy == null) 36 { 37 throw new ArgumentNullException(nameof(configurePolicy)); 38 } 39 40 var policyBuilder = new AuthorizationPolicyBuilder(); 41 configurePolicy(policyBuilder); 42 PolicyMap[name] = policyBuilder.Build(); 43 } 44 //获取指定名称的策略 45 public AuthorizationPolicy GetPolicy(string name) 46 { 47 if (name == null) 48 { 49 throw new ArgumentNullException(nameof(name)); 50 } 51 52 return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null; 53 } 54 } AuthorizationOptions

举个栗子:

 1 services.AddControllersWithViews(); 2 services.AddRazorPages(); 3 services.AddAuthorization(opt => 4 { 5 opt.AddPolicy(“授权策略1”, builer => { 6 builer.RequireRole(“admin”, “manager”); 7 builer.AddAuthenticationSchemes(“cookie”, “google”); 8 //继续配置…. 9 }); 10 opt.AddPolicy(“授权策略2”, builer => { 11 builer.RequireRole(“teacher”); 12 builer.AddAuthenticationSchemes(“wechat”, “qq”); 13 //继续配置…. 14 }); 15 }); View Code

 

3.1.2、具体注册了哪些服务:

  • 策略评估器IPolicyEvaluator:名字有点诡异。默认实现PolicyEvaluator,授权中间件委托它来实现身份验证和授权处理,它内部会调用AuthorizationService,进而执行所有授权处理器AuthorizationHandler
  • 授权服务IAuthorizationService:上一篇有说,不详述了,默认实现DefaultAuthorizationService,除了授权中间件会调用它来进行授权处理,我们业务代码中也可以调用它来做授权验证,比如:针对资源的特殊授权
  • 授权策略提供器IAuthorizationPolicyProvider:默认实现DefaultAuthorizationPolicyProvider,可以通过它来获取指定名称的授权,它就是从全局授权策略列表里去找,也就是上面说的AuthorizationOptions中
  • 授权处理器提供器IAuthorizationHandlerProvider:默认实现DefaultAuthorizationHandlerProvider,通过它来获取系统中所有的授权处理器,其实就是从IOC容器中获取
  • 授权评估器IAuthorizationEvaluator:默认实现DefaultAuthorizationEvaluator,授权处理器AuthorizationHandler在执行完授权后,结果是存储在AuthorizationHandlerContext中的,这里的评估器只是根据AuthorizationHandlerContext创建一个授权结果AuthorizationResult,给了我们一个机会来加入自己的代码而已
  • 授权处理器上下文对象的工厂IAuthorizationHandlerContextFactory:默认实现DefaultAuthorizationHandlerContextFactory,授权处理器AuthorizationHandler在授权时需要传入AuthorizationHandlerContext(上面说了授权完成后的结果也存储在里面)。所以在执行授权处理器之前需要构建这个上下文对象,就是通过这个工厂构建的,主要的数据来源就是 当前 或者  指定的  授权策略AuthorizationPolicy
  • 授权处理器IAuthorizationHandler:默认实现PassThroughAuthorizationHandler。授权的主要逻辑在授权处理器中定义,授权服务在做授权时会遍历系统所有的授权处理器逐一验证,而验证往往需要用到授权依据,PassThroughAuthorizationHandler比较特殊,它会看授权依据是否已经实现了IAuthorizationHandler,如过是,则直接把授权依据作为授权处理器进行执行。因为多数情况下一个授权处理器类型是专门针对某种授权依据定义的。

这些接口都是扩展点,就问你怕不怕?当然绝大部分时候我们不用管,默认的就足够用了。

 

3.2、注册授权中间件

主要注意的位置的为题,必须在路由和身份验证之后。

1 app.UseRouting();
2 app.UseAuthentication();
3 app.UseAuthorization();

扩展方法内部注册了AuthorizationMiddleware

 

四、请求阶段的处理流程

如果你对mvc稍有经验,就晓得在一个Action上使用[Authorize]就可以实施授权,现在我们假设我们在默认mvc项目中的HomeController定义如下Action,并应用授权标签

1         [Authorize(Policy = "p1")]//使用全局授权策略中的"p1"进行授权判断
2         [Authorize(AuthenticationSchemes = "google")]//只允许使用google身份验证登录的用户访问
3         [Authorize(Roles = "manager")]//只允许角色为manager的访问
4         public IActionResult Privacy()
5         {
6             return View();
7         }

4.1、授权中间件AuthorizationMiddleware

核心步骤如下:

步骤1、2得益于asp.net core 3.x的终结点路由,我们可以在进入MVC框架前就拿到Action及其之上应用的各种Atrribute,从而得到我们对当前授权策略定制所需要的数据

步骤3会根据得到IAuthorizeData集合(AuthorizeAttribute实现了IAuthorizeData,它不再是一个过滤器)创建当前准备用来做授权的策略。授权策略中 “身份验证方案列表” 和 “授权依据列表”,就是通过这里的标签来的。具体来说:

  • [Authorize(Policy = “p1”)]:会通过“p1”去全局授权策略(AuthorizationOptions对象中)拿到对应的策略,然后与当前策略合并,也就是把“p1”策略中的身份验证方案列表、授权依据列表与当前策略合并。
  • [Authorize(AuthenticationSchemes = “google”)]:用来定制当前策略的“身份验证方案列表”,当然最终和上面说的合并,
  • [Authorize(Roles = “manager”)]:会创建一个RolesAuthorizationRequirement类型的授权依据加入到当前策略中

这些Attribute可以应用多次,最终都是来定制当前授权策略的。后续步骤都会依赖此授权策略。

步骤4中,若发现本次授权策略中定义了多个身份验证方案,则会注意进行身份验证,得到的多张证件会合并到当前用户HttpContext.User中,当然默认身份验证得到的用户信息也在其中。

上面步骤4、6是委托策略评估器PolicyEvaluator来完成的,往下看..

 

4.2、策略评估器PolicyEvaluator

核心任务就两个,身份验证、进行授权

4.2.1、AuthenticateAsync

若策略没有设置AuthenticationSchemes,则只判断下当前请求是否已做身份验证,若做了就返回成功
若策略设置了AuthenticationSchemes,则遍历身份验证方案逐个进行身份验证处理 context.AuthenticateAsync(scheme); ,将所有得到的用户标识重组成一个复合的用户标识。

4.2.2、AuthorizeAsync

调用IAuthorizationService进行权限判断,若成功则返回成功。
否则     若身份验证通过则 PolicyAuthorizationResult.Forbid() 直接通知身份验证方案,做拒绝访问处理;否则返回质询

所以授权检查的任务又交给了授权服务AuthorizationService

 

4.3、授权服务AuthorizationService

核心步骤如下:

步骤2还会判断AuthorizationOptios.InvokeHandlersAfterFailure,来决定当某个处理器发生错误时,是否停止执行后续的授权处理器。

 

4.4、授权处理器AuthorizationHandler

前面讲过,默认只注册了一个PassThroughAuthorizationHandler授权处理器,它会遍历当前授权策略中实现了IAuthorizationHandler的授权依据(意思说这些对象既是授权处理器,也是授权依据)。直接执行它们。

public class PassThroughAuthorizationHandler : IAuthorizationHandler
{
    public async Task HandleAsync(AuthorizationHandlerContext context)
    {
        foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>())
        {
            await handler.HandleAsync(context);
        }
    }
}

以基于角色的授权依据RolesAuthorizationRequirement为例,它继承于AuthorizationHandler<RolesAuthorizationRequirement>,且实现IAuthorizationRequirement

 1 public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
 2 {
 3     //省略部分代码...
 4     public IEnumerable<string> AllowedRoles { get; }
 5     protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
 6     {
 7         if (context.User != null)
 8         {
 9             bool found = false;
10             if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
11             {
12                 // Review: What do we want to do here?  No roles requested is auto success?
13             }
14             else
15             {
16                 found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
17             }
18             if (found)
19             {
20                 context.Succeed(requirement);
21             }
22         }
23         return Task.CompletedTask;
24     }
25 }

 

五、最后

可以感受到asp.net core 3.x目前的权限设计棒棒哒,默认的处理方式已经能满足大部分需求,即使有特殊需求扩展起来也非常简单,前面注册部分看到注册了各种服务,且都有默认实现,这些服务在授权检查的不同阶段被使用,如果有必要我们可以自定义实现某些接口来实现扩展。本篇按默认流程走了一波,最好先看前一篇。这时候再去看官方文档或源码应该更容易。日常开发使用其实参考官方文档就足够了,无非就是功能权限和数据权限,看情况也许不会写后续的应用或扩展篇了。