.Net Core 通过依赖注入和动态加载程序集实现宿主程序和接口实现类库完全解构..._weixin_34128839的博客-CSDN博客

mikel阅读(652)

来源: .Net Core 通过依赖注入和动态加载程序集实现宿主程序和接口实现类库完全解构…_weixin_34128839的博客-CSDN博客

网上很多.Net Core依赖注入的例子代码,例如再宿主程序中要这样写:

services.AddTransient<Interface1, Class1>();

其中Interface1是接口,Class1是接口的实现类,一般我们会将接口项目和实现类项目分开成两个项目以实现解耦。

但这段代码却要求宿主程序要引用实现类项目,所以这里的解构实现的并不彻底,要完全解耦就是要实现宿主程序不引用实现类项目。
或者把注入的代码改成这样:

 services.Add(new ServiceDescriptor(serviceType: typeof(Interface1),
                                          implementationType: Type.GetType("ClassLibrary1.Class1, ClassLibrary1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"),
                                          lifetime: ServiceLifetime.Transient));

其实这段代码也要求宿主类引用实现类库项目,不然运行会出错,只有采用动态加载程序集的方式才能实现宿主程序不引用实现类:

 var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Users\pesu\source\repos\DynamicLoadDependencyInjectionConsoleApp\ClassLibrary1\bin\Debug\netcoreapp2.0\ClassLibrary1.dll");

上面代码将实现类库程序集动态加载到宿主程序,然后将注入的代码改成这样:

  services.Add(new ServiceDescriptor(serviceType: typeof(Interface1),
                                          implementationType: myAssembly.GetType("ClassLibrary1.Class1"),
                                          lifetime: ServiceLifetime.Transient));

其中ClassLibrary1.Class1是Interface1的实现类,完整代码如下:

using InterfaceLibrary;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Runtime.Loader;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var myAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"C:\Users\pesu\source\repos\DynamicLoadDependencyInjectionConsoleApp\ClassLibrary1\bin\Debug\netcoreapp2.0\ClassLibrary1.dll");
         
            IServiceCollection services = new ServiceCollection();
            //注入
            services.AddTransient<ILoggerFactory, LoggerFactory>();
           
            services.Add(new ServiceDescriptor(serviceType: typeof(Interface1),
                                          implementationType: myAssembly.GetType("ClassLibrary1.Class1"),
                                          lifetime: ServiceLifetime.Transient));

            //构建容器
            IServiceProvider serviceProvider = services.BuildServiceProvider();
            //解析
            serviceProvider.GetService<ILoggerFactory>().AddConsole(LogLevel.Debug);
            var cls = serviceProvider.GetService<Interface1>();
            cls.Say();
            Console.ReadKey();
        }
    }
}

输出:


这有什么用呢?

ASP.NET Core系列:读取配置文件 - libingql - 博客园

mikel阅读(812)

来源: ASP.NET Core系列:读取配置文件 – libingql – 博客园

1. 控制台应用

新建一个控制台应用,添加两个Package:

Install-Package Microsoft.Extensions.Configuration
Install-Package Microsoft.Extensions.Configuration.Json

1.1 单个配置文件

创建配置文件appsettings.json:

{
  "Data": "10000",
  "ConnectionStrings": {
    "DevContext": "开发库",
    "ProdContext": "生产库"
  }
}

读取配置文件:

using System;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");
            var config = builder.Build();

            Console.WriteLine(config["Data"]); // 配置键
            Console.WriteLine(config.GetSection("ConnectionStrings:DevContext").Value); // 分层键
        }
    }
}

通过ConfigurationBuilder对象来创建ConfigurationRoot对象,进行读取配置文件。

SetBasePath:设置配置文件基础路径

AddJsonFile:添加读取的Json文件

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");

配置文件可选及修改自动加载设置:

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");

配置键约定:

(1)键不区分大小写。 例如,ConnectionString 和 connectionstring 被视为等效键。

(2)分层键使用冒号分隔符 (:)

1.2 多个配置文件

AddJsonFile:添加多个配置文件。

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json")
    .AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);

{
  "Data": "10000",
  "ConnectionStrings": {
    "DefaultContext": "默认库"
  }
}

{
  "Data": "10000",
  "ConnectionStrings": {
    "DefaultContext": "开发库"
  }
}
using System;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .AddJsonFile("appsettings.Development.json");
            var config = builder.Build();

            Console.WriteLine(config.GetConnectionString("DefaultContext")); // 输出:开发库
        }
    }
}
using System;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.Development.json")
                .AddJsonFile("appsettings.json");
            var config = builder.Build();

            Console.WriteLine(config.GetConnectionString("DefaultContext")); // 输出:默认库
        }
    }
}

配置键相同时,读取选择最后一个添加的文件(AddJsonFile)。

若要读取所有添加文件的配置信息,可遍历ConfigurationRoot的Providers属性。

using System;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                .AddJsonFile("appsettings.Development.json");
            var config = builder.Build();

            foreach (var provider in config.Providers)
            {
                provider.TryGet("ConnectionStrings:DefaultContext", out string defaultContext);
                Console.WriteLine(defaultContext);
            }

            // 输出:默认库
            //      开发库
        }
    }
}

1.3 配置读取绑定

(1)Key读取(键值对)

{
  "AppID": 10000,
  "Node": {
    "ID": "1",
    "Text": "节点"
  }
}
var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");
var config = builder.Build();

var appID = config["AppID"];
var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json");
var config = builder.Build();

var nodeid = config.GetSection("Node").GetSection("ID").Value;

(2)绑定简单数据类型 GetValue<T>

添加Package:Microsoft.Extensions.Configuration.Binder

using System;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");
            var config = builder.Build();

            var appID = config.GetValue<int>("AppID");
            Console.WriteLine(appID);
        }
    }
}

(3)绑定到类对象  Get<T>

using System;
using System.Collections.Generic;
using System.Text;

namespace Libing.Core.ConsoleApp
{
    public class Node
    {
        public int ID { get; set; }

        public string Text { get; set; }
    }
}
using System;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json");
            var config = builder.Build();

            var node = config.GetSection("Node").Get<Node>();

            Console.WriteLine("{0}-{1}", node.ID, node.Text);
        }
    }
}

(4)绑定集合对象

{
  "Node": {
    "ID": 1,
    "Text": "节点",
    "Children": [
      {
        "ID": 11,
        "Text": "子节点1"
      },
      {
        "ID": 12,
        "Text": "子节点2"
      },
      {
        "ID": 13,
        "Text": "子节点3"
      }
    ]
  }
}
using System;

using System.Collections.Generic;

using System.IO;
using Microsoft.Extensions.Configuration;

namespace Libing.Core.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile(cfg =>
                {
                    cfg.Path = "appsettings.json";
                    cfg.Optional = false;
                    cfg.ReloadOnChange = true;
                });
            var config = builder.Build();

            var children = config.GetSection("Node:Children").Get<IEnumerable<Node>>();
            foreach (var child in children)
            {
                Console.WriteLine("{0}-{1}", child.ID, child.Text);
            }
        }
    }
}

2. API应用程序

新建API应用程序,在根目录下自动创建的Program.cs:

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

其中,CreateDefaultBuilder创建默认配置,包括:

(1)加载IConfiguration配置,读取文件appsettings.json;

(2)加载IConfiguration配置,读取文件appsettings.[EnvironmentName].json;

(3)……

2.1 ConfigureServices中键值读取

在根目录下文件Startup.cs中ConfigureServices()中采用键值对方式读取:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    int appid = Configuration.GetValue<int>("AppID");
    var nodes = Configuration.GetSection("Node:Children").Get<IEnumerable<Node>>();
}

2.2 ConfigureServices中使用Options

添加安装包:Microsoft.Extensions.Options.ConfigurationExtensions

{
  "Node": {
    "ID": 1,
    "Text": "NodeText"
  }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    services.AddOptions();
    services.Configure<Node>(Configuration.GetSection("Node"));
}

在控制器中,通过构造函数注入,读取配置。

public class LogController : ControllerBase
{
    private Node node;
    public LogController(IOptions<Node> option)
    {
        node = option.Value;
    }

    [HttpGet]
    public Node Get()
    {
        int id = node.ID;
        string text = node.Text;

        return node;
    }
}

2.3 读取自定义配置文件

在项目根目录下,新建配置文件 config.json:

{
  "AppID": "10000"
}

修改Program.js,增加读取config.json文件。

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            config.SetBasePath(Directory.GetCurrentDirectory());
            config.AddJsonFile("config.json", optional: true, reloadOnChange: true);
        })
        .UseStartup<Startup>();
}

在Startup.cs中调用:

public void ConfigureServices(IServiceCollection services)
{
    // 省略其它代码...
    string appid = Configuration.GetSection("AppID").Value;
}

2.4 独立类中读取配置

在.Net Framework中,ConfigurationManager类用于读取配置文件。但在.Net Core中没有该类,新建类ConfigurationManager用于读取配置文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using System.IO;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace Libing.Portal.Core.Api.Common.Configs
{
    public static class ConfigurationManager
    {
        public readonly static IConfiguration Configuration;

        static ConfigurationManager()
        {
            Configuration = new ConfigurationBuilder()
               .SetBasePath(Directory.GetCurrentDirectory())
               .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
               .Build();
        }

        public static T GetSection<T>(string key) where T : class, new()
        {
            return new ServiceCollection()
                .AddOptions()
                .Configure<T>(Configuration.GetSection(key))
                .BuildServiceProvider()
                .GetService<IOptions<T>>()
                .Value;
        }

        public static string GetSection(string key)
        {
            return Configuration.GetValue<string>(key);
        }
    }
}

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

mikel阅读(715)

来源: 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阅读(670)

来源: 在 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阅读(775)

来源: 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阅读(783)

来源: 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阅读(926)

来源: 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阅读(742)

来源: 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阅读(772)

来源: 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阅读(832)

来源: 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