.NET5从零基础到精通:全面掌握.NET5开发技能【第三章】

章节

第一章:https://www.cnblogs.com/kimiliucn/p/17613434.html
第二章:https://www.cnblogs.com/kimiliucn/p/17620153.html
第三章:https://www.cnblogs.com/kimiliucn/p/17620159.html


十三、权限验证

13.1-基于Seesion/Cookies的权限认证

为了拦截一些操作:

  • 传统的授权方式:Seesion、Cookies来完成:
    • 在请求某个Action之前来做校验,验证当前操作者是否登录过,登录过就有权限。
    • 如果没有权限就调到到登录页面去。
    • AOP-Filter、ActionResult

13.1.1-传统的登录需要匿名
当注册全局权限验证码的时候,需要将Login取消权限验证,不然会产生这个错误:
image.png
解决:
(1)在【CustomActionAuthrizaFilterAttribute】中写代码写上这个代码支持匿名,继承自Attribute,实现IActionFilter接口。
image.png
(2)在Login的Action写上这个特性。
image.png


13.2-基于鉴权授权

通过中间件来支持;
image.png
(1)在【Startup.cs】中创建的【Configure】方法中进行中间件,使用中间件使用在App.UseRouting()之后,在app.UseEndpoints()之前。

#region 第一步:告诉框架说我要使用鉴权授权功能
   	app.UseAuthentication();//鉴权:检测有没有登录,登录的是谁,赋值给User
  	app.UseAuthorization();//授权:检测有没有权限,是否能够访问后续的页面
#endregion

(2)在【Startup.cs】中创建的【ConfigureServices】方法中增加一个AddAuthentication。

#region 增加一个授权AddAuthentication
  //用Cookie
  services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
  {
        //如果授权是被,就跳转到这个路径中
       options.LoginPath = new PathString("Eighth/Login");
  });
#endregion

(3)指定哪些Action需要做鉴权授权,直接标记特性;可以标记在控制器、Action,也可以标记在全局。
image.png
(4)在控制器的Login登录方法中修改如下:

 #region 鉴权:鉴权,检测有没有登录,登录的是谁,赋值给User 
                    //rolelist 是登录成功后用户的角色---是来自于数据库的查询;不同的用户会查询出不同的角色;
                    var rolelist = new List<string>() {
                            "Admin",
                            "Teacher",
                            "Student"
                    };

                    //ClaimTypes.Role就是做权限认证的标识;
                    var claims = new List<Claim>()//鉴别你是谁,相关信息
                    {
                        new Claim(ClaimTypes.Role,"Admin"),
                        new Claim(ClaimTypes.Name,name),
                        new Claim("password",password),//可以写入任意数据
                        new Claim("Account","Administrator"),
                        new Claim("role","admin"),
                        new Claim("admin","admin"),
                    };
                    foreach (var role in rolelist)
                    {
                        claims.Add(new Claim(ClaimTypes.Role, role));
                    }
                    //将用户放在ClaimsPrincipal里面去了;相当身份证,将信息写到身份证里面去;
                    ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
                    HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, new AuthenticationProperties
                    {
                        ExpiresUtc = DateTime.UtcNow.AddMinutes(30),//过期时间:30分钟

                    }).Wait();
                    #endregion

image.png
(5)当注册到控制器上、或者注册到全局上,又会出现这样的问题。
image.png
解决:在Login的Action上加上匿名特性,这个匿名是来自于框架的;

  [AllowAnonymousAttribute] //匿名

13.3-鉴权授权-角色授权(目前看是将角色定义死了)

不同的用户,可能会存在不同的角色,然而不同的角色在访问不同的页面的时候,需要做不同的拦截。——角色授权其实就是通过角色不同,做不通的权限拦截。
1.保证上一个(13.2)代码是不变。
2.在Action需要加上对应拥有访问该视图的角色。
image.png
当访问【Index03】时,会被拦截,跳转到拦截页面:
image.png


13.4-鉴权授权-策略授权

之前的角色授权是在代码中把【角色】定义死了,我们更希望能够自己来完成校验逻辑:
(1)创建一个类为【CustomAuthorizationHandler】,这个类专用来检验逻辑的,要求继承自【AuthorizationHandler<>】泛型类(泛型类中要求实现【IAuthorizationRequirement】接口:见步骤2)然后在CustomAuthorizationHandler类中实现【HandleRequirementAsync】抽象方法,写逻辑。

public class CustomAuthorizationHandler : AuthorizationHandler<CustomAuthorizationRequirement>
    {

        public CustomAuthorizationHandler() { 

        }


        /// <summary>
        /// 实现抽象方法
        /// </summary>
        /// <param name="context"></param>
        /// <param name="requirement"></param>
        /// <returns></returns>
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement)
        {
            if (requirement.Name == "Policy01") { 
                //策略1的逻辑
            }
            if (requirement.Name == "Policy02")
            {
                //策略2的逻辑
            }
            if (requirement.Name == "Policy03")
            {
                //策略3的逻辑
            }
            if (true) { 
            
            }

            //在这里可以定义自己的规则
            { 
                /*
                 其实这里可以去数据库里面做一些查询,然后根据用户的信息,做计算:如果符合就context.Succd(requirement)
                否则就Task。CompletedTask
                 */
            }

            //context.User 鉴权成功(登录成功后),用户的信息
            var role = context.User.FindFirst(u => u.Value.Contains("admin"));
            if (role != null) {
                context.Succeed(requirement);//验证通过了
            }
            return Task.CompletedTask;//验证不通过
        }
    }

(2)创建一个为类【CustomAuthorizationRequirement】,改类实现【IAuthorizationRequirement】接口,用来放在AuthorizationHandler<>泛型类中。
image.png
(3)怎么让其生效?在【Startup.cs】中创建的【ConfigureServices】方法中注册支持策略授权,也可以支持多种策略认证。

            #region 支持多种策略认证
            services.AddAuthorization(options =>
            {
                options.AddPolicy("customPolicy", policy =>
                {
                    policy.AddRequirements(new CustomAuthorizationRequirement("Policy01"));
                });
            });
            services.AddAuthorization(options =>
            {
                options.AddPolicy("customPolicy01", policy =>
                {
                    policy.AddRequirements(new CustomAuthorizationRequirement("Policy02"));
                });
            });
            services.AddAuthorization(options =>
            {
                options.AddPolicy("customPolicy02", policy =>
                {
                    policy.AddRequirements(new CustomAuthorizationRequirement("Policy03"));
                });
            });
            #endregion


            //注册支持策略授权
            services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();

十四、跨平台本质

  • web应用程序是一个控制台;——Main程序的入口

image.png

  • 跨平台的主要原因:

跨平台的原因在于框架已经内置了一个主机,只要是程序启动,就是启动了主机,就可以监听端口;请求来了,只要是请求这个端口,主机就可以相应,所以无论是Windows还是在Linux下开发,都是要第一步完成环境的;这样就不同拘泥于在Windows还是Linux上。


十五、中间件

15.1-什么是中间件?

  • 中间件的执行,是一个俄罗斯套娃;
  • 先Use先执行,每一次Use一个中间件,其实就是在之前的基础上,套了一层;
  • 请求来了以后,真正执行的时候,是一层一层的内部执行,在执行出来。

增加程序的扩展性:
如果想要增加一层,直接增加一个中间件就可以来完成。


15.2-常用的中间件

15.2.1-app.Run:中断式,只要使用当前中间件,后面的中间件都不执行;
image.png

15.2.2-app.Map:判断路径中包含什么内容。
image.png

15.2.3-app.MapWhen:判断式,两个委托,第一个委托做为判断条件内容,第二个委托,是要执行的逻辑
image.png


15.3-中间件扩展-引用

(1)中间件的内容可以独立开,放在一个独立的类中,需要有一定的规则如下(创建了3个中间件):


    /// <summary>
    ///  要求构造函数带有RequestDelegate参数类型——目的是为了得到下一个中间件;
    ///  必须包含async Task Invoke方法,方法参数为HttpContext
    /// </summary>
    public class FirstMiddleware
    {

        private readonly RequestDelegate _next;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="next"></param>
        public FirstMiddleware(RequestDelegate next) {
            _next = next;
        }


        public async Task Invoke(HttpContext context) {
            await context.Response.WriteAsync($"<h1 style='color:red'>{nameof(FirstMiddleware)}——Hello Word Start</h1>");
            await _next(context);
            await context.Response.WriteAsync($"<h2 style='color:blue'>{nameof(FirstMiddleware)}——Hello Word End</h1>");
        }
    }

(2)在【Startup.cs】中创建的【Configure】方法中通过以下方法使用中间件:
image.png


十六、EF Core

16.1-EF Core可以做什么?

通过实体和数据库的映射,可以通过实体的操作完成对数据的操作。

映射:

  • 从数据库到代码视图的映射
  • 从代码到数据库的映射

16.2-DB First(数据库优先)

DB First:现有数据库,然后通过映射得到视图(和数据库的表对应);
(1)创建一个控制台应用程序,用来操作并测试。
image.png
(2)管理Nuget包,安装对应需要的包。
image.png
image.png
image.png

Install-Package Microsoft.EntityFramewrorkCore
Install-Package Microsoft.Entit3FramewrorkCore.Sqlserver
Install-Package Microsoft.EntityFramewrorkCore.Tools

工具--Nuget包管理器--程序包管理器控制台-命令执行(建议使用这种):
Scaffold-DbContext "Data Source=.;Initial Catalog=CovidAPI;User ID=sa;Password=3344520" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entity -Force -Context CovidAPIDbContext -ContextDir /

--可以简写为:
Scaffold-DbContext "Data Source=.;database=CovidAPI;uid=sa;pwd=3344520" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entity


命令参数:
-0utputDir  实体文件所存放的文件目录
-ContextDir   DbContext文件存放的目录
-Context   DbContext文件名
-Schemas  需要生成实体数据的数据表所在的模式
-Tables  等需要生成实体数据的数据表的集合
-DataArnotations
-UseDatabaseNames  直接使用数据库中的表名和列名(某些版本不支持)
-Force  强制执行,重写已经存在的实体文件

(3)将【EFCore.DbFirst】设置为启动项目,并在【程序包管理器控制台】中选中摸默认项目为【EFCore.DbFirst】,然后执行以上命令:
image.png
命令执行完毕,然后会根据数据库中的表生成以下实体和DbContext:
image.png


16.3-Code First(代码优先-迁移)

迁移:代码优先,先有代码再有数据库;数据库随着业务变化迁移改变。
迁移命令:
image.png
(1)可以通过EFCore代码有的API从代码生成数据库。
image.png
(2)可以通过迁移命令生成数据库。
2.1-需要引入程序包
image.png
2.2-将【EFCore.CodeFirst】设置为启动项目,并在【程序包管理器控制台】中选中摸默认项目为【EFCore.CodeFirst】,然后执行对应的命令:
image.png
然后可以看到生成了迁移文件和快照文件。
image.png


16.4-EFCore抓取SQL语句

16.4.1-日志输出
(1)打开Nuget包安装日志输出包。
image.png
(2)在数据上下文对象中的【OnConfiguring】方法中进行配置。
image.png

16.4.2-SQL Srever Profiler工具
(1)在SQL Srever数据库中【工具】——>【SQL Server Profiler】。
image.png
(2)打开之后进行数据库连接,和服务器是同一个服务器。
image.png
(3)选择模板和事件。
image.png
image.png


16.5-EFCore-LinqQuery(LINQ查询)

#region 其他查询
            using (CovidAPIDbContext context = new CovidAPIDbContext())
            {

                //Lambda查询
                {
                    var idlist = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 134, 46, 23, 46, 34 };//in查询
                    var list = context.Employees.Where(u => idlist.Contains(u.Id));//in查询
                    foreach (var item in list)
                    {
                        Console.WriteLine(item.Name);
                    }
                }

                //LINQ查询
                {
                    var list = from u in context.Employees
                               where new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }.Contains(u.Id)
                               select u;
                    foreach (var item in list)
                    {
                        Console.WriteLine(item.Name);
                    }
                }


                //一般用于分页:Skip、Take——跳过第一条2,取2条
                {
                    //var list = (from u in context.Employees
                    //            where new int[] { 1, 2, 3, 4, 5, 6, 7, 7 }.Contains(u.Id)
                    //            orderby u.Id
                    //            select new
                    //            {
                    //                Name = u.Name,
                    //                Img = u.PictureUrl
                    //            }).Skip(1).Take(2);
                    //foreach (var item in list)
                    //{
                    //    Console.WriteLine(item.Name);
                    //}
                }


                //多条件
                {
                    var list = context.Employees.Where(u => u.Name.StartsWith("三") && u.Name.EndsWith("三哈"))
                        .Where(u => u.Name.StartsWith("张"))
                        .Where(u => u.Name.Length < 5)
                        .OrderBy(u => u.Id);
                    foreach (var item in list)
                    {
                        Console.WriteLine(item.Name);
                    }
                }


                //连接查询
                {
                    var list = (from u in context.Employees
                                join c in context.Departments
                                on u.DepartmentId equals c.Id  //注意:条件不能写 "==" 等于号,要使用equals
                                where new int[] { 1, 2, 3, 4, 5, 6, 7 }.Contains(u.Id)
                                select new
                                {
                                    Id = u.Id,
                                    Nmae = u.Name,
                                    Bumen = c.Name
                                }).OrderBy(u => u.Id);
                    foreach (var item in list)
                    {
                        Console.WriteLine($"{item.Id}---{item.Nmae}---{item.Bumen}");
                    }
                }


                //左连接
                //没有右连接,如果要做右连接,就把顺序调换一下就行了
                {
                    var list = (from u in context.Employees
                                join c in context.Departments on u.DepartmentId equals c.Id
                                into ucList
                                from uc in ucList.DefaultIfEmpty()
                                where new int[] { 1, 2, 3, 4, 5, 6 }.Contains(u.Id)
                                select new
                                {
                                    Id = u.Id,
                                    Nmae = u.Name,
                                    Bumen = uc.Name
                                }).OrderBy(u => u.Id);
                    foreach (var item in list)
                    {
                        Console.WriteLine($"{item.Id}---{item.Nmae}---{item.Bumen}");
                    }
                }
                #endregion
            }

16.5-EFCore-执行查询、修改SQL语句

  #region   如果遇到非常复杂的查询——建议直接写SQL语句
            using (CovidAPIDbContext context = new CovidAPIDbContext()) {
                //查询
                {
                    try
                    {
                        string sql1 = @"select * from dbo.Employees where Id > @Id";
                        SqlParameter parameter1 = new SqlParameter("@Id", 4);
                        var query = context.Employees.FromSqlRaw<Employee>(sql1, parameter1);
                        foreach (var item in query) {
                            Console.WriteLine(item.Name);
                        }
                    }
                    catch (Exception ex) {
                        Console.WriteLine(ex.Message);
                        throw;
                    }
                }


                //修改
                {
                    string sql2 = @"update dbo.Employees set Name ='修改update' where id=@id";
                    SqlParameter parameter2 = new SqlParameter("@id", 4);
                    int flg = context.Database.ExecuteSqlRaw(sql2, parameter2);
                }
            }
            #endregion

16.6-EFCore-State(动态跟踪)

增删改的动作是统一由SavaChanges以后才落实到数据库中去的:
数据库的增删改动作都是统一由SavaChanges之后,统一提交到数据库,是通过状态跟踪,任何一个增删改查的操作都会记录一个状态在内存中,增删改查的状态,一旦SavaChanges,就根据状态落实到数据库中去。

// 摘要:当前这个实体没有被上下文所跟踪
Detached=0,

// 摘要:实体正在被上下文跟踪,并且存在于数据库中,他的数据库中的数据没有被更改。
Unchanged=1,

// 摘要:实体被上下文跟踪,并且存在数据库中,并已标记从数据库中删除,并还没有删除。
Deleted=2,

// 摘要:实体被上下文跟踪,部分或他的所有属性都已经被修改。
Modified=3,

// 摘要:实体被上下文跟踪,但在数据库中还不存在。
Added=4

image.png
注意:
状态跟踪实现了增删改便捷,但是也会有性能消耗;因为每次都要去和内存的副本的状态去做比较的。


16.7-EFCore-事务(调优小技巧)

SavaChanges就是保证事务的,多个对于数据库的操作,统一SavaChanges,就是开启了一个事务的。

EF Core自带的两个事务:
(1)SavaChanges
(2)IDbContextTransaction
image.png


16.8-EFCore-调优小技巧

(1)尽量不要进行Tolist();
image.png
(2)尽量不要使用FirstOrDefault,要使用Find进行查询。
image.png
(3)只做查询时,不需要进行增删改,就去掉状态跟踪。
image.png
全局取消
image.png


十七、EFCore整合-分层架构

17.1-分层架构

image.png

  • 没有分层的缺点:

    • 职责不清晰
    • 如果有一处修改,可能会导致需要重新修改,需要重新测试。
  • 分层的优点:

    • 职责更清晰
    • 需求的变更不同修改全部代码
    • 人员更好调配——让更专业的人做专业的事。
  • 三层架构:

  • UI:展示给用户,视图层

  • BLL:业务逻辑层

  • DAL:数据访问层

分层以后:要求不能跨层调用,UI层——BLL层——DAL层


上一章节:https://www.cnblogs.com/kimiliucn/p/17620153.html
原文地址:https://www.cnblogs.com/kimiliucn/p/17620159.html

posted @ 2023-08-10 22:47  西瓜程序猿  阅读(90)  评论(0编辑  收藏  举报