
学无先后,达者为师!爱写代码并乐于分享技术但不喜欢搞基!目前专注于ASP.NET Core微服务架构的研究。能够利用现有开源的微服务相关的技术开发支持高并发,高可用的分布式系统!本人微账信号:jkingzhu。.NETCore实战项目交流群637326624
linux终端 终端的概念 终端本质上是对应着 Linux 上的 /dev/tty 设备,Linux 的多用户登陆就是通过不同的 /dev/tty 设备完成的,Linux 默认提供了 6 个纯命令行界面的 “terminal”(准确的说这里应该是 6 个 virtual consoles)来让用户登录。在物理机系统上你可以通过使用[Ctrl]+[Alt]+[F1]~[F6]进行切换。 Shell 通常在图形界面中对实际体验带来差异的不是不同发行版的各种终端模拟器,而是这个 Shell(壳)。有壳就有核,这里的核就是指 UNIX/Linux 内核,Shell 是指“提供给使用者使用界面”的软件(命令解析器),类似于 DOS 下的 command(命令行)和后来的 cmd.exe 。普通意义上的 Shell 就是可以接受用户输入命令的程序。它之所以被称作 Shell 是因为它隐藏了操作系统底层的细节。同样的 UNIX/Linux 下的图形用户界面 GNOME 和 KDE,有时也被叫做“虚拟 shell”或“图形 shell”。 重要且常用的快捷键及技巧 1) 重要快捷键:[Tab]使用Tab键来进行命令补全,Tab键一般是在字母Q旁边,这个技巧给你带来的最大的好处就是当你忘记某个命令的全称时可以只输入它的开头的一部分,然后按下Tab键就可以得到提示或者帮助完成:[Ctrl+c]想想你有没有遇到过这种情况,当你在 Linux 命令行中无意输入了一个不知道的命令,或者错误地使用了一个命令,导致在终端里出现了你无法预料的情况,比如,屏幕上只有光标在闪烁却无法继续输入命令,或者不停地输出一大堆你不想要的结果。你想要立即停止并恢复到你可控的状态,那该怎么办呢?这时候你就可以使用Ctrl+c键来强行终止当前程序(你可以放心它并不会使终端退出)。其他一些常用快捷键按键 作用Ctrl+d 键盘输入结束或退出终端Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fgCtrl+a 将光标移至输入行头,相当于Home键Ctrl+e 将光标移至输入行末,相当于End键Ctrl+k 删除从光标所在位置到行末Alt+Backspace 向前删除一个单词Shift+PgUp 将终端显示向上滚动Shift+PgDn 将终端显示向下滚动 2) 学会利用历史输入命令很简单,你可以使用键盘上的方向上键↑,恢复你之前输入过的命令,你一试便知。 3) 学会使用通配符通配符是一种特殊语句,主要有星号(*)和问号(?),用来对字符串进行模糊匹配(比如文件名、参数名)。当查找文件夹时,可以使用它来代替一个或多个真正字符;当不知道真正字符或者懒得输入完整名字时,常常使用通配符代替一个或多个真正字符。先使用 touch 命令创建 2 个文件,后缀都为 txt: $ touch adsfasd.txt wergjlkas.txt$ ls *.txt在创建文件的时候,如果需要一次性创建多个文件,比如:“love_1_linux.txt,love_2_linux.txt,... love_10_linux.txt”。在 Linux 中十分方便:$ touch love_{1..10}_linux.txtShell 常用通配符:字符 含义 匹配 0 或多个字符 匹配任意一个字符 list] 匹配 list 中的任意单一字符 !list] 匹配 除list 中的任意单一字符以外的字符 c1-c2] 匹配 c1-c2 中的任意单一字符 如:[0-9][a-z] string1,string2,...} 匹配 string1 或 string2 (或更多)其一字符串 c1..c2} 匹配 c1-c2 中全部字符 如{1..10} 4) 学会在命令行中获取帮助 在 Linux 环境中,如果你遇到困难,可以使用man命令,它是Manual pages的缩写。$ man $ man 1 ls会显示第一区段中的ls命令 man 页面。man 手册的内容很多,涉及了 Linux 使用过程中的方方面面。为了便于查找,man 手册被进行了分册(分区段)处理,在 Research UNIX、BSD、OS X 和 Linux 中,手册通常被分为8个区段 Linux 用户管理 用户管理 请打开终端,输入命令:$ who am i或者$ who mom likeswho 命令其它常用参数参数 说明-a 打印能打印的全部-d 打印死掉的进程-m 同am i,mom likes-q 打印当前登录用户数及用户名-u 打印当前登录用户登录信息-r 打印运行等级 创建用户 在 Linux 系统里, root 账户拥有整个系统至高无上的权利,比如 新建/添加 用户。su,su- 与 sudosu 可以切换到用户 user,执行时需要输入目标用户的密码,sudo 可以以特权级别运行 cmd 命令,需要当前用户属于 sudo 组,且需要输入当前用户的密码。su - 命令也是切换用户,同时环境变量也会跟着改变成目标用户的环境变量。现在我们新建一个叫 yilezhu 的用户:$ sudo adduser yilezhu这个命令不但可以添加用户到系统,同时也会默认为新用户创建 home 目录:$ ls /home现在你已经创建好一个用户,并且你可以使用你创建的用户登录了,使用如下命令切换登录用户:$ su -l yilezhu退出当前用户跟退出终端一样可以使用 exit 命令或者使用快捷键 Ctrl+d。 用户组 在 Linux 里面每个用户都有一个归属(用户组),用户组简单地理解就是一组用户的集合,它们共享一些资源和权限,同时拥有私有资源,就跟家的形式差不多,你的兄弟姐妹(不同的用户)属于同一个家(用户组),你们可以共同拥有这个家(共享资源),爸妈对待你们都一样(共享权限),你偶尔写写日记,其他人未经允许不能查看(私有资源和权限)。当然一个用户是可以属于多个用户组的,正如你既属于家庭,又属于学校或公司。方法一:使用 groups 命令$ groups yilezhu可以查看下 /etc/sudoers.d/yilezhu 文件,我们在 /etc/sudoers.d 目录下创建了这个文件,从而给 yilezhu 用户赋予了 sudo 权限:$sudo cat /etc/sudoers.d/yilezhu方法二:查看 /etc/group 文件$ cat /etc/group | sort这里 cat 命令用于读取指定文件的内容并打印到终端输出,后面会详细讲它的使用。 | sort 表示将读取的文本进行一个字典排序再输出没找到,没关系,你可以使用命令过滤掉一些你不想看到的结果:$ cat /etc/group | grep -E "yilezhu"/etc/group 的内容包括用户组(Group)、用户组口令、GID 及该用户组所包含的用户(User),每个用户组一条记录。格式如下:group_name:password:GID:user_list你看到上面的 password 字段为一个 x 并不是说密码就是它,只是表示密码不可见而已。将其它用户加入 sudo 用户组默认情况下新创建的用户是不具有 root 权限的,也不在 sudo 用户组,可以让其加入 sudo 用户组从而获取 root 权限:$ su -l yilezhu$ sudo ls会提示 lilei 不在 sudoers 文件中,意思就是 lilei 不在 sudo 用户组中,至于 sudoers 文件(/etc/sudoers)你现在最好不要动它,操作不慎会导致比较麻烦的后果。使用 usermod 命令可以为用户添加用户组,同样使用该命令你必需有 root 权限,你可以直接使用 root 用户为其它用户添加用户组,或者用其它已经在 sudo 用户组的用户使用 sudo 命令获取权限来执行该命令。这里我用 root 用户执行 sudo 命令将 yilezhu 添加到 sudo 用户组,让它也可以使用 sudo 命令获得 root 权限:$ su root # 此处需要输入root用户密码$ groups yilezhu$ sudo usermod -G sudo yilezhu$ groups yilezhu然后你再切换回 yilezhu 用户,现在就可以使用 sudo 获取 root 权限了。 删除用户 删除用户是很简单的事:$ sudo userdel -rf yilezhu 拓展 adduser 和 useradd 的区别是什么?答:useradd 只创建用户,创建完了用 passwd yilezhu 去设置新用户的密码。adduser 会创建用户,创建目录,创建密码(提示你设置),做这一系列的操作。其实 useradd、userdel 这类操作更像是一种命令,执行完了就返回。而 adduser 更像是一种程序,需要你输入、确定等一系列操作 Linux 文件权限 查看文件权限 使用较长格式列出文件: $ ls -l 显示除了 .(当前目录)和 ..(上一级目录)之外的所有文件,包括隐藏文件(Linux 下以 . 开头的文件为隐藏文件)。 $ ls -A 查看某一个目录的完整属性,而不是显示目录里面的文件属性: $ ls -dl <目录名> 显示所有文件大小,并以普通人类能看懂的方式呈现: $ ls -AsSh 其中小 s 为显示文件大小,大 S 为按文件大小排序,若需要知道如何按其它方式排序,请使用“man”命令查询。 变更文件所有者 假设目前是 yilezhu 用户登录,新建一个文件,命名为 “ huawei ”: $ touch huawei 现在,换回到 root 用户身份,使用以下命令变更文件所有者为 root : $ cd /home/yilezhu $ ls huawei $ sudo chown root huawei 3.3 修改文件权限 如果你有一个自己的文件不想被其他用户读、写、执行,那么就需要对文件的权限做修改,这里有两种方式: 方式一:二进制数字表示 每个文件的三组权限(拥有者,所属用户组,其他用户,记住这个顺序是一定的)对应一个 " rwx ",也就是一个 “ 7 ” ,所以如果我要将文件“ huawei ”的权限改为只有我自己可以用那么就这样: 为了演示,我先在文件里加点内容: $ echo "echo "hello root"" > huawei 然后修改权限: $ chmod 700 huawei 方式二:加减赋值操作 完成上述相同的效果,你可以: $ chmod go-rw huawei g、o 还有 u 分别表示 group、others 和 user,+ 和 - 分别表示增加和去掉相应的权限。 Linux 目录结构 FHS 标准 FHS 定义了两层规范,第一层是, / 下面的各个目录应该要放什么文件数据,例如 /etc 应该放置设置文件,/bin 与 /sbin 则应该放置可执行文件等等。 第二层则是针对 /usr 及 /var 这两个目录的子目录来定义。例如 /var/log 放置系统登录文件,/usr/share 放置共享数据等等。 sudo apt-get update sudo apt-get install tree 列出所有文件 $ tree / 目录路径 路径 使用 cd 命令可以切换目录,在 Linux 里面使用 . 表示当前目录,.. 表示上一级目录(注意,我们上一节介绍过的,以 . 开头的文件都是隐藏文件,所以这两个目录必然也是隐藏的,你可以使用 ls -a 命令查看隐藏文件), - 表示上一次所在目录,~ 通常表示当前用户的 home 目录。使用 pwd 命令可以获取当前所在路径(绝对路径)。 进入上一级目录: $ cd .. 进入你的 home 目录: $ cd ~ 或者 cd /home/<你的用户名> 使用 pwd 获取当前路径: $ pwd 绝对路径 关于绝对路径,简单地说就是以根" / "目录为起点的完整路径,以你所要到的目录为终点,表现形式如: /usr/local/bin,表示根目录下的 usr 目录中的 local 目录中的 bin 目录。 相对路径 相对路径,也就是相对于你当前的目录的路径,相对路径是以当前目录 . 为起点,以你所要到的目录为终点,表现形式如: usr/local/bin (这里假设你当前目录为根目录)。你可能注意到,我们表示相对路径实际并没有加上表示当前目录的那个 . ,而是直接以目录名开头,因为这个 usr 目录为 / 目录下的子目录,是可以省略这个 . 的(以后会讲到一个类似不能省略的情况);如果是当前目录的上一级目录,则需要使用 .. ,比如你当前目录为 home 目录,根目录就应该表示为 ../../ ,表示上一级目录( home 目录)的上一级目录( / 目录)。 下面我们以你的 home目录为起点,分别以绝对路径和相对路径的方式进入 /usr/local/bin 目录: 绝对路径 $ cd /usr/local/bin 相对路径 $ cd ../../usr/local/bin 提示:在进行目录切换的过程中请多使用 Tab 键自动补全,可避免输入错误,连续按两次 Tab 可以显示全部候选结果。 Linux 文件的基本操作 新建 新建空白文件 使用 touch 命令创建空白文件,关于 touch 命令,其主要作用是来更改已有文件的时间戳的(比如,最近访问时间,最近修改时间),但其在不加任何参数的情况下,只指定一个文件名,则可以创建一个指定文件名的空白文件(不会覆盖已有同名文件),当然你也可以同时指定该文件的时间戳 新建目录 使用 mkdir(make directories)命令可以创建一个空目录,也可同时指定创建目录的权限属性。 创建名为“ mydir ”的空目录: $ mkdir mydir 使用 -p 参数,同时创建父目录(如果不存在该父目录),如下我们同时创建一个多级目录(这在安装软件、配置安装路径时非常有用): $ mkdir -p father/son/grandson 复制 复制文件 使用 cp(copy)命令复制一个文件到指定目录。 将之前创建的“ test ”文件复制到“ /home/root/father/son/grandson ”目录中: $ cp test father/son/grandson 复制目录 如果直接使用 cp 命令复制一个目录的话,会出现如下错误: 要成功复制目录需要加上 -r 或者 -R 参数,表示递归复制,就是说有点“株连九族”的意思: $ cp -r father family 删除 删除文件 使用 rm(remove files or directories)命令删除一个文件: $ rm test 有时候你会遇到想要删除一些为只读权限的文件,直接使用 rm 删除会显示一个提示,如下: 你如果想忽略这提示,直接删除文件,可以使用 -f 参数强制删除: $ rm -f test 删除目录 跟复制目录一样,要删除一个目录,也需要加上 -r 或 -R 参数: $ rm -r family 移动文件与文件重命名 移动文件 使用 mv(move or rename files)命令移动文件(剪切)。将文件“ file1 ”移动到 Documents 目录: mv 源目录文件 目的目录: $ mkdir Documents $ mv file1 Documents 重命名文件 将文件“ file1 ”重命名为“ myfile ”: mv 旧的文件名 新的文件名: $ mv file1 myfile 查看文件 使用 cat,tac 和 nl 命令查看文件 前两个命令都是用来打印文件内容到标准输出(终端),其中 cat 为正序显示,tac 为倒序显示。 比如我们要查看之前从 /etc 目录下拷贝来的 passwd 文件: $ cat passwd 可以加上 -n 参数显示行号: $ cat -n passwd nl 命令,添加行号并打印,这是个比 cat -n 更专业的行号打印命令。 这里简单列举它的常用的几个参数: -b : 指定添加行号的方式,主要有两种: -b a:表示无论是否为空行,同样列出行号("cat -n"就是这种方式) -b t:只列出非空行的编号并列出(默认为这种方式) -n : 设置行号的样式,主要有三种: -n ln:在行号字段最左端显示 -n rn:在行号字段最右边显示,且不加 0 -n rz:在行号字段最右边显示,且加 0 -w : 行号字段占用的位数(默认为 6 位) 使用 more 和 less 命令分页查看文件 如果说上面的 cat 是用来快速查看一个文件的内容的,那么这个 more 和 less 就是天生用来"阅读"一个文件的内容的,比如说 man 手册内部就是使用的 less 来显示内容。其中 more 命令比较简单,只能向一个方向滚动,而 less 为基于 more 和 vi (一个强大的编辑器,我们有单独的课程来让你学习)开发,功能更强大 使用 head 和 tail 命令查看文件 $ tail /etc/passwd 甚至更直接的只看一行, 加上 -n 参数,后面紧跟行数: $ tail -n 1 /etc/passwd 查看文件类型 前面我提到过,在 Linux 中文件的类型不是根据文件后缀来判断的,我们通常使用 file 命令查看文件的类型: $ file /bin/ls 搜索文件 与搜索相关的命令常用的有 whereis,which,find 和 locate 。whereis 简单快速$whereis wholocate 快而全它可以用来查找指定目录下的不同文件类型,如查找 /etc 下所有以 sh 开头的文件:$ locate /etc/sh注意,它不只是在 /etc 目录下查找,还会自动递归子目录进行查找。查找 /usr/share/ 下所有 jpg 文件:$ locate /usr/share/*.jpg注意要添加 * 号前面的反斜杠转义,否则会无法找到。如果想只统计数目可以加上 -c 参数,-i 参数可以忽略大小写进行查找,whereis 的 -b、-m、-s 同样可以使用。which 小而精which 本身是 Shell 内建的一个命令,我们通常使用 which 来确定是否安装了某个指定的软件,因为它只从 PATH 环境变量指定的路径中去搜索命令:$ which manfind 精而细find 应该是这几个命令中最强大的了这条命令表示去 /etc/ 目录下面 ,搜索名字叫做 interfaces 的文件或者目录。这是 find 命令最常见的格式,千万记住 find 的第一个参数是要搜索的地方:$ sudo find /etc/ -name interfaces意 find 命令的路径是作为第一个参数的, 基本命令格式为 find [path] [option] [action] 。与时间相关的命令参数:参数 说明-atime 最后访问时间-ctime 最后修改文件内容的时间-mtime 最后修改文件属性的时间下面以 -mtime 参数举例:-mtime n:n 为数字,表示为在 n 天之前的“一天之内”修改过的文件-mtime +n:列出在 n 天之前(不包含 n 天本身)被修改过的文件-mtime -n:列出在 n 天之内(包含 n 天本身)被修改过的文件-newer file:file 为一个已存在的文件,列出比 file 还要新的文件名列出 home 目录中,当天(24 小时之内)有改动的文件:$ find ~ -mtime 0列出用户家目录下比 Code 文件夹新的文件:$ find ~ -newer /home/root/Code 文件打包与压缩 文件后缀名 说明*.zip zip 程序打包压缩的文件*.rar rar 程序压缩的文件*.7z 7zip 程序压缩的文件*.tar tar 程序打包,未压缩的文件*.gz gzip 程序(GNU zip)压缩的文件*.xz xz 程序压缩的文件*.bz2 bzip2 程序压缩的文件*.tar.gz tar 打包,gzip 程序压缩的文件*.tar.xz tar 打包,xz 程序压缩的文件*tar.bz2 tar 打包,bzip2 程序压缩的文件*.tar.7z tar 打包,7z 程序压缩的文件 zip 压缩打包程序 使用 zip 打包文件夹:$ zip -r -q -o yilezhu.zip /home/root$ du -h yilezhu.zip$ file yilezhu.zip上面命令将目录 /home/root 打包成一个文件,并查看了打包后文件的大小和类型。第一行命令中,-r 参数表示递归打包包含子目录的全部内容,-q 参数表示为安静模式,即不向屏幕输出信息,-o,表示输出文件,需在其后紧跟打包输出文件名。后面使用 du 命令查看打包后文件的大小(后面会具体说明该命令)。设置压缩级别为 9 和 1(9 最大,1 最小),重新打包:$ zip -r -9 -q -o yilezhu_9.zip /home/root -x ~/*.zip$ zip -r -1 -q -o yilezhu_1.zip /home/root -x ~/*.zip这里添加了一个参数用于设置压缩级别 -[1-9],1 表示最快压缩但体积大,9 表示体积最小但耗时最久。最后那个 -x 是为了排除我们上一次创建的 zip 文件,否则又会被打包进这一次的压缩文件中,注意:这里只能使用绝对路径,否则不起作用。我们再用 du 命令分别查看默认压缩级别、最低、最高压缩级别及未压缩的文件的大小:$ du -h -d 0 *.zip ~ | sort通过 man 手册可知:h, --human-readable(顾名思义,你可以试试不加的情况)d, --max-depth(所查看文件的深度) 使用 unzip 命令解压缩 zip 文件 将 yilezhu.zip 解压到当前目录:$ unzip yilezhu.zip使用安静模式,将文件解压到指定目录:$ unzip -q yilezhu.zip -d ziptest上述指定目录不存在,将会自动创建。如果你不想解压只想查看压缩包的内容你可以使用 -l 参数:$ unzip -l yilezhu.zip使用 -O(英文字母,大写 o)参数指定编码类型:unzip -O GBK 中文压缩文件.zip(解决中文编码问题) rar 打包压缩命令 rar 也是 Windows 上常用的一种压缩文件格式,在 Linux 上可以使用 rar 和 unrar 工具分别创建和解压 rar 压缩包。 安装 rar 和 unrar 工具:$ sudo apt-get update$ sudo apt-get install rar unrar从指定文件或目录创建压缩包或添加文件到压缩包:$ rm *.zip$ rar a yilezhu.rar .上面的命令使用 a 参数添加一个目录 ~ 到一个归档文件中,如果该文件不存在就会自动创建。注意:rar 的命令参数没有 -,如果加上会报错。从指定压缩包文件中删除某个文件:$ rar d yilezhu.rar .zshrc查看不解压文件:$ rar l yilezhu.rar使用 unrar 解压 rar 文件全路径解压:$ unrar x yilezhu.rar去掉路径解压:$ mkdir tmp$ unrar e yilezhu.rar tmp/ tar 打包工具 在 Linux 上面更常用的是 tar 工具,tar 原本只是一个打包工具,只是同时还是实现了对 7z、gzip、xz、bzip2 等工具的支持,这些压缩工具本身只能实现对文件或目录(单独压缩目录中的文件)的压缩,没有实现对文件的打包压缩,所以我们也无需再单独去学习其他几个工具,tar 的解压和压缩都是同一个命令,只需参数不同,使用比较方便。 下面先掌握 tar 命令一些基本的使用方式,即不进行压缩只是进行打包(创建归档文件)和解包的操作。 创建一个 tar 包:$ tar -cf yilezhu.tar ~ 上面命令中,-c 表示创建一个 tar 包文件,-f 用于指定创建的文件名,注意文件名必须紧跟在 -f 参数之后,比如不能写成 tar -fc yilezhu.tar,可以写成 tar -f yilezhu.tar -c ~。你还可以加上 -v 参数以可视的的方式输出打包的文件。上面会自动去掉表示绝对路径的 /,你也可以使用 -P 保留绝对路径符。 解包一个文件(-x 参数)到指定路径的已存在目录(-C 参数):$ mkdir tardir$ tar -xf yilezhu.tar -C tardir只查看不解包文件 -t 参数:$ tar -tf yilezhu.tar保留文件属性和跟随链接(符号链接或软链接),有时候我们使用 tar 备份文件当你在其他主机还原时希望保留文件的属性(-p 参数)和备份链接指向的源文件而不是链接本身(-h 参数):$ tar -cphf etc.tar /etc对于创建不同的压缩格式的文件,对于 tar 来说是相当简单的,需要的只是换一个参数,这里我们就以使用 gzip 工具创建 *.tar.gz 文件为例来说明。 我们只需要在创建 tar 文件的基础上添加 -z 参数,使用 gzip 来压缩文件:$ tar -czf yilezhu.tar.gz ~解压 *.tar.gz 文件:$ tar -xzf yilezhu.tar.gz现在我们要使用其它的压缩工具创建或解压相应文件只需要更改一个参数即可:压缩文件格式 参数*.tar.gz -z*.tar.xz -J*tar.bz2 -j 总结 其实今天总结的内容挺多的,主要是因为对于我这个.NET Core开发者来说Linux是一个全新的领域,所以感觉需要记忆的内容太多太多了!而作为开发者,虽然不需要掌握Linux的核心优化等等内容,但是基本的操作还是需要掌握的,毕竟公司里面基本是开发运维的工作一肩挑的,所以,Linux我来了!后续在.net core程序进行在Linux系统上部署的话可能还需要涉及一些命令,这个后期用到了在进行记录吧!今天就先到这里了!感谢大家的阅读!
连着两天更新叙述性的文章大家可别以为我转行了!哈哈!今天就继续讲讲我们的.NET Core实战项目之CMS系统的教程吧!这个系列教程拖得太久了,所以今天我就以菜单部分的增删改查为例来讲述下我的项目分层之间的协同工作吧!如果你觉得文中有任何不妥的地方还请留言或者加入DotNetCore实战千人交流群637326624跟大伙进行交流讨论吧! 本文已收录至《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/10263714.html 写在前面 前面的章节中我们基本的增删改查都有了,甚至后台模板我们也找到并集成到了我们的CMS系统中了!那么剩下的就是对功能的开发了。对于功能的开发部分,我不会全部都拿出来讲,只会以一个菜单的例子来进行讲解!话不多说,开是吧! 各层之间的协作 先来讲讲我的分层协作的设计思路。虽然借助了DDDLite的部分思想,但是又与其不通,因为小项目严格按照DDD的思想来进行开发完全是找虐。如果有需要我后期会对结构进行调整来向DDD层次迁移。先上一张图吧: 这里所有的底层方法都是在Repository层进行的,加入仓储接口层的原因是为了解耦,一路跟着教程走来的朋友一定知道我目前我的仓储层是按照SQLServer数据库进行开发的,后期我会对MySQL,甚至PgSql的支持!这样的话对应用层丝毫不会有影响。 为什么这里用了应用层的服务?因为如果不实用应用层的话,设计到一些逻辑判断等等的话会把控制器搞的很庞大,代码量太多!为了使控制器简洁所以我加入了服务层的概念,这样服务层处理业务逻辑,把结果返回给控制器即可!当然如果涉及到多个事件的处理的话可能还需要借助MediatR来进行实现!什么你不知道MediatR是什么?那你可以看看我的这篇《ASP.NET Core中使用MediatR实现命令和中介者模式》文章的讲述。 实体层充当数据库实体映射以及DTO及ViewModel的角色!对于实体对象模型我更喜欢贫血模式的整洁干净的实体对象!不喜欢充斥各种代码的充血对象。所以里面都是POCO的简单生成。而ViewModel这个层可能我对这个层的概念设计有点模糊,所以DTO跟ViewModel的都混在一起了!这里你不用太过惊讶,因为你完全可以按照自己的理解来进行整理!自由发挥吧! 菜单的增删改查实现 这一节我们就开始写菜单的增删改查的代码实现吧! 菜单的列表页面功能实现 首选在我们的Czar.Cms.Admin 项目的Controllers控制器下建立MenuController控制器!如图所示: 里面的代码如下: public class MenuController : BaseController { private readonly IMenuService _service; public MenuController(IMenuService service) { _service = service; } public IActionResult Index() { return View(); } } 我们这里先列出首页(也就是列表页的代码)然后创建对应的Index.cshtml视图 Index.cshtml视图的代码如下: @{ ViewData["Title"] = "后台菜单管理"; } <blockquote class="layui-elem-quote quoteBox"> <form class="layui-form"> @Html.AntiForgeryToken() <div class="layui-inline"> <div class="layui-input-inline"> <input type="text" class="layui-input searchVal" placeholder="请输入菜单名称" /> </div> <a class="layui-btn search_btn" data-type="reload">搜索</a> </div> <div class="layui-inline"> <a class="layui-btn layui-btn-normal addMenu_btn"><i class="layui-icon">&#xe61f;</i>添加菜单</a> </div> <div class="layui-inline"> <a class="layui-btn layui-btn-danger layui-btn-normal delAll_btn"><i class="layui-icon layui-icon-delete"></i>批量删除</a> </div> </form> </blockquote> <table id="menuList" lay-filter="menuList"></table> <!--操作--> <script type="text/html" id="menuListBar"> <a class="layui-btn layui-btn-xs" lay-event="edit">编辑</a> <a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="del">删除</a> </script> <script type="text/html" id="IsDisplay"> {{# if(d.IsDisplay ===true){ }} <input type="checkbox" name="IsDisplay" value="{{d.Id}}" lay-filter="IsDisplay" lay-skin="switch" lay-text="是|否" checked> {{# } else{ }} <input type="checkbox" name="IsDisplay" value="{{d.Id}}" lay-filter="IsDisplay" lay-skin="switch" lay-text="是|否"> {{# }}} </script> @section Scripts{ <script type="text/javascript" src="~/layui/layui.js"></script> <script type="text/javascript" src="~/js/menu/menuList.js?_=@DateTime.Now.ToString("yyyyMMddHHmmss")"></script> } 可能对于大多数人来说看到这个视图很懵逼,怎么没有列表的信息啊,语法什么的也都看不懂啊?别急,这里用到的是Layui的一些语法!我们拆分下来看: <form class="layui-form">这个部分就是搜索功能部分 <table id="menuList" lay-filter="menuList"></table>就是表格 <script type="text/html" id="IsDisplay">这个是layui模板部分 在wwwroot\js\menu\下面创建一个menuList.js的js文件,来对页面的列表进行下初始化。并对一些操作进行控制。由于代码太长,所以只粘贴加载表格的部分代码如下所示: 对应的LoadData里面的代码如下: 是不是很简洁,那是因为所有的业务代码都在服务层实现了,不信?我把代码粘贴出来给你看: 这时候体会到服务层的好处了吧! 运行起来看下效果吧: 菜单增加修改功能实现 首先还是要写控制器方法来显示视图,代码如下: 由于修改编辑页面菜单有层级关系,所以我们需要加载顶层的菜单(画外音:只支持两级菜单)所以我们加载编辑页面的时候需要把顶层的菜单给加载出来,方法如下: 列表页弹出编辑或者新增的功能是在menu.js中实现的,代码如下: 新增或者编辑的时候需要判断菜单的别名是否重复,这里是通过layui的验证模块然后使用ajax实现的,视图代码如下: js代码如下: 先判断是否符合规则,然后判断是否存在,这个需要对新增或者编辑分别进行处理!新增的时候需要判断是否存在即可,编辑的时候需要判断除自己外有没有重复的,代码如下: 可能你更喜欢看服务层及仓储层的代码,当然我也会毫不吝啬的贴出来,只是可能会消耗你些许流量来查看图片。 服务层: 仓储层代码(由于本人比较懒,所以只实现同步方法,异步获取的方法后期再补上吧,相信参照其他的写法你有何能自行实现): 这里需要注意,我在抽象接口里面都加了注释,所以实现里面就没加了,相信你也能看懂。换句话说,我懒~~~~ 结果提交,这里需要注意只要涉及到结果提交的我都会用到ValidateAntiForgeryToken 还有就是我的结果提交全部是通过ajax进行的,并且把防伪Token放在Token里面的,代码如下: 至于为什么这里的Headers设置为X-CSRF-TOKEN-yilezhu这个你可以看我的上一节课程《.NET Core实战项目之CMS 第十四章 开发篇-防止跨站请求伪造(XSRF/CSRF)攻击处理》 里面有讲述,所以这里就不做过多的讲述了。我们直接上结果提交的代码吧。 很简洁对不对?寥寥几行代码,可是实现真的这么简单吗?看看服务层你就知道了。 public BaseResult AddOrModify(MenuAddOrModifyModel item) { var result = new BaseResult(); Menu model; if (item.Id == 0) { //TODO ADD model = _mapper.Map<Menu>(item); model.AddManagerId = 1; model.IsDelete = false; model.AddTime = DateTime.Now; if (_repository.Insert(model) > 0) { result.ResultCode = ResultCodeAddMsgKeys.CommonObjectSuccessCode; result.ResultMsg = ResultCodeAddMsgKeys.CommonObjectSuccessMsg; } else { result.ResultCode = ResultCodeAddMsgKeys.CommonExceptionCode; result.ResultMsg = ResultCodeAddMsgKeys.CommonExceptionMsg; } } else { //TODO Modify model = _repository.Get(item.Id); if (model != null) { _mapper.Map(item, model); model.ModifyManagerId = 1; model.ModifyTime = DateTime.Now; if (_repository.Update(model) > 0) { result.ResultCode = ResultCodeAddMsgKeys.CommonObjectSuccessCode; result.ResultMsg = ResultCodeAddMsgKeys.CommonObjectSuccessMsg; } else { result.ResultCode = ResultCodeAddMsgKeys.CommonExceptionCode; result.ResultMsg = ResultCodeAddMsgKeys.CommonExceptionMsg; } } else { result.ResultCode = ResultCodeAddMsgKeys.CommonFailNoDataCode; result.ResultMsg = ResultCodeAddMsgKeys.CommonFailNoDataMsg; } } return result; } 是不是业务还蛮复杂的,如果都放在控制器处理想想控制器是不是很恐怖,所以说引入服务层很有必要,把一些逻辑移到服务层让控制器只用来显示数据多好! 删除功能实现 你以为删除功能很简单吗?没错,是很简单,可是我们在设计数据库的时候加入了IsDeleted,看到这个相信你已经猜到了,我们所有的删除操作都是软删除哦!至于为什么这样做?原因就是不想删错了后悔!我只能说这么多了,只有经历惨痛的经历可能才会这样做!还有就是删除之前我会进行js的弹窗提醒,如下图所示,提醒您是否真的要删除! 好了,按照惯例我们第一步是不是要上js的代码啊?那还等什么?立马奉上 注意这里删除的时候也是需要进行防伪验证的,防止别人进行接口恶意删除,下面看下控制器中的代码,哇真干净就一行代码啊,有木有! 其实我想说服务层的代码有超过二十行,不信?我截图给你看吧!好好数数,加上换行是不是有二十行。 总结 今天讲的内容比较简单就是我们这个CMS系统设计的各层之前如何联动工作来实现增删改查业务的,望对大家了解这个系统有所帮助!至于其他的业务功能大家都可以参照这个进行开发!比如角色管理,用户管理等等!下节我们就来实现用户登录模块的功能。
清晨起床,震惊了,窗外一片雪白,大雪纷飞,我承认我词穷了,说再多话也描述不了此刻的大好心情。所以,话不多说,先上一张朋友圈的图吧! 趁着这么“好的”天气以及这么好的心情突然想写点东西记录一下自己的2018这一年以及2019年的这一天以及对.NET Core的看法。 俗话说“瑞雪兆丰年”,其实我想说这句话说得很对,为什么说很对呢?下面我就好好说道说道! 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10244634.html 今天好事连连 今天张队公众号推文推送了自己写的博文 这一年,习惯了清晨醒来第一件事就是看看我所关注的公众号推送的技术文章,其中最喜欢看的当属张队的DotNet公众号了。这里真的很佩服张队,每天早上为我们推送DotNetCore相关的技术盛宴,从关注后就从来没有断更过,这不是一般人所能做到的,这里再次表达对张队的敬意!这里免费帮张队推广下他的公众号名称叫“dotNet跨平台”。这一天,刷公众号刚好刷到张队的公众号推送了自己前两天写的《.NET Core实战项目之CMS 第十四章 开发篇-防止跨站请求伪造(XSRF/CSRF)攻击处理》这篇文章!可能我的拖延症比较厉害,所以后面更新的慢了,看的人也就没那么多了,所以阅读量越来越少,不过我还是会坚持把这个系列更新完的!此为第一件好事。 博客园编辑推荐推荐了自己的博文 这一天,早上刚到公司很忙,所以到十一点多才有时间逛逛博客园,看看大牛们发布的知识分享文章。结果看到首页【编辑推荐】部分文章标题很熟悉,所以就点进去看了,结果这正是我前天晚上分享的技术文章。如下图所示,因为【编辑推荐】部分显示标题没有显示全,所以以为这不是我的文章!真的很惊喜。 因为我分享的文章每次都能到首页推荐,但是【编辑推荐】还是第一次,所以怎一个激动了得。所以我激动的在自己的DotNetCore实战千人群里面“厚颜无耻”的艾特了全体,让大家能帮忙点个推荐。 所以,不到一会功夫这篇文章的推荐数就从15到了三十个!这里为自己的“厚颜无耻”表示道歉!同时感谢那些朋友们的支持! .NET Core实战项目之CMS系列教程所带来的产物 .NET Core实战项目之CMS系列教程所带来的产物足以说明.NET Core的群体还是蛮庞大的,大家积极学习以及接触.NET Core的积极性还是蛮好的。这也坚定了我继续坚持.NET Core的决心。 CzarCms这个实战教程的Star数破100这一天,CzarCms的Star数破了100,可能很多人会哈哈大笑,Star破一百?哈哈,不到千星的项目还好意思拿来说事?真不要脸!其实我想说,这也算是我从去年11月中旬决定开这个《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》这个系列开始,到现在一个半月的时间成果吧!也是自己从一个默默无闻的拿来主义者到一个乐于分享技术的分享者的转变的成功吧。虽然Star数不多,这个项目目前也还是个半成品,不过我会将他完善,并达到生产级项目的水平。 .NET Core项目实战交流群人数突破一千两百人这一天,其实目前这个这个交流人群人数才一千一百九十多人,这同样也是从11月中旬到现在这个系列教程开始以来所带来的产物吧!在这个群里我也是抱着学习的态度来跟大家进行交流,从他们身上我也学到了很多东西,大伙也都很积极的交流经验,分享技术!偶尔也会聊点骚!这也正是我们程序员的特征,如果面对面的交流可能半天也憋不出来一个屁,但是在网上聊的话却能谈天谈地!别看我分享技术的时候能写上千的文字,但是让我说出来的话,那你得在我脖子上架把刀才行了。 “DotNetCore实战”公众号人数突破500人这一天,我跟“金焰的世界”的“DotNetCore实战”这个公众号人数突破500人了。虽然只开通了 短短一个月的时间,也只是在我的文章中进行穿插着宣传,但是增长的人数也足以说明越来越多的人喜欢.NET Core愿意学习.NET Core接触新鲜事物的积极性。当然这个公众号也只是为了第一时间推送我跟“金焰的世界”的博文而设的,当然最近我也会每天晚上九点钟左右推送最新的.NET Core技术文章。可能让我像张队那样每天推送,有点困难。但是我也会坚持每天记性推送的!这里向张队学习,至于究竟能坚持多久,那就看大伙的监督了! .NET Core相关的利好消息让人振奋 .NET Core3.0的新特性 这一天,张队公众号推文中有篇描述.NET Core 3.0 特性的推文。里面讲述了.NET Core 的下一个主要版本最近进入了预览阶段,.NET Core 3.0 将支持使用 Windows Presentation Foundation (WPF)、Windows Forms(WinForms)、Entity Framework (EF)、Blazor、 C# 8 和.NETStandard 2.1 构建桌面应用程序。其他的就不过多介绍了,其中觉得比较新的就是对WPF以及WinForms的支持。同时这里还有一个很大的关注点就是.NET Core 3 的另一个主要关注点是物联网,它支持在 Raspberry Pi 和 Arduino 设备上使用的 GPIO、PWM、SPI 和 I2C API。正如石头哥所感慨的“最后需要注意的是,.NET Core 3 的另一个主要关注点是物联网,它支持在 Raspberry Pi 和 Arduino 设备上使用的 GPIO、PWM、SPI 和 I2C API。我的物联网梦想!”。这些都将在.NET Core 3中进行呈现。 微软自证开源决心:GitHub 私有库免费无限开放 1 月 7 日,GitHub CEO Nat Friedman 于官方博客公开发文,称“New year, new GitHub”,宣布从此将免费无限地为普通用户提供私有仓库服务,同时面向企业和组织推出了更简单统一的产品 GitHub Enterprise,共计两大主要更新。但是似乎昨天才开始刷屏,今天也在刷屏。 无论坊间“阴谋论”几何,微软似乎都已经在拥抱开源这条路上越走越远了,无论是连续三年超越 Facebook、Google 名列 GitHub 第一,还是加入开源专利联盟 OIN(Open Invention Network),抑或是收购事件后的 10 月新品 GitHub Actions,以及此番的私仓免费开放,微软一直在用行动“洗白”过去扎根于开发者记忆中的开源“Anti-fan”形象。 曾经各种“GitHub”看衰的言论也逐渐变了风向,微软更是从“强娶女儿的渣男”一跃升级成“爸爸”,开发者们直言微软这次“干得漂亮”。要说此次更新令人意外的程度,更有评论调侃称“我不懂英文你不要骗我”…… 俗话说的好,大树底下好乘凉,微软越成功,我们这些依托微软的开发者们也才能有更多更好的机会。 Oracle对JDK的收费以及.NET Core给我们的机遇 其实这一块前段时间炒的很火,最近声音在慢慢的淡化。所以我也不对这块做过多的阐述,其实我这里向引用张队一篇文章的话:2018年年末的听到大量的互联网公司裁员消息,但是我服务这几家客户都有很强烈的招聘.NET Core开发人员的需求,这是大量学习Java的同学转向.NET Core平台的好时机,我一直认为做Java开发的同学比做传统.NET的人员更容易用好.NET Core, Java相比C#,在使用了C#后你不会再去想用Java,而且.NET Core有你非常熟悉的Spring Cloud那一套约定的编程范式,然而见过很多.NET 开发人员, 依赖注入都没用过,更别说用Linux了。在这里我没有贬低.NET开发人员的意思,技术更多的还是要靠自己的努力,我们努力一起成为一群不被时代抛弃的程序猿。 新年展望 我会跟“金焰的世界”一起把公众号运营好,坚持为大家分享更多实战技巧。如果可以的话这个CMS系列教程反向比较好的话,我会再录一个系列的.NET Core视频教程回馈给大家!极客时间订阅的的一些教程进行学习,努力提升自己,至少能够不掉队,努力向DevOps转变,提升!同时多向张队,大石头等圈内大牛学习,提升自己的眼界!最后祝大家元旦快乐,算是晚到的祝福吧!也同时祝大家新年快乐,虽然有点早! 最后 窗外,大雪还在纷飞,既猛又烈,但是风景却很好,正如2018年的互联网圈,各种风起云涌,而微软独占鳌头重回世界第一市值的王座,虽然这些都与我无关,但是我想对大伙说,尤其是对.NET Core说,这会是一个很好的机遇!最后的最后在这2019年的第一场既猛又烈的大雪中,让我们一起立个Flag,一起加油,努力吧! 最后的最后来一张镇楼符:
最近有个需求就是一个抽象仓储层接口方法需要SqlServer以及Oracle两种实现方式,为了灵活我在依赖注入的时候把这两种实现都给注入进了依赖注入容器中,但是在服务调用的时候总是获取到最后注入的那个方法的实现,这时候就在想能不能实现动态的选择使用哪种实现呢?如果可以的话那么我只需要在配置文件中进行相应的配置即可获取到正确的实现方法的调用,这样的话岂不快哉!今天我们就来一起探讨下实现这种需求的几种实现方式吧。 代码演示 在开始实现的方式之前,我们先模拟下代码。由于真实系统的结构比较复杂,所以这里我就单独建一个类似的项目结构代码。项目如下图所示: 接下来我来详细说下上面的结果作用及代码。 MultiImpDemo.I 这个项目是接口项目,里面有一个简单的接口定义ISayHello,代码如下: public interface ISayHello { string Talk(); } 很简单,就一个模拟讲话的方法。 MultiImpDemo.A 这个类库项目是接口的一种实现方式,里面有一个SayHello类用来实现ISayHello接口,代码如下: /** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述: *│ 作 者:yilezhu *│ 版 本:1.0 *│ 创建时间:2019/1/7 17:41:33 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 命名空间: MultiImpDemo.A *│ 类 名: SayHello *└──────────────────────────────────────────────────────────────┘ */ using MultiImpDemo.I; using System; using System.Collections.Generic; using System.Text; namespace MultiImpDemo.A { public class SayHello : ISayHello { public string Talk() { return "Talk from A.SayHello"; } } } MultiImpDemo.B 这个类库项目是接口的另一种实现方式,里面也有一个SayHello类用来实现ISayHello接口,代码如下: /** *┌──────────────────────────────────────────────────────────────┐ *│ 描 述: *│ 作 者:yilezhu *│ 版 本:1.0 *│ 创建时间:2019/1/7 17:41:45 *└──────────────────────────────────────────────────────────────┘ *┌──────────────────────────────────────────────────────────────┐ *│ 命名空间: MultiImpDemo.B *│ 类 名: SayHello *└──────────────────────────────────────────────────────────────┘ */ using MultiImpDemo.I; using System; using System.Collections.Generic; using System.Text; namespace MultiImpDemo.B { public class SayHello:ISayHello { public string Talk() { return "Talk from B.SayHello"; } } } MultiImpDemo.Show 这个就是用来显示我们模拟效果的API项目,首选我们在ConfigureServices中加入如下的代码来进行上述两种实现方式的注入: services.AddTransient<ISayHello, MultiImpDemo.A.SayHello>(); services.AddTransient<ISayHello, MultiImpDemo.B.SayHello>(); 在api实现里面获取服务并进行模拟调用: private readonly ISayHello sayHello; public ValuesController(ISayHello sayHello) { this.sayHello = sayHello; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHello.Talk() }; } 代码很简单对不对?你应该看的懂吧,这时候我们运行起来项目,然后访问API'api/values'这个接口,结果总是显示如下的结果: 两种需求对应两种实现 这里有两种业务需求!第一种业务中只需要对其中一种实现方式进行调用,如:业务需要SqlServer数据库的实现就行了。第二种是业务中对这两种实现方式都有用到,如:业务急需要用到Oracle的数据库实现同时也有用到SqlServer的数据库实现,需要同时往这两个数据库中插入相同的数据。下面分别对这两种需求进行解决。 业务中对这两种实现方式都有用到 针对这种情况有如下两种实现方式: 第二种实现方式 其实,在ASP.NET Core中,当你对一个接口注册了多个实现的时候,构造函数是可以注入一个该接口集合的,这个集合里是所有注册过的实现。 下面我们先改造下ConfigureServices,分别注入下这两种实现 services.AddTransient<ISayHello, A.SayHello>(); services.AddTransient<ISayHello,B.SayHello>(); 接着继续改造下注入的方式,这里我们直接注入IEnumerable<ISayHello>如下代码所示: private readonly ISayHello sayHelloA; private readonly ISayHello sayHelloB; public ValuesController(IEnumerable<ISayHello> sayHellos) { sayHelloA = sayHellos.FirstOrDefault(h => h.GetType().Namespace == "MultiImpDemo.A"); sayHelloB = sayHellos.FirstOrDefault(h => h.GetType().Namespace == "MultiImpDemo.B"); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHelloA.Talk() , sayHelloB.Talk()}; } 然后运行起来看下效果吧 利用AddTransient的扩展方法public static IServiceCollection AddTransient<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService : class; 然后根据我们的配置的实现来进行服务实现的获取。下面就让我们利用代码来实现一番吧: services.AddTransient<A.SayHello>(); services.AddTransient<B.SayHello>(); services.AddTransient(implementationFactory => { Func<string, ISayHello> accesor = key => { if (key.Equals("MultiImpDemo.A")) { return implementationFactory.GetService<A.SayHello>(); } else if (key.Equals("MultiImpDemo.B")) { return implementationFactory.GetService<B.SayHello>(); } else { throw new ArgumentException($"Not Support key : {key}"); } }; return accesor; }); 当然了,既然用到了我们配置文件中的代码,因此我们需要设置下这个配置: 然后我们具体调用的依赖注入的方式需要变化一下: private readonly ISayHello sayHelloA; private readonly ISayHello sayHelloB; private readonly Func<string, ISayHello> _serviceAccessor; public ValuesController(Func<string, ISayHello> serviceAccessor) { this._serviceAccessor = serviceAccessor; sayHelloA = _serviceAccessor("MultiImpDemoA"); sayHelloB = _serviceAccessor("MultiImpDemoB"); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHelloA.Talk() , sayHelloB.Talk()}; } 然后运行看下效果吧: 可以看到A跟B的实现都获取到了!效果实现! 业务只需要对其中一种实现方式的调用 这时候我们可以根据我们预设的配置来动态获取我们所需要的实现。这段话说的我自己都感觉拗口。话不多少,开鲁吧!这里我将介绍三种实现方式。 根据我们的配置文件中设置的key来进行动态的注入。 这种方式实现之前首先得进行相应的配置,如下所示: "CommonSettings": { "ImplementAssembly": "MultiImpDemo.A" } 然后在注入的时候根据配置进行动态的进行注入: services.AddTransient<ISayHello, A.SayHello>(); services.AddTransient<ISayHello, B.SayHello>(); 然后在服务调用的时候稍作修改: private readonly ISayHello sayHello; public ValuesController(IEnumerable<ISayHello> sayHellos,IConfiguration configuration) { sayHello = sayHellos.FirstOrDefault(h => h.GetType().Namespace == configuration.GetSection("CommonSettings:ImplementAssembly").Value); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHello.Talk() }; } OK,到这里运行一下看下效果吧!然后改下配置文件再看下效果! 第二种实现方式,即接口参数的方式这样可以避免上个方法中反射所带来的性能损耗。 这里我们改造下接口,接口中加入一个程序集的属性,如下所示: public interface ISayHello { string ImplementAssemblyName { get; } string Talk(); } 对应的A跟B中的实现代码也要少做调整: A: public string ImplementAssemblyName => "MultiImpDemo.A"; public string Talk() { return "Talk from A.SayHello"; } B: public string ImplementAssemblyName => "MultiImpDemo.B"; public string Talk() { return "Talk from B.SayHello"; } 然后,在实现方法调用的时候稍微修改下: private readonly ISayHello sayHello; public ValuesController(IEnumerable<ISayHello> sayHellos,IConfiguration configuration) { sayHello = sayHellos.FirstOrDefault(h => h.ImplementAssemblyName == configuration.GetSection("CommonSettings:ImplementAssembly").Value); } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { sayHello.Talk() }; } 效果自己运行下看下吧! 第三种实现是根据配置进行动态的注册 首先修改下ConfigureServices方法: var implementAssembly = Configuration.GetSection("CommonSettings:ImplementAssembly").Value; if (string.IsNullOrWhiteSpace(implementAssembly)) throw new ArgumentNullException("CommonSettings:ImplementAssembly未配置"); if (implementAssembly.Equals("MultiImpDemo.A")) { services.AddTransient<ISayHello, A.SayHello>(); } else { services.AddTransient<ISayHello, B.SayHello>(); } 这样的话就会根据我们的配置文件来进行动态的注册,然后我们像往常一样进行服务的调取即可: private readonly ISayHello _sayHello; public ValuesController(ISayHello sayHello) { _sayHello = sayHello; } // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { _sayHello.Talk() }; } 运行即可得到我们想要的效果! 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10236163.html
通过 ASP.NET Core,开发者可轻松配置和管理其应用的安全性。 ASP.NET Core 中包含管理身份验证、授权、数据保护、SSL 强制、应用机密、请求防伪保护及 CORS 管理等等安全方面的处理。 通过这些安全功能,可以生成安全可靠的 ASP.NET Core 应用。而我们这一章就来说道说道如何在ASP.NET Core中处理“跨站请求伪造(XSRF/CSRF)攻击”的,希望对大家有所帮助! 本文已收录至《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/10229954.html 写在前面 上篇文章发出来后很多人就去GitHub上下载了源码,然后就来问我说为什么登录功能都没有啊?还有很多菜单点着没反应。这里统一说明一下,是因为我的代码是跟着博客的进度在逐步完善的,等这个系列写完的时候才代表这个CMS系统的完成!因此,现在这个CMS系统还是一个半成品,不过我会尽快来完成的!废话不多说,下面我们先介绍一下跨站请求伪造(XSRF/CSRF)攻击”的概念,然后再来说到一下ASP.NET Core中是如何进行处理的吧! 什么是跨站请求伪造(XSRF/CSRF) 在继续之前如果不给你讲一下什么是跨站请求伪造(XSRF/CSRF)的话可能你会很懵逼,我为什么要了解这个,不处理又有什么问题呢?CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS非常不同,并且攻击方式几乎相左。XSS利用站点内的信任用户,而CSRF则通过伪装来自受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击往往不大流行(因此对其进行防范的资源也相当稀少)和难以防范,所以被认为比XSS更具危险性。CSRF在 2007 年的时候曾被列为互联网 20 大安全隐患之一。其他安全隐患,比如 SQL 脚本注入,跨站域脚本攻击等在近年来已经逐渐为众人熟知,很多网站也都针对他们进行了防御。然而,对于大多数人来说,CSRF 却依然是一个陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在着 CSRF 漏洞,从而被黑客攻击而使 Gmail 的用户造成巨大的损失。 跨站请求伪造(XSRF/CSRF)的场景 这里为了加深大家对“跨站请求伪造(XSRF/CSRF)”的理解可以看如下所示的图: 如上图所示: 用户浏览位于目标服务器 A 的网站。并通过登录验证。 获取到 cookie_session_id,保存到浏览器 cookie 中。 在未登出服务器 A ,并在 session_id 失效前用户浏览位于 hacked server B 上的网站。 server B 网站中的<img src = "http://www.cnblog.com/yilezhu?creditAccount=1001160141&transferAmount=1000">嵌入资源起了作用,迫使用户访问目标服务器 A 由于用户未登出服务器 A 并且 sessionId 未失效,请求通过验证,非法请求被执行。 试想一下如果这个非法请求是一个转账的操作会有多恐怖! 跨站请求伪造(XSRF/CSRF)怎么处理? 既然跨站请求伪造(XSRF/CSRF)有这么大的危害,那么我们如何在ASP.NET Core中进行处理呢?其实说白了CSRF能够成功也是因为同一个浏览器会共享Cookies,也就是说,通过权限认证和验证是无法防止CSRF的。那么应该怎样防止CSRF呢?其实防止CSRF的方法很简单,只要确保请求是自己的站点发出的就可以了。那怎么确保请求是发自于自己的站点呢?ASP.NET Core中是以Token的形式来判断请求。我们需要在我们的页面生成一个Token,发请求的时候把Token带上。处理请求的时候需要验证Cookies+Token。这样就可以有效的进行验证了!其实说到这里可能有部分童鞋已经想到了,@Html.AntiForgeryToken() 没错就是它,在.NET Core中起着防止 跨站请求伪造(XSRF/CSRF)的作用,想必大伙都会使用!下面我们再一起看看ASP.NET Core的使用方式吧。 ASP.NET Core MVC是如何处理跨站请求伪造(XSRF/CSRF)的? 警告:ASP.NET Core使用 ASP.NET Core data protection stack 来实现防请求伪造。如果在服务器集群中需配置 ASP.NET Core Data Protection,有关详细信息,请参阅 Configuring data protection。 在ASP.NET Core MVC 2.0或更高版本中,FormTagHelper为HTML表单元素注入防伪造令牌。例如,Razor文件中的以下标记将自动生成防伪令牌: <form method="post"> ··· </form> 类似地, IHtmlHelper.BeginForm默认情况下生成防伪令牌,当然窗体的方法不是 GET。(你懂的) 当Html表单包含method="post"并且下面条件之一 成立是会自动生成防伪令牌。 action属性为空( action="") 或者 未提供action属性(<form method="post">)。 当然您也可以通过以下方式禁用自动生成HTML表单元素的防伪令牌: 明确禁止asp-antiforgery,例如 <form method="post" asp-antiforgery="false"> ... </form> 通过使用标签帮助器! 禁用语法,从标签帮助器转化为表单元素。 <!form method="post"> ... </!form> 在视图中移除FormTagHelper,您可以在Razor视图中添加以下指令移除FormTagHelper: @removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers 提示:Razor页面会自动受到XSRF/CSRF的保护。您不必编写任何其他代码,有关详细信息,请参阅XSRF/CSRF和Razor页面。 为抵御 CSRF 攻击最常用的方法是使用同步器标记模式(STP)。 当用户请求的页面包含窗体数据使用 STP: 服务器发送到客户端的当前用户的标识相关联的令牌。 客户端返回将令牌发送到服务器进行验证。 如果服务器收到与经过身份验证的用户的标识不匹配的令牌,将拒绝请求。 该令牌唯一且不可预测。 该令牌还可用于确保正确序列化的一系列的请求 (例如,确保请求序列的: 第 1 页–第 2 页–第 3 页)。所有在ASP.NET Core MVC 和 Razor 页模板中的表单都会生成 antiforgery 令牌。 以下两个视图生成防伪令牌的示例: CSHTML复制 <form asp-controller="Manage" asp-action="ChangePassword" method="post"> ... </form> @using (Html.BeginForm("ChangePassword", "Manage")) { ... } 显式添加到防伪令牌<form>而无需使用标记帮助程序与 HTML 帮助程序元素 @Html.AntiForgeryToken : CSHTML复制 <form action="/" method="post"> @Html.AntiForgeryToken() </form> 在每个前面的情况下,ASP.NET Core 添加类似于以下一个隐藏的表单字段: CSHTML复制 <input name="__RequestVerificationToken" type="hidden" value="CfDJ8NrAkS ... s2-m9Yw"> ASP.NET Core 包括三个筛选器来处理 antiforgery 令牌: ValidateAntiForgeryToken AutoValidateAntiforgeryToken IgnoreAntiforgeryToken 防伪选项 自定义防伪选项中Startup.ConfigureServices: C#复制 services.AddAntiforgery(options => { // Set Cookie properties using CookieBuilder properties†. options.FormFieldName = "AntiforgeryFieldname"; options.HeaderName = "X-CSRF-TOKEN-yilezhu"; options.SuppressXFrameOptionsHeader = false; }); †设置防伪Cookie属性使用的属性CookieBuilder类。 选项 描述 Cookie 确定用于创建防伪 cookie 的设置。 FormFieldName 防伪系统用于呈现防伪令牌在视图中的隐藏的窗体字段的名称。 HeaderName 防伪系统使用的标头的名称。 如果null,系统会认为只有窗体数据。 SuppressXFrameOptionsHeader 指定是否禁止显示生成X-Frame-Options标头。 默认情况下,值为"SAMEORIGIN"生成标头。 默认为 false。 有关详细信息,请参阅CookieAuthenticationOptions。 在我们的CMS系统中的Ajax请求就是使用的自定义HeaderName的方式进行验证的,不知道大家有没有注意到! 需要防伪验证 ValidateAntiForgeryToken实质上是一个过滤器,可应用到单个操作,控制器或全局范围内。除了具有IgnoreAntiforgeryToken属性的操作,否则所有应用了这个属性的Action都会进行防伪验证。如下所示: C#复制 [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel account) { ManageMessageId? message = ManageMessageId.Error; var user = await GetCurrentUserAsync(); if (user != null) { var result = await _userManager.RemoveLoginAsync( user, account.LoginProvider, account.ProviderKey); if (result.Succeeded) { await _signInManager.SignInAsync(user, isPersistent: false); message = ManageMessageId.RemoveLoginSuccess; } } return RedirectToAction(nameof(ManageLogins), new { Message = message }); } ValidateAntiForgeryToken属性所修饰的操作方法包括 HTTP GET 都需要一个Token进行验证。 如果ValidateAntiForgeryToken特性应用于应用程序的控制器上,则可以应用IgnoreAntiforgeryToken来对它进行重载以便忽略此验证过程。 备注:ASP.NET Core 不支持自动将 antiforgery 令牌应用到GET 请求上。 ASP.NET Core MVC在Ajax中处理跨站请求伪造(XSRF/CSRF)的注意事项 ValidateAntiForgeryToken 在进行Token验证的时候Token是从Form里面取的。但是ajax中,Form里面并没有东西。那token怎么办呢?这时候我们可以把Token放在Header里面。相信看了我的源码的童鞋一定对这些不会陌生!如下代码所示: $.ajax({ type: 'POST', url: '/ManagerRole/AddOrModify/', data: { Id: $("#Id").val(), //主键 RoleName: $(".RoleName").val(), //角色名称 RoleType: $(".RoleType").val(), //角色类型 IsSystem: $("input[name='IsSystem']:checked").val() === "0" ? false : true, //是否系统默认 Remark: $(".Remark").val() //用户简介 }, dataType: "json", headers: { "X-CSRF-TOKEN-yilezhu": $("input[name='AntiforgeryKey_yilezhu']").val() }, success: function (res) {//res为相应体,function为回调函数 if (res.ResultCode === 0) { var alertIndex = layer.alert(res.ResultMsg, { icon: 1 }, function () { layer.closeAll("iframe"); //刷新父页面 parent.location.reload(); top.layer.close(alertIndex); }); //$("#res").click();//调用重置按钮将表单数据清空 } else if (res.ResultCode === 102) { layer.alert(res.ResultMsg, { icon: 5 }, function () { layer.closeAll("iframe"); //刷新父页面 parent.location.reload(); top.layer.close(alertIndex); }); } else { layer.alert(res.ResultMsg, { icon: 5 }); } }, error: function (XMLHttpRequest, textStatus, errorThrown) { layer.alert('操作失败!!!' + XMLHttpRequest.status + "|" + XMLHttpRequest.readyState + "|" + textStatus, { icon: 5 }); } }); 如上代码所示我是先获取Token代码然后把这些代码放到ajax请求的Head里面再进行post请求即可! 开源地址 这个系列教程的源码我会开放在GitHub以及码云上,有兴趣的朋友可以下载查看!觉得不错的欢迎Star GitHub:https://github.com/yilezhu/Czar.Cms 码云:https://gitee.com/yilezhu/Czar.Cms 如果你觉得这个系列对您有所帮助的话,欢迎以各种方式进行赞助,当然给个Star支持下也是可以滴!另外一种最简单粗暴的方式就是下面这种直接关注我们的公众号了: 总结 今天我先从跨站点请求伪造的概念以及原理入手,然后给大家讲解了如何进行跨站点请求伪造的处理,后面引出了在ASP.NET Core中如何对其进行处理的!同时给大家说了在Ajax处理中的注意事项,希望能对大伙有所帮助!另外如果你有不同的看法欢迎留言,或者加入NET Core千人群637326624讨论。
作者:依乐祝 原文链接:https://www.cnblogs.com/yilezhu/p/10170712.html 今天在写CzarCms的UnitOfWork的使用使用到了这个TransactionScope事务,因此对它进行了相关资料的查阅并记录如下,希望对大伙在.NET Core中使用有所帮助。 写在前面 您是否曾尝试使用C#代码来实现事务?通常,我们在SQL中一次执行多个Insert / Update语句的话可能就会使用到事务。事务遵循ACID(原子性,一致性,隔离性,持久性)规则,这样所有的语句要么全部执行成功要么全部被取消并执行回滚操作。 而我们今天要讲的TransactionScope则可以允许我们在应用程序级别实现这个过程。在某些情况下,您可能需要在同一个数据库甚至多个数据库(分布式事务)中执行不同的操作,或者由于某些其他约束,它无法在数据库级别来完成,或者应用程序的开发人员对数据库的接触较少,那么这时候TransactionScope将会让你游刃有余。 什么是TransactionScope呢? TransactionScope作为System.Transactions的一部分被引入到.NET 2.0。同时SqlClient for .NET Core 从 2.1 及以上版本开始提供对System.Transactions的支持 。 它是一个类,它提供了一种简单的方法,可以将一组操作作为事务的一部分来进行处理,而不必担心场景背后的复杂性。如果某个操作在执行的过程中失败的话,则整个事务将失败并执行回滚操作,从而撤消已完成的所有操作。所有这些都将由框架处理,从而确保数据的一致性。 如何使用TransactionScope呢? 要使用它,您需要添加System.Transactions的引用,如果你使用的是.net core的话。这个引用被包含在netcoreapp2.2\System.Transactions.Local.dll 中, 该引用是框架库的一部分(通常默认情况下不会自动添加)。添加后,在我们想要使用它的地方添加名称空间 System.Transactions即可。代码如下所示: try { using (TransactionScope scope = new TransactionScope()) { // Do Operation 1 // Do Operation 2 //... // 如果所有的操作都执行成功,则Complete()会被调用来提交事务 // 如果发生异常,则不会调用它并回滚事务 scope.Complete(); } } catch (ThreadAbortException ex) { // 处理异常 } 在上面的代码中我们可以看到我们在创建TransactionScope实例时使用了using 语句块及Disposable块,它确保了当dispose离开块并结束事务范围时调用dispose来进行资源的释放。在一个Transaction范围中,我们可以做多个连接甚至链接到不同数据库的操作的,如下所示: using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString1)) { con.Open(); // 执行操作 1 // 执行操作 2 //... } using (con = new SqlConnection(conString2)) { con.Open(); // 执行操作 1 // 执行操作 2 //... } scope.Complete(); } 下面我们使用两个不同的数据库连接字符串来连接不同的数据库。当然我们也可以根据我们的业务要求使用尽可能多数据库。我们也可以再事务中嵌套事务。如下代码所示: public void DoMultipleTransaction() { try { using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString1)) { con.Open(); // 执行操作1 } OtherTransaction(); scope.Complete(); } } catch (ThreadAbortException ex) { // 处理异常 } } private void OtherTransaction() { using (TransactionScope scope = new TransactionScope()) { using (con = new SqlConnection(conString2)) { con.Open(); // 执行操作 } scope.Complete(); } } 这里最顶层的事务范围称为根范围。另外这里需要注意的是即使通过调用scope.Complete()完成内部事务(上面的OtherTransaction ),如果由于各种原因无法调用rootscope complete,那么整个事务也将被回滚包括内部的事务。 *注意:执行分布式trsanctions时,您可能会收到以下异常之一* 服务器上的MSDTC不可用 已禁用分布式事务管理器(MSDTC)的网络访问。 这两个错误都是由于同样的原因,第一个是在数据库和应用程序是同一个服务器时发生的,而在另一个则是服务跟数据库分别部署在两台服务器上。对于同一台服务器,请转到run-> cmd-> services.msc。运行名为Distributed Transaction Coordinator的服务并自动启动启动类型,以便在系统重新启动时再次启动它。对于2,你可能需要参照这个链接的内容进行相应的设置 TransactionScope 类提供了多个重载构造函数,它们接受 TransactionScopeOption 类型的枚举,而该枚举定义事务范围行为。 TransactionScope对象有以下三个选项: Required:联接环境事务,或者在环境事务不存在的情况下创建新的环境事务。 RequiresNew:成为新的根范围,也就是说,启动一个新事务并使该事务成为其自己范围中的新环境事务。 Suppress:根本不参与事务。 因此没有环境事务。 如果用 Required] 实例化范围并且存在环境事务,则该范围会联接该事务。 相反,如果不存在环境事务,该范围就会创建新的事务并成为根范围。 这是默认值。 在使用 Required时,无论范围是根范围还是仅联接环境事务,该范围中的代码都不需要有不同的行为。 该代码在这两种情况下的行为应相同。 如果用 RequiresNew 实例化范围,则它始终为根范围。 它会启动一个新事务,并且其事务成为该范围中的新环境事务。 如果用 Suppress 实例化范围,则无论是否存在环境事务,范围都从不参与事务。 始终使用此值实例化的作用域具有null作为其环境事务。 下面来让我们看一组实例代码: using (TransactionScope scope = new TransactionScope()) { // 联接环境事务,或者在环境事务不存在的情况下创建新的环境事务。 using (TransactionScope scope1 = new TransactionScope(TransactionScopeOption.Required)) { // Do Operation scope1.Complete(); } //成为新的根范围,也就是说,启动一个新事务并使该事务成为其自己范围中的新环境事务。 using (TransactionScope scope2 = new TransactionScope(TransactionScopeOption.RequiresNew)) { // Do Operation scope2.Complete(); } //根本不参与事务。 因此没有环境事务。 using (TransactionScope scope3 = new TransactionScope(TransactionScopeOption.Suppress)) { // Do Operation scope3.Complete(); } scope.Complet 在这里,我们使用不同的TransactionScopeOptions在父事务下创建了三个事务。默认情况下,范围是required ,这里父事务就是采用的这个默认参数进行创建的。它是一个创建新事务的根范围,并将其标记为环境事务。scope1也是使用required创建的,因为我们已经有了一个环境事务(范围),所以它加入到父事务中。scope2是使用RequiresNew选项创建的,这意味着它是一个独立于环境事务处理的新事务。scope3是用suppress创建的选项,这意味着它不参与任何环境事务。无论环境事务是否成功执行,它都会被执行。父(全局)范围完成后,将提交所有环境事务。 注意点 EF Core 依赖数据库提供程序以实现对 System.Transactions 的支持。 虽然支持在 .NET Framework 的 ADO.NET 提供程序之间十分常见,但最近才将 API 添加到 .NET Core,因此支持并未得到广泛应用。 如果提供程序未实现对 System.Transactions 的支持,则可能会完全忽略对这些 API 的调用。 SqlClient for .NET Core 从 2.1 及以上版本开始支持 System.Transactions。如果尝试在低版本中 如.NET Core 2.0中尝试使用该功能将引发异常。 自版本 2.1 起,.NET Core 中的 System.Transactions 实现将不包括对分布式事务的支持,因此不能使用 TransactionScope 或 CommittableTransaction 来跨多个资源管理器协调事务。主要是不依赖windows中的mstsc功能。 异步方法使用时需要注意: 在下面的例子中,我们在TransactionScope内部使用await。 using(var scope = new TransactionScope()) { var groups = await Context.ProductGroups.ToListAsync()。ConfigureAwait(false); } 看起来没有问题,但它会抛出一个 System.InvalidOperationException:`A TransactionScope must be disposed on the same thread that it was created.` 原因是默认情况下TransactionScope不会从一个线程切换到另一个线程。为了解决这个问题,我们必须使用 TransactionScopeAsyncFlowOption.Enabled: using(var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { var groups = await Context.ProductGroups.ToListAsync()。ConfigureAwait(false); } 现在应该可以了吧?这取决于下面的情况。 如果我们使用和不使用TransactionScopeAsyncFlowOption这个选项的时候都使用了相同的数据库连接,并且第一次执行的时候没有使用这个选项,那么我们会得到另一个异常: System.InvalidOperationException:`Connection currently has transaction enlisted. Finish current transaction and retry.` 换句话说,由于第一个访问的原因,第二个会话将会失败。如下代码所示: try { using (var scope = new TransactionScope()) { // We know this one - System.InvalidOperationException: // TransactionScope必须放在与创建它相同的线程上。 var groups = await Context.ProductGroups.ToListAsync().ConfigureAwait(false); } } catch (Exception e) { // error handling } using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // Implemented correctly but throws anyways // System.InvalidOperationException: // 当前连接已经被记录。完成当前事务并重试。 var groups = await Context.ProductGroups.ToListAsync().ConfigureAwait(false); } 想象一下,如果第一个调用是在第三方库或您正在使用的框架中完成的,二您不了解其中的代码 - 如果您之前没有看到此错误,那么你讲无从下手来解决这个问题。 ## 总结 本文带着大家熟悉了一遍TransactionScope并对其使用进行了介绍!同时介绍了在.NET Core中使用TransactionScope的一些注意事项!希望对大家有所帮助。
SQL Server Management Studio 17.4或更高版本的SSMS中提供了SQL Server漏洞侦测(VA)功能,此功能允许SQL Server扫描您的数据库以查找潜在的安全漏洞,并且可以针对SQL Server 2012或更高版本运行。如果您还没有使用SSMS上的较新版本,请不要担心,您可以在此处 进行下载。 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10157012.html 写在前面 当我们对数据进行任何类型的扫描时总是让我很担心,因为进行数据库扫描时的性能影响可能真的会毁了你的一天。幸运的是,VA是轻量级的,并且可以在不影响性能的情况下运行,同时可以深入了解并指出我们可以在哪里改进SQL Server的安全性。该过程被设计成使用知识库规则来满足数据隐私标准和遵从性,这些规则寻找与Microsoft最佳实践的偏差从而给出。 实战演练 要运行漏洞侦测,只需选择我们需要扫描的数据库,然后右键单击并选择“任务”。在这里,您将看到漏洞评估选项接着选择扫描漏洞。如果您之前运行过一个VA,则可以通过选择“打开现有扫描”来访问它。 它会弹出一个窗口,然后我们按下图所示选择好结果保存的位置。单击“确定”后,该过程将运行。 在这里,点击上面的确定按钮后就会立即执行,执行完成后将弹出结果窗口。这里你可以看到我们的CzarCms的检查结果中有6个失败的检查结果,52个已通过的检查结果。它列出了每个检查的明细并给出了对应的评定的风险等级。 在错误列表中随便单击一个的失败检查结果,我们将看到更多详细信息以及对应的修复步骤,并提供进行修复的脚本(想想是不是有点小激动呢)。赶紧打开看看吧。 这里我们简单选择第二个吧,VA1143 - 'dbo' user should not be used for normal service operation 什么你看不懂?我擦,不会百度翻译啊?““dbo”用户不应该用于正常的服务操作”,啥意思呢?“dbo”或数据库所有者是一个用户帐户,它隐含了执行数据库中所有活动的权限。sysadmin固定服务器角色的成员自动映射到dbo。此规则检查dbo不是唯一允许访问此数据库的帐户。请注意,在新创建的干净数据库中,在创建其他角色之前,此规则将失败。总结一句话就是你得为你的数据库创建一个单独的用户来提高安全性。如图所示: 您可以在下面看到,它向我们描述了没有遵循的最佳实践规则,并提供了一个查询,我们可以运行该查询来查看结果。我真的很喜欢这个特性,并且它是一个方便的脚本,用于以后评估其他服务器的健康状况时使用。它甚至给了我们一个小的复制按钮,以复制出脚本和选项打开它在查询窗口。 只指出错误而不给出解决方法的行为是可耻的,所以伟大的微软给出了我们来补救的步骤以及脚本。这里我们进一步向下滚动,您将看到建议的补救步骤和脚本。如果没有提供脚本,它将为您提供一个链接,通过这个链接可以找到有关如何解决问题的正确文档。在我看来,VA做了很好的解释解决问题所需的东西。请记住,虽然这是由Microsoft创建的,但我还是建议你在生产环境部署之前来运行这个漏洞检查并进行相关的补救。 这里需要注意一下,如下图所示你可以设置结果基线 。基线允许您对结果报告中的错误结果进行接收,这样在下次漏洞扫描的时候这个错误的结果就不会出现在错误列表里面了。 通过将结果标记为BASELINE,您告诉VA,这个错误在您的环境中是可接受的,尽管它可能不符合最佳实践或监管标准。将来与基线匹配的任何内容都标记为在后续扫描中传递,并将记录按自定义基线传递的原因。这个基线匹配的结果会在后期的漏洞扫描进行传递,如下所示:我讲两个结果设置为了基线 当我再次扫描时,我们将会看到这一点。如下所示,扫描报告现在显示我只有1个失败(我没有修复的问题),附加信息列显示原因的基线。  总结 SQL Server漏洞评估是评估数据隐私、安全性和遵从性标准的一个非常好的第三方工具,并且非常容易使用。纸上得来终觉浅,还不赶紧尝试一下,看看数据库存在哪些可以提升的地方吧。
上篇给大家从零开始搭建了一个我们的ASP.NET Core CMS系统的开发框架,具体为什么那样设计我也已经在第十篇文章中进行了说明。不过文章发布后很多人都说了这样的分层不是很合理,什么数据库实体应该跟仓储放在一起形成领域对象,什么ViewModel应该放在应用层结构仓储层与UI层。其实我想说的是,这样都没问题,看你自己的理解了!我上篇文章已经说了,如果你愿意,完全可以把所有的层融合在一起,随意合并分离这个依你个人喜好。我也是本着简单原则以及合适原则的思想来进行那样的分层结构,觉得这样层次更分明些。还有虽然现在DDD的思想很流行,但是实现起来确很复杂,小项目就别那样折腾了。如果你有不同的意见,欢迎加群讨论。什么?你问我群号?自己找去,我才不会告诉你! 本文已收录至《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10112406.html 写在前面 今天我们就进入.NET Core实战项目之CMS的开发篇了,在开始之前呢我们首先需要把我们前面设计的逻辑模型转换成对应的物理模型,再根据我们物理模型生成相应的数据库脚本,接着我们就新建数据库,然后执行下我们生成的脚本即可。当然这么多表如果一个一个的写对应的数据库实体模型,一个一个的写仓储层代码以及服务层代码,感觉就是在搬砖啊,有木有,所以当然得自己实现个代码生成器来自动生成这些代码了!下面我们一步步来先生成下数据库然后再打造一个实体模型的代码生成器吧! 数据库生成 生成物理模型 首先用pdm打开我们设计的逻辑模型文件,后缀名是ldm的文件,如下图所示: 依次点击“Tools”-》"Generate Logical Data Model",如下图所示。或者直接使用快捷键Ctrl+Shift+P 打开物理模型生成选项对话框。 如下图所示选择号对应的数据库后,自定义物理模型的名称代码后点击确定即可生成物理模型。这里数据库类型有很多选择如:mysql,sqlserver,oracle等等,我们选择sqlserver2008,你可以随意从下拉框选择一个数据库进行生成(当然要跟你的数据库对应)! 注意这里生成的物理模型默认是不会生成注释的如下图所示: 怎么办呢?如何才能讲Name 列的内容拷贝到Comment这个里面呢?因为这个Commment里面的内容才会真正的转换到数据库字段的注释。 这时候你需要Tools->Execute Commands->Edit/Run Scripts (或者快捷键Ctrl+Shift+X)打开脚本执行的窗口,然后把下面的代码拷贝进行 run一下即可。 '代码一:将Name中的字符COPY至Comment中 Option Explicit ValidationMode = True InteractiveMode = im_Batch Dim mdl ' the current model ' get the current active model Set mdl = ActiveModel If (mdl Is Nothing) Then MsgBox "There is no current Model " ElseIf Not mdl.IsKindOf(PdPDM.cls_Model) Then MsgBox "The current model is not an Physical Data model. " Else ProcessFolder mdl End If ' This routine copy name into comment for each table, each column and each view ' of the current folder Private sub ProcessFolder(folder) Dim Tab 'running table for each Tab in folder.tables if not tab.isShortcut then tab.comment = tab.name Dim col ' running column for each col in tab.columns col.comment= col.name next end if next Dim view 'running view for each view in folder.Views if not view.isShortcut then view.comment = view.name end if next ' go into the sub-packages Dim f ' running folder For Each f In folder.Packages if not f.IsShortcut then ProcessFolder f end if Next end sub 这里脚本执行的很快,你也可以把脚本保存起来下次再用,执行后的效果如下所示: 我们的Comment这一行的内容已经跟Name一样了! 数据库脚本生成 首先打开我们生成的物理模型,扩展名为pdm的文件,如下图所示,乍一看跟物理模型差不多,实际上还是有区别的! 然后依次如下图所示选择“Database”->"Generate Database" 或者快捷键Ctrl+G打开数据库生成选项对话框 如下图所示设置一下生成的数据库脚本的路径以及脚本名称即可生成数据库脚本文件,如下图所示: 到我们上面设置的文件夹里即可查看到我们生成的数据库脚本,如下图所示: 数据库生成 打开我们的数据库,并新建一个名为CzarCms的新的数据库,如下图所示: 选择我们新建的数据库,然后按照如下图所示的方式打开我们刚才生成的数据库脚本 如下图所示确认一下目前选择的是你刚新建的数据库,然后点击执行,执行下脚本 不出以外的话会出现如下图所示的“命令已成功完成”的消息,这表示脚本执行成功了,然后刷新下我们刚才的数据库,可以看到我们的表已经生成成功了! 这里你可以检查下,看看生成的数据库表有没有问题,如果有问题的话,重新走一遍流程生成脚本然后执行下就行了,不过需要注意的是,如果你数据库中有数据就要当心了,重新生成的脚本会drop掉你的表重新创建,所以如果是个别字段出问题的话就逻辑模型以及物理模型修改后,手动在数据库中修改即可! 这里我给每个表的主键设计了自增,给isdelete等等设置了默认的0,以及addtime设置了getdate()等等。 实体模型生成器编写 好了,上面我已经带着你一步一步的演示了数据库的创建过程,下面我就带着你实现一个简单的POCO实体对象的代码生成器吧!什么?市面上不是有很多代码生成器吗?靠,我就是要带着你自己实现一个,咋滴?是用别人的爽,还是用自己实现的爽呢?自己琢磨吧! 思考 大家先脑补一下,如果是你想根据数据库实现一个代码生成器你的思路是怎样的呢?是不是首先得获取下数据库里面的所有的表,然后获取这些表对应的列以及列的类型,是否为空等等信息。然后再建一个模板,循环这些表的信息来根据模板创建对应的文件呢。至于模板文件可能你会想到T4或者CodeSmish模板等等,可这些都太复杂了,复杂的语法以及灵活性的问题我这里选择另一个文本文件的形式来进行代码的生成。 这个代码生成器的灵感以及部分代码来自于Zxw.Framework.NetCore,这个框架的github地址是:https://github.com/VictorTzeng/Zxw.Framework.NetCore/tree/master/Zxw.Framework.NetCore 有兴趣的小伙伴可以看下。 下面就让我们简单实现下我们自己的实体模型代码生成器吧. 实体代码生成器 首先我们创建一个Option对象来接收我们所需要的参数,比如说:数据库类型,数据库连接字符串,作者,实体模型的命名空间等等,如下所示: /// <summary> /// yilezhu /// 2018.12.12 /// 代码生成选项 /// </summary> public class CodeGenerateOption { /// <summary> /// 数据库连接字符串 /// </summary> public string ConnectionString { get; set; } /// <summary> /// 数据库类型 /// </summary> public string DbType { get; set; } /// <summary> /// 作者 /// </summary> public string Author { get; set; } /// <summary> /// 代码生成时间 /// </summary> public string GeneratorTime { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); /// <summary> /// 输出路径 /// </summary> public string OutputPath { get; set; } /// <summary> /// 实体命名空间 /// </summary> public string ModelsNamespace { get; set; } } 从数据库里面获取所有表的脚本,这里我只是简单的实现了下SqlServer的代码,后续我会对这块进行提取封装,并支持MySql,Oracle,PSQL等等: //TODO 从数据库获取表列表以及生成实体对象 if (_options.DbType != DatabaseType.SqlServer.ToString()) throw new ArgumentNullException("这是我的错,目前只支持MSSQL数据库的代码生成!后续更新MySQL"); DatabaseType dbType = DatabaseType.SqlServer; string strGetAllTables = @"SELECT DISTINCT d.name as TableName, f.value as TableComment FROM sys.syscolumns AS a LEFT OUTER JOIN sys.systypes AS b ON a.xusertype = b.xusertype INNER JOIN sys.sysobjects AS d ON a.id = d.id AND d.xtype = 'U' AND d.name <> 'dtproperties' LEFT OUTER JOIN sys.syscomments AS e ON a.cdefault = e.id LEFT OUTER JOIN sys.extended_properties AS g ON a.id = g.major_id AND a.colid = g.minor_id LEFT OUTER JOIN sys.extended_properties AS f ON d.id = f.major_id AND f.minor_id = 0"; List<DbTable> tables = null; using (var conn = new SqlConnection(_options.ConnectionString)) { tables = conn.Query<DbTable>(strGetAllTables).ToList(); 遍历每个表然后获取每个表对应的列(也是只实现的SqlServer的代码) tables.ForEach(item => { string strGetTableColumns = @"SELECT a.name AS ColName, CONVERT(bit, (CASE WHEN COLUMNPROPERTY(a.id, a.name, 'IsIdentity') = 1 THEN 1 ELSE 0 END)) AS IsIdentity, CONVERT(bit, (CASE WHEN (SELECT COUNT(*) FROM sysobjects WHERE (name IN (SELECT name FROM sysindexes WHERE (id = a.id) AND (indid IN (SELECT indid FROM sysindexkeys WHERE (id = a.id) AND (colid IN (SELECT colid FROM syscolumns WHERE (id = a.id) AND (name = a.name))))))) AND (xtype = 'PK')) > 0 THEN 1 ELSE 0 END)) AS IsPrimaryKey, b.name AS ColumnType, COLUMNPROPERTY(a.id, a.name, 'PRECISION') AS ColumnLength, CONVERT(bit, (CASE WHEN a.isnullable = 1 THEN 1 ELSE 0 END)) AS IsNullable, ISNULL(e.text, '') AS DefaultValue, ISNULL(g.value, ' ') AS Comment FROM sys.syscolumns AS a LEFT OUTER JOIN sys.systypes AS b ON a.xtype = b.xusertype INNER JOIN sys.sysobjects AS d ON a.id = d.id AND d.xtype = 'U' AND d.name <> 'dtproperties' LEFT OUTER JOIN sys.syscomments AS e ON a.cdefault = e.id LEFT OUTER JOIN sys.extended_properties AS g ON a.id = g.major_id AND a.colid = g.minor_id LEFT OUTER JOIN sys.extended_properties AS f ON d.id = f.class AND f.minor_id = 0 WHERE (b.name IS NOT NULL) AND (d.name = @TableName) ORDER BY a.id, a.colorder"; item.Columns = conn.Query<DbTableColumn>(strGetTableColumns, new { TableName = item.TableName }).ToList(); 接下来就是对数据库获取的列进行一个转换,根据数据库字段类型转换成对应的C#类型了 item.Columns.ForEach(x => { var csharpType = DbColumnTypeCollection.DbColumnDataTypes.FirstOrDefault(t => t.DatabaseType == dbType && t.ColumnTypes.Split(',').Any(p => p.Trim().Equals(x.ColumnType, StringComparison.OrdinalIgnoreCase)))?.CSharpType; if (string.IsNullOrEmpty(csharpType)) { throw new SqlTypeException($"未从字典中找到\"{x.ColumnType}\"对应的C#数据类型,请更新DbColumnTypeCollection类型映射字典。"); } x.CSharpType = csharpType; }); 既然所有的表以及表对应的列我们都拿到了,那么我们就可以进行代码的生成了,当然在生成之前还得创建我们的模板文件: // 本代码由代码生成器生成请勿随意改动 // 生成时间 {GeneratorTime} using System; namespace {ModelsNamespace} { /// <summary> /// {Author} /// {GeneratorTime} /// {Comment} /// </summary> public class {ModelName} { {ModelProperties} } } 看到没有,很简单的POCO对象的样子,接下来就是生成对应的模板了,具体怎么生成呢?思考下:是不是首先读取模板文件到一个string里面,然后就是简单的replace了!很简单吧,具体的代码我都上传到了Github上,文章末尾我会给出地址。另外为了大家引用的方便我已经把这个Czar.Cms.Core项目制作成了Nuget包,大家只需要搜索这个包引用下就可以用了!什么?Nuget包怎么引用啊?骚年你可以上天了~ 测试实体代码生成器 Czar.Cms.Test 这个项目添加Nuget包引用,引用后的Nuget如下所示: 接下来就是新建一个测试类,然后创建一个依赖注入容器,并把我们需要的Option传递进去,如下所示: /// <summary> /// 构造依赖注入容器,然后传入参数 /// </summary> /// <returns></returns> public IServiceProvider BuildServiceForSqlServer() { var services = new ServiceCollection(); services.Configure<CodeGenerateOption>(options => { options.ConnectionString = "Data Source=.;Initial Catalog=CzarCms;User ID=sa;Password=1;Persist Security Info=True;Max Pool Size=50;Min Pool Size=0;Connection Lifetime=300;";//这个必须 options.DbType = DatabaseType.SqlServer.ToString();//数据库类型是SqlServer,其他数据类型参照枚举DatabaseType//这个也必须 options.Author = "yilezhu";//作者名称,随你,不写为空 options.OutputPath = @"E:\workspace\vs2017\Czar.Cms\src\Czar.Cms.Models";//实体模型输出路径,为空则默认为当前程序运行的路径 options.ModelsNamespace = "Czar.Cms.Models";//实体命名空间 }); services.AddSingleton<CodeGenerator>();//注入Model代码生成器 return services.BuildServiceProvider(); //构建服务提供程序 } 接着就是写我们的测试方法了,代码如下: [Fact] public void GeneratorModelForSqlServer() { var serviceProvider= BuildServiceForSqlServer(); var codeGenerator = serviceProvider.GetRequiredService<CodeGenerator>(); codeGenerator.GenerateModelCodesFromDatabase(); Assert.Equal(0,0); } 运行一下我们的Live Unit Testing 然后看一下我们的Czar.Cms.Models下面已经生成了对应的实体文件,如下图所示: 随便打开一个看小效果如下:我标注的你猜猜看都是对应的哪个Options 开源地址 这个系列教程的源码我会开放在GitHub以及码云上,有兴趣的朋友可以下载查看!觉得不错的欢迎StarGitHub:https://github.com/yilezhu/Czar.Cms码云:https://gitee.com/yilezhu/Czar.Cms如果你觉得这个系列对您有所帮助的话,欢迎以各种方式进行赞助,当然给个Star支持下也是可以滴!另外一种最简单粗暴的方式就是下面这种直接关注我们的公众号了:第一时间收到更新推送。 总结 这篇文章我们一步一步的生成了我们的数据库,然后手把手带着你实现了我们自己的实体模型代码生成器来简化我们的开发过程。接下来我们就开始实现仓储层应用层的代码了,同时我们会提取通用部分的代码来进行模板代码生成来简化我们的工作!俗话说的好,不会偷懒的程序员不是一个好爸爸,好丈夫,好儿子,减少代码的时间多抽点时间陪陪家人吧!如果你有其他想法可以在下方留言,或者加群637326624跟大伙一起讨论。共同进步!共勉!
这两天比较忙,周末也在加班,所以更新的就慢了一点,不过没关系,今天我们就进行千呼万唤的系统开发框架的设计。不知道上篇关于架构设计的文章大家有没有阅读,如果阅读后相信一定对架构设计有了更近一部的理解,如果你没有阅读也希望大家能好好阅读一下!其实说白了,架构是为了应对软件系统复杂度而提出的一个解决方案,架构设计的最终目的也就是为了让复杂的问题简单化!今天我们就结合架构设计的思想来进行我们的CMS实战项目的架构设计,接着再设计下开发框架吧。如果你有其他看法或者见解欢迎加入我们的实战项目交流群637326624 跟大伙共同交流! 本文已收录至《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/10094357.html 写在前面 仔细想想我们的这个极简CMS系统,可以说很简单,简单到都无须进行特殊的架构设计,只需按照你所熟悉的编码方式直接进行快速的编码实现即可,如果做得好的话,访问量上来了你再加一个缓存处理完全能够支撑一定的并发!如下图所示:我们前期先进行单体架构的实现,等后期分布式系列实战课程的时候再讲解如何进行分布式微服务架构的实现。 看到没有,标准的单体架构,只是在数据库层之前加了一个缓存的设计来应对一些并发的情况!既然架构设计确定了,那么我们就进行开发框架的搭建吧!如果架构的复杂点的话,可能涉及到数据库集群,站点集群及负载均衡,可是我们完全没必要那样玩!一个阶段设计一个阶段的架构,要知道天猫也不是刚开始就架构的这么完善支持这么高的并发的!而是经过这么多次双十一的考验之后慢慢完成到今天这个能够支持每秒这么次并发的!说白了,架构是一个演变的过程,而并非设计的越复杂,越完善就表示架构设计的就越好的(有点拗口,自己理解下),而要结合实际,让需求来驱动架构。在分析设计阶段,需要考虑一定的人力与时间去"跳出代码,总揽全局",为业务和IT技术之间搭建一座"桥梁"。 CMS系统开发框架 话不多数,先看下我的项目结构截图吧! 本来想进行很复杂的框架的实现的,仿照DDD的思想进行开发框架的搭建,后来想想何必呢,这么简单的系统搞得那么复杂,严重影响开发效率,反而得不偿失。后来经过深思熟虑后精简精简再精简,斟酌斟酌再斟酌后就有了上面这样的项目结构。乍一看10个项目,是不是吓得马上就要关闭网页了呢?下面我会给你详细讲解每一个项目的作用以及所要实现的功能。 其实明眼人一看这个结构就已经知道了每个模块所要实现的功能了,这样的分层设计可谓简单的都不需要我过多介绍,你都能明白每一个项目是用来干什么的(明白人也可以进行项目的再度融合,甚至简单粗暴的合并到一个项目里面,不过本人更喜欢这种分层的设计感觉结构更清晰)。可是我这里还是要啰嗦两句给你介绍下: 既然微软已经在前两天将正式版的.NET Core SDK升级到了2.2的版本,那么我们的CMS系统就用.NET Core2.2进行搭建吧!当然,你在练习的时候也可以使用2.1进行,没有强制要求。 注意:ASP.NET Core2.2对VisualStudio有一定的要求必须是2017的高版本才能用。其目前的版本是15.8.4 总之尽量不要低于我这个版本,我正准备升级呢! UI 用户UI层:这个就是我们CMS系统所要呈现的用户界面,而我们得CMS系统又包含后台管理模块以及前台网站模块,因此这个解决方案文件夹下面有两个ASP.NET Core网站项目,留个思考题给你吧,猜猜看哪个项目是后台管理模块,哪个项目是前台网站模块呢?把你的答案写在留言区或者加群跟大伙讨论下吧! Application 应用层:这个层提供对用户界面的接口访问,用户界面层的两个模块如果想跟数据库交互都需要通过这个层来进行。这个应用层起到用户界面跟数据库操作进行解耦的作用。 Repositonry 仓储层:这个层主要就是跟数据库的交互了,任何跟数据库有关的操作都在这层来进行实现,看了上面的图相信你已经猜到了,前期我只是实现SqlServer的仓储实现,至于其他数据库的实现你只需要再建一个Czar.Cms.Repository.数据库名 的仓储实现就可以了!这里我们也是采用依赖抽象而不依赖具体实现所以方便后期的扩展。 Entity 实体对象层:这个层感觉有点多余,完全可以把这个界面融合到其他层,但是我并没有这样做,目的也是让结构更清晰,更容易理解。这里有两个项目,相信一路看教程过来的朋友一定还记得我的第二篇文章《.NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了 》中用的是ViewModel而不是直接用实体对象了!因为实际引用中可能我们页面中需要的数据跟我们数据库中的数据并不完全一样的,而且,有时候我们页面中可能包含了更多地信息,这时候我们怎么往视图中传递数据呢?这时候我们就有了ViewModel的概念。比方说:我们的有一个订单详细页要同时显示订单的信息,以及订单对应的商品列表,这时候怎么办呢?我们用一个ViewModel包含了订单实体,并且包含了商品的列表就可以更方便的把数据传递到视图里面了! Infrastructure 基础设施层:这个层也是我们代码的核心层了,我们会在这里实现很多我们通用的方法,比方说帮助类,对字符串String进行一些扩展,序列化与反序列化,HTTP请求,过滤器,日志功能,中间件的扩展等等。总之这个里面包含了Czar.Cms的所有核心。 Test 测试层:这个层不用多说了吧,就是对系统进行测试的!里面包含单元测试以及集成测试! 相信通过我上面的介绍你一定会感觉到这个CMS系统的开发框架的层次非常清晰了吧!其实作为新手时期的我也是,看到项目太多的话就从心里面害怕,其实大伙大可不必,看到让你害怕的事情就要勇敢的面对它,战胜它,一定要跳出自己的舒适区。 GitHub与码云上的项目开源地址 今天我们搭建的这个项目的结构我已经同步更新到Github以及码云上了,有兴趣的朋友可以下载查看!觉得不错的欢迎StarGitHub:https://github.com/yilezhu/Czar.Cms码云:https://gitee.com/yilezhu/Czar.Cms如果你觉得这个系列对您有所帮助的话,欢迎以各种方式进行支持,最简单有效的就是博客园给个推荐,GitHub给个Star。 总结 本文我首先带着大家理解了一下架构设计的目的,以及架构设计的演变性。接着对我们这个ASP.NET Core的CMS实战项目进行了开发框架的设计。并对每个项目的所要实现的功能以及各自的职责进行了相关的介绍!相信你已经能够清楚的明白了这个架构的思想!到此,设计篇已经结束,接下来就让我们进行真正的项目开发吧即开发篇的开始!
写在前面 上一篇文章中我带着大家进行了权限部分的极简设计,也仅仅是一个基本的权限设计。不过你完全可以基于这套权限系统设计你的更复杂的权限系统,当然更复杂的权限系统要根据你的业务来进行,因为任何脱离实际业务的权限设计都是耍流氓!今天这篇文章我们就对CMS系统的内容进行设计。同时下篇文章准备带着大家理解一下架构设计。 这几天我也想了很多,要不要把这个CMS做的尽可能完善,考虑的尽可能周到!想想还是算了,前面还是以极简为主,不然的话严重影响这个系列教程的进度,导致已经有很多朋友都留言要崔更了(这里非常感谢大家对我这个系列文章的期待,毕竟第一次写一个系列的文章)。权限设计部分就提现了极简主义,这篇内容管理呢,更提现了极简主义,只设计文章的管理以及文章分类的管理。先带着大家把这个教程走完。前期主要实现让你可以通过这个CMS系统搭一个极简主义的博客网站吧!毕竟,这个.NET Core实战项目之CMS也是为了带着大家能够系统的开发一个.NET Core项目。如果你在阅读的过程中有任何的问题,欢迎大家在留言区进行留言,或者加入.NET Core实战项目交流群637326624跟大伙一起交流经验。 本文已收录至《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》作者:依乐祝原文链接:https://www.cnblogs.com/yilezhu/p/10073642.html 需求分析 由于目前的需求是这个CMS系统要满足一个博客系统的功能。当然一个博客系统首先要有权限系统,这个我们上篇文章里面的权限设计已经能够满足功能了。可光有权限系统还不够,还要有新建文章,发布文章,文章分类的功能。复杂点的还要有文章评论子系统,留言子系统,友情链接子系统,甚至还包含SEO优化的部分,再复杂点就像博客园一样,还包含会员子系统,会员也可以发布文章等等。但是你以为我会把这些都设计进来吗?骚年,你想多了!我们还是慢慢迭代吧,这里先进行博客内容以及博客分类的设计吧!至于其他的功能以后再慢慢迭代吧!同时这个项目我会一直开源在GitHub上,持续的更新,这些功能后期都会有的。综上,我们的需求很明确:文章管理,以及文章分类管理!文章要求记录阅读量。就这么简单,惊不惊喜?意不意外?哈哈!要不怎么说极简呢? 逻辑模型设计 这次设计的比较顺畅,没有一点点停顿,可以说一步到位,为什么??因为实在是极简啊!不信?我就直接上图了!都不用备注,你就能看懂!当然,设计的PDM文章我今天就会放到GitHub上。地址,在文章最后给出。 是不是很简单,就两张表,可这两张表包含的内容可不少。下面我们就来说道说道 表详细说明 分类表 分类表,顾名思义就是文章的分类,这里分类中有个父分类ID可以进行循环嵌套,这样就可以让分类具有子分类的功能,理论上支持无限嵌套,但是傻瓜才会真的嵌套那么多次吧!另外分类中加入了SEO相关的标题,关键字,以及描述!什么SEO有什么用?自己百度去。表中具体的字段我就不一一列举出来了!因为我会把PDM放到GitHub上面,你完全可以使用PowerDesigner打开看一下。 文章表 文章表就是我们的主表了!一切都是为它服务!因为权限系统作为支撑系统,分类作为文章的辅助,而主角肯定是文章表本身了!前台页面展示也都是展示文章的内容。这里文章我们有浏览量,有了浏览量我们就知道了我们的文章的受欢迎程度。同时,文章表也加入了诸如,是否轮播图播放,是否置顶,是否热门等等字段,好处是我们可以丰富我们的页面功能,通过这些属性来自定义每个部分显示的文章内容!当然你也可以通过分类进行设置,这个你自由发挥!既然是博客系统,文章的SEO功能肯定是不能少的,作为我们的主角,肯定也得有SEO标题,关键字,内容字段,让我们可以自定义这些内容,这里有人或许会问了,万一我没写这些字段怎么办呢?当然给默认值了,这里思考下我会怎么给默认值吧!如果我们看到了比较好的文章,想要转载怎么办呢?这里当然要给你留个来源跟作者的字段了。不然,你不留来源跟作者的话,当心别人会告你侵权哦!废话有点多,pdm文件我会放到GitHub上,自己去看吧!注释写的又那么全,内容又那么少,理解起来又那么容易,你要再不想看的话,我也没办法了! GitHub地址 这里我会把权限设计以及内容管理设计的逻辑视图上传到GayHub上,这里给出地址。觉得不错的,可以给个Star!后续我们也会在这个GayHub仓库进行开发的! GitHub:https://github.com/yilezhu/Czar.Cms码云:https://gitee.com/yilezhu/Czar.Cms 总结 不善于作总结的程序员不是一个好作者!本篇文章带着大家设计了一下我们将要实战的CMS系统的内容管理模块,也是最核心的模块!但,我们却进行了简单的不能再简单的设计!因为如果进行太详细的设计的话,会严重影响更新的进度,目前已经有很多小伙伴崔更了!!!上篇文章的权限设计以及本篇文章的内容设计的逻辑视图的PDM文件我也已经上传到GayHub上了,有兴趣的朋友可以下载查看!下篇文章我们就一起聊聊架构设计!
这次无论如何也要记录下,原因是今天在一台Windows2008R2的电脑上安装.NET Core SDK后再命令行执行dotnet --info 居然爆出了“Failed to load the hostfxr.dll”的问题,之前也遇到过,但是解决了,却没有做记录,害的这里又google了一把!所以写篇文章记录下。 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10057789.html 具体的错误信息如下: Failed to load the dll from [C:Program Filesdotnethostfxr版本号hostfxr.dll], HRESULT: 0x80070057The library hostfxr.dll was found, but loading it from C:Program Filesdotnethostfxr版本号hostfxr.dll failed Installing .NET Core prerequisites might help resolve this problem.http://go.microsoft.com/fwlink/?LinkID=798306&clcid=0x409 解决方法是需要安装KB2533623 这个补丁。这里需要注意下,不同系统版本需要对应具体的补丁,有32位与64位之分。具体的信息你可以访问这个页面进行查看,https://support.microsoft.com/en-us/help/2533623/microsoft-security-advisory-insecure-library-loading-could-allow-remot 文中提供了windows7以及Windows Server 2008 R2对应的更新,大伙下载安装一下。然后再执行dotnet --info 命令,就可以看到久违的界面: 貌似只有Windows7 以及Windows Server 2008 R2才会出现这个问题。 当然还有其他的情况导致.net core sdk 运行异常的!有时候还需要安装 KB2999226这个更新,等下次遇到再补上吧!因为上次出现需要安装KB2999226这个更新的时候我没有做记录啊!
写在前面 这篇我们对用户权限进行极简设计并保留其扩展性。首先很感谢大家的阅读,前面六章我带着大家快速入门了ASP.NET Core、ASP.NET Core的启动过程源码解析及配置文件的加载过程源码解析并引入依赖注入的概念、Git的快速入门、Dapper的快速入门、Vue的快速入门。不知道大伙掌握的怎么样了!如果你有兴趣的话可以加入我们的.NET Core实战项目群637326624跟更多的小伙伴共同进行交流下。 接下来我们就正式进入.NET Core实战项目之CMS的设计篇了。在设计篇呢,我们需要对数据库进行设计,而数据库的设计又分为功能部分设计以及用户权限部分设计。作为设计篇的第一篇,我们先进行权限部分的设计吧!希望对你进行权限设计有所启发。 本篇已经收录至《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10056094.html 需求分析 首先,做一个东西之前必须把需求搞清楚。网上关于权限管理需求分析以及设计的文章也比较多,这里把我们需要实现的这个简单的CMS系统将要实现的权限部分内容罗列如下: 由于水平有限,有设计不合理的地方还请给予指正,我会以迅雷不及掩耳之势给以纠正,从而能够正确的引导更多的人。 权限资源 菜单权限:管理员跟内容编辑者登录系统所拥有的功能菜单是不一样的(先实现这块) 按钮权限:管理员有文章审核的功能,而内容编辑者没有(文章审核通过后才能进行发布,最近听群里小伙伴说权限控制如何控制到按钮,这个后期会考虑加上) 数据权限:内容编辑者A看不到内容编辑者B发表的文章,而管理员可以看到A跟B的文章(这个后期也会考虑加上) 字段权限:内容编辑者看不到文章的审批人是谁,而管理员能看到(这个后期也会考虑加上,而且脱离业务的字段权限,有点耍流氓的感觉) 目前权限部分第一版只实现菜单权限部分,后期会扩展到按钮权限,数据权限以及字段权限!因为如果设计的太多的话对很多新手朋友可能很难消化,同时如果设计的太多的话反而增加系统的复杂性,影响后面课程的进度。 用户用户是应用系统的具体操作者,我这里设计的是不能把权限直接分配给用户,如果用户想拥有某个权限,必须先为这个用户创建一个角色,然后给这个角色分配相应的权限,从而间接的让用户拥有了系统的权限(说的有点拗口,大伙将就着看吧)。当然国内的情况是总有些人比较特殊,这时候可以专门为这个人创建一个特殊的角色来解决问题。 角色为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,以上所有的权限资源都可以分配给角色,然后通过给用户分配某个角色,从而达到给用户分分配目的(就是为了解耦资源权限和用户)角色和用户是N:N的关系。 设计 经过N次优化的数据库结构设计。本来数据库核心表中有很多多对多的关系(用户与角色/角色与菜单等),所以中间多了很多关联关系表,后来想想觉得何苦呢,为什么大伙都喜欢这样的设计,所以为了简化这个过程我进行了如下的设计: 这里你可能会问我:所有多对多的关系都放在一张表里面,怎么保证性能呢?什么?性能?没有千万级别的数据,别跟我谈性能。如果你的系统几十万数据时都会很卡的话,还是乖乖的去恶补一下数据库基础吧。 数据库设计我采用的是PowerDesigner,首先打开软件,新建一个概念模型。然后对这块的设计如下: 上图可能不清晰,所以下面我会对每个表进行详细的说明。 表详细说明 后台管理员 后台管理员顾名思义就是对我们的后台进行管理的人。这里考虑到后期扩展可能会用到会员系统(Users)因此这里的后台管理员表名使用Manager 。后台管理员包含的信息有:主要信息:主键,角色ID,是否锁定登录相关信息:用户名,密码个性化信息:昵称,头像联系方式信息:手机号码,邮箱地址登录相关信息:登录次数,最后一次登录IP,最后一次登录时间操作相关信息:添加人,添加时间,修改人,修改时间其他信息:是否删除,备注 后台管理员角色 这里为了使后台管理员与后台菜单进行解耦引入了角色的概念。一个后台管理员想要具有某个菜单的功能必须给它分配相应角色才能可以,角色又分为系统管理员和超级管理员。超级管理员的角色不能进行修改,拥有后台的所有权限。而系统管理员的功能则可以进行个性化的定制来满足需求。 主要信息:主键,角色类型(超级管理员以及系统管理员),角色名称,是否系统默认(系统默认不能删除,防止误删除)操作相关信息:添加人,添加时间,修改人,修改时间其他 信息:是否删除,备注 后台管理菜单 后台管理菜单是后台的功能导航。是具体功能的单位,当然每个后台管理菜单还包含相应的操作权限,这块我们后期再做具体操作的设计,前期为了考虑大部分人所以这里暂不考虑,但是我已经预留了字段,聪明如你,应该猜得到这是哪个字段吧!主要信息:主键,父菜单ID个性化信息:名称,显示名称,图标地址,链接地址,排序字段,操作权限(没错,保留字段,为后期操作权限做准备)操作信息:添加人,添加时间,修改人,修改时间其他信息:是否删除 角色权限表 用来设计角色权限,由于目前只有菜单权限,后期可以在此表进行操作权限,以及其他权限的扩展:主要信息:主键,角色ID,菜单ID其他信息:操作类型 操作日志 顾名思义,就是对后台管理员的各种操作进行简要的记录主要信息:主键,操作类型操作信息:操作人,操作时间,操作IP,操作人名称其他信息:备注 总结 今天带着大家进行用户权限模块的设计,通过再三的斟酌只保留了这五张表,所以保留下来的这五张表也都个个是精华。之前设计的时候想不通为什么那么热衷于那么多的多对多设计,这样的极简设计也别有一番风味,瞬间感觉整个世界都简单了很多。如果又觉得我的设计不合理的话,还请大家在下面留言或者加我联系我吧!写文章需要动力,希望大家给个推荐支持一下哈!
写在前面 上面文章我给大家介绍了Dapper这个ORM框架的简单使用,大伙会用了嘛!本来今天这篇文章是要讲Vue的快速入门的,原因是想在后面的文章中使用Vue进行这个CMS系统的后台管理界面的实现。但是奈何Vue实现的SPA有一定的门槛,不太适合新手朋友,所以为了照顾大多数人,我准备还是采用asp.net core mvc+html+js+css+layui这个传统的技术栈来实现。但是,不管怎么说我还是会把Vue的基本使用给大伙介绍一下!当然,如果这篇文章我也是抱着学习的态度跟大家一起来了解Vue的,如果你想通过这篇文章就能熟练的使用Vue那你就太天真了!目前,作为后端的我对Vue的掌握也仅仅停留在入门阶段。后期再带着大家一起把这个项目升级到Vue吧! 本篇文章已经收纳入《.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划》另附上.NET Core实战项目交流群:637326624 有兴趣的朋友可以共同交流技术经验。作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/10035275.html Vue是什么 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。说白了Vue.js就是当下很火的一个JavaScript MVVM库(前端库)。相比于Angular.js,Vue.js提供了更加简洁、更易于理解的API,使得我们能够快速地上手并使用Vue.js。如果你之前已经习惯了用jQuery操作DOM,学习Vue.js时请先抛开手动操作DOM的思维,因为Vue.js是数据驱动的,你无需手动操作DOM。它通过一些特殊的HTML语法,将DOM和数据绑定起来。一旦你创建了绑定,DOM将和数据保持同步,每当变更了数据,DOM也会相应地更新。当然了,在使用Vue.js时,你也可以结合其他库一起使用,比如jQuery。 Vue.js的特点 简洁: HTML 模板 + JSON 数据,再创建一个 Vue 实例,就这么简单。 数据驱动: 自动追踪依赖的模板表达式和计算属性。 组件化: 用解耦、可复用的组件来构造界面。 轻量: 生产版本删除了警告后共30.90KB min+gzip,无依赖(2.5.17版本)。 快速: 精确有效的异步批量 DOM 更新。 模块友好: 通过 NPM 或 Bower 安装,无缝融入你的工作流。 如何学习Vue.js 这里介绍几个比较好的Vue学习的网站,都是免费的!大伙可以跟着系统的学习下。毕竟我最开始也是看了下面的官方教程以及菜鸟教程里面的Vue教程的。Vue的官方中文教程:https://cn.vuejs.org/v2/guide/index.htmlVue.js菜鸟教程:http://www.runoob.com/vue2/vue-tutorial.htmlGitHub地址:https://github.com/vuejs/vueReleases地址:https://github.com/vuejs/vue/releases 快速开始运行Vue.js Vue的安装 这里需要注意的是Vue 不支持 IE8 及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。目前最新稳定版本是:2.5.17。目前就两种我认为常用的安装方式罗列如下:至于NPM以及CLI的方式我就不推荐了,专业的前端玩的东西我感觉高大上,懒得折腾。 直接下载并用 在开发环境下不要使用压缩版本,不然你就失去了所有常见错误相关的警告! 开发版本:https://vuejs.org/js/vue.js 包含完整的警告和调试模式 生产版本:https://vuejs.org/js/vue.min.js 删除了警告,30.90KB min+gzip CDN 方式直接引入csn地址即可 官方推荐链接到一个你可以手动更新的指定版本号: <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> 你可以在 cdn.jsdelivr.net/npm/vue 浏览 NPM 包的源代码。 Vue 也可以在 unpkg 和 cdnjs 上获取 (cdnjs 的版本更新可能略滞后)。 请确认了解不同构建版本并在你发布的站点中使用生产环境版本,把 vue.js 换成 vue.min.js。这是一个更小的构建,可以带来比开发环境下更快的速度体验。 Vue.js输出一个Hello World 程序员界万年不变的定理,开始一门语言就从Hello World开始。下面就让我们快速开始用Vue.js输出一个“Hello World!”吧 新建一个html文件,不要跟我说你不知道怎么创建一个html文件,不然你会被“达丝蒂”。这里我们用Visual Studio Code来进行代码的编写。 打开上面新建的helloworld.html文件,并输入如下的内容: 然后在浏览器中打开这个html文件看到如下的结果: 我们已经成功创建了第一个 Vue 应用!看起来这跟渲染一个字符串模板非常类似,但是 Vue 在背后做了大量工作。现在数据和 DOM 已经被建立了关联,所有东西都是响应式的。我们要怎么确认呢?打开你的浏览器的 JavaScript 控制台 (就在这个页面打开),并修改 app.__vue__.message 的值并按回车,你将看到上例相应地更新。 Vue.js常用的指令 Vue.js的指令是以v-开头的,它们作用于HTML元素,指令提供了一些特殊的特性,将指令绑定在元素上时,指令会为绑定的目标元素添加一些特殊的行为,我们可以将指令看作特殊的HTML特性(attribute)。接下来我们就给大家分别来介绍一下Vue中比较常用的指令 v-mode 在Vue.js中可以使用v-model指令,它能轻松实现表单输入和应用状态之间的双向绑定。为了演示这个效果,我们新建一个sample02.html的文件,然后输入如下的代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello World</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <p>{{ message }}</p> <input v-model="message"> </div> <script> new Vue({ el: '#app', data: { message: 'Hello World!' } }) </script> </body> </html> 可以看到文本框的内容发生变化后,对应的文本框上面的文本也发生了变化,这里没有截成动态图,大家可以自行测试。 v-if ,v-else,v-else-if 条件判断指令,大家看着是不是觉得很熟悉呢,简直就跟c#中的if, else if,else 一毛一样啊(当然又有些区别,不过用法一样)下面我们给出要一个实例代码来进行演示,这里我们新建要给sample03.html的文件,然后输入如下的代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello World</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <p>{{ score }}</p> <input v-model="score"> <div v-if="score>=90"> 优秀 </div> <div v-else-if="score>=80&&score<90"> 优 </div> <div v-else-if="score>=70&&score<80"> 良 </div> <div v-else-if="score>=60&&score<70"> 及格 </div> <div v-else> 不及格 </div> </div> <script> new Vue({ el: '#app', data: { score: 100 } }) </script> </body> </html> 在浏览器中打开看到如下的界面,因为初始值我们设置的是100 当你在输入框中改变值的时候,对应的文本框上面及下面的值都会发生变化,大伙可以自己试一下。 v-show v-show也是条件渲染指令,和v-if指令不同的是,使用v-show指令的元素始终会被渲染到HTML,它只是简单地为元素设置CSS的style属性。下面我们创建一个sample04.html的文件,然后输入如下的代码进行测试: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello World</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <p v-show="score<60">成绩为:{{ score }}不及格了!</p> <p v-show="score>=60">成绩为:{{ score }}及格了!</p> <input v-model="score"> </div> <script> new Vue({ el: '#app', data: { score: 50 } }) </script> </body> </html> 然后在浏览器中看到如下的结果 这时候我们查看一下源文件,可以看到下面的那个多了一些style="display:none" 的样式。但是html代码还是被渲染出来了 v-for 循环使用 v-for 指令。v-for 指令需要以 site in sites 形式的特殊语法, sites 是源数据数组并且 site 是数组元素迭代的别名。v-for 可以绑定数据到数组来渲染一个列表下面我们创建一个sample05.html的文件,然后输入如下的代码进行测试: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello World</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app"> <h2>解决问题途径</h2> <ol> <li v-for="method in methods"> {{ method.name }} </li> </ol> </div> <script> new Vue({ el: '#app', data: { methods: [ { name: '谷歌' }, { name: '必应' }, { name: '百度' }, { name: '群里问别人'} ] } }) </script> </body> </html> 然后再浏览器中打开,看到如下的结果: v-bind v-bind指令可以在其名称后面带一个参数,中间放一个冒号隔开,这个参数通常是HTML元素的特性(attribute),如:<div v-bind:class="{ active: isActive }"></div>我们新建一个sample06.html的文件,然后输入如下的代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello World</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .active { font-size: 20px; color: red; } </style> </head> <body> <div id="app"> <p v-bind:class="{active:isActive}">{{ message }}</p> <input v-model="message"> </div> <script> new Vue({ el: '#app', data: { message: 'Hello World!', isActive: true } }) </script> </body> </html> 再浏览器中打开可以看到如下的结果 v-bind指令可以缩写为一个冒号 ,<div :class="{ active: isActive }"></div> v-on v-on指令用于监听DOM事件,它的用语法和v-bind是类似的,例如监听button元素的点击事件:<button v-on:click="doSomething"> 下面我们简单的进行下代码的演示,老规矩,新建一个sample07.html文件,然后输入如下的代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Hello World</title> <!-- 开发环境版本,包含了有帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <style> .active { font-size: 20px; color: red; } </style> </head> <body> <div id="app"> <p class="active">您点击了{{counter}}次!</p> <button v-on:click="addCounter">快点我看效果吧!</button> </div> <script> new Vue({ el: '#app', data: { counter:0 }, methods:{ addCounter:function(){ this.counter++; } } }) </script> </body> </html> 然后在浏览器中打开,并点击按钮按下效果吧 注:v-on指令可以缩写为@符号,如:<button @click="addCounter">快点我看效果吧!</button> 总结 今天带着大家学习了一下Vue.js这个前端框架。首先通过一个Hello World的实例开始,然后给大家介绍了Vue.js中常用的一些指令,并且每个指令都给出了一个实例代码,大伙可以自己跑一下看下效果。当然这也仅仅是Vue的基础,像涉及比较深的组件,路由,动画等等内容没有过多的讲解。主要还是让大家快速的了解一下Vue。同时为了照顾更多的朋友。有兴趣的朋友可以加下我们的.NET Core实战项目交流群637326624,跟大伙进行交流。好了,到这里.NET Core实战项目之CMS的入门篇就结束了。接下来我们就将进入设计篇的内容了!大家准备好了嘛?
写在前面 上篇文章我们讲了如在在实际项目开发中使用Git来进行代码的版本控制,当然介绍的都是比较常用的功能。今天我再带着大家一起熟悉下一个ORM框架Dapper,实例代码的演示编写完成后我会通过Git命令上传到GitHub上,正好大家可以再次熟悉下Git命令的使用,来巩固上篇文章的知识。本篇文章已经收入.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划 有兴趣的朋友可以加入.NET Core项目实战交流群进行交流。 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/10024091.html Dapper是什么 Dapper是.NET下一个轻量级的ORM框架,它和Entity Framework或Nhibnate不同,属于轻量级的,并且是半自动的。也就是说实体类都要自己写。它没有复杂的配置文件,一个单文件就可以了。Dapper通过扩展你的IDbConnection来进行工作的。如果你想了解更多内容的话请点击这里。 Dapper快速入门 前面几篇文章我们进行介绍的时候都是手动在代码里面创建的模拟数据,这篇文章我们就结合Dapper来从数据库进行相关的操作。为了演示的方便,这里的实例代码我们就使用一个简单地asp.net core控制台程序来进行。 开始前的准备 在我们的项目文件夹,单击鼠标右键选择“在当前文件夹下面打开Git Bash” 然后输入git checkout Master 切换回Mater分支,然后输入git checkout -b Sample05 创建一个新的名为“Sample05”的分支,如下所示: 使用vs2017创建一个新的项目,名称为“Sample05” 位置位于我们当前的目录,如下图所示: 接下来打开数据库,新建一个Content内容表,表结构还沿用之前教程中的实体,这里只给出MSSql的脚本:至于MySql的你自己建了,如果你实在不会的话可以到群里问其他小伙伴要吧 CREATE TABLE [dbo].[content]( [id] [int] IDENTITY(1,1) NOT NULL, [title] [nvarchar](50) NOT NULL, [content] [nvarchar](max) NOT NULL, [status] [int] NOT NULL, [add_time] [datetime] NOT NULL, [modify_time] [datetime] NULL, CONSTRAINT [PK_Content] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO ALTER TABLE [dbo].[content] ADD CONSTRAINT [DF_Content_status] DEFAULT ((1)) FOR [status] GO ALTER TABLE [dbo].[content] ADD CONSTRAINT [DF_content_add_time] DEFAULT (getdate()) FOR [add_time] GO CREATE TABLE [dbo].[comment]( [id] [int] IDENTITY(1,1) NOT NULL, [content_id] [int] NOT NULL, [content] [nvarchar](512) NOT NULL, [add_time] [datetime] NOT NULL, CONSTRAINT [PK_comment] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[comment] ADD CONSTRAINT [DF_comment_add_time] DEFAULT (getdate()) FOR [add_time] GO 项目中新增数据库表对应的实体对象,代码如下: public class Content { /// <summary> /// 主键 /// </summary> public int id { get; set; } /// <summary> /// 标题 /// </summary> public string title { get; set; } /// <summary> /// 内容 /// </summary> public string content { get; set; } /// <summary> /// 状态 1正常 0删除 /// </summary> public int status { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime add_time { get; set; } = DateTime.Now; /// <summary> /// 修改时间 /// </summary> public DateTime? modify_time { get; set; } } public class Comment { /// <summary> /// 主键 /// </summary> public int id { get; set; } /// <summary> /// 文章id /// </summary> public int content_id { get; set; } /// <summary> /// 评论内容 /// </summary> public string content { get; set; } /// <summary> /// 添加时间 /// </summary> public DateTime add_time { get; set; } = DateTime.Now; } 项目中添加Dapper的Nugets包,相信一路看教程过来的你一定知道怎么新增Nuget包吧,这里就不过多介绍了。 实战演示 插入操作:将一个对象插入到数据库中,代码如下: /// <summary> /// 测试插入单条数据 /// </summary> static void test_insert() { var content = new Content { title = "标题1", content = "内容1", }; using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"INSERT INTO [Content] (title, [content], status, add_time, modify_time) VALUES (@title,@content,@status,@add_time,@modify_time)"; var result = conn.Execute(sql_insert, content); Console.WriteLine($"test_insert:插入了{result}条数据!"); } } 一次批量插入多条数据,测试代码如下: /// <summary> /// 测试一次批量插入两条数据 /// </summary> static void test_mult_insert() { List<Content> contents = new List<Content>() { new Content { title = "批量插入标题1", content = "批量插入内容1", }, new Content { title = "批量插入标题2", content = "批量插入内容2", }, }; using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"INSERT INTO [Content] (title, [content], status, add_time, modify_time) VALUES (@title,@content,@status,@add_time,@modify_time)"; var result = conn.Execute(sql_insert, contents); Console.WriteLine($"test_mult_insert:插入了{result}条数据!"); } } 执行下代码查看到控制台输出如下的结果: 然后到数据库查看下表中的数据如下: 下面我们再分别测试下删除一条数据,与一次删除多条数据吧,代码如下: /// <summary> /// 测试删除单条数据 /// </summary> static void test_del() { var content = new Content { id = 2, }; using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"DELETE FROM [Content] WHERE (id = @id)"; var result = conn.Execute(sql_insert, content); Console.WriteLine($"test_del:删除了{result}条数据!"); } } /// <summary> /// 测试一次批量删除两条数据 /// </summary> static void test_mult_del() { List<Content> contents = new List<Content>() { new Content { id=3, }, new Content { id=4, }, }; using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"DELETE FROM [Content] WHERE (id = @id)"; var result = conn.Execute(sql_insert, contents); Console.WriteLine($"test_mult_del:删除了{result}条数据!"); } } 然后去数据库里查看,发现主键为2,3,4的数据都已经被删除了,如下图所示: 下面我们再测试下修改吧,也是分别测试一次只修改一条数据(主键为5),与一次批量修改多条数据(主键为6,7) /// <summary> /// 测试修改单条数据 /// </summary> static void test_update() { var content = new Content { id = 5, title = "标题5", content = "内容5", }; using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"UPDATE [Content] SET title = @title, [content] = @content, modify_time = GETDATE() WHERE (id = @id)"; var result = conn.Execute(sql_insert, content); Console.WriteLine($"test_update:修改了{result}条数据!"); } } /// <summary> /// 测试一次批量修改多条数据 /// </summary> static void test_mult_update() { List<Content> contents = new List<Content>() { new Content { id=6, title = "批量修改标题6", content = "批量修改内容6", }, new Content { id =7, title = "批量修改标题7", content = "批量修改内容7", }, }; using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"UPDATE [Content] SET title = @title, [content] = @content, modify_time = GETDATE() WHERE (id = @id)"; var result = conn.Execute(sql_insert, contents); Console.WriteLine($"test_mult_update:修改了{result}条数据!"); } } 现在我们执行下测试代码看下结果吧 再到数据库中查看下数据,上步骤5中最后一张图相比较 增删改都测试了,下面就开始测试查询吧,我们分别来测试下查询指定的数据以及一次查询多条数据来看下结果吧。还是先上代码,: /// <summary> /// 查询单条指定的数据 /// </summary> static void test_select_one() { using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"select * from [dbo].[content] where id=@id"; var result = conn.QueryFirstOrDefault<Content>(sql_insert, new { id=5}); Console.WriteLine($"test_select_one:查到的数据为:"); } } /// <summary> /// 查询多条指定的数据 /// </summary> static void test_select_list() { using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"select * from [dbo].[content] where id in @ids"; var result = conn.Query<Content>(sql_insert, new { ids=new int[] { 6,7} }); Console.WriteLine($"test_select_one:查到的数据为:"); } } 然后我们打上断点然后去看下结果吧!这里图片我没有截成功,所以就不贴了。 关联查询,Dapper的强大之处就在于其关联查询了!为了测试的方便,我们给主键为5的content添加两个comment中,这个插入的代码就不贴出来了,留给大家自行书写吧,如果不会的话可以加群问群里的其他小伙伴吧。这里需要新建一个类 public class ContentWithCommnet { /// <summary> /// 主键 /// </summary> public int id { get; set; } /// <summary> /// 标题 /// </summary> public string title { get; set; } /// <summary> /// 内容 /// </summary> public string content { get; set; } /// <summary> /// 状态 1正常 0删除 /// </summary> public int status { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime add_time { get; set; } = DateTime.Now; /// <summary> /// 修改时间 /// </summary> public DateTime? modify_time { get; set; } /// <summary> /// 文章评论 /// </summary> public IEnumerable<Comment> comments { get; set; } } 然后就是测试代码,运行的查询测试代码如下:查询id为5的文章,文章是包含评论列表的 代码如下: static void test_select_content_with_comment() { using (var conn = new SqlConnection("Data Source=127.0.0.1;User ID=sa;Password=1;Initial Catalog=Czar.Cms;Pooling=true;Max Pool Size=100;")) { string sql_insert = @"select * from content where id=@id; select * from comment where content_id=@id;"; using (var result = conn.QueryMultiple(sql_insert, new { id = 5 })) { var content = result.ReadFirstOrDefault<ContentWithComment>(); content.comments = result.Read<Comment>(); Console.WriteLine($"test_select_content_with_comment:内容5的评论数量{content.comments.Count()}"); } } } 结果如下所示,调试的代码没法截图我也很无奈。 GitHub源码 GitHub的测试源码已经上传,https://github.com/yilezhu/Czar.Cms/tree/Sample05 放在Czar.Cms的Sample05分支上面。大家可以参考下,觉得有用的话记得star哦! 总结 本文给大家演示了Dapper的常用方法,不过都是通过同步的方式进行操作的,如果你想使用异步的话可以自行进行测试。文中的大部分内容都有截图,个别调试无法截图的大伙可以自行调试查看!相信通过本文的实例讲解,大伙应该能够使用dapper进行相应的开发!下一篇文章我们将进行vue的讲解!当然也只是进行很浅层次的讲解。因为我是一个后端,也是抱着学习的态度来进行vue的记录的!主要是以快速上为主。
作者:依乐祝原文链接:https://www.cnblogs.com/yilezhu/p/9998021.html 写在前面 上篇文章我给大家讲解了ASP.NET Core的概念及为什么使用它,接着带着你一步一步的配置了.NET Core的开发环境并创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表。不知道你有没有跟着敲下代码,千万不要做眼高手低的人哦。这篇文章我们就会设计一些复杂的概念了,因为要对ASP.NET Core的启动及运行原理、配置文件的加载过程进行分析,依赖注入,控制反转等概念的讲解等。俗话说,授人以鱼不如授人以渔,所以文章旨在带着大家分析源码,让大家能知其然更能知其所以然。为了偷懒,继续使用上篇文章的例子了!有兴趣的朋友可以加群相互交流!再次感谢张队的审稿! 本文已收录至.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划 点击可以查看更多教程。 ASP.NET Core启动源码解析 这部分我就带着大家一起看下asp.net core项目的运行流程吧!顺带着了解下asp.net core的运行原理,说的不好的话,希望大家给以指正,从而能够正确的帮助更多的人。 首先上一下上篇文章的项目结构吧,如下所示,熟悉C#的朋友应该知道,要找程序的入库,那么就应该找到Main方法。而asp.net core的main方法就在Program.cs文件中。 打开后看到如下的代码,我加了注释,大伙将就看下,下面我们来一步一步的分析 /// <summary> /// Main方法,程序的入口方法 /// </summary> /// <param name="args"></param> public static void Main(string[] args) { CreateWebHostBuilder(args)//调用下面的方法,返回一个IWebHostBuilder对象 .Build()//用上面返回的IWebHostBuilder对象创建一个IWebHost .Run();//运行上面创建的IWebHost对象从而运行我们的Web应用程序换句话说就是启动一个一直运行监听http请求的任务 } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args)//使用默认的配置信息来初始化一个新的IWebHostBuilder实例 .UseStartup<Startup>();// 为Web Host指定了Startup类 可以看到asp.net core程序实际上就是一个控制台程序,运行一个webhost对象从而启动一个一直运行的监听http请求的任务。所以我们的重点就是分析一下这个WebHost创建的过程: 创建IWebHostBuilder-》创建IWebHost-》然后运行创建的IWebHost。 这里我们从IWebHostBuilder的Build分析下创建的过程,有兴趣的朋友可以看下,没兴趣的朋友可以直接跳到下一个步骤继续阅读。 首先到aspnetcore的github开源地址https://github.com/aspnet/AspNetCore/tree/release/2.1 上去下载源码(我们使用的是2.1)。然后使用vscode打开解压后的文件夹。至于vscode如何加载文件,你可以看我这篇文章使用Visual Studio Code开发.NET Core看这篇就够了 当然你也可以在上面的网页上直接找到相应的目录浏览也是可以的。(看结构好像是使用vscode进行开发的) 根据IWebHostBuilder的命名空间我们找到了它的实现,路径为src/Hosting/Hosting/src/WebHostBuilder.cs 通过上面的代码我们可以看到首先是通过BuildCommonServices来构建一个ServiceCollection。为什么说这么说呢,先让我们我们跳转到BuidCommonServices方法中看下吧。 可以看到, var services = new ServiceCollection(); 首先new一个ServiceCollection然后往services里面注入很多内容,比如:WebHostOptions ,IHostingEnvironment ,IHttpContextFactory ,IMiddlewareFactory 等等(其实这里已经设计到依赖注入的概念了,先思考下吧),然后我们在后续就可以使用了!最后这个BuildCommonServices就返回了这个services对象。 在上面的依赖注入中有一个方法,不知道大家注意到没有,因为我们在步骤2贴出的代码里面有一个UseStartup<Startup>() 其实在上面的BuildCommonServices方法中也有对IStartup的注入的。首先,判断Startup类是否继承于IStartup接口,如果是继承的,那么就可以直接加入在services 里面去,如果不是继承的话,就需要通过ConventionBasedStartup(methods)把method转换成IStartUp后注入到services里面去。结合上面我们的代码,貌似我们平时用的时候注入的方式都是采用后者。 我们再回到build方法拿到了BuildCommonServices方法构建的ServiceCollection实例后,通过GetProviderFromFactory(hostingServices) 方法构造出了IServiceProvider 对象。到目前为止,IServiceCollection和IServiceProvider都拿到了。然后根据IServiceCollection和IServiceProvider对象构建WebHost对象。构造了WebHost实例还不能直接返回,还需要通过Initialize对WebHost实例进行初始化操作。那我们看看在初始化函数Initialize中,都做了什么事情吧。 这里我们把代码导航到src/Hosting/Hosting/src/Internal/WebHost.cs找到Initialize方法。如下图所示:主要就是一个EnsureApplicationServices 方法。 我们继续导航查看这个方法的内容如下:就是拿到Startup 对象,然后把_applicationServiceCollection 中的对象注入进去。 至此我们build中注册的对象以及StartUp中注册的对象都已经加入到依赖注入容器中了,接下来就是Run起来了。这个run的代码在srcHostingHostingsrcWebHostExtensions.cs中,代码如下:WebHost执行RunAsync运行web应用程序并返回一个只有在触发或关闭令牌时才完成的任务(这里又涉及到异步编程的知识了,咱们以后再详细讲解) 。这就是我们运行ASP.Net Core程序的时候,看到的那个命令行窗口了,如果不关闭窗口或者按Ctrl+C的话是无法结束的。 至此启动的过程的源码分析完成了。 配置文件 上面给大家介绍了ASP.NET Core的启动过程,中间牵扯到了一些依赖注入的概念。关于依赖注入的概念呢,我们后面再说,这里先给大家讲解下配置文件的加载过程。 打开上篇文章我们创建的项目,并在appsettings.json里面加入如下内容: { "Logging": { "LogLevel": { "Default": "Warning" } }, "Content": { "Id": 1, "title": "title1", "content": "content1", "status": 1, "add_time": "2018-11-21 16:29", "modify_time": null }, "AllowedHosts": "*" } 然后在Startup类中ConfigureServices中注册TOptions对象如下所示: services.Configure<Content>(Configuration.GetSection("Content"));//注册TOption实例对象 这段代码也就是从appsettings.json这个配置文件中的Content这个节点匹配到Content这个对象上。 修改下ContentController这个控制器代码如下: private readonly Content contents; public ContentController(IOptions<Content> option) { contents = option.Value; } /// <summary> /// 首页显示 /// </summary> /// <returns></returns> public IActionResult Index() { return View(new ContentViewModel { Contents=new List<Content> { contents} }); } 按下F5运行下,然后导航到Content目录看到如下页面:说明成功从appsettings.json这个文件中加载了内容。这一切是怎么发生的呢?下面我们就一步一步的来分析。 我们回过头来看我们的Main方法,发现里面有一个CreateDefaultBuilder方法,就是这个方法里面为我们做了一些默认的设置,然后加载我们的配置文件的! 我们在源码里面找到CreateDefaultBuilder 的源码(反正我找了半天,起初在Hosting下面找,实际上在MetaPackages下面的),位置在srcMetaPackagessrcMicrosoft.AspNetCoreWebHost.cs 有的人可能找不到哦,可以看到这个方法会在ConfigureAppConfiguration 的时候默认加载appsetting文件,并做一些初始的设置,所以我们不需要任何操作,就能加载appsettings 的内容了。 既然知道了原理后,我们就试着重写下这个ConfigureAppConfiguration 然后加载我们自定义的json文件吧。 鼠标右键新建一个Content.json文件,然后输入如下的内容: { "ContentList": { "Id": 1, "title": "title1 from diy json", "content": "content1 from diy json", "status": 1, "add_time": "2018-11-21 16:29", "modify_time": null } } 然后打开Program.cs。按如下代码进行改造: /// <summary> /// Main方法,程序的入口方法 /// </summary> /// <param name="args"></param> public static void Main(string[] args) { CreateWebHostBuilder(args)//调用下面的方法,返回一个WebHostBuilder对象 .Build()//用上面返回的WebHostBuilder对象创建一个WebHost .Run();//运行上面创建的WebHost对象从而运行我们的Web应用程序换句话说就是启动一个一直运行监听http请求的任务 } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args)//使用默认的配置信息来初始化一个新的IWebHostBuilder实例 .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddJsonFile("Content.json",optional:false,reloadOnChange:false) .AddEnvironmentVariables(); }) .UseStartup<Startup>();// 为Web Host指定了Startup类 然后Startup里面ConfigureServices中的代码修改如下:  然后按下F5运行下代码吧,如下图所示,从我们最新添加的json文件中加载出来数据了。  这里多讲一点,传统asp.net的web.config文件如果有更改的话是必须要重启站点才能使,配置文件生效的,但是asp.net core的配置文件是支持热更新的,及不重启网站也能加载更新,只需要设置一下属性即可,如下图所示:  配置文件的源码解读这块就到这里了。下面开始依赖注入的讲解。 依赖注入与控制反转 如果大家仔细阅读文章的话,相信已经看出来了,我上面提到过好几次依赖注入的概念。那么究竟什么是依赖注入呢?下面我们就拿我们上面的ContentController来好好的来理解下。依赖注入:当一个对象ContentController需要另一个对象Content来协同完成任务的时候,那么这个ContentController就对这个Content对象产生了依赖关系。那么在这个ContentController中,是怎么注入的呢?就是从控制器中注入的了,如下图所示: 从asp.net 转过来的你是不是想起了之前的千篇一律的new对象啊。没对象自己new(要是女朋友也能new多好啊……)当然除了单例对象,静态哈。 这里又设计一个概念就是控制反转。 那么什么是控制反转呢?你上面看到没有,你自己new对象就是整转,因为你自己创建自己所要使用的对象,。那么这种不需要你自己new对象,而是直接传进来就是控制反转了。(不知道比喻的恰不恰当哈) 依赖注入与控制反转你是否已经了解了呢,喜欢思考的朋友可能会问了,那这个构造函数里面的IOptions<Content> option 又是怎么出来的?这里就要引入一个容器的概念了。 什么是容器呢? 这里创建IOptions<Content> option 这个对象的东西就是容器。还记得上面我们分析源码的时候,IServiceCollection 里面注入了很多东西吗?其实就是往IServiceCollection 这个容器里面注入方法,这样其他地方使用的时候就能自动注入了。 这就是容器的好处,由容器来统一管理实例的创建和销毁,你只需要关心怎么用就行了,不需要关系怎么创建跟销毁。 当然容器创建的实例都是有生命周期的,。下面罗列一下,就不过多的讲解了。 Transient: 每一次访问都会创建一个新的实例 Scoped: 在同一个Scope内只初始化一个实例 ,可以理解为( 每一个request级别只创建一个实例,同一个http request会在一个 scope内) Singleton :整个应用程序生命周期以内只创建一个实例 使用的方式也很简单,我会在接下来的课程中详细的通过实例来进行讲解!因为现在的例子还没发演示。 总结 本文一步一步带着你先分析了ASP.NET Core的启动过程及运行的原理,紧接着给你讲了配置文件的加载过程及原理,并通过示例代码演示了如何加载自定义的配置文件,最后引出了依赖注入以及控制反转的概念,并通过对我们上面例子的分析来紧身对依赖注入以及控制反转的理解。至此让你知其然更知其所以然。对ASP.NET Core的原理相信你已经了然于胸了!有问题的小伙伴可以加群637326624讨论。那么接下来让我们再准备下dapper,vue以及git的快速入门就开始我们的asp.net core cms的实战课程吧!还是那句话基础很重要,基础打好,后面才能事半功倍。谢谢大家。
作者:依乐祝原文链接:https://www.cnblogs.com/yilezhu/p/9985451.html本来这篇只是想简单介绍下ASP.NET Core MVC项目的(毕竟要照顾到很多新手朋友),但是转念一想不如来点猛的(考虑到急性子的朋友),让你通过本文的学习就能快速的入门ASP.NET Core。既然是快速入门所以过多过深的内容我这里就一笔带过了!然后在后面的一些列文章中再慢慢的对其中的概念进行阐述。本文已收录至.NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划 点击可以查看更多教程。 .NET Core是什么 很多朋友看到.NET Core就认为是ASP.NET Core,其实这是有误区的,因为.NET Core 是开放源代码的通用开发平台 (是一个“平台”),基于这个开放平台我们可以开发像ASP.NET Core应用程序, Windows 10 通用 Windows 平台 (UWP),Tizen等等,而我们系列教程就是用.NET Core开发ASP.NET Core应用程序。而且由 Microsoft官方团队 和 .NET社区成员共同在 GitHub 上进行维护。 它跨平台(支持 Windows、macOS 和 Linux),并且可用于生成设备、云和 IoT 应用程序。.NET Core 还具有以下特性: 跨平台: 可以在 Windows、macOS 和 Linux 操作系统上运行。 跨体系结构保持一致: 在多个体系结构(包括 x64、x86 和 ARM)上以相同的行为运行代码。 命令行工具: 包括用于本地开发和持续集成方案中的易于使用的命令行工具。 部署灵活: 可以包含在应用或已安装的并行用户或计算机范围中。 可搭配 Docker 容器使用。 兼容性:.NET Core 通过 .NET Standard与 .NET Framework、Xamarin 和 Mono 兼容。 开放源:.NET Core 是一个开放源平台,使用 MIT 和 Apache 2 许可证。 .NET Core 是一个 .NET Foundation 项目。 由 Microsoft 支持:.NET Core背后依托强大的Microsoft团队 进行维护。 什么是ASP.NET Core ASP.NET Core 是一个由微软创建的,用于构建 web 应用、API、微服务 的 web 框架。它使用常见的模式,诸如 MVC(Model-View-Controller)、依赖注入,和一个由中间件构成的请求处理管道。它基于 Apache 2.0 许可证开放源码,就是说,源代码可以自由获取,并且欢迎社区成员以 缺陷修复 和 新功能提交 的方式进行贡献。 ASP.NET Core 运行在微软的 .NET 运行时库上,类似于 Java 的 虚拟机(JVM)或者 Ruby 的解释器。有几种语言(C#,Visual Basic,F#)可以用来编写 ASP.NET Core 程序。C# 是最常见的选择,当然我大多数人都是采用C#来进行开发的。你可以在 Windows、Mac,和 Linux 上构建并运行 ASP.NET Core 应用。 为什么要用ASP.NET Core开发应用程序 现存的 web 框架选项已经很多了:Node/Express、Spring、Ruby on Rails、Django、Laravel 等等,数不胜数。ASP.NET Core 又有什么可取之处呢?为什么要用ASP.NET Core开发应用程序呢? 速度 ASP.NET Core 很快。因为 .NET Core 是编译运行的,执行速度远高于解释执行的语言,比如 JavaScript 或者 Ruby、ASP.NET Core 也已经为多线程和异步任务作了专门的优化。与使用 Node.js 写的代码相比,执行速度高出 5-10 倍是很正常的。 生态 ASP.NET Core 可能初出茅庐,但 .NET 却已久经考验。在 NuGet(.NET 的包管理系统,类似 npm、Ruby gems,或者 Maven)上有成千上万的软件包。有现成的包可用来完成 JSON 反序列化、数据库连接、PDF生成,或者几乎你能想到的任何需求。 安全性 微软的开团队很注重安全性,ASP.NET Core 从创建基础就是安全的。它已经自动处理了 净化输入数据 和 跨域伪造请求(CSRF),你就不用操心这些了。你同时还享有 .NET 编译器的静态类型检测的优势,它像个时刻警惕着,还有些强迫症的审校者。这样,在使用一个变量或者某些数据时,那些无意识的错误就插翅难逃。 跨平台 可以运行在安装了 .NET 运行时库的 Windows、Mac或者Linux上。 开源 .NET Core 属于开放源(MIT 许可证),由 Microsoft 于 2014 年提供给 .NET Foundation。 现在它是最活跃的 .NET Foundation 项目之一。 可由个人和企业自由采用,包括用于个人、学术或商业目的。 同时开源也就意味着在你出现问题的时候你可以阅读其源代码来获取解决问题的方法,再者你也可以在Gayhub上提Issue 数百万开发人员使用过(并将继续使用)ASP.NET 4.x创建 Web 应用。 ASP.NET Core 是重新设计的 ASP.NET 4.x,更改了体系结构,形成了更精简的模块化框架。 ASP.NET Core 同时具有如下优点: 生成 Web UI 和 Web API 的统一场景。 针对可测试性进行构建。 Razor Pages可以使基于页面的编码方式更简单高效。 能够在 Windows、macOS 和 Linux 上进行开发和运行。 开放源代码和以社区为中心。 集成新式客户端框架和开发工作流。 基于环境的云就绪配置系统。 内置依赖项注入。 轻型的高性能模块化 HTTP 请求管道。 能够在 IIS、Nginx、Apache、Docker上进行托管或在自己的进程中进行自托管。 基于 .NET Core运行时,可以使用并行应用版本控制。…… .NET Core环境搭建 在继续进行ASP.NET Core代码的编写前,我们需要安装 .NET Core的运行环境。这部分我们就一步一步的进行 .NET Core的环境搭建吧。 首先你可以Google搜索一下.NET Core,如果没错的话第一个就是微软的官方下载地址,当然你可以点击这里进行下载 (目前sdk最新的是v2.1.500,runtime最新的版本是v2.1.6 )进行开发的话下载SDK即可。 双击你下载好的sdk然后傻瓜式的一步一步的进行安装即可,微软的软件的安装太简单的,以至于我如果再细说你们都会嫌我啰嗦了。所以,这里我只贴一张安装成功的图吧。 接下来按住 Shift+鼠标右键,然后选择“在此处打开Powershell窗口”或者“在此处打开命令行窗口”。然后输入dotnet --info 查看下我们已经安装的.NET Core 的信息,当前运行的环境,已经以往安装的版本信息,我的版本比较多,因为我用了很长时间了。如果你第一次安装可能只有一个。出现下面第二张图的界面也就说明我们的.net core开发环境已经就绪了!下面就让我们撸起袖子开始干吧。 快速创建一个ASP.NET Core项目并进行实战演练 这里为了照顾到更多的小伙伴,我就不实用CLI命令行来创建ASP.NET Core项目了,还是中规中矩的使用VS2017吧!什么vs2017需要激活码?那你可以使用社区版的啊!社区版的话,学习已经够用了!废话说了一堆,我们开始吧! 首先第一步肯定是打开你的VS2017了,然后点击左上角“文件”-》“新建”-》“项目”(或者你嫌麻烦,可以使用Ctrl+Shift+N这个快捷键),打开如下的创建新项目对话框,然后按照如图所示进行选择并点击确定吧(什么?你居然没创建成功?那么我觉得你是在侮辱我了): 哈哈,上图点击确定后并没有创建成功,而是会弹出下一个对话框,如下所示,他会让你选择目标框架是.NET Core还是.NET Framework;是选择创建一个空的解决方案还是创建一个带有模板的web项目!至于各自的区别,有兴趣的朋友可以每个都创建一下然后对比下各自的区别哦!这里我们按照下图所示选择MVC的web应用程序: 创建成功后,看到如下的结构,标准的MVC结构,不过跟.net framework时代的MVC又有所不同。wwwroot:网站的静态文件目录(为什么在这里就能加载呢?大家可以先思考下) appsettings.json:配置文件,比如数据库连接字符串等等配置信息。 Program.cs:程序入口文件(里面有个Main方法); Startup.cs启动配置文件 ; 依赖项:管理项目所依赖的第三方组件的安装,配置,升级 Controller:控制器 Models:实体 Views:视图 由于篇幅有限,就不过多的讲解了。 按下键盘的F5或者如下图所示点击运行按钮,看下效果吧! 如果不出意外的话你将看到如下图所示的界面。 看到没有,就这么简单我们就运行起来了一个ASP.NET Core的MVC站点。到这里是不是就已经结束了呢?骚年你想多了,因为我还要让你多会点东西。,所以这个第六条就是用来说废话的,然后作为一个分割。 Models:在Models文件夹上右键新建两个类:一个Content类;一个ContentViewModel类 ,代码如下(这里就不教你怎么创建类了,如果跟你说了,那就是在侮辱你的智商了): namespace Sample01.Models { /// <summary> /// 2018.11.19 /// 祝雷 /// 内容实体 /// </summary> public class Content { /// <summary> /// 主键 /// </summary> public int Id { get; set; } /// <summary> /// 标题 /// </summary> public string title { get; set; } /// <summary> /// 内容 /// </summary> public string content { get; set; } /// <summary> /// 状态 1正常 0删除 /// </summary> public int status { get; set; } /// <summary> /// 创建时间 /// </summary> public DateTime add_time { get; set; } /// <summary> /// 修改时间 /// </summary> public DateTime modify_time { get; set; } } } namespace Sample01.Models { /// <summary> /// 2018.11.19 /// 祝雷 /// Content视图模式 /// </summary> public class ContentViewModel { /// <summary> /// 内容列表 /// </summary> public List<Content> Contents { get; set; } } } Controller:模型建好了,那么我们就新建一个控制器,然后再创建一些模拟的数据吧,代码如下: namespace Sample01.Controllers { /// <summary> /// 2018.11.19 /// 祝雷 /// Content控制器 /// </summary> public class ContentController : Controller { /// <summary> /// 首页显示 /// </summary> /// <returns></returns> public IActionResult Index() { var contents = new List<Content>(); for (int i = 1; i < 11; i++) { contents.Add(new Content { Id=i,title=$"{i}的标题",content= $"{i}的内容",status=1, add_time=DateTime.Now.AddDays(-i)}); } return View(new ContentViewModel { Contents=contents}); } } } Views:模型跟控制器都建好了,那我们就建一个视图来显示我们创建的数据吧!我们可以有很多种方式创建这个视图,这里给你介绍一种傻瓜式的,把鼠标放在Index大括号里面,然后鼠标右键选择创建视图,如下所示即可创建视图文件,位置在/Views/Content/Index.cshtml文件: 我们按照如下的代码稍微改造下这个View: @model ContentViewModel @using Humanizer; @{ ViewData["Title"] = "内容列表"; } <div class="panel panel-default todo-panel"> <div class="panel-heading">@ViewData["Title"]</div> <table class="table table-hover"> <thead> <tr> <td> <input type="checkbox" class="done-checkbox"></td> <td>序号</td> <td>标题</td> <td>内容</td> <td>添加时间</td> </tr> </thead> @foreach (var item in Model.Contents) { <tr> <td> <input type="checkbox" class="done-checkbox"> </td> <td>@item.Id</td> <td>@item.title</td> <td>@item.content</td> <td>@item.add_time.Humanize()</td> </tr> } </table> </div> 然后修改下布局文件,位于 Views/Shared/_Layout.cshtml 的布局文件里面存放着所有视图的“基础”HTML。其中就包括导航栏,它被显示在每个页面的顶端。为了向导航栏添加新条目,我们需要再这个文件中增加我们的Content乐目,代码如下: <li><a asp-area="" asp-controller="Content" asp-action="Index">Content</a></li> 到这里代码基本完成,按下你的F5键,然后导航到Content看下效果吧:  这里是真的结束了! 源代码 你以为我会上传源代码?骚年,想什么呢,这么简单,还是自己敲下吧!不要妄图做眼高手低的人哦! 另外,如果你按照代码一步一步的敲出来,可能也不能运行成功,What?因为视图中有一个Nuget包需要你自己来添加,而我没指出来,目的就是要大家自己动手来解决一下哈!再啰嗦一句,不要做眼高手低的人哦! 总结 好了,又到了总结时间,本文首先给你讲解了什么是.NET Core?什么又是ASP.NET Core?接着带着你一步一步的配置了.NET Core的开发环境。最后又带着你一步一步的创建了一个ASP.NET Core的mvc项目,同时又通过一个实战教你如何在页面显示一个Content的列表。如果你跟着楼主一点一点的把代码敲起来,然后跑起来了!那么你会发现ASP.NET Core原来这么简单。什么?你觉得简单?那么下一篇文章,博主就带给你一些复杂的概念,什么依赖注入啊,配置文件的加载啊(分析下源码呗)等等!至此,快速入门ASP.NET Core看这篇就够了,圆满结束。有问题的小伙伴可以加入.NET Core实战项目交流群:637326624
作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9977862.html 写在前面 千呼万唤始出来,首先,请允许我长吸一口气!真没想到一份来自28岁老程序员的自白 这篇文章会这么火,更没想到的是张善友队长的公众号居然也转载了这篇文章,这就导致两天的时间就有两百多位读者朋友加入了.NET Core实战项目交流群(欢迎更多小伙伴进入交流.NET Core经验)!这让我顿感亚历山大!我自己的文笔有多差我是知道的,所以就有点担心写不好!同时我也得到了很多朋友的鼓励,所以我会很认真的来分享每一篇文章,希望能对大家入门.NET Core有所帮助!当然一个人的能力是有限的,如果我的文章中有出现错误的话,也希望大家能够帮我指正,这样才能更好地服务更多的后来者!同时教程的编写我会采用敏捷开发的思想,先大致梳理下,后期会做持续更新的!这个系列我尽量每周三篇的速度来进行编写! 面向的对象 由于加群的大部分读者朋友都没怎么接触过.NET Core,甚至只是刚听说过.NET Core所以我会从最基础的概念开始写起,通过一个简单的CMS系统的实战项目,让你知其然更知其所以然!如果你是.NET Core的老鸟,那么这个系列的文章也会有你可以借鉴的地方!当然如果你觉得自己的能力足够强的话也可以看我们的另一个系列《【.NET Core微服务实战-统一身份认证】开篇及目录索引》这个系列有一定的门槛,但却是国内不可多得的用.NET Core开发统一身份认证方面的系列文章。 篇章结构 这个篇章结构会随着系列教程的深入做相应的变化!请大家持续关注。 入门篇 入门篇主要是带大家快速入门,并掌握.NET Core中最常用的概念为后面的开发篇做准备。只有掌握了这些知识你才算半只脚踏入了.NET Core的世界,掌握概念后再实际动手做的话你才能理解的更深刻,所以这里希望大家一定要跟着动手做,不要做眼高手低的人。 .NET Core实战项目之CMS 第一章 入门篇-开篇及目录索引 .NET Core实战项目之CMS 第二章 入门篇-快速入门ASP.NET Core看这篇就够了 .NET Core实战项目之CMS 第三章 入门篇-源码解析配置文件及依赖注入 .NET Core实战项目之CMS 第四章 入门篇-Git的快速入门及实战演练 .NET Core实战项目之CMS 第五章 入门篇-Dapper的快速入门看这篇就够了 .NET Core实战项目之CMS 第六章 入门篇-Vue的快速入门及其使用 设计篇 进行一个简单CMS系统的数据库逻辑结构的设计,不要跟我说什么Code First有多么先进,DB First多么Outer。在结果导向上我更习惯使用设计工具对整个系统设计后,再进行相关的开发。 .NET Core实战项目之CMS 第七章 设计篇-用户权限设计 .NET Core实战项目之CMS 第八章 设计篇-内容管理设计 .NET Core实战项目之CMS 第九章 设计篇-数据库生成待更新 开发篇 顾名思义,带着大家按照我们设计的数据库进行相关功能的开发!待更新 测试篇 编写相应的测试用例,涉及单元测试,集成测试!待更新 部署篇 对前面开发的系统进行Windows部署或者在Linux系统上进行部署。待更新 开发工具 俗话说得好,工欲善其事必先利其器、巧妇难为无米之炊,,一款好的工具能够让你事半功倍!如果你连工具都懒得装的话,那么劝你右上角点击关闭按钮,离开本系列教程吧!暂时罗列如下,不定期更新。 代码编写工具 既然大家要进行.NET Core的开发,那么就强烈建议大家使用Visual Studio2017或者Visual Studio Code进行开发吧!VS2017的使用很简单,跟之前的几个版本的使用方式都大同小异,而Visual Studio Code的使用可能大家会比较陌生,好在有我的这篇《使用Visual Studio Code开发.NET Core看这篇就够了》文章可以教大家如何进行开发! 数据库工具 SqlServer2008R2及以上。当然系列文章演示的时候我会使用SqlServer进行演示。至于MySql以及Oracle的话大家也可以结合着教程修改下Sql语句即可。 数据库设计工具 Power Design、 源代码管理工具 git。现代开发如果你还不知道Git我想你真应该考虑下使用这个分布式的版本控制工具了!相比集中式的版本控制工具如SVN他有着与生俱来的诸多好处! 技术栈 .NET Core2.1+AutoFac+ FluentValidation +Dapper+Vue+Redis+SqlServer/Mysql
写在前面 很幸运,28岁的我头发还没有掉光,更幸运的是28岁的我开始了博客园的写作生活!这样的技术分享经历让我拓展了自己的朋友圈!有幸结识了像张善友张队(连续13年的微软MVP),大石头(NewLife团队),nicye(CSRedisCore作者)等圈内大牛!真的感觉自己很幸运!最近看了很多人写自己的程序员生活,所以趁着今天出差的空隙也想给28岁的自己做个叙述!于是就有了这篇文章! 毕业实习的半年 不知道大家是否跟我一样,在大学过着虚度光阴的生活。那时候CF比较火,所以在大学的时候除了会打CF以外,别无收获(至今,还时不时的玩着这款游戏,都8年了)!因此大四的后半年的时候很沮丧,可是有一次去人才市场应聘的时候,居然被一家公司给“录取了”!为什么录取了加双引号呢,因为,这家公司打着招聘的幌子,让我交了两千多块钱上了他们的培训班!你没看错,我就这样稀里糊涂的交了两千多块钱上了他们的培训课,而且是网络课程!之后经过一个月的培训后经介绍到了杭州的一家网络公司做实习生。然后就在那家公司呆了六七个月!做的工作也都是跟路由器,交换机相关的工作,配置各种网络环境!这时候还没用上.net. 第一次与.net结缘及苦学经历 由于实习的时候是在杭州,而我的女朋友(现在已经是我老婆了)是在合肥,异地的感觉真不好受,所以在十一国庆节后就开始思考着回合肥了!毕竟当时我女朋友也在合肥工作,而且合肥距离我老家淮北以及我女朋友的老家安庆太湖都很近!所以就在网上搜了下合肥的招聘信息,结果发现网络的工作好少啊,又看了下软件开发,发现软件开发的工作好多啊,于是乎想转软件开发,那时候比较火的就是.net与java了!经左手跟右手的剪刀石子布,选择了.net。既然决定了,那就得开始学习了!记得12年那时候传智播客很火,所以在网上down了传智播客关于.net的基础以及进阶教程(好像是石坤的)开始了每天晚上苦学的生活!说实在的,高考都没那么用心学过一样技术,而且是从零开始!经过一个多月的学习,我在合肥投了两份简历,后跟两家公司约好了面试时间,然后趁着面试前的几天在网上又down了C#面试一百题的面试题,然后全背下来了!就风尘仆仆的回合肥面试了!结果有一家公司的面试题居然跟我从网上down的一毛一样,可想而知,我答得又多么好了!后来我就以三千块钱一个月的工资进了第一家公司,在这家公司一待就待了近五年,当然这都是后话。 第一份正式工作的经历 上面也说了,我面试的时候是幸运的,就因为面试题刚好是我从网上down的C#面试一百题,所以我都会!但是入职了后,肯定是要露馅的啊!可能由于刚毕业的原因,所以那时候的同事给了我很多的帮助,再次很感谢他们!在这里我能给大家爆一下吗,我刚进公司的时候,连vs2010怎么连接数据库的都不会,还是同事教我的呢!但是我用了一个月就上手了,然后就能独立的完成经理交给我的任务了!我究竟是怎么做到的呢?入职的第二天我就买了一个小册子,然后我把vs连接数据库的字符串给写到小册子上面,在操作数据库过程中使用到的数据库连接对象,等等我不熟悉的概念也全部百度下,再把意思及用全部写到小册子上面!回去又看了一遍传智播客的教程,也用小册子做了笔记!当好我当时上下班都需要做一个小时左右的公交车!因此公交车上就成了我充电的地方,坐上公交车以后就把小册子拿出来从第一页浏览到最后一页,刚开始就是死记硬背,一遍又一边的记忆!可能我的记忆力比较差,所以我都不记得看了多少遍了!反正足足过了一个多月的这种公交车充电的生活!记得很清楚的是入职后一个多月的时候,经历交给我一个任务,把一个数据库的数据同步到另一个数据库里面去!而且要可视化的操作!然后我花了一个星期的时间就独立完成了!那时候真的感觉满满的成就感! 入职后第二年第一次提离职 第二年的时候我跟老总提了一次离职,原因是一年了公司还没有给我加工资,而且我在外面投了简历,别人给我开了五千的工资,而我所在的公司给我的还是三千的工资!但是老总给说什么自己现在有难处,留下来再帮帮他,等公司项目验收了会给我项目奖金,他现在很不容易,很困难,让我一定再留下来帮帮他!身为程序员的我,本就不喜欢讲话,再加上老总声情并茂的诉苦,结果单纯的我就信以为真,然后傻傻地又留下来了,留下来了!工资给主动的加了五百!第一次提离职以失败而告终!不过后来我自学silverlight在工作之余接了一点私活来补贴家用!当然这都是后话了! 老板主动给我加薪并提拔为技术合伙人 转眼到了第三年,有一次老总找到了我跟我谈了很久说给我加工资(从3500加到7000),而且这次加工资后以后就不要跟他提加工资的事情了!而且,后面公司准备成立一个分公司(公司名字里都有我的名字)让我技术入股当合伙人!跟其他几个公司一个准备包装起来再成立一家集团公司搞上市!反正说了一大堆!结果我同意了,而且我也更加认真的工作了!又经过两年多的发展,从开始的十几个人,发展到六七十个人!公司也引进了一批科大的高材生!业务,也拓展了很多,发展势头也很好,都在准备上市的事情了! 从第一家公司毅然决然的离职 结果在15年的时候公司进来一个总经理,一个让人跟他对话都感觉他心里在算计你的一个人!天天把老总抬得高高的,然后跟老总分工,老总主外,他主内,管理整个公司的大小事务,然后在公司里胡搞拔搞,上下一片怨言。更甚至在我们工作的地方装起了摄像头,有事没事的监控我们,感觉很不爽,再加上在这个所谓的总经理的带领下公司的业务变得越来越差,再加上融资困难,感觉上市无望的我又经过了一年多的煎熬后于17年五月份的时候毅然决然的提出了离职!这一次老总又一次声情并茂的挽留我,但是我已不是初出茅庐的单纯少年了!所以这次我成功的离职了!现在回想起来还好当时提前离职了,因为前几天更之前的同事聊天,听他们说公司倒闭了,老板还欠了他们的工资没发呢!现在真的很庆幸! 第二家公司的经历 第二家公司呆了差不多一年的时间,说起第二家公司是做视频监控的跟我之前的工作毫不相关,但是好在离我家很近,只有四五公里左右,开车的话十分钟左右就能到!在这家公司虽然呆的时间不长,但是我也机缘巧合的认识了一些朋友,也学习并使用了很多新技术,比如说:第一次使用了Linux系统,Hadoop,Zokeeper,Elasticsearch等等大数据技术以及视频结构化相关的技术!并进行相应的落地!转眼到了18年随着.NET Core2.0的发布,我觉得是时候回归.net的了!因为对比之下我发现自己更适合进行.NET的开发!而且利用闲余时间用.NET Core做了一个cms系统后,感觉很多无处不在的依赖注入,以及全新的开发方式感觉虽然有点不适应,但是开发起来很爽!所以就想着换一家.net的公司进行工作了!当时我老婆也有点反对,毕竟离家很近,而且基本不加班,周六周日又能在家陪小孩! 目前公司的求职及工作情况 但是我还是私下投了简历,然后就进了我现在的公司,为什么选择这个公司呢,说实在话这个公司的薪水不是很高!而我看中的就是这家公司积极使用新技术,勇于尝试新技术的活力!再者说就是这家公司已经开始使用.NET Core了!刚好我又对.NET Core感兴趣,所以就进了现在的公司!而这时候已经是18年的5月20日了,在这一天我开始了新公司的工作!进公司后就利用.NET Core相关的技术进行一些后台的开发!然后几天后完成了第一个后台服务的开发,这时候在进行测试的时候发现异步进行数据库新增的时候如果并发比较大的时候出现重复数据的问题,最后得以解决。后就想着记录一下吧!所以就在博客园注册了账号进行了记录,这也是我的第一篇文章!由于那时候对NET Core了解的还不是很透彻所以就到博客园看那些大牛的文章,然后每天上下班做地铁的二十分钟天天看他们的文章,不知不觉自己的技术就得到了提升,再加上我们的技术经理也很喜欢.NET Core,所以有事没事我们就一起交流些心德,互相提升.NET Core技术!我们聊得最多的就是今天.NET Core做了哪些更新,明天哪个开源项目又升级了!张队的公众号又更新的什么技术文章!目前我们也天天在讨论这些!感觉对.NET Core有聊不完的话题。再后来就是我开始了自己的第一篇正式的用心的分享技术的文章asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 没想到这篇文章居然会有这么多阅读量,最重要的是被张队转载到了公众号。感觉自己从吃瓜群众转身一变成了主角!所以一发不可收拾谢了持续很多文章!在这里非常感谢读者朋友!是你们让我有了继续写下去的动力!再后来就认识了像张善友张队(连续13年的微软MVP),大石头(NewLife团队),nicye(CSRedisCore作者)等圈内大牛。 目前的状况以及接下来的计划 目前我们已经把.NET Core应用到我们的所有新项目。并准备对老项目进行.NET Core的升级改造!而且封装了一套基于Ocelot进行改造升级后的统一认证平台!当然,我也写了一系列Ocelot的教程有兴趣的朋友可以看下!而且随着.NET Core的越来越成熟,所以有必要为.NET Core做一次宣传,为.NET Core社区做一次贡献!为了帮助更多新手朋友们了解.NET Core以及入门.NET Core!所以我计划写一个.NET Core实战项目的教程!这个教程以一个CMS系统为例为大家介绍如何使用.NET Core从零开始开发并进行相关的部署!为了方便.NET Core实战项目的交流,我特意建了一个QQ群,群号是637326624!有兴趣的朋友可以提前加一下!而且我另一个朋友也已经开了一个.NET Core项目实战的系列文章https://www.cnblogs.com/jackcao/p/9928879.html 也统一的在这个群里进行交流!希望能对大家学习.NET Core有所帮助!在这里在这里再一次感谢大家的阅读! 作者:依乐祝原文链接:https://www.cnblogs.com/yilezhu/p/9966945.html
作者:依乐祝原本链接:https://www.cnblogs.com/yilezhu/p/9947905.html 引子 为什么写这篇文章呢?因为.NET Core的生态越来越好了!之前玩转.net的时候操作Redis相信大伙都使用过一些组件,但都有一些缺点,如ServiceStack.Redis 是商业版,免费版有限制;StackExchange.Redis 是免费版,但是内核在 .NETCore 运行时经常有 Timeout的问题,暂无法解决(据农码一生大佬说:https://github.com/StackExchange/StackExchange.Redis/issues/871 试试StackExchange.Redis 2.0 呢,超时问题好像解决了。但还是有朋友说,2.0也还是会出现超时的问题)有兴趣的可以试试;csredis作者在 2014 年以后就没有更新了,它不支持 .net core,但是它的源码可读性很强非常干净,几乎无任何依赖。但是随着.NET Core生态的越来越好,又涌现了一批我们国内大牛开发的支持.Net Core的Redis组件,供我们选择。 NewLife.Redis 他是NewLife团队开发的,已经在ZTO大数据实时计算中广泛应用,200多个Redis实例稳定工作一年多,每天处理近1亿包裹数据,日均调用量80亿次。 CSRedis (这里我更喜欢把它叫做CSRedisCore)这是另一个国内大牛nicye 开发的,为人很低调,所以了解他的人很少!目前我项目中广泛使用的也是这个。作者前不久刚做了一个几大Redis组件的性能测试.net core 2.0 redis驱动性能比拼 有兴趣的可以打开链接看一下。 注:此CSRedis(今天本文的主角CSRedisCore) 非彼CSRedis(.net 时代的组件,很久没更新了,不支持.net core) NewLife.Redis的使用方法在前两天的Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南)文章中已经分享了!文章也有视频教程。所以今天的文章将介绍另一个玩转Redis的神器-CSRedis了! 基本使用 CSRedisCore的使用很简单,就需要实例化一个CSRedisClient(集群连接池)对象然后初始化一下RedisHelper就可以了,他的方法名与redis-cli基本保持一致。所以说你可以像使用redis-cli命令一样来使用它。作者最近也支持了Pipeline功能以及MGet,MSet等提高效率的功能!话不多少下面我们将通过一个个实例来看下他的操作吧。 简单使用 获取Nuget包(目前版本3.0.18)!哈,没错,使用前要通过Nuget来安装下引用,什么?你不知道怎么使用Nuget包?对不起,右上角点下“X” 关掉网页就可以了。 nuget Install-Package CSRedisCore 几种启动模式介绍: 普通模式: var csredis = new CSRedis.CSRedisClient("127.0.0.1:6379,password=123,defaultDatabase=13,poolsize=50,ssl=false,writeBuffer=10240,prefix=key前辍"); 官方集群模式:假设你已经配置好 redis-trib 集群,定义一个【普通模式】的 CSRedisClient 对象,它会根据 redis-server 返回的 MOVED | ASK 错误记录slot,自动增加节点 Nodes 属性。 127.0.0.1:6379,password=123,defaultDatabase=0,poolsize=50,ssl=false,writeBuffer=10240,prefix= 其他节点在运行过程中自动增加,确保每个节点密码一致。 警告:本模式与【分区模式】同时使用时,切记不可设置“prefix=key前辍”(或者全部设置成一样),否则会导致 keySlot 计算结果与服务端不匹配,无法记录 slotCache。 注意:官方集群不支持多 keys 的命令、【管道】、Eval(脚本)等众多杀手级功能。 分区模式:本功能实现多个服务节点分担存储(作者自己实现的一种方式),与官方的分区、集群、高可用方案不同。 例如:缓存数据达到500G,如果使用一台redis-server服务器光靠内存存储将非常吃力,使用硬盘又影响性能。可以使用此功能自动管理N台redis-server服务器分担存储,每台服务器只需约 (500/N)G 内存,且每台服务器匀可以配置官方高可用架构。 var csredis = new CSRedis.CSRedisClient(null, "127.0.0.1:6371,password=123,defaultDatabase=11,poolsize=10,ssl=false,writeBuffer=10240,prefix=key前辍", "127.0.0.1:6372,password=123,defaultDatabase=12,poolsize=11,ssl=false,writeBuffer=10240,prefix=key前辍", "127.0.0.1:6373,password=123,defaultDatabase=13,poolsize=12,ssl=false,writeBuffer=10240,prefix=key前辍", "127.0.0.1:6374,password=123,defaultDatabase=14,poolsize=13,ssl=false,writeBuffer=10240,prefix=key前辍"); //实现思路:根据key.GetHashCode() % 节点总数量,确定连向的节点 //也可以自定义规则(第一个参数设置) 今天我只给大家演示怎么来进行使用,所以采用了普通模式,代码如下所示: static void Main(string[] args) { //普通模式 var csredis = new CSRedis.CSRedisClient("127.0.0.1:6379,password=123,defaultDatabase=1,poolsize=50,ssl=false,writeBuffer=10240"); //初始化 RedisHelper RedisHelper.Initialization(csredis); //Install-Package Caching.CSRedis (本篇不需要) //注册mvc分布式缓存 //services.AddSingleton<IDistributedCache>(new Microsoft.Extensions.Caching.Redis.CSRedisCache(RedisHelper.Instance)); Test(); Console.ReadKey(); } static void Test() { RedisHelper.Set("name", "祝雷");//设置值。默认永不过期 //RedisHelper.SetAsync("name", "祝雷");//异步操作 Console.WriteLine(RedisHelper.Get<String>("name")); RedisHelper.Set("time", DateTime.Now, 1); Console.WriteLine(RedisHelper.Get<DateTime>("time")); Thread.Sleep(1100); Console.WriteLine(RedisHelper.Get<DateTime>("time")); // 列表 RedisHelper.RPush("list", "第一个元素"); RedisHelper.RPush("list", "第二个元素"); RedisHelper.LInsertBefore("list", "第二个元素", "我是新插入的第二个元素!"); Console.WriteLine($"list的长度为{RedisHelper.LLen("list")}"); //Console.WriteLine($"list的长度为{RedisHelper.LLenAsync("list")}");//异步 Console.WriteLine($"list的第二个元素为{RedisHelper.LIndex("list",1)}"); //Console.WriteLine($"list的第二个元素为{RedisHelper.LIndexAsync("list",1)}");//异步 // 哈希 RedisHelper.HSet("person","name", "zhulei"); RedisHelper.HSet("person", "sex", "男"); RedisHelper.HSet("person", "age", "28"); RedisHelper.HSet("person", "adress", "hefei"); Console.WriteLine($"person这个哈希中的age为{RedisHelper.HGet<int>("person","age")}"); //Console.WriteLine($"person这个哈希中的age为{RedisHelper.HGetAsync<int>("person", "age")}");//异步 // 集合 RedisHelper.SAdd("students","zhangsan", "lisi"); RedisHelper.SAdd("students", "wangwu"); RedisHelper.SAdd("students", "zhaoliu"); Console.WriteLine($"students这个集合的大小为{RedisHelper.SCard("students")}"); Console.WriteLine($"students这个集合是否包含wagnwu:{RedisHelper.SIsMember("students", "wangwu")}"); } 通过上面的代码大家可以看到对于Redis的操作都是使用RedisHelper这个类来实现的。而且,对Redis的所有操作名称都跟Redis-Cli命令高度一致!这样就会方便很多!同时对所有的方法在实现上都有同步异步的操作!这里建议进行Redis操作的话都尽量使用同步操作。原因在上篇也进行了介绍!这里就不再次进行介绍了!。 执行的结果如下所示: 大#家可以摘录代码然后拷贝到一个新的控制台程序中运行即可! 高级使用 上面给大家介绍了一些通用的使用方法,接下来呢我们进行一些高级方法的使用。包括订阅/发布,PipeLine,缓存壳等等。 订阅与发布 //普通订阅 RedisHelper.Subscribe( ("chan1", msg => Console.WriteLine(msg.Body)), ("chan2", msg => Console.WriteLine(msg.Body))); //模式订阅(通配符) RedisHelper.PSubscribe(new[] { "test*", "*test001", "test*002" }, msg => { Console.WriteLine($"PSUB {msg.MessageId}:{msg.Body} {msg.Pattern}: chan:{msg.Channel}"); }); //模式订阅已经解决的难题: //1、分区的节点匹配规则,导致通配符最大可能匹配全部节点,所以全部节点都要订阅 //2、本组 "test*", "*test001", "test*002" 订阅全部节点时,需要解决同一条消息不可执行多次 //发布 RedisHelper.Publish("chan1", "123123123"); //无论是分区或普通模式,RedisHelper.Publish 都可以正常通信 //不加缓存的时候,要从数据库查询 var t1 = Test.Select.WhereId(1).ToOne(); //一般的缓存代码,如不封装还挺繁琐的 var cacheValue = RedisHelper.Get("test1"); if (!string.IsNullOrEmpty(cacheValue)) { try { return JsonConvert.DeserializeObject(cacheValue); } catch { //出错时删除key RedisHelper.Remove("test1"); throw; } } var t1 = Test.Select.WhereId(1).ToOne(); RedisHelper.Set("test1", JsonConvert.SerializeObject(t1), 10); //缓存10秒 //使用缓存壳效果同上,以下示例使用 string 和 hash 缓存数据 var t1 = RedisHelper.CacheShell("test1", 10, () => Test.Select.WhereId(1).ToOne()); var t2 = RedisHelper.CacheShell("test", "1", 10, () => Test.Select.WhereId(1).ToOne()); var t3 = RedisHelper.CacheShell("test", new [] { "1", "2" }, 10, notCacheFields => new [] { ("1", Test.Select.WhereId(1).ToOne()), ("2", Test.Select.WhereId(2).ToOne()) }); Pipeline及MGet,MSet 使用管道模式,打包多条命令一起执行,从而提高性能。 var ret1 = RedisHelper.StartPipe().Set("a", "1").Get("a").EndPipe(); var ret2 = RedisHelper.StartPipe(p => p.Set("a", "1").Get("a")); var ret3 = RedisHelper.StartPipe().Get("b").Get("a").Get("a").EndPipe(); //与 RedisHelper.MGet("b", "a", "a") 性能相比,经测试差之毫厘 压力测试对比 到这里你可能要问了,CSRedisCore性能如何呢?跟其他的Redis组件相比又如何呢、这里给出一个链接.net core 2.0 redis驱动性能比拼?.net core 2.0 redis驱动性能比拼,上面有作者做的测试,大伙可以看下,我也做个截图分享 总结 今天给大家介绍了.NET Core玩转Redis的又一傻瓜式神器CSRedisCore的使用,由于篇幅有限,所以还有很多方法没有进行演示。大伙可以按照本文的方法自行进行测试!(基本RedisCli里面有的命令,都有对应的方法实现!)看到.net core的生态越来越好!有很多优秀的工具以及框架在开源!作为.net Corer的你开森嘛?
热场准备 熟悉的开场白,大家晚上好啊,今天给大家分享的是Redis在大数据中的使用,可能真正讲的是一些redis的使用技巧,Redis基本的一些东西。 首先给大家个地址,源码以及实例都在里面,当然今天的分享也是按照里面的实例来进行的,大家可以先进行下载。http://git.newlifex.com/NewLife/NewLife.Redis当然这里也附上Redis的下载地址:windows:https://github.com/MicrosoftArchive/redis/releases http://x.newlifex.com/Redis-x64-3.2.100.msi Linux:https://redis.io/download 开始 Redis封装架构讲解 实际上NewLife.Redis是一个完整的Redis协议的功能的实现,但是redis的核心功能并没有在这里面,Redis的核心功能的实现是在NewLife.Core里面。这里可以打开看一下,NewLife.Core里面有一个NewLife.Caching的命名空间,里面有一个Redis类里面实现了Redis的基本功能,另一个类是RedisClient是Redis的客户端。Redis的核心功能就是有这两个类实现。RedisClient代表着Redis客户端对服务器的一个连接。Redis真正使用的时候有一个Redis连接池,里面存放着很多个RedisClient对象。 所以我们Redis的封装有两层,一层是NewLife.Core里面的Redis以及RedisClient。另一层就是NewLife.Redis。这里面的FullRedis是对Redis的实现了Redis的所有的高级功能。这里你也可以认为NewLife.Redis是Redis的一个扩展。 Test实例讲解Redis的基本使用 实例 打开Program.cs看下代码 这里XTrace.UseConsole();是向控制台输出日志,方便调试使用查看结果。 接下来看第一个例子Test1。具体的我都在代码中进行了注释,大家可以看下 static void Test1() { var ic = Redis.Create("127.0.0.1:6379", 3);//创建Redis实例,得到FullRedis对象 //var ic = new FullRedis();//另一种实例化的方式 //ic.Server = "127.0.0.1:6379"; //ic.Db = 3;//Redis中数据库 ic.Log = XTrace.Log;//显示日志,进行Redis操作把日志输出,生产环境不用输出日志 // 简单操作 Console.WriteLine("共有缓存对象 {0} 个", ic.Count);//缓存对象数量 ic.Set("name", "大石头");//Set K-V结构,Set第二个参数可以是任何类型 Console.WriteLine(ic.Get<String>("name"));//Get泛型,指定获取的类型 ic.Set("time", DateTime.Now, 1);//过期时间秒 Console.WriteLine(ic.Get<DateTime>("time").ToFullString()); Thread.Sleep(1100); Console.WriteLine(ic.Get<DateTime>("time").ToFullString()); // 列表 var list = ic.GetList<DateTime>("list"); list.Add(DateTime.Now); list.Add(DateTime.Now.Date); list.RemoveAt(1); Console.WriteLine(list[list.Count - 1].ToFullString()); // 字典 var dic = ic.GetDictionary<DateTime>("dic"); dic.Add("xxx", DateTime.Now); Console.WriteLine(dic["xxx"].ToFullString()); // 队列 var mq = ic.GetQueue<String>("queue"); mq.Add(new[] { "abc", "g", "e", "m" }); var arr = mq.Take(3); Console.WriteLine(arr.Join(",")); // 集合 var set = ic.GetSet<String>("181110_1234"); set.Add("xx1"); set.Add("xx2"); set.Add("xx3"); Console.WriteLine(set.Count); Console.WriteLine(set.Contains("xx2")); Console.WriteLine("共有缓存对象 {0} 个", ic.Count); } Set的时候如果是字符串或者字符数据的话Redis会直接保存起来(字符串内部机制也是保存二进制),如果是其他类型会默认进行json序列化然后再保存起来 Get的时候如果是字符串或者字符数据会直接获取,如果是其他类型会进行json反序列化 Set第三个参数过期时间单位是秒。 vs调试小技巧,按F5或者直接工具栏“启动”会编译整个解决方案会很慢(VS默认),可以选中项目然后右键菜单选择调试->启动新实例。会只编译将会用到的项目,这样对调试来说会快很多。 大家运行调试后可以看到控制台输出的内容:向右的箭头=》是ic.Log=XTrace.Log输出的日志 字典的使用:对象的话需要把json全部取出来然后转换成对象,而字典的话就可以直接取某个字段。 队列是List结构实现的,使用场景可以上游数据太多,下游处理不过来的时候,那么就可以使用这个队列。上游的数据发到队列,然后下游慢慢的消费。另一个应用,跨语言的协同工作,比方说其他语言实现的程序往队列里面塞数据,然后另一种语言来进行消费处理。哈,这种方式类似mq的概念,虽然有点low,但是也很好用。 集合,用的比较多的是用在一个需要精确判断的去重功能。像我们每天有三千万订单,这三千万订单可以有重复,这时候我想统计下一共有订单,这时候直接数据库group by是不大可能的,因为数据库中分了十几张表,这里分享个实战经验:比方说揽收,商家发货了,网点要把件收回来,但是收回来之前网点不知道自己有多少货啊,这时候我们做了一个功能,也就是订单会发送到我们公司来,我们会建一个time_site的key的集合,而且集合本身有去重的功能,而且我们可以很方便的通过set.Count功能来统计数量,当件被揽收以后,我们后台把这个件从集合中Remove掉.然后这个Set中存在的就是网点还没有揽收的件,这时候通过Count就会知道这个网点今天还有多少件没有揽收。实际使用中这个数量比较大,因为有几万个网点。 Redis中布隆过滤器,去重的,面试的时候问的比较多 小经验分享: 数据库中不合法的时间处理:判断时间中的年份,是否大于2000年。如果小于2000就认为不合法。习惯大于小于号不习惯用等于号,这样可以处理很多意外的数据 Set的时候最好指定过期时间防止有些需要删除的数据,我们忘记删了 Redis异步尽量不用,因为Redis延迟本身很小,大概在100us-200us,再一个就是Redis本身是单线程的,异步任务切换的耗时比网络耗时还要大。 List用法:物联网中数据上传,量比较大时,我们可以把这些数据先放在Redis的List中,比如说一秒钟1万条,然后再批量取出来然后批量插入数据库中。这时候要设置好key,可以前缀+时间,对于已经处理的List可以进行remove移除。 压力测试 接下来看第四个例子,我们直接做压力测试,代码如下: static void Main(String[] args) { XTrace.UseConsole(); // 激活FullRedis,否则Redis.Create会得到默认的Redis对象 FullRedis.Register(); Test4(); Console.ReadKey(); } static void Test4() { var ic = Redis.Create("127.0.0.1:6379", 5); //var ic = new MemoryCache(); ic.Bench(); } 运行的结果如下图所示: 测试就是进行get,set remove,累加等的操作。大家可以看到在我本机上轻轻松松的到了六十万,多线程的时候甚至到了一百多万。为什么会达到这么高的ops呢,下面给大家说一下。 Bench 会分根据线程数分多组进行添删改压力测试。 rand 参数,是否随机产生key/value。 batch 批大小,分批执行读写操作,借助GetAll/SetAll进行优化。 Redis中NB的函数来提升性能 上面的操作如果大家都掌握的基本算Redis入门了,接下来进行进阶。会了基本比别人更胜一筹了。 GetAll()与SetAll() GetAll:比方说我要取十个key,这个时候可以用getall。这时候redis就执行了一次命令。比方说我要取10个key那么用get的话要取10次,如果用getall的话要用1次。一次getall时间大概是get的一点几倍,但是10次get的话就是10倍的时间,这个账你应该会算吧。强烈推荐大家用getall。 setall 跟getall相似。批量设置K-V. setall与getall性能很恐怖,官方公布的ops也就10万左右,为什么我们的测试轻轻松松到五十万甚至上百万,因为我们就用了setall,getall。 如果get,set两次以上,建议用getall,setall Redis管道Pipeline 比如执行10次命令会打包成一个包集体发过去执行,这里实现的方式是StartPipeline()开始,StopPipeline()结束中间的代码就会以管道的形式执行。这里推荐使用我们的更强的武器,AutoPipeline自动管道属性。管道操作到一定数量时,自动提交,默认0。使用了AutoPipeline,就不需要StartPipeline,StopPipeline指定管道的开始结束了! Add与Replace Add:Redis中没有这个Key就添加,有了就不要添加,返回false Replace:有则替换,还会返回原来的值,没有则不进行操作 Add跟Replace就是实现Redis分布式锁的关键 Redis使用技巧,经验分享 在项目的Readme中,这里摘录下: 特性 在ZTO大数据实时计算广泛应用,200多个Redis实例稳定工作一年多,每天处理近1亿包裹数据,日均调用量80亿次 低延迟,Get/Set操作平均耗时200~600us(含往返网络通信) 大吞吐,自带连接池,最大支持1000并发 高性能,支持二进制序列化(默认用的json,json很低效,转成二进制性能会提升很多) Redis经验分享 在Linux上多实例部署,实例个数等于处理器个数,各实例最大内存直接为本机物理内存,避免单个实例内存撑爆(比方说8核心处理器,那么就部署8个实例) 把海量数据(10亿+)根据key哈希(Crc16/Crc32)存放在多个实例上,读写性能成倍增长 采用二进制序列化,而非常见的Json序列化 合理设计每一对Key的Value大小,包括但不限于使用批量获取,原则是让每次网络包控制在1.4k字节附近,减少通信次数(实际经验几十k,几百k也是没问题的) Redis客户端的Get/Set操作平均耗时200~600us(含往返网络通信),以此为参考评估网络环境和Redis客户端组件(达不到就看一下网络,序列化方式等等) 使用管道Pipeline合并一批命令 Redis的主要性能瓶颈是序列化、网络带宽和内存大小,滥用时处理器也会达到瓶颈 其它可查优化技巧以上经验,源自于300多个实例4T以上空间一年多稳定工作的经验,并按照重要程度排了先后顺序,可根据场景需要酌情采用! 缓存Redis的兄弟姐妹 Redis实现ICache接口,它的孪生兄弟MemoryCache,内存缓存,千万级吞吐率。 各应用强烈建议使用ICache接口编码设计,小数据时使用MemoryCache实现; 数据增大(10万)以后,改用Redis实现,不需要修改业务代码。 提问环节聊聊大数据中Redis使用的经验,问题 一条数据多个key怎么设置比较合理? 如果对性能要求不是很高直接用json序列化实体就好,没必要使用字典进行存储。 队列跟List有什么区别?左进右出的话用List还是用队列比较好? 队列其实就是用List实现的,也是基于List封装的。左进右出的话直接队列就好。Redis的List结构比较有意思,既可以左进右出,也能右进左出。所以它既可以实现列表结构,也能队列,也能实现栈 存放多个字段的类性能一样吗? 大部分场景都不会有偏差,可能对于大公司数据量比较大的场景会有些偏差 可否介绍一下使用Redis进行数据计算、统计的场景? 略。自己看视频吧!o(∩_∩)o 哈哈!(因为我没听清!) 大数据写入到数据库之后 比如数据到亿以上的时候 统计分析这块 查询这块 能不能分享些经验。 分表分库,拆分到一千万以内。 CPU为何暴涨? 程序员终极理念:CPU达到百分百,然后性能达到最优,尽量不要浪费。最痛恨的是:如果cpu不到百分百,性能没法提升了,说明代码有问题! 视频地址 视频已经上传至百度云,大家可以自行下载观看链接:https://pan.baidu.com/s/1sOW_PLjxQE8C2msbDfizeA 提取码:c7dp 观看指南(笑笑提供) 总结 虽然Redis会用,但是没有像大石头这样的大数据使用场景。今天的视频收获颇丰,可能大部分人跟我一样,没有大石头的使用场景,但是值得借鉴的经验还是很丰富的!期待下一次的精彩分享。 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9941208.html 主讲人:大石头 记录人:依乐祝
作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9926078.html 在本文中,我将带着大家一步一步的通过图文的形式来演示如何在Visual Studio Code中进行.NET Core程序的开发,测试以及调试。尽管Visual Studio Code的部分功能还达不到Visual Studio的水平,但它实际上已经足够强大来满足我们的日常开发。而且其轻量化,插件化以及跨平台的特性则是VS所不具备的。而且Visual Studio Code还可以通过社区来创建一系列的扩展来增强其功能,且社区已经足够活跃。我们可以期待更多很酷的扩展和功能来增强VS Code,这将使在这个轻量级,跨平台编辑器中的开发.NET Core应用程序更加流畅和有趣。赶紧跟着博主一起开始今天的文章吧! 为什么要写这篇文章? 因为上篇文章也说了,.NET Core已经全面跨平台了,而且我们也在尝试使用Linux了,但是上篇CentOS开发ASP.NET Core入门教程 中使用的CLI进行.NET Core开发的话,感觉很不适应。毕竟从.net过度过来的我们已经习惯了使用Microsoft的Visual Studio进行开发。那么有没有一款媲美Visual Studio的开发工具可以让我们能够在Linux系统上进行高效的.NET Core开发呢?答案是肯定的,因为微软已经开发了一个名为Visual Studio Code的跨平台和开源的文本编辑器。Visual Studio Code是如此强大和令人惊叹,因为它提供了内置的智能提醒,调试功能和Git支持。而且Visual Studio Code提供了强大的插件扩展功能。使得你可以在插件扩展库里面找到满足你需求的插件。如果你没有在他们的扩展库中找到它,那么你还可以自己创建一个插件并使用它。很酷,对吗?那就开始吧! 安装 这部分,我们将讲解如何进行Visual Studio Code的安装,配置以便进行.NET Core的开发 准备工作 安装.NET Core SDK。具体的安装方式大伙可以点击这里进行查看并进行安装。因为微软的东西都比较傻瓜式,所以这里就不演示了。 安装Visual Studio Code。您可以从此处 然后根据您的操作系统进行选择下载,不同操作系统的安装过程可能会有所不同 您可以在此处 查看Visual Studio Code的安装说明。还是 因为微软的东西都比较傻瓜式,所以这里就不演示了。 在Visual Studio Code 中安装C# 扩展以便让Visual Studio Code 支持C#的开发,当然你也可以安装其他语言的扩展来进行其他编程语言的开发,比如说python,go等等。为了安装c#的扩展,你可以通过Visual Studio Code左侧工具栏中的Extensions图标或使用键盘快捷键Ctrl + Shift + X打开Extensions视图。在搜索框中搜索C#并从列表中安装扩展程序。如下图所示: 这里需要注意下,安装完成之后,需要重启下Visual Studio Code才能够使用C#扩展功能。 重启之后会出现如下的界面,表示已经安装好了C#扩展 使用Visual Studio Code开发基本的.NET Core程序 既然环境都已经准备好了,那么现在我们就开始使用Visual Studio Code开发一个.NET Core应用程序吧! 在电脑上一个位置创建一个名为DotNetCoreSample的空文件夹,然后右键单击该文件夹,从弹出的菜单中选择“使用Visual Studio Code打开”。这将打开Visual Studio Code,并将选定该文件夹作为工作区。当然也可以通过下图所示的步骤来打开这个文件夹,这个按照你的习惯来操作就好。 使用Ctrl+Shift+` 快捷键在 Visual Studio Code 中快速打开终端,如下图所示: 接下来我们使用dotnet new console --name DotNetCoreSample 命令来在这个打开的终端里面创建一个基础的控制台程序并进行restore。如下图所示 接下来我们打开生成的Program.cs 文件,Visual Studio Code会安装OmniSharp插件,然后会在右下角弹出如下图所示的是否需要生成用来构建以及调试的资产文件的询问窗口,这里点击“是”就会帮我们生成“launch.json”以及“task.json”文件,这些文件将有助于使用Visual Studio代码构建和调试应用程序 。 下面我们修改下Program.cs 文件中的内容,添加下面这行代码。然后保存文件,并把鼠标移动到终端,然后终端cd到我们的项目目录cd DotNetCoreSample。输入dotnet run 然后按下Enter键,可以看到如下所示的内容: Visual Studio Code中vscode-solution-explorer解决方案管理器插件的使用 可能很多.neter朋友们刚开始使用Visual Studio Code的时候很不适应各种命令行dotnet命令来创建项目以及解决方案。幸运的是,Visual Studio Code扩展中提供了类似于Visual Studio的解决防范资源管理的插件来解决这个问题。下面我们一步一步的看下如何使用此插件吧! 打开Visual Studio Code扩展,然后输入vscode-solution-explorer,然后如下图所示进行安装。 安装后插件后,VS Code Explorer左侧栏中将多了一个显示名为“SOLUTION EXPLORER”的新窗格。 接下来我们使用它来创建解决方案,并在解决方案中添加项目吧。我们按下快捷键Ctrl + Shift + P 然后选择“Create a new empty solution ” VS Code 将提示我们输入一个解决方案的名称。我们输入一个SimpleCalculator 作为解决方案的名称。  现在,VS Code将使用我们提供的名称创建一个空的解决方案。在后台,我们安装的扩展将执行dotnet new sln 命令。您可以在“SOLUTION EXPLORER”窗格中看到空白解解决方案。然后此扩展程序将询问你是否创建模板文件夹请参见下图。如果允许,它将在.vscode / solution-explorer 目录中添加一些模板。 现在,让我们向这个空白的解决方案中添加类库和控制台应用程序。右键单击解决方案(在Solution Explorer窗格中),然后从上下文菜单中选择Add new project选项。这将列出.NET CLI提供的可用项目类型(请参见下图)。选择“类库”选项。 系统将询问您将使用哪种语言。选择C#,编辑器将提示输入项目名称。 像我们之前给出的那样给出MathOperations的名称。类库已添加到解决方案中。 重复相同的步骤并添加名为“Calculator ”的控制台应用程序。请记住从项目模板中选择控制台应用程序。 现在我们需要在控制台应用程序中添加类库项目的引用。右键单击控制台应用程序项目,然后从上下文菜单中选择“添加引用”选项。由于解决方案中只有两个项目,扩展程序将自动添加另一个项目的引用。如果有两个以上的项目,我们需要从列表中选择项目。 导航到类库目录MathOperations。将Class1.cs 类文件重命名为MathOperations.cs。在类中添加一个两个数字的简单简单加法的方法,代码如下: public static class MathOperation { public static int Add(int num1, int num2) => num1 + num2; } 修改导航到Calculator控制台程序并在Program.cs文件中使用类库中的方法。这里大家可以使用Shift + Alt + F 快捷键格式化代码。如下所示: static void Main(string[] args) { int num1 = 10; int num2 = 20; int sum = MathOperation.Add(num1, num2); // Method from class library Console.WriteLine($"{num1} + {num2} = {sum}"); Console.ReadLine(); } 现在,右键单击解决方案资源管理器树中的控制台应用程序项目,然后从上下文菜单中选择“运行”选项。您可以看到.NET CLI将在后台运行应用程序。并在Output窗口中输出结果,如下图所示。 Visual Studio Code在.NET Core应用程序中运行测试插件 单元测试是软件开发不可或缺的一部分。这里我不打算详细解释单元测试,因为有很多在线资源。我只给大家介绍如何在.NET Core应用程序中包含单元测试以及可用于运行单元测试的Visual Studio Code的扩展。 首先让我们该写下数学运算的类库方法 public static class MathOperation { public static int Add(int num1, int num2) => num1 + num2; public static int Subtract(int num1, int num2) => num1 - num2; public static int Multiply(int num1, int num2) => num1 * num2; public static int Divide(int num1, int num2) => num1 / num2; } 现在,我们需要在解决方案中添加一个单元测试项目。 我们可以使用.NET CLI或上面提到的Solution Explorer扩展来添加单元测试项目。要通过Solution Explorer扩展添加项目,请右键单击解决方案,然后 从上下文菜单中选择“ 添加新项目 ”。从项目模板中选择xUnit Test Project 并命名为 MathOperationTests。创建测试项目后,将MathOperations类库的引用添加到测试项目中。 如果您使用的是.NET CLI,则需要运行以下命令。 dotnet new xunit -n MathOperationTests dotnet add MathOperationTests\MathOperationTests.csproj reference MathOperations\MathOperations.csproj dotnet sln SimpleCalculator.sln add MathOperationTests\MathOperationTests.csproj 将UnitTest1.cs重命名为OperationTests.cs。也要在代码中更改类名。现在我们将为类库方法添加一些测试。 public class OperationTests { [Fact] public void AddTwoNumbers_ReturnsSum() { var num1 = 10; var num2 = 20; var result = MathOperation.Add(num1, num2); Assert.Equal(30, result); } [Fact] public void SubtractTwoNumbers_ReturnsDifference() { var num1 = 20; var num2 = 10; var result = MathOperation.Subtract(num1, num2); Assert.Equal(10, result); } [Fact] public void MultiplyTwoNumbers_ReturnsProduct() { var num1 = 10; var num2 = 20; var result = MathOperation.Multiply(num1, num2); Assert.Equal(200, result); } [Fact] public void DivideTwoNumbers_ReturnsQuotient() { var num1 = 20; var num2 = 10; var result = MathOperation.Divide(num1, num2); Assert.Equal(2, result); } } 现在,我们需要运行我们创建的测试。我们为此使用.NET CLI。打开终端。导航到MathOperationTests目录。输入dotnet test命令。我们将获得以下输出。 如您所见,输出信息量较少。如果我们在Visual Studio中有类似于Test Explorer的东西来执行我们的单元测试并查看结果,那将会很好。好消息是有一个名为.NET Core Test Explorer的Visual Studio Code插件。下面按照下图所示在Visual Studio代码中安装此扩展吧。这里不过多说明了 安装扩展程序后,您可以在左侧活动栏中看到一个烧杯图标。单击该图标,您将看到测试的侧栏面板,其中列出了项目中发现的单元测试。测试项目将显示在按命名空间和类分组的树视图中。您还可以看到每个测试的“运行”按钮和顶部的“全部运行”按钮。单击Run All按钮,您可以看到正在执行的所有测试及其结果。 我们可以看到所有测试都已通过,并在测试资源管理器窗格中标有绿色勾号。现在让我们让测试失败。我将更改Add方法的逻辑以使测试失败。 public static int Add(int num1, int num2) => num1 - num2;//这里有bug 现在再次运行测试。我们可以看到我们对Add方法的测试失败,并在test explorer窗格中用红色符号标记。 如果我们导航到我们编写的测试方法,我们可以看到它现在在Assert方法中有一个红色的波浪下划线。如果我们将鼠标悬停在该波浪线上,将显示一个信息框,显示测试的实际值和预期值。VS代码的底部面板(终端所在的面板)的“ 问题”选项卡中显示相同的信息。这可以在下图中看到。 修复错误并再次运行测试,以便所有测试都通过,我们可以再次看到绿色标记。 Visual Studio Code中顺畅的调试.NET Core应用程序 在这部分,我们将了解如何在Visual Studio Code中顺畅的调试.NET Core应用程序。为了在Visual Studio Code中调试.NET Core应用程序,我们需要为VS Code安装C#扩展。(上面我们已经安装过了) 我们首先在Calculator控制台程序的Program.cs文件中加入断点。与Visual Studio类似,我们可以通过单击源代码文件的左边距,或者将光标放在一行代码上并按F9,在源代码中设置行断点。断点在编辑器的左边缘显示为红点。 要开始调试,请按F5。这将自动将调试器附加到我们的Calculator应用程序来启动应用程序。我们可以看到执行在我们设置的断点处停止,这有助于我们在调试时了解当前的程序状态。 这里需要注意下,需要修改launch.json中的对应路径以及项目名称为Calculator。 我们可以看到VS Code的Debug视图在编辑器的左侧打开。Debug视图显示与调试相关的所有信息。我们还可以注意到编辑器顶部出现了一个调试工具栏。调试时,调试工具栏可用于代码导航选项。这里调试试图的大部分功能跟vs2017差不多,因此这里不做过多地阐述了。 总结 在本文中,我已经为大家一步一步的通过图文教程解释了如何在Visual Studio Code中进行.NET Core程序的开发,测试以及调试。赶紧下载一个试试吧!你会发现你会越来越喜欢他的!本文参考:https://www.c-sharpcorner.com/article/create-a-net-core-development-environment-using-visual-studio-code2/
作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9891346.html 因为之前一直没怎么玩过CentOS,大多数时间都是使用Win10进行开发,然后程序都部署在Window Server2008或者Window Server2012上!因此想尝试下Linux系统。最后经过选型选了比较流行的CentOS系统。正好,今晚要加班,所以在数据备份的空隙,写了今天这篇关于使用CentOS开发ASP.NET Core的入门教程。干货不多,主要是为了记录自己向Linux迈出第一步的大门。大家将就着看吧! 前言 程序员的江湖一直有这么一个传说,就是入坑的第一个程序都是向世界问好,所以这篇CentOS开发ASP.NET Core入门教程的第一篇也仅仅是搭建环境,然后向世界输出“Hello World!”。 CentOS系统ASP.NET Core开发环境的搭建 这里假设大家已经装了Linux虚拟机或者买了阿里云的Linux服务器。而且在Windows开发机上安装 xshell ,xshell用于SSH连接Linux服务器(当然,你也可以用其他的软件,这么不过多阐述)。下面用xshell连接上你的linux服务。然后开始进入正式的部署吧。 安装.Net Core SDK 要开始构建.Net Core应用程序前,你需要安装.NET Core SDK(软件开发工具包)即可。具体怎么安装呢?有以下几个步骤: 添加dotnet 产品Feed(就是为了告诉微软,我们的服务器要使用.net Core sdk了) 在安装.net sdk之前呢,你需要注册Microsoft密钥,注册产品存储库并安装所需的依赖项。这个步骤每台服务器只需要执行一次既可以了。命令如下所示: sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm 安装.Net Core SDK 首先要安装可用的产品更新,然后才是安装.Net Core SDK。在命令行,分别运行下面的命令。 sudo yum update y sudo yum install dotnet-sdk-2.1 y 大家注意一下,有两个“y”的原因是,命令执行的中途会停顿下人,让你确认下是否进行安装,你要输入“y”确认安装才会执行安装的。 在CentOS上创建你的第一个.Net Core 应用程序 前面安装好.net core sdk以后,我们输入如下命令来看下我们是否安装成功吧! dotnet --info 如果出现上面的图说明我们已经安装成功了。上面显示有.Net Core的版本信息。 接下来我们新建一个文件夹名字叫“netcore”用来存放我们的asp.net core应用程序。然后进入这个文件夹 mkdir netcore cd netcore 输入如下的命令来创建第一个ASP.NET Core应用程序 dotnet new console -o myFirstApp cd myFirstApp 该dotnet命令为您创建一个新的控制台应用程序。该-o参数为新的应用程序创建一个名为myFirstApp的目录。该cd myFirstApp命令将切换到这个新的应用程序目录。 然后输入ls命令可以看到下图所示的三个文件: 该myFirstApp文件夹中的主文件是 Program.cs。默认情况下,它已包含了向控制台输入“Hello World!”所需的代码。 使用如下的命令来运行下这个应用程序吧。 dotnet run 如果不出意外的话,大家可以看到,程序向我们输入了Hello World的!至此,我们在Centos上的第一个.Net Core程序就跑起来了! 总结 今天也是忙里偷闲,利用加班的间隙写了这篇window向CentOS进击的第一篇入门教程!既然ASP.NET Core已经全面跨平台了,那我们也得学会改变,学着使用Linux系统!不管你愿不愿意,这是一个趋势!金庸的江湖已去,而我们程序员的江湖还在继续!多一种技能在身,总归是好事!
作者依乐祝原文地址https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中我将解释命令模式以及如何利用基于命令模式的第三方库来实现它们以及如何在ASP.NET Core中使用它来解决我们的问题并使代码简洁。因此我们将通过下面的主题来进行相关的讲解。 什么是命令模式? 命令模式的简单实例以及中介者模式的简单描述 MVC中的瘦控制器是什么?我们是如何实现使控制器变瘦的 我们如何在我们的.NET Core应用程序中使用MediatR 使用命令和事件的实例 命令模式及其简单实例 从根本上讲命令模式是一种数据驱动的设计模式属于行为模式的范畴。命令是我们可以执行的某种操作或行为它可以是活动的一部分。一个活动可以有一个或多个命令和实现。 我们可以这样来说请求以命令的形式包裹在对象中并传给调用对象。调用者代理对象查找可以处理该命令的合适的对象并把该命令传给相应的对象该对象执行命令 。 一个简单的例子是多种类型的消息。Message类包含SendEmail和SendSms等属性和方法。使用两种类型的命令并且需要一个接口它应该由实现了EmailMessageCommand和SMSMessageCommand的类类继承。还使用代理类来调用特定类型的消息类来处理操作。 Main class class Program { static void Main(string[] args) { Message message = new Message(); message.CustomMessage = "Welcome by Email"; EmailMessageCommand emailMessageCommand = new EmailMessageCommand(message); Message message2 = new Message(); message2.CustomMessage = "Welcome by SMS"; SmsMessageCommand smsMessageCommand = new SmsMessageCommand(message2); Broker broker = new Broker(); broker.SendMessage(emailMessageCommand); broker.SendMessage(smsMessageCommand); Console.ReadKey(); } } 消息类 public class Message { public string CustomMessage { get; set; } public void EmailMessage() { Console.WriteLine($"{CustomMessage} : Email Message sent"); } public void SmsMessage() { Console.WriteLine($"{CustomMessage} : Sms Message sent"); } } 接口和代理类 public interface IMessageCommand { void DoAction(); } public class Broker { public void SendMessage(IMessageCommand command) { command.DoAction(); } } 命令 public class EmailMessageCommand : IMessageCommand { private Message oMessage; public EmailMessageCommand(Message oMessage) { this.oMessage = oMessage; } public void DoAction() { oMessage.EmailMessage(); } } public class SmsMessageCommand : IMessageCommand { private Message oMessage; public SmsMessageCommand(Message oMessage) { this.oMessage = oMessage; } public void DoAction() { oMessage.SmsMessage(); } } 输出 什么是瘦控制器我们为什么需要它什么是MediatR 当我们开始使用MVC框架进行开发时逻辑是用控制器的动作方法编写的就像我们有一个简单的电子商务应用程序其中用户应该会下订单。我们有一个控制器OrderController用来管理订单。当用户下订单时我们应该在数据库中保存记录。在此之前我们有一个简化的代码。然而经过一段时间后我们意识到还有一个确认电子邮件的业务需求。现在第二步是发送确认电子邮件给客户。后来我们意识到在这个步骤之后我们还需要执行另一个操作即记录信息等。最后我们还需要将用户的信息保存到CRM中。关键是它会增长控制器的大小。现在我们可以称之为“臃肿控制器”。基于命令的体系结构允许我们发送命令来执行某些操作并且我们有单独的命令处理程序使关注点分离和提高单一职责。为了实现这个架构我们可以使用第三方库比如MediatRMediator.它为我们做了很多基础工作。中介模式定义了一个对象该对象封装了一组对象是如何交互的。 中介模式的优势及MediatR如何帮助我们实现中介模式 中介模式定义了一个对象该对象封装了一组对象是如何交互的如维基百科定义的。 它通过保持对象彼此明确地相互引用来促进松散耦合。 它通过允许通信被卸载到一个只处理这类的类来促进单一责任原则。 MediatR库如何帮助我们 MediatR允许我们通过让控制器Action向处理程序发送请求消息来将控制器与业务逻辑解耦。MediatR库支持两种类型的操作。 命令预期输出结果 事件请求者不关心接下来发生了什么不期待结果 我们已经介绍了命令模式因此是时候定义一些命令并使用MediatR发出命令了。 在ASP.NET Core中安装 我们需要从NuGet安装MediatR和MediatR.Extensions.Microsoft.DependencyInjection包。 当这两个软件包安装完毕后我们需要添加services.AddMediatR(); 到startup.cs文件。看起来像这样。 现在我们可以使用.NET Core 项目中的MediatR了。 实例 第一个示例演示了使用MediatR使用请求/响应类型的操作。它期望对请求做出一些反应。第二个示例将向您展示一个事件其中多个处理程序执行它们的工作调用者并不关心接下来会发生什么也不期望任何结果/响应。 第一个例子 在这种场景下我们希望注册用户并期望对请求做出一些响应。如果响应返回true我们可以像登录用户一样进行进一步的操作。首先我们需要创建一个继承自IRequest的类。 public class NewUser: IRequest<bool> { public string Username { get; set; } public string Password { get; set; } } IRequest是指请求的响应是布尔响应。现在需要一个处理程序来处理这种类型的请求。 public class NewUserHandler : IRequestHandler<NewUser, bool> { public Task<bool> Handle(NewUser request, CancellationToken cancellationToken) { // save to database return Task.FromResult(true); } } 现在我们有了命令和它的处理程序我们可以调用MediatR在我们的控制器中做一些操作。这些是Home控制器的动作方法。 public class HomeController : Controller { private readonly IMediator _mediator; public HomeController(IMediator mediator) { _mediator = mediator; } [HttpGet] public ActionResult Register() { return View(); } [HttpPost] public ActionResult Register(NewUser user) { bool result = _mediator.Send(user).Result; if (result) return RedirectToAction("Login"); return View(); } } 第一个例子的结论 注册操作方法使用了[HttpPost]属性进行修饰并接受新的用户注册请求。然后它请求MediatR 进行处理。它期望来自请求的结果/响应如果结果是真的则将用户重定向到登录页面。这里我们有简洁的代码大部分的工作是在控制器外部完成的。这实现了对不同操作的处理的关注点分离SoC和单一责任的分离。在第二个示例中我们将演示使用多个处理程序对命令执行不同操作的场景。 第二个实例 在这种情况下我们使NewUser 继承了INotification public class NewUser : INotification { public string Username { get; set; } public string Password { get; set; } } 现在有三个处理程序逐个执行以完成他们的工作。这些都是从INotificationHandler继承下来的。 public class NewUserHandler : INotificationHandler<NewUser> { public Task Handle(NewUser notification, CancellationToken cancellationToken) { //Save to log Debug.WriteLine(" **** Save user in database *****"); return Task.FromResult(true); } } 第二个处理程序在下面的代码中定义。 public class EmailHandler : INotificationHandler<NewUser> { public Task Handle(NewUser notification, CancellationToken cancellationToken) { //Send email Debug.WriteLine(" **** Email sent to user *****"); return Task.FromResult(true); } } 这是第三个处理程序的代码 public class LogHandler : INotificationHandler<NewUser> { public Task Handle(NewUser notification, CancellationToken cancellationToken) { //Save to log Debug.WriteLine(" **** User save to log *****"); return Task.FromResult(true); } } 然后我们控制器中的代码像下面这样 public class AccountsController : Controller { private readonly IMediator _mediator; public AccountsController(IMediator mediator) { _mediator = mediator; } [HttpGet] public ActionResult Login() { return View(); } [HttpGet] public ActionResult Register() { return View(); } [HttpPost] public ActionResult Register(NewUser user) { _mediator.Publish(user); return RedirectToAction("Login"); } } 第二个例子的结论 此应用程序的输出如下当用户注册后三个处理程序逐个执行——分别是NewUserHandler、EmailHandler和LogHandler并执行它们的操作。 这里我们使用了Publish 方法而不是Send 函数。发布将调用订阅了NewUser 类的所有处理程序。这只是一个示例我们可以根据命令进行思考然后按照我们在命令模式中讨论的方式相应地执行一些操作。 Mediatr是如何提供帮助的 它可以用来隐藏实现的细节用来使控制器代码更加干净和可维护可以重用多个处理程序并且每个处理程序都有自己的责任因此易于管理和维护。 在我的下一篇文章中我将尝试解释CQRS架构模式及其优点以及如何使用MediatR来实现CQRS。 原文地址https://www.c-sharpcorner.com/article/command-mediator-pattern-in-asp-net-core-using-mediatr2/
作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9852711.html 上篇文章给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储到数据库中。并没有对实现原理进行相应的阐述。今天抽空把实现的原理给大家说道说道。明白原理后,大家就可以自行改写进行扩展来满足自身需要了!再次感觉张队的审稿,并给出的修改意见! 源码解析过程 大家可以自行分析Ocelot的源码,我通过分析ocelot的源码得出,如果要实现重写配置文件的方式,只需要写一个类来实现IFileConfigurationRepository这个接口即可。 代码如下: /// <summary> /// yilezhu /// 2018.10.22 /// 实现从SQLSERVER数据库中提取配置信息 /// </summary> public class SqlServerFileConfigurationRepository : IFileConfigurationRepository { private readonly IOcelotCache<FileConfiguration> _cache; private readonly IOcelotLogger _logger; private readonly ConfigAuthLimitCacheOptions _option; public SqlServerFileConfigurationRepository(ConfigAuthLimitCacheOptions option, IOcelotCache<FileConfiguration> cache, IOcelotLoggerFactory loggerFactory) { _option = option; _cache = cache; _logger = loggerFactory.CreateLogger<SqlServerFileConfigurationRepository>(); } public Task<Response> Set(FileConfiguration fileConfiguration) { _cache.AddAndDelete(_option.CachePrefix + "FileConfiguration", fileConfiguration, TimeSpan.FromSeconds(1800), ""); return Task.FromResult((Response)new OkResponse()); } /// <summary> /// 提取配置信息 /// </summary> /// <returns></returns> public async Task<Response<FileConfiguration>> Get() { var config = _cache.Get(_option.CachePrefix + "FileConfiguration", ""); if (config != null) { return new OkResponse<FileConfiguration>(config); } #region 提取配置信息 var file = new FileConfiguration(); string glbsql = "select top 1 * from OcelotGlobalConfiguration where IsDefault=1"; //提取全局配置信息 using (var connection = new SqlConnection(_option.DbConnectionStrings)) { var result = await connection.QueryFirstOrDefaultAsync<OcelotGlobalConfiguration>(glbsql); if (result != null) { var glb = new FileGlobalConfiguration(); glb.BaseUrl = result.BaseUrl; glb.DownstreamScheme = result.DownstreamScheme; glb.RequestIdKey = result.RequestIdKey; if (!String.IsNullOrEmpty(result.HttpHandlerOptions)) { glb.HttpHandlerOptions = result.HttpHandlerOptions.ToObject<FileHttpHandlerOptions>(); } if (!String.IsNullOrEmpty(result.LoadBalancerOptions)) { glb.LoadBalancerOptions = result.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>(); } if (!String.IsNullOrEmpty(result.QoSOptions)) { glb.QoSOptions = result.QoSOptions.ToObject<FileQoSOptions>(); } if (!String.IsNullOrEmpty(result.ServiceDiscoveryProvider)) { glb.ServiceDiscoveryProvider = result.ServiceDiscoveryProvider.ToObject<FileServiceDiscoveryProvider>(); } file.GlobalConfiguration = glb; //提取路由信息 string routesql = "select * from OcelotReRoutes where OcelotGlobalConfigurationId=@OcelotGlobalConfigurationId and IsStatus=1"; var routeresult = (await connection.QueryAsync<OcelotReRoutes>(routesql, new { OcelotGlobalConfigurationId=result.Id })).AsList(); if (routeresult != null && routeresult.Count > 0) { var reroutelist = new List<FileReRoute>(); foreach (var model in routeresult) { var m = new FileReRoute(); if (!String.IsNullOrEmpty(model.AuthenticationOptions)) { m.AuthenticationOptions = model.AuthenticationOptions.ToObject<FileAuthenticationOptions>(); } if (!String.IsNullOrEmpty(model.CacheOptions)) { m.FileCacheOptions = model.CacheOptions.ToObject<FileCacheOptions>(); } if (!String.IsNullOrEmpty(model.DelegatingHandlers)) { m.DelegatingHandlers = model.DelegatingHandlers.ToObject<List<string>>(); } if (!String.IsNullOrEmpty(model.LoadBalancerOptions)) { m.LoadBalancerOptions = model.LoadBalancerOptions.ToObject<FileLoadBalancerOptions>(); } if (!String.IsNullOrEmpty(model.QoSOptions)) { m.QoSOptions = model.QoSOptions.ToObject<FileQoSOptions>(); } if (!String.IsNullOrEmpty(model.DownstreamHostAndPorts)) { m.DownstreamHostAndPorts = model.DownstreamHostAndPorts.ToObject<List<FileHostAndPort>>(); } //开始赋值 m.DownstreamPathTemplate = model.DownstreamPathTemplate; m.DownstreamScheme = model.DownstreamScheme; m.Key = model.Key; m.Priority = model.Priority ?? 0; m.RequestIdKey = model.RequestIdKey; m.ServiceName = model.ServiceName; m.Timeout = model.Timeout ?? 0; m.UpstreamHost = model.UpstreamHost; if (!String.IsNullOrEmpty(model.UpstreamHttpMethod)) { m.UpstreamHttpMethod = model.UpstreamHttpMethod.ToObject<List<string>>(); } m.UpstreamPathTemplate = model.UpstreamPathTemplate; reroutelist.Add(m); } file.ReRoutes = reroutelist; } } else { throw new Exception("未监测到配置信息"); } } #endregion if (file.ReRoutes == null || file.ReRoutes.Count == 0) { return new OkResponse<FileConfiguration>(null); } return new OkResponse<FileConfiguration>(file); } } 当然,既然我们已经重新实现了这个接口,那么就得进行相应的DI了。这里我们扩展下IOcelotBuilder方法,代码如下,主要就是进行相应的服务的DI: /// <summary> /// yilezhu /// 2018.10.22 /// 基于Ocelot扩展的依赖注入 /// </summary> public static class ServiceCollectionExtensions { /// <summary> /// 添加默认的注入方式,所有需要传入的参数都是用默认值 /// </summary> /// <param name="builder"></param> /// <returns></returns> public static IOcelotBuilder AddAuthLimitCache(this IOcelotBuilder builder, Action<ConfigAuthLimitCacheOptions> option) { builder.Services.Configure(option); builder.Services.AddSingleton( resolver => resolver.GetRequiredService<IOptions<ConfigAuthLimitCacheOptions>>().Value); #region 注入其他配置信息 //重写提取Ocelot配置信息, builder.Services.AddSingleton(DataBaseConfigurationProvider.Get); //builder.Services.AddHostedService<FileConfigurationPoller>(); builder.Services.AddSingleton<IFileConfigurationRepository, SqlServerFileConfigurationRepository>(); //注入自定义限流配置 //注入认证信息 #endregion return builder; } } 接下来就是重写,OcelotBuild里面配置文件的获取方式了。这里我选择的是对IApplicationBuilder进行扩展,因为这样方便做一些其他的事情,比如,重写限流,集成自定义的验证等等。具体代码如下: /// <summary> /// yilezhu /// 2018.10.22 /// 扩展IApplicationBuilder,新增use方法 /// </summary> public static class OcelotMiddlewareExtensions { /// <summary> /// 扩展UseOcelot /// </summary> /// <param name="builder"></param> /// <returns></returns> public static async Task<IApplicationBuilder> UseAhphOcelot(this IApplicationBuilder builder) { await builder.UseAhphOcelot(new OcelotPipelineConfiguration()); return builder; } /// <summary> /// 重写Ocelot,带参数 /// </summary> /// <param name="builder"></param> /// <param name="pipelineConfiguration"></param> /// <returns></returns> public static async Task<IApplicationBuilder> UseAhphOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var configuration = await CreateConfiguration(builder); ConfigureDiagnosticListener(builder); return CreateOcelotPipeline(builder, pipelineConfiguration); } private static async Task<IInternalConfiguration> CreateConfiguration(IApplicationBuilder builder) { // make configuration from file system? // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this //var fileConfig = builder.ApplicationServices.GetService<IOptionsMonitor<FileConfiguration>>(); var fileConfig = await builder.ApplicationServices.GetService<IFileConfigurationRepository>().Get(); // now create the config var internalConfigCreator = builder.ApplicationServices.GetService<IInternalConfigurationCreator>(); var internalConfig = await internalConfigCreator.Create(fileConfig.Data); //Configuration error, throw error message if (internalConfig.IsError) { ThrowToStopOcelotStarting(internalConfig); } // now save it in memory var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>(); internalConfigRepo.AddOrReplace(internalConfig.Data); //fileConfig.OnChange(async (config) => //{ // var newInternalConfig = await internalConfigCreator.Create(config); // internalConfigRepo.AddOrReplace(newInternalConfig.Data); //}); var adminPath = builder.ApplicationServices.GetService<IAdministrationPath>(); var configurations = builder.ApplicationServices.GetServices<OcelotMiddlewareConfigurationDelegate>(); // Todo - this has just been added for consul so far...will there be an ordering problem in the future? Should refactor all config into this pattern? foreach (var configuration in configurations) { await configuration(builder); } if (AdministrationApiInUse(adminPath)) { //We have to make sure the file config is set for the ocelot.env.json and ocelot.json so that if we pull it from the //admin api it works...boy this is getting a spit spags boll. var fileConfigSetter = builder.ApplicationServices.GetService<IFileConfigurationSetter>(); // await SetFileConfig(fileConfigSetter, fileConfig.Data); } return GetOcelotConfigAndReturn(internalConfigRepo); } private static bool AdministrationApiInUse(IAdministrationPath adminPath) { return adminPath != null; } //private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor<FileConfiguration> fileConfig) //{ // var response = await fileConfigSetter.Set(fileConfig.CurrentValue); // if (IsError(response)) // { // ThrowToStopOcelotStarting(response); // } //} private static bool IsError(Response response) { return response == null || response.IsError; } private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) { var ocelotConfiguration = provider.Get(); if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) { ThrowToStopOcelotStarting(ocelotConfiguration); } return ocelotConfiguration.Data; } private static void ThrowToStopOcelotStarting(Response config) { throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); } private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); //重写自定义管道 pipelineBuilder.BuildAhphOcelotPipeline(pipelineConfiguration); var firstDelegate = pipelineBuilder.Build(); /* inject first delegate into first piece of asp.net middleware..maybe not like this then because we are updating the http context in ocelot it comes out correct for rest of asp.net.. */ builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; builder.Use(async (context, task) => { var downstreamContext = new DownstreamContext(context); await firstDelegate.Invoke(downstreamContext); }); return builder; } private static void ConfigureDiagnosticListener(IApplicationBuilder builder) { var env = builder.ApplicationServices.GetService<IHostingEnvironment>(); var listener = builder.ApplicationServices.GetService<OcelotDiagnosticListener>(); var diagnosticListener = builder.ApplicationServices.GetService<DiagnosticListener>(); diagnosticListener.SubscribeWithAdapter(listener); } } 这其中最主要的代码就是,重写配置文件获取这块。我在下面进行了截图,并圈出来了,大家自行查看吧。 代码重写好了。由于我们服务注册时通过扩展IOcelotBuilder,所以,我们需要在ConfigureServices方法引入Ocelot服务的时候比Ocelot多写一个方法,并传入相关的配置信息,如下所示: services.AddOcelot()//注入Ocelot服务 .AddAuthLimitCache(option=> { option.DbConnectionStrings = "Server=.;Database=Ocelot;User ID=sa;Password=1;"; }); 这里的目的就是为了注入我们实现了IFileConfigurationRepository接口的SqlServerFileConfigurationRepository这个类。 接下来就是在管道中使用我们重写的Ocelot服务了。如下所示,在Configure方法中按如下代码进行使用: app.UseAhphOcelot().Wait(); 好了,以上就是实现的整个过程了。经过这么一分析是不是觉得很简单呢。当然具体为什么按照上面处理就能够从数据库获取配置了呢,这个还需要你分析了源码后才能了解。我也只是给你引路,传达我实现的思路。 源码 https://github.com/yilezhu/Ocelot.ConfigAuthLimitCache 总结 今天抽空对上篇文章进行了补充说明,目的是给大家阐述下,配置文件存储到数据库中的实现过程及原理。让你能够根据自身需要来进行改写来满足你的业务需求。当然我也只是给你引路,具体为什么这样实现下就能够成功呢?答案在Ocelot的源码中。 Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 Ocelot简易教程(四)之请求聚合以及服务发现 Ocelot简易教程(五)之集成IdentityServer认证以及授权 Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据 Ocelot简易教程(七)之配置文件数据库存储插件源码解析
本来这篇文章在昨天晚上就能发布的,悲剧的是写了两三千字的文章居然没保存,结果我懵逼了。今天重新来写这篇文章。今天我们就一起来探讨下如何重写Ocelot配置文件的存储方式以及获取方式。 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9807125.html 很多人都说配置文件的配置很繁琐,如果存储在数据库就方便很多,可以通过自定义UI界面在后台进行路由的配置,然后通过调用Administration API让修改后的路由规则立即生效。当然这都是后话了。今天就教你手把手的来把配置文件放到数据库中,然后在数据库中进行路由的配置。当然,我会在Github上开放源代码供大家参考。至于Nuget包的话,今天还没来得及弄,等明天晚上弄好,再发布Nuget包吧,今天先引用下源代码来使用吧。大家委屈一下吧。本文还是沿用之前的系列文章里面的Demo。所以可以先下载之前系列文章里面的Demo源码。https://github.com/yilezhu/OcelotDemo 实例教程集成步骤 Github上下载重写的配置文件的源代码,地址:https://github.com/yilezhu/Ocelot.ConfigAuthLimitCache 然后把项目文件拷贝到。系列文章的源代码下面,并添加项目引用。如下所示: 项目添加进来后的结构如下所示: OcelotDemo网关项目作如下修改,Programs.cs文件移除对Ocelot.json文件的引用,因为配置文件的获取方式已经改成了从数据库中获取,所以,你需要新建一个数据库,然后执行数据库脚本创建数据库表,这里只给出Mssql的数据库脚本https://github.com/yilezhu/Ocelot.ConfigAuthLimitCache/blob/master/Ocelot.sql ,在项目源代码下面,大家自行下载。 ConfigureServices服务中Ocelot的注入的同时需要注入我们的扩展方法,如下所示: services.AddOcelot()//注入Ocelot服务 .AddAuthLimitCache(option=> { option.DbConnectionStrings = "Server=.;Database=Ocelot;User ID=sa;Password=1;"; }) .AddConsul(); 注意:这里需要传入SqlServer的数据库连接字符串,由于博主扩展使用的Dapper+MSSQL所以这里需要传入步骤1中创建的数据库的链接字符串。 我们在数据库中配置一个路由吧,如下所示:字段名称基本都是跟Ocelot原生配置名称一样,只是扩展了一些字段方便后期做限流的 大家看到没有,这条路由的意思是接受/ss1/{通配符} 的路由,然后转到到下面就是/api/{通配符} 。 路由配置好了,那就让我们启动一下项目看下效果吧。 上面是正常的访问结果,当我们访问一个错误的路由的时候,再看看吧。 看到没有,返回了404的状态码,感觉不够友好,所以,我们也进行了改造。直接看结果吧 为了看到效果,你需要在Configure中少做下修改 app.UseAhphOcelot().Wait(); 然后我们重新启动下Ocelot网关项目,重新访问下6中的Url吧。 看到没有,返回的数据更友好,而且是200的状态。当然大家也可以忽略这个功能哈。 源码地址: Demo地址:https://github.com/yilezhu/OcelotDemo 扩展插件地址:https://github.com/yilezhu/Ocelot.ConfigAuthLimitCache 总结 本文主要通过实例讲述如何集成,将配置文件存储到数据库的插件。源码已经开源,今天暂时没有发布Nuget包,明天再发布吧。当然你可以自行扩展代码。实现你自己的业务。我把配置文件存储到数据库的目的就是方便后面做UI管理方便,还有就是可以基于这些路由在数据库中对每个客户端进行单独的限流。最后感谢大家的阅读。 Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 Ocelot简易教程(四)之请求聚合以及服务发现 Ocelot简易教程(五)之集成IdentityServer认证以及授权 Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据 Ocelot简易教程(七)之配置文件数据库存储插件源码解析
最近比较懒,所以隔了N天才来继续更新第五篇Ocelot简易教程,本篇教程会先简单介绍下官方文档记录的内容然后在前几篇文档代码的基础上进行实例的演示。目的是为了让小白也能按照步骤把代码跑起来。当然,在开始之前你要对IdentityServer有一定的了解,并且能够进行IdentityServer的集成,如果你还不会集成IdentityServer的话还是先看看我的这篇Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)文章吧。里面有一步一步的集成IdentityServer的实例。 好了,废话说完了,那就让我们开始进入今天的主题吧!Ocelot认证与授权。 概念表述 认证 为了验证ReRoutes并随后使用Ocelot的任何基于声明的功能,例如授权或使用令牌中的值修改请求。 用户必须像往常一样在他们的Startup.cs中注册认证服务,惟一的不同是他们需要给每个认证注册提供一个方案,例如 public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "OcelotKey"; services.AddAuthentication() .AddJwtBearer(authenticationProviderKey, x => { }); } 在此示例中,OcelotKey是此提供程序已注册的方案。然后我们将其映射到配置中的ReRoute,例如 "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, "AuthenticationOptions": { "AuthenticationProviderKey": "OcelotKey", "AllowedScopes": [] } } ] 当Ocelot运行时,它将查看此ReRoutes中 AuthenticationOptions节点下面的AuthenticationProviderKey并检查是否有使用给定密钥注册的身份验证提供程序。如果没有,那么Ocelot不会启动,如果有的话ReRoute将在执行时使用该提供者。 如果对ReRoute进行了身份验证,则Ocelot将在执行身份验证中间件时调用与其关联的认证方案。如果请求失败,则认证Ocelot返回http的状态代码为401即未授权状态。 JWT令牌 如果您想使用JWT令牌进行身份验证,可能来自OAuth之类的提供程序,您可以正常注册您的身份验证中间件,例如 public void ConfigureServices(IServiceCollection services) { var authenticationProviderKey = "OcelotKey"; services.AddAuthentication() .AddJwtBearer(authenticationProviderKey, x => { x.Authority = "test"; x.Audience = "test"; }); services.AddOcelot(); } 然后将身份验证提供程序密钥映射到配置中的ReRoute,例如 "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, "AuthenticationOptions": { "AuthenticationProviderKey": "OcelotKey", "AllowedScopes": [] } } ] Identity Server Bearer Tokens认证 接下来上今天的主角了。identityServer认证方式。为了使用IdentityServer承载令牌,请按照惯例在ConfigureServices 中使用方案(密钥)注册您的IdentityServer服务。 如果您不明白如何操作,请访问IdentityServer文档。或者查看我的这篇Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)文章。 var authenticationProviderKey = "OcelotKey"; var identityServerOptions = new IdentityServerOptions(); Configuration.Bind("IdentityServerOptions", identityServerOptions); services.AddAuthentication(identityServerOptions.IdentityScheme) .AddIdentityServerAuthentication(authenticationProviderKey, options => { options.RequireHttpsMetadata = false; //是否启用https options.Authority = $"http://{identityServerOptions.ServerIP}:{identityServerOptions.ServerPort}";//配置授权认证的地址 options.ApiName = identityServerOptions.ResourceName; //资源名称,跟认证服务中注册的资源列表名称中的apiResource一致 options.SupportedTokens = SupportedTokens.Both; } ); services.AddOcelot()//注入Ocelot服务 .AddConsul(); 然后将身份验证提供程序密钥映射到配置中的ReRoute,例如 "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, "AuthenticationOptions": { "AuthenticationProviderKey": "OcelotKey", "AllowedScopes": [] } } ] 允许访问的范围(Allowed Scopes) 如果将范围添加到AllowedScopes,Ocelot将获得类型范围的所有用户声明(从令牌中),并确保用户具有列表中的所有范围。 这是一种基于范围限制对ReRoute访问的方式。(我也没用过这种方式,感觉有点类似IdentityServer Scope的概念) 实例演示集成IdentityServer 新建一个OcelotDemo.Auth asp.net core web api项目 项目进行IdentityServer服务端相关的配置,这里为了演示的方便采用硬编码的方式进行的配置。具体配置可以参考Asp.NetCoreWebApi图片上传接口(二)集成IdentityServer4授权访问(附源码)这篇文章 在网关项目OcelotDemo中添加Nuget包 Install-Package IdentityServer4.AccessTokenValidation 在OcelotDemo项目中的Startup.cs中加入identityServer验证,如下所示: var authenticationProviderKey = "OcelotKey"; var identityServerOptions = new IdentityServerOptions(); Configuration.Bind("IdentityServerOptions", identityServerOptions); services.AddAuthentication(identityServerOptions.IdentityScheme) .AddIdentityServerAuthentication(authenticationProviderKey, options => { options.RequireHttpsMetadata = false; //是否启用https options.Authority = $"http://{identityServerOptions.ServerIP}:{identityServerOptions.ServerPort}";//配置授权认证的地址 options.ApiName = identityServerOptions.ResourceName; //资源名称,跟认证服务中注册的资源列表名称中的apiResource一致 options.SupportedTokens = SupportedTokens.Both; } ); services.AddOcelot()//注入Ocelot服务 .AddConsul(); 在ocelot.json中需要加入验证的ReRoute中,修改为如下的配置代码: "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, "AuthenticationOptions": { "AuthenticationProviderKey": "OcelotKey", "AllowedScopes": [] } } ] 打开PostMan测试一下代码吧,首先访问一下http://localhost:1000/values 这时候返回的结果是401未授权的状态,如下图所示: 然后访问我们上面新建的IdentityServer服务器并获取Token。如下图所示配置对应的参数进行获取: 然后使用我们获取到的access_token进行Ocelot网关接口的访问,如下所示进行配置: 可以看到结果返回了200代码,并且结果在Good以及Order之间进行切换。因为Ocelot.json文件中对路由进行了RoundRobin的负载均衡的策略。 授权 Ocelot支持基于声明的授权,该授权在身份验证后运行。这意味着如果您有要授权的Url,则可以将以下内容添加到ReRoute配置中。 "RouteClaimsRequirement": { "UserType": "registered" } 在此示例中,当调用授权中间件时,Ocelot将检查用户是否具有声明类型UserType以及是否已注册该声明的值。如果不是,则用户将不被授权,并且将响应403禁止访问的状态码。 当然这种授权的方式在大部分业务场景中都是不适用的,需要自己重写Ocelot的中间件才能实现。通过Ocelot中间件的重写你可以实现自己的授权逻辑,如果你还有限流的需求,比如说对每个客户端进行不同的限流策略。比方说,有三个客户端A,B,C。访问相同的URL,但是我们要控制A,每分钟只能访问10次,B每分钟能访问20次,而C不允许访问。针对这个场景Ocelot却没有相关的实现。但是我们可以通过重写Ocelot中间件来实现它。由于篇幅有限,所以今天就不进行介绍了。但是我会抽时间进行相关的实现,并分享给大家。 源码 本篇博文的源码已经上传到Github。可以进行参考。https://github.com/yilezhu/OcelotDemo 总结 本文先大致介绍一下Ocelot如何集成认证授权,然后通过实例进行了IdentityServer集成的演示,希望能对大家有一定的参考作用。当然文中也提到了,应对复杂的授权以及限流需要自行重写Ocelot中间件进行实现。具体如何实现呢,我会尽快分享给大家。同样的通过重写Ocelot中间件我们还可以把ocelot.json的配置信息存储到数据库并缓存到Redis中!最后,感谢大家的阅读! Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 Ocelot简易教程(四)之请求聚合以及服务发现 Ocelot简易教程(五)之集成IdentityServer认证以及授权 Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据 Ocelot简易教程(七)之配置文件数据库存储插件源码解析 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9807125.html
作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9703460.htmlNET Core项目发布的时候你有没有注意到这两个选项呢?有没有纠结过框架依赖与独立部署到底有什么区别呢?如果有的话那么这篇文章可以参考下!为什么要写这篇文章呢?因为今天同事问我框架依赖与独立部署到底应该选哪个呢?有什么区别。印象中只知道框架依赖发布后文件比独立部署要小很多,然后就是独立部署不占用net core的共享资源,而框架依赖需要与其他net core程序共享net core的一些资源。感觉很模糊,所以查了下资料整理如下,希望对大家有所帮助。 依赖框架的部署 (FDD) 定义 框架依赖的部署:顾名思义,依赖框架的部署 (FDD) 依赖目标系统上存在共享系统级版本的 .NET Core。 由于已存在 .NET Core,因此应用在 .NET Core 安装程序间也是可移植的。 应用仅包含其自己的代码和任何位于 .NET Core 库外的第三方依赖项。 FDD 包含可通过在命令行中使用 dotnet 实用程序启动的 .dll 文件。 例如,dotnet app.dll 就可以运行一个名为 app 的应用程序。对于 FDD,仅部署应用程序和第三方依赖项。 不需要部署 .NET Core,因为应用将使用目标系统上存在的 .NET Core 版本。 这是定目标到 .NET Core 的 .NET Core 和 ASP.NET Core 应用程序的默认部署模型。 优点 不需要提前定义 .NET Core 应用将在其上运行的目标操作系统。 因为无论什么操作系统,.NET Core 的可执行文件和库都是用通用的 PE 文件格式,因此,无论什么基础操作系统,.NET Core 都可执行应用。 部署包很小。 只需部署应用及其依赖项,而无需部署 .NET Core 本身。 许多应用都可使用相同的 .NET Core 安装,从而降低了主机系统上磁盘空间和内存使用量。 缺点 仅当主机系统上已安装你设为目标的 .NET Core 版本或更高版本时,应用才能运行。 如果不了解将来版本,.NET Core 运行时和库可能发生更改。 在极少数情况下,这可能会更改应用的行为。 独立部署 (SCD) 定义 独立部署:与 FDD 不同,独立部署 (SCD) 不依赖目标系统上存在的共享组件。 所有组件(包括 .NET Core 库和 .NET Core 运行时)都包含在应用程序中,并且独立于其他 .NET Core 应用程序。 SCD 包括一个可执行文件(如 Windows 平台上名为 app 的应用程序的 app.exe),它是特定于平台的 .NET Core 主机的重命名版本,还包括一个 .dll 文件(如 app.dll),而它是实际的应用程序。对于独立部署,可以部署应用和所需的第三方依赖项以及生成应用所使用的 .NET Core 版本。 创建 SCD 不包括各种平台上的 .NET Core 本机依赖项,因此运行应用前这些依赖项必须已存在。 从 NET Core 2.1 SDK(版本 2.1.300)开始,.NET Core 支持修补程序版本前滚。 在创建独立部署时,.NET Core 工具会自动包含你的应用程序所指向的 .NET Core 版本的最新服务的运行时。 (最新服务的运行时包括安全修补程序和其他 bug 修复程序。)服务的运行时不需要存在于你的生成系统上;它会从 NuGet.org 自动下载。FDD 和 SCD 部署使用单独的主机可执行文件,使你可以使用发布者签名为 SCD 签署主机可执行文件。 优点 可以对与应用一起部署的 .NET Core 版本具有单独的控制权 请放心,目标系统可以运行你的 .NET Core 应用,因为你提供的是应用将在其上运行的 .NET Core 版本 缺点 由于 .NET Core 包含在部署包中,因此必须提前选择为其生成部署包的目标平台 部署包相对较大,因为需要将 .NET Core 和应用及其第三方依赖项包括在内。 从.NET Core 2.0 开始,可以通过使用 .NET Core 全球化固定模式在 Linux 系统上减少大约 28 MB 的部署大小。 通常,Linux 上的 .NET Core 依赖于 ICU 库来实现全球化支持。 在固定模式下,库不包含在部署中,并且所有区域性的行为均类似于固定区域性。 向系统部署大量独立的 .NET Core 应用可能会使用大量磁盘空间,因为每个应用都会复制 .NET Core 文件 实例演示 .NET Core 应用的部署发布 上面已经说了,可以将 .NET Core 应用程序部署为依赖框架的部署或独立部署,前者包含应用程序二进制文件,但依赖目标系统上存在的 .NET Core,而后者同时包含应用程序和 .NET Core 二进制文件。 不包含第三方依赖的框架依赖的部署 为项目创建一个目录,并将其设为当前目录 在命令行中,键入 dotnet new console 以创建新的 C# 控制台项目 在编辑器中打开 Program.cs 文件,然后使用下列代码替换自动生成的代码。 它会提示用户输入文本,并显示用户输入的个别词。 它使用正则表达式 \w+ 来将输入文本中的词分开。 using System; using System.Text.RegularExpressions; namespace Applications.ConsoleApps { public class ConsoleParser { public static void Main() { Console.WriteLine("Enter any text, followed by <Enter>:\n"); String s = Console.ReadLine(); ShowWords(s); Console.Write("\nPress any key to continue... "); Console.ReadKey(); } private static void ShowWords(String s) { String pattern = @"\w+"; var matches = Regex.Matches(s, pattern); if (matches.Count == 0) { Console.WriteLine("\nNo words were identified in your input."); } else { Console.WriteLine($"\nThere are {matches.Count} words in your string:"); for (int ctr = 0; ctr < matches.Count; ctr++) { Console.WriteLine($" #{ctr,2}: '{matches[ctr].Value}' at position {matches[ctr].Index}"); } } } } } 运行 dotnet restore(请参阅注释)命令,还原项目中指定的依赖项。 使用 dotnet build命令生成应用程序,或使用 dotnet run命令生成并运行应用程序。 完成程序调试和测试后,使用下列命令创建部署 dotnet publish -f netcoreapp2.1 -c Release 这将创建一个应用的发行版(而不是调试版)。 生成的文件位于名为“publish”的目录中,该目录位于项目的 bin 目录的子目录中。 与应用程序的文件一起,发布过程将发出包含应用调试信息的程序数据库 (.pdb) 文件。 该文件主要用于调试异常。 可以选择不将其与应用程序的文件一起分布。 但是,如果要调试应用的发布版本,则应保存该文件。 可以采用任何喜欢的方式部署完整的应用程序文件集。 例如,可以使用简单的 copy 命令将其打包为 Zip 文件,或者使用选择的安装包进行部署。 安装成功后,用户可通过使用 dotnet 命令或提供应用程序文件名(如 dotnet fdd.dll)来执行应用程序。除应用程序二进制文件外,安装程序还应捆绑共享框架安装程序,或在安装应用程序的过程中将其作为先决条件进行检查。 安装共享框架需要管理员/根访问权限。 包含第三方依赖项的依赖框架的部署 要使用一个或多个第三方依赖项来部署依赖框架的部署,需要这些依赖项都可供项目使用。 在运行 dotnet restore命令之前,还需执行额外两个步骤: 向 csproj 文件的 部分添加对所需第三方库的引用。 以下 部分包含 Json.NET 的依赖项(作为第三方库): <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> </ItemGroup> 如果尚未安装,请下载包含第三方依赖项的 NuGet 包。 若要下载该包,请在添加依赖项后执行 dotnet restore命令。 因为依赖项在发布时已从本地 NuGet 缓存解析出来,因此它一定适用于你的系统。 请注意,如果依赖框架的部署具有第三方依赖项,则其可移植性只与第三方依赖项相同。 例如,如果某个第三方库只支持 macOS,该应用将无法移植到 Windows 系统。 当第三方依赖项本身取决于本机代码时,也可能发生此情况。 Kestrel 服务器就是一个很好的示例,它需要 libuv 的本机依赖项。 当为具有此类第三方依赖项的应用程序创建 FDD 时,已发布的输出会针对每个本机依赖项支持(存在于 NuGet 包中)的运行时标识符 (RID) 包含一个文件夹。 不包含第三方依赖项的独立部署 部署没有第三方依赖项的独立部署包括创建项目、修改 csproj 文件、生成、测试以及发布应用。 一个用 C# 编写的简单示例可说明此过程。 该示例演示如何使用命令行中的 dotnet 实用工具创建独立部署。 为项目创建一个目录,并将其设为当前目录。 在命令栏行中,键入 dotnet new console,在该目录中创建新的 C# 控制台项目 在编辑器中打开 Program.cs 文件,然后使用下列代码替换自动生成的代码。 它会提示用户输入文本,并显示用户输入的个别词。 它使用正则表达式 \w+ 来将输入文本中的词分开。 using System; using System.Text.RegularExpressions; namespace Applications.ConsoleApps { public class ConsoleParser { public static void Main() { Console.WriteLine("Enter any text, followed by <Enter>:\n"); String s = Console.ReadLine(); ShowWords(s); Console.Write("\nPress any key to continue... "); Console.ReadKey(); } private static void ShowWords(String s) { String pattern = @"\w+"; var matches = Regex.Matches(s, pattern); if (matches.Count == 0) { Console.WriteLine("\nNo words were identified in your input."); } else { Console.WriteLine($"\nThere are {matches.Count} words in your string:"); for (int ctr = 0; ctr < matches.Count; ctr++) { Console.WriteLine($" #{ctr,2}: '{matches[ctr].Value}' at position {matches[ctr].Index}"); } } } } } 在 csproj 文件(该文件用于定义应用的目标平台)的 部分中创建 标记,然后指定每个目标平台的运行时标识符 (RID)。 请注意,还需要添加分号来分隔 RID。 请查看运行时标识符目录,获取运行时标识符列表。例如,以下 部分表明应用在 64 位 Windows 10 操作系统和 64 位 OS X 10.11 版本的操作系统上运行。 <PropertyGroup> <RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers> </PropertyGroup> 请注意, 元素可能出现在 csproj 文件的任何 中。 本节后面部分将显示完整的示例 csproj 文件。 运行 dotnet restore命令,还原项目中指定的依赖项。 运行 dotnet restore(请参阅注释)命令,还原项目中指定的依赖项。特别是如果应用面向 Linux,则可以通过利用全球化固定模式来减小部署的总规模。 全球化固定模式适用于不具有全局意识且可以使用固定区域性的格式约定、大小写约定以及字符串比较和排序顺序的应用程序。要启用固定模式,右键单击“解决方案资源管理器”中的项目(不是解决方案),然后选择“编辑 SCD.csproj”。 然后将以下突出显示的行添加到文件中: <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.1</TargetFramework> </PropertyGroup> <ItemGroup> <RuntimeHostConfigurationOption Include="System.Globalization.Invariant" Value="true" /> </ItemGroup> </Project> 在命令行中,使用 dotnet run 生成命令。 调试并测试程序后,为应用的每个目标平台创建要与应用一起部署的文件。同时对两个目标平台使用 dotnet publish 命令,如下所示: dotnet publish -c Release -r win10-x64 dotnet publish -c Release -r osx.10.11-x64 这将为每个目标平台创建一个应用的发行版(而不是调试版)。 生成的文件位于名为“发布”的子目录中,该子目录位于项目的 .binReleasenetcoreapp2.1 子目录的子目录中。 请注意,每个子目录中都包含完整的启动应用所需的文件集(既有应用文件,也有所有 .NET Core 文件)。 与应用程序的文件一样,发布过程将生成包含应用调试信息的程序数据库 (.pdb) 文件。 该文件主要用于调试异常。 可以选择不使用应用程序文件打包该文件。 但是,如果要调试应用的发布版本,则应保存该文件。可按照任何喜欢的方式部署已发布的文件。 例如,可以使用简单的 copy 命令将其打包为 Zip 文件,或者使用选择的安装包进行部署。下面是此项目完整的 csproj 文件。 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.1</TargetFramework> <RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers> </PropertyGroup> </Project> 包含第三方依赖项的独立部署 部署包含一个或多个第三方依赖项的独立部署包括添加依赖项。 在运行 dotnet restore命令之前,还需执行额外两个步骤: 将对任何第三方库的引用添加到 csproj 文件的 部分。 以下 部分使用 Json.NET 作为第三方库。 <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> </ItemGroup> 如果尚未安装,请将包含第三方依赖项的 NuGet 包下载到系统。 若要使依赖项对应用适用,请在添加依赖项后执行 dotnet restore命令。 因为依赖项在发布时已从本地 NuGet 缓存解析出来,因此它一定适用于你的系统。下面是此项目的完整 csproj 文件: <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.1</TargetFramework> <RuntimeIdentifiers>win10-x64;osx.10.11-x64</RuntimeIdentifiers> </PropertyGroup> <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="10.0.2" /> </ItemGroup> </Project> 部署应用程序时,应用中使用的任何第三方依赖项也包含在应用程序文件中。 运行应用的系统上不需要第三方库。请注意,可以只将具有一个第三方库的独立部署部署到该库支持的平台。 这与依赖框架的部署中具有本机依赖项和第三方依赖项相似,其中的本机依赖项必须与部署应用的平台兼容。 备注:从 .NET Core 2.0 开始,无需运行 dotnet restore,因为它由所有需要还原的命令隐式运行,如 dotnet new、dotnet build 和 dotnet run。 总结 本文首先介绍了框架依赖与独立部署的概念,然后分别介绍了框架依赖与独立部署的优缺点让大家加深理解!最后通过一个实例来讲述了如何进行框架依赖与独立部署。采用的实例使用的是控制台的方式进行的,当然你也可以使用vs进行发布。
Ocelot简易教程(四)之请求聚合以及服务发现 上篇文章给大家讲解了Ocelot的一些特性并对路由进行了详细的介绍,今天呢就大家一起来学习下Ocelot的请求聚合以及服务发现功能。希望能对大家有所帮助。 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9695639.html 请求聚合 Ocelot允许你声明聚合路由,这样你可以把多个正常的ReRoutes打包并映射到一个对象来对客户端的请求进行响应。比如,你请求订单信息,订单中又包含商品信息,这里就设计到两个微服务,一个是商品服务,一个是订单服务。如果不运用聚合路由的话,对于一个订单信息,客户端可能需要请求两次服务端。实际上这会造成服务端额外的开销。这时候有了聚合路由后,你只需要请求一次聚合路由,然后聚合路由会合并订单跟商品的结果都一个对象中,并把这个对象响应给客户端。使用Ocelot的此特性可以让你很容易的实现前后端分离的架构。为了实现Ocelot的请求功能,你需要在ocelot.json中进行如下的配置。这里我们指定了了两个正常的ReRoutes,然后给每个ReRoute设置一个Key属性。最后我们再Aggregates节点中的ReRouteKeys属性中加入我们刚刚指定的两个Key从而组成了两个ReRoutes的聚合。当然我们还需要设置UpstreamPathTemplate匹配上游的用户请求,它的工作方式与正常的ReRoute类似。 注意:不要把Aggregates中UpstreamPathTemplate设置的跟ReRoutes中的UpstreamPathTemplate设置成一样。 下面我们先上个实例例子先!演示代码已经同步更新Github上。有兴趣的朋友可以查看源码:https://github.com/yilezhu/OcelotDemo 在开始实例前先把我们的ocelot Nuget包升级到最新的12.0.0版本,当然你也可以不进行升级。这里需要注意一下,如果你升级到12.0.0的版本的话,那么你config.AddOcelot()的用法会发生改变,需要传入参数config.AddOcelot(hostingContext.HostingEnvironment) 1.为了演示的需要这里我们新增一个类库项目,分别新建两个类,一个是商品Good类,一个是订单Order类(这里只是为了演示的需要,所以代码很简陋)如下所示: public class Goods { public int Id { get; set; } public string Content { get; set; } } public class Orders { public int Id { get; set; } public string Content { get; set; } } 接下来我们给OrderApi以及GoodApi分别新建一个控制器,并返回相应的实体。如下所示: //GoodApi项目中 [Route("api/[controller]")] [ApiController] public class GoodController : ControllerBase { // GET api/Good/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { var item = new Goods { Id = id, Content = $"{id}的关联的商品明细", }; return JsonConvert.SerializeObject(item); } } //OrderApi项目中 [Route("api/[controller]")] [ApiController] public class OrderController : ControllerBase { // GET api/Order/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { var item = new Orders { Id=id, Content=$"{id}的订单明细", }; return JsonConvert.SerializeObject(item); } } 接下来我们分别在ocelot.good.json以及ocelot.order.json中新增一个路由,并给出Keys.如下所示: 这里注意,跟上篇文章中的路由不同的是,这里多了一个Key属性。 //ocelot.good.json { "DownstreamPathTemplate": "/api/Good/{id}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 } ], "UpstreamPathTemplate": "/good/{id}", "UpstreamHttpMethod": [ "Get", "Post" ], "Key": "Good", "Priority": 2 } //ocelot.order.json { "DownstreamPathTemplate": "/api/Order/{id}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/order/{id}", "UpstreamHttpMethod": [ "Get", "Post" ], "Key": "Order", "Priority": 2 } 在ocelot.all.json中加入聚合配置,如下所示: "Aggregates": [ { "ReRouteKeys": [ "Good", "Order" ], "UpstreamPathTemplate": "/GetOrderDetail/{id}" } ] 注意:这里Aggregates跟ReRoutes同级,ReRouteKeys中填写的数组就是上面步骤3中设置的Key属性对应的值。 我们分别运行起来三个项目,然后访问接口地址:http://localhost:1000/GetOrderDetail/1 会得到如下的聚合响应内容: 格式化后代码如下: { "Good":{ "Id":1, "Content":"1的关联的商品明细" }, "Order":{ "Id":1, "Content":"1的订单明细" } } 眼尖的朋友可能已经猜到了。聚合路由返回的内容就是json串。json串由ReRouteKeys组成,每个Key的内容就是具体下游响应的内容了!实例代码已经同步更新到Github上,地址:https://github.com/yilezhu/OcelotDemo Ocelot将始终使用聚合请求返回内容类型application/json。还有需要注意的是聚合请求不会返回404请求。如果两个下游都返回404状态码的话,这里聚合后的响应也不会返回404,只会返回空的json串,拿上面的实例,如果两个下游都返回404的话,那么他的响应代码类似下面这样: { "Good": , "Order": } 如果下游服务返回404,则聚合将仅为该下游服务返回任何内容。即使所有下游都返回404,它也不会将聚合响应更改为404。 服务发现 Ocelot允许您指定服务发现提供程序,并将使用它来查找Ocelot将请求转发到的下游服务的主机和端口。目前,这仅在GlobalConfiguration部分中受支持,这意味着相同的服务发现提供程序将用于为ReRoute级别指定ServiceName的所有ReRoutes。 Consul 在使用Consul前你首先要做的就是安装在Ocelot中提供Consul支持的NuGet包Install-Package Ocelot.Provider.Consul然后将下面的内容添加在ConfigureServices方法中 services.AddOcelot()//注入Ocelot服务 .AddConsul(); GlobalConfiguration中需要加入以下内容。如果您未指定主机和端口,则将使用Consul默认值。 "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul" } 注意:如果你采用AddOcelot()这种方式来自动加载ocelot配置文件的方式,那么你需要新建一个ocelot.global.json文件,然后加入上面的配置:如下所示: { "GlobalConfiguration": { "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul" } } } 然后重新运行dotnet run命令会自动合并配置信息到Ocelot.json中,生成的对应内容如下: ```C# "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul", "Token": null, "ConfigurationKey": null, "PollingInterval": 0 } 这个上篇文章中已经进行了相关的介绍。 为了告诉Ocelot ReRoute是为其主机和端口使用服务发现提供程序,您必须在下游请求时添加要使用的ServiceName和负载均衡器。目前,Ocelot可以使用RoundRobin和LeastConnection算法。如果未指定负载均衡器,则Ocelot将不会对请求进行负载均衡。 { "DownstreamPathTemplate": "/api/posts/{postId}", "DownstreamScheme": "https", "UpstreamPathTemplate": "/posts/{postId}", "UpstreamHttpMethod": [ "Put" ], "ServiceName": "product", "LoadBalancerOptions": { "Type": "LeastConnection" }, } 设置此项后,Ocelot将从服务发现提供程序中查找下游主机和端口,并跨任何可用服务进行负载平衡请求。 动态路由 作者的想法是在使用服务发现提供程序时启用动态路由。在此模式下,Ocelot将使用上游路径的第一个段来与服务发现提供程序一起查找下游服务。 例如,使用https://api.yilezhu.cn/product/products 等网址调用ocelot 。Ocelot将采用产品路径的第一部分product,并将其用作在Consul中查找服务的Key。如果consul返回一个服务,Ocelot将使用从consul返回的主机和端口以及剩余路径段组合后的Url来进行请求的响应。,如:http:// hostfromconsul:portfromconsul/products。Ocelot将正常向下游URL转发查询字符串。即query 要启用动态路由,您需要在配置中保留0个ReRoutes。目前您无法混合动态和配置ReRoutes。除此之外,您还需要指定上面概述的Service Discovery提供程序详细信息和下游http / https方案作为DownstreamScheme。 除此之外,您还可以设置RateLimitOptions,QoSOptions,LoadBalancerOptions和HttpHandlerOptions,DownstreamScheme(您可能希望在https上调用Ocelot,但可以通过http与私有服务进行通信),这些将应用于所有动态ReRoutes。 配置可能看起来像: { "ReRoutes": [], "Aggregates": [], "GlobalConfiguration": { "RequestIdKey": null, "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul", "Token": null, "ConfigurationKey": null }, "RateLimitOptions": { "ClientIdHeader": "ClientId", "QuotaExceededMessage": null, "RateLimitCounterPrefix": "ocelot", "DisableRateLimitHeaders": false, "HttpStatusCode": 429 }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "BaseUrl": null, "LoadBalancerOptions": { "Type": "LeastConnection", "Key": null, "Expiry": 0 }, "DownstreamScheme": "http", "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false } } } Ocelot还允许您设置DynamicReRoutes,允许您为每个下游服务设置速率限制规则。如果您有一个产品和搜索服务,并且您希望对另一个进行速率限制,则此功能非常有用。这方面的一个例子如下。 { "DynamicReRoutes": [ { "ServiceName": "product", "RateLimitRule": { "ClientWhitelist": [], "EnableRateLimiting": true, "Period": "1s", "PeriodTimespan": 1000.0, "Limit": 3 } } ], "GlobalConfiguration": { "RequestIdKey": null, "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8523, "Type": "Consul" }, "RateLimitOptions": { "ClientIdHeader": "ClientId", "QuotaExceededMessage": "", "RateLimitCounterPrefix": "", "DisableRateLimitHeaders": false, "HttpStatusCode": 428 } "DownstreamScheme": "http", } } 此配置意味着如果您在/product/上进入Ocelot请求,则动态路由将启动,并且ocelot将使用针对DynamicReRoutes部分中的产品服务的速率限制设置。 GitHub地址 https://github.com/yilezhu/OcelotDemo Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 Ocelot简易教程(四)之请求聚合以及服务发现 总结 本文接着上篇文章进行了Ocelot请求聚合功能以及服务发现功能的介绍,并且对Ocelot动态路由功能也进行了简单的阐述。对于请求聚合这块进行了相关实例代码的演示,并已经更新到Github上面了!希望能对大家有所帮助!
作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9664977.html 上篇《Ocelot简易教程(二)之快速开始2》教大家如何快速跑起来一个ocelot实例项目,也只是简单的对Ocelot进行了配置,这篇文章会给大家详细的介绍一下Ocelot的配置信息。希望能对大家深入使用Ocelot有所帮助。 上篇中也提到了,最简单的Ocelot如下面所示,只有简单的两个节点,一个是ReRoutes,另一个就是GlobalConfiguration关于这两个节点的作用,上篇也已经讲述了,这里再简单的讲下ReRoutes:告诉Ocelot如何处理上游的请求。GlobalConfiguration:顾名思义就是全局配置,此节点的配置允许覆盖ReRoutes里面的配置,你可以在这里进行通用的一些配置信息。 { "ReRoutes": [], "GlobalConfiguration": {} } 下面呢给出ReRoute 的所有的配置信息,当然在实际使用的时候你没有必要全部进行配置,只需要根据你项目的实际需要进行相关的配置就可以了。 "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}",//下游路由模板 "UpstreamPathTemplate": "/good/{everything}",//上游路由模板 "UpstreamHttpMethod": [ "Get", "Post" ],//上游请求方法 "AddHeadersToRequest": {}, "UpstreamHeaderTransform": {}, "DownstreamHeaderTransform": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": null, "FileCacheOptions": { "TtlSeconds": 0, "Region": null }, "ReRouteIsCaseSensitive": false, "ServiceName": null, "DownstreamScheme": "http", "QoSOptions": {//Qos相关配置 "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancerOptions": {//负载均衡相关选项 "Type": "RoundRobin", "Key": null, "Expiry": 0 }, "RateLimitOptions": {//限流相关配置 "ClientWhitelist": [], "EnableRateLimiting": false, "Period": null, "PeriodTimespan": 0.0, "Limit": 0 }, "AuthenticationOptions": {//认证相关选项 "AuthenticationProviderKey": null, "AllowedScopes": [] }, "HttpHandlerOptions": {//HttpHandler相关的配置 "AllowAutoRedirect": false,//是否对下游重定向进行响应 "UseCookieContainer": false,//是否启动CookieContainer储存cookies "UseTracing": false, "UseProxy": true }, "DownstreamHostAndPorts": [//下游端口及host { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamHost": null,//上游Host "Key": null, "DelegatingHandlers": [], "Priority": 1, "Timeout": 0, "DangerousAcceptAnyServerCertificateValidator": false } 当然上面的配置项我就不一一的进行介绍,因为很多配置相信大家根据意思都能知道个大概了。我只会对比较常用的配置做下介绍。而且在接下来的文章中对对每个节点进行单独的详细的介绍。在介绍之前呢先看Ocelot的几个特性。 Ocelot特性介绍 合并配置文件 这个特性允许用户创建多个配置文件来方便的对大型项目进行配置。试想一下,如果你的项目有几十个路由规则需要配置的话,那么在一个配置文件进行配置应该很痛苦吧,有了这个特性后,你就可以创建多个配置文件。Ocelot会自动合并他们。在加载配置文件的时候 你可以通过下面的方式来调用AddOcelot()方法来替换直接加载某个配置的写法 如:AddJsonFile(“ocelot.json”) .ConfigureAppConfiguration((hostingContext, config) => { config .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddOcelot() .AddEnvironmentVariables(); }) 在这种情况下,Ocelot会寻找所有匹配了 (?i)ocelot.([a-zA-Z0-9]*).json 的文件,然后合并他们。如何你要设置GlobalConfiguration 属性,那么你需要建立一个ocelot.global.json 的文件来进行全局的配置。 这里上一个例子吧!可以方便大家的理解。 新建一个ocelot.good.json文件,并加入下面的配置: { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/good/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" } } ] } 然后再新建一个ocelot.order.json文件,并加入下面的配置: { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/order/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" } } ] } 最后新建一个ocelot.all.json文件,并把上篇文章中的路由拷贝到里面: { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" } } ], "GlobalConfiguration": { } } 然后修改下,Program.cs文件中的代码如下: public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { config .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddOcelot() .AddEnvironmentVariables(); }) .UseUrls("http://localhost:1000") .UseStartup<Startup>(); 这里最重要的代码就是config.AddOcelot()了。这段代码就会按照上面的规则查找所有符合条件的文件并合并路由。合并后的代码如下: { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "AddHeadersToRequest": {}, "UpstreamHeaderTransform": {}, "DownstreamHeaderTransform": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": null, "FileCacheOptions": { "TtlSeconds": 0, "Region": null }, "ReRouteIsCaseSensitive": false, "ServiceName": null, "DownstreamScheme": "http", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancerOptions": { "Type": "RoundRobin", "Key": null, "Expiry": 0 }, "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": false, "Period": null, "PeriodTimespan": 0.0, "Limit": 0 }, "AuthenticationOptions": { "AuthenticationProviderKey": null, "AllowedScopes": [] }, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false, "UseProxy": true }, "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamHost": null, "Key": null, "DelegatingHandlers": [], "Priority": 1, "Timeout": 0, "DangerousAcceptAnyServerCertificateValidator": false }, { "DownstreamPathTemplate": "/api/{everything}", "UpstreamPathTemplate": "/good/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "AddHeadersToRequest": {}, "UpstreamHeaderTransform": {}, "DownstreamHeaderTransform": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": null, "FileCacheOptions": { "TtlSeconds": 0, "Region": null }, "ReRouteIsCaseSensitive": false, "ServiceName": null, "DownstreamScheme": "http", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancerOptions": { "Type": "RoundRobin", "Key": null, "Expiry": 0 }, "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": false, "Period": null, "PeriodTimespan": 0.0, "Limit": 0 }, "AuthenticationOptions": { "AuthenticationProviderKey": null, "AllowedScopes": [] }, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false, "UseProxy": true }, "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamHost": null, "Key": null, "DelegatingHandlers": [], "Priority": 1, "Timeout": 0, "DangerousAcceptAnyServerCertificateValidator": false }, { "DownstreamPathTemplate": "/api/{everything}", "UpstreamPathTemplate": "/order/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "AddHeadersToRequest": {}, "UpstreamHeaderTransform": {}, "DownstreamHeaderTransform": {}, "AddClaimsToRequest": {}, "RouteClaimsRequirement": {}, "AddQueriesToRequest": {}, "RequestIdKey": null, "FileCacheOptions": { "TtlSeconds": 0, "Region": null }, "ReRouteIsCaseSensitive": false, "ServiceName": null, "DownstreamScheme": "http", "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "LoadBalancerOptions": { "Type": "RoundRobin", "Key": null, "Expiry": 0 }, "RateLimitOptions": { "ClientWhitelist": [], "EnableRateLimiting": false, "Period": null, "PeriodTimespan": 0.0, "Limit": 0 }, "AuthenticationOptions": { "AuthenticationProviderKey": null, "AllowedScopes": [] }, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false, "UseProxy": true }, "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamHost": null, "Key": null, "DelegatingHandlers": [], "Priority": 1, "Timeout": 0, "DangerousAcceptAnyServerCertificateValidator": false } ], "DynamicReRoutes": [], "Aggregates": [], "GlobalConfiguration": { "RequestIdKey": null, "ServiceDiscoveryProvider": { "Host": null, "Port": 0, "Type": null, "Token": null, "ConfigurationKey": null, "PollingInterval": 0 }, "RateLimitOptions": { "ClientIdHeader": "ClientId", "QuotaExceededMessage": null, "RateLimitCounterPrefix": "ocelot", "DisableRateLimitHeaders": false, "HttpStatusCode": 429 }, "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 0, "DurationOfBreak": 0, "TimeoutValue": 0 }, "BaseUrl": null, "LoadBalancerOptions": { "Type": null, "Key": null, "Expiry": 0 }, "DownstreamScheme": null, "HttpHandlerOptions": { "AllowAutoRedirect": false, "UseCookieContainer": false, "UseTracing": false, "UseProxy": true } } } Ocelot的合并方式是先对满足格式的文件遍历查找,然后循环加载他们,并提取所有的ReRoutes以及AggregateReRoutes 的数据。如果发现ocelot.global.json ,则添加到GlobalConfiguration 中。然后Ocelto会将合并后的配置保存在ocelot.json的文件中,当Ocelot运行时会加载这个合并后的ocelot.json文件,从而加载了所有的配置。 注意:这里需要注意的是Ocelot在合并的过程中不会对内容进行验证,只有在最终合并的配置进行校验,所以如果发现问题的话,那么你需要检查最终生成的ocelot.json 是否出错了! 在consul中存储配置 这里你首先要做的就是安装Ocelot中提供的Consul的NuGet包,Nuget安装方式: Install-Package Ocelot.Provider.Consul 然后在注册服务时添加如下内容:Ocelot将会尝试在Consul KV存储并加载配置。 services .AddOcelot() .AddConsul() .AddConfigStoredInConsul(); 当然你还得把下面的配置添加到你的ocelot.json文件中。这里定义Ocelot如何查找Consul根并从Consul中加载并存储配置. "GlobalConfiguration": { "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 9500 } } 变化时重新加载配置文件 Ocelot支持在配置文件发生改变的时候重新加载json配置文件。在加载ocelot.json文件的时候按照下面进行配置,那么当你手动更新ocelot.json文件时,Ocelot将重新加载ocelot.json配置文件。 config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true); 配置Key 如果你使用Consul进行配置,你可能需要配置Key以便区分多个配置,为了指定Key,你需要在json配置文件中的ServiceDiscoveryProvider部分设置ConfigurationKey属性: "GlobalConfiguration": { "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 9500, "ConfigurationKey": "Oceolot_A" } } 在此实例中,Ocelot将会在Consul查找时使用Oceolot_A 作为配置的Key.如果没有设置ConfigurationKey 则Ocelot将使用字符串InternalConfiguration 作为此配置的Key 跟踪重定向和使用CookieContainer 在ReRoute配置中可以使用HttpHandlerOptions来设置HttpHandler行为: AllowAutoRedirect是一个值,指示请求是否应遵循重定向响应。如果请求应自动遵循来自下游资源的重定向响应,则将其设置为true; 否则是假的。默认值为false。 UseCookieContainer是一个值,指示处理程序是否使用CookieContainer属性存储服务器cookie并在发送请求时使用这些cookie。默认值为false。请注意,如果您使CookieContainer,则Ocelot会为每个下游服务缓存HttpClient。这意味着对该DownstreamService的所有请求将共享相同的cookie。 SSL 错误处理 如果你想忽略SSL 警告/错误,你可以在你的ReRoute 配置中加上如下配置: "DangerousAcceptAnyServerCertificateValidator": false 当然作者是不建议这样做的,最好的方式是创建你本地以及远程所信任的证书。 Ocelot路由详解 路由 Ocelot的最主要的功能是接收传入的http请求并将其转发到下游服务。 Ocelot使用ReRoute节点描述将一个请求路由到另一个请求。为了让路由在Ocelot中起作用,您需要在配置中设置ReRoute: { "ReRoutes": [ ] } 要配置ReRoute,您需要在ReRoutes json数组中至少添加一个: { "DownstreamPathTemplate": "/api/good/{goodId}",//下游路由模板 "DownstreamScheme": "http",//下游路由请求的方式 "DownstreamHostAndPorts": [//下游路由的Host以及端口 { "Host": "localhost", "Port": 1001, } ], "UpstreamPathTemplate": "/good/{goodId}",//上游路由请求的模板 "UpstreamHttpMethod": [ "Put", "Delete" ]//上游路由请求的方式 } DownstreamPathTemplate,DownstreamScheme和DownstreamHostAndPorts定义请求将转发到的URL。 DownstreamHostAndPorts是一个集合,用于定义您希望将请求转发到的任何下游服务的主机和端口。通常这只包含一个条目,但有时你希望对下游请求服务进行负载均衡,这个时候你就可以添加多个条目,并配合负载均衡选项进行相关的负载均衡设置。 UpstreamPathTemplate是Ocelot用于标识要用于给定请求的DownstreamPathTemplate对应的URL。使用UpstreamHttpMethod以便Ocelot可以区分具有不同HTTP谓词的请求到相同的URL。您可以设置特定的HTTP方法列表,也可以设置一个空列表以允许所有的。 在Ocelot中,您可以以{something}的形式将变量的占位符添加到模板中。占位符变量需要同时出现在DownstreamPathTemplate和UpstreamPathTemplate属性中。请求时Ocelot将尝试请求时进行替换。 你也可以像下面这样配置,捕获所有的路由: { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001, }, { "Host": "localhost", "Port": 1002, } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ] } 这个配置将会把路径+查询字符串统统转发到下游路由. 注意:默认的ReRouting的配置是不区分大小写的,如果需要修改此配置,可以通过下面进行配置: "ReRouteIsCaseSensitive": true 这意味着Ocelot将尝试将传入的上游URL与上游模板匹配时,区分大小写。 全部捕获 Ocelot的路由还支持捕获所有样式路由,用户可以指定他们想要匹配所有请求。 如果您设置如下所示的配置,则所有请求都将直接代理。占位符{url}名称不重要,任何名称都可以使用。 { "DownstreamPathTemplate": "/{url}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001, } ], "UpstreamPathTemplate": "/{url}", "UpstreamHttpMethod": [ "Get" ] } 上面配置的全部捕获的优先级低于任何其他法人ReRoute。如果您的配置中还有下面的ReRoute,那么Ocelot会在全部捕获之前匹配它。 { "DownstreamPathTemplate": "/", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001, } ], "UpstreamPathTemplate": "/", "UpstreamHttpMethod": [ "Get" ] } 上游主机 此功能允许您根据上游主机获得ReRoutes。这通过查看客户端使用的主机头,然后将其用作我们用于识别ReRoute的信息的一部分来工作。 要使用此功能,请在配置中添加以下内容。 { "DownstreamPathTemplate": "/", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001, } ], "UpstreamPathTemplate": "/", "UpstreamHttpMethod": [ "Get" ], "UpstreamHost": "yilezhu.cn" } 仅当主机标头值为yilezhu.cn时,才会匹配上面的ReRoute。 如果您没有在ReRoute上设置UpstreamHost,那么任何主机头都将与之匹配。这意味着如果你有两个相同的ReRoutes,除了UpstreamHost,其中一个为null而另一个不为null 那么Ocelot将支持已设置的那个。 优先级 你可以通过ocelot.json文件的ReRoutes节点中的Priorty属性来设置匹配上游HttpRequest的优先级顺序比如,下面两个路由: { "UpstreamPathTemplate": "/goods/{catchAll}" "Priority": 0 } 以及 { "UpstreamPathTemplate": "/goods/delete" "Priority": 1 } 上面两个路由中,如果向Ocelot发出的请求时/goods/delete格式的话,则Ocelot会优先匹配/goods /delete 的路由。 动态路由 作者的想法是在使用服务发现提供程序时启用动态路由,这样您就不必提供ReRoute的配置。我们会在服务发现那一章进行详细的介绍。 查询字符串 Ocelot允许您指定一个查询字符串作为DownstreamPathTemplate的一部分,如下例所示。 { "ReRoutes": [ { "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", "UpstreamHttpMethod": [ "Get" ], "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50110 } ] } ], "GlobalConfiguration": { } } 在此示例中,Ocelot将使用上游路径模板中{unitId}的值,并将其作为名为unitId的查询字符串参数添加到下游请求中! Ocelot还允许您将查询字符串参数放在UpstreamPathTemplate中,以便您可以将某些查询与某些服务匹配。 { "ReRoutes": [ { "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", "UpstreamHttpMethod": [ "Get" ], "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50110 } ] } ], "GlobalConfiguration": { } } 在此示例中,Ocelot将仅匹配具有匹配的url路径的请求,并且查询字符串以unitId = something开头。您可以在此之后进行其他查询,但必须以匹配参数开头。此外,Ocelot将交换查询字符串中的{unitId}参数,并在下游请求路径中使用它。 源码地址 当然是放上实例中的源码地址了:https://github.com/yilezhu/OcelotDemo Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 总结 本文主要是对Ocelot的新特性以及路由进行详细的介绍,这些介绍对你使用ocelot会有很大的帮助。下篇文章呢,我会对请求聚合以及服务发现以及动态路由进行记录,敬请期待!同时需要说明一点是,本文大部分内容是翻译自官方文档,当然中间穿插着自己在使用过程中一些理解,希望大家能够喜欢!
为什么这篇的标题叫“Ocelot简易教程(二)之快速开始2”呢,因为很多朋友跟我说上一篇“ Ocelot简易教程(二)之快速开始1”内容太少了,只是简单介绍Ocelot的一些简单配置,让Ocelot能跑起来!所以才有了这篇快速开始2.在这篇文章中,我会一步一步记录怎么跑起来一个Ocelot项目,并简单的介绍一下Ocelot怎么实现接口间的负载均衡!此篇文章的代码我会放在我的github上面。后续深入的记录Ocelot的使用的时候也会沿用这次的代码。本文作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9638417.html 一步一步开始 演示项目概述 这次的演示项目因为要实现Ocelot的负载均衡的功能,因此至少需要三个项目:Ocelot网关,OrderApi,GoodApi .名字随便取的,可能博主认为这样起名字比较顺口,没有其他特别的含义。 新建上面的三个asp.net core web api项目 相信看这篇文章的朋友至少应该懂asp.net core了吧,不然你也接触不到Ocelot的。所以,这里假设大家都会创建asp.net core web api(因为真的没什么技术含量)。创建后的项目接口如下图所示: 给OcelotDemo网关项目添加Ocelot包以及配置文件 首先给OcelotDemo添加Nuget包,可以右键“管理Nuget”包,然后搜索Ocelot添加,如下图所示: 也可以通过命令 Install-Package Ocelot进行安装。方式随你喜欢。 然后给OcelotDemo项目新建一个json文件,新建的方式就是,右键OcelotDemo项目,然后选择“添加”-》“新建项”,在弹出的窗口里面找到json文件,并写好文件的名字即可,(当然,也可以用快捷键 Ctrl+Shilt+A)如下图所示: 当然,你还需要右键你刚刚设置的ocelot.json文件,并设置“复制到输出目录”的属性为“始终复制”如下所示: 接下来,可以按照我写的非常简单的配置进行配置。 { "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" } } ], "GlobalConfiguration": { } } 上面配置,有两个节点,分别是ReRoutes数组,以及GlobalConfiguration。这里简单做一下阐述: ReRoutes:告诉Ocelot如何处理上游的请求。 DownstreamPathTemplate:下游的路由模板,即真实处理请求的路径模板 DownstreamScheme:请求的方式,如:http,htttps DownstreamHostAndPorts:下游的IP以及端口,可以有多个(如果使用负载均衡),方便实现负载均衡,当然你也可以使用服务发现,实现下游服务的自动注册与发现,这篇文章不会讲解。 UpstreamPathTemplate:上游请求的模板,即用户真实请求的链接 UpstreamHttpMethod:上游请求的http方法,是个数组,你可以写多个。 LoadBalancerOptions:负载均衡选项(DownstreamHostAndPorts有多个的时候才能看到效果),有三种方式 LeastConnection : 将请求发往最空闲的那个服务器 RoundRobin :轮流发送 NoLoadBalance :不启用负载均衡,总是发往第一个请求或者服务发现的那个服务器 GlobalConfiguration:顾名思义就是全局配置,此节点的配置允许覆盖ReRoutes里面的配置,你可以在这里进行通用的一些配置信息。 OcelotDemo中添加Ocelot支持。 首先在OcelotDemo项目的Program.cs中加载上一步我们添加的Ocelot的配置文件,如下所示: public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { config .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("Ocelot.json") .AddEnvironmentVariables(); }) .UseUrls("http://localhost:1000") .UseStartup<Startup>(); 然后,在Startup.cs中,添加Ocelot服务并启用Ocelot中间件,这里你需要在Startup.cs文件中引入下面两个命名空间: using Ocelot.DependencyInjection; using Ocelot.Middleware; 然后,分别在ConfigureServices中注册Ocelot服务以及Configure中启用Ocelot中间件: public void ConfigureServices(IServiceCollection services) { services.AddOcelot();//注入Ocelot服务 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseOcelot().Wait();//使用Ocelot中间件 app.UseMvc(); } 这时候,Ocelot的配置基本完成了,下面我们验证下Ocelot有没有正常工作,以及我们配置的负载均衡,有没有起作用吧。 测试Ocelot项目以及负载均衡有没有起作用 这里,首先分别对三个项目的ValuesController控制器做如下改造: OcelotDemo项目ValuesController中的Get方法改造如下: [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1 from Ocelot", "value2 from Ocelot" }; } OrderApi项目中的ValuesController中的Get方法改造如下: [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1 from Order Api", "value2 from Order Api" }; } GoodApi项目中的ValuesController中的Get方法改造如下: [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1 from Good Api", "value2 from Good Api" }; } 结合我们上面的配置,可以知道改造的目的就是为了如下的测试,我们做出如下假设: 为了验证上游模板/{everything} 对应下游模板/api/{everything} 我们请求的路径即为http://localhost:1000/Values 并且把GoodApi以及GoodApi响应路径分别设置为:http://localhost:1001/api/Values ,http://localhost:1003/api/Values 并得到正确的响应 为了验证Ocelot项目起作用,我们请求ocelotDemo项目的路径values,应该返回GoodApi,或者OrderApi项目中values控制器的值,而不是返回ocelotDemo中values控制器的值 为了验证Ocelot负载均衡起作用,我们连续访问ocelotDemo项目的路径values,则返回的结果应该是轮流返回GoodApi以及OrderApi的值。 下面我们利用 dotnet run命令分别启动三个项目,当然你也可以多项目启动。然后访问OcelotDemo项目的Url并访问Values控制器,完整路径“http://localhost:1000/Values”,看到 OcelotDemo的Url:http://localhost:1000 GoodApi的Url:http://localhost:1001 OrderApi的Url:http://localhost:1002 我们第一次访问http://localhost:1000/Values 得到如下结果: 说明我们的假设1以及假设2都是没问题的。也就是说我们的Ocelot已经在起作用了,而且根据上下游路由进行了映射。 接下来我们刷新下页面,得到如下结果: 说明我们的假设3即Ocelot的负载均衡也起作用了。当然,你可以多刷新几次,可以看到返回的结果在GoodApi与Order Api之间来回切换。因为我们的负载均衡策略就是轮询啊! 最后 这篇文章主要是为了让记录如何快速的开始使用Ocelot。关于Ocelot的配置很多都没有描述,包括限流以及熔断策略,以及多配置文件自动加载功能,等等。这个超出了快速开始的范围,当然这些高级的用法会在下面的系列文章中会一一记录。 总结 本篇文章只是记录了,如何快速的成功的使用ocelot,并进行了简单的配置,配置中又引入了一个负载均衡的策略,最后通过代码进行了相关的实现以及测试!关于其他的一些配置,我会在下面的文章中进行阐述。这里只是对上篇文章“Ocelot简易教程(二)之快速开始1”的补充!感谢大家的阅读!
Ocelot是为.net core量身定做的,目前是基于 netstandard2.0进行构建的。 .NET Core 2.1中如何使用呢? 安装NuGet package 使用nuget安装Ocelot及其依赖项。您需要创建一个netstandard2.0项目并将其Package安装到项目中。然后按照下面的“启动”和“ 配置”节点启动并运行。安装命令 Install-Package Ocelot 你可以通过下面的链接查看Ocelot的历史版本https://www.nuget.org/packages/Ocelot/ 目前最新版是10.0.4。最新版最近正在进行重构,更新比较频繁。 配置 以下配置是一个非常基础的Ocelot.json配置,他不会做任何事情,但却可以让ocelot正常运行。 { "ReRoutes": [], "GlobalConfiguration": { "BaseUrl": "https://api.yilezhu.cn" } } 这个配置里面最重要的是BaseUrl。Ocelot需要知道它正在运行的URL,以便执行Header查找和替换以及某些管理配置。设置此URL时,它应该是客户端将看到Ocelot运行的外部URL,例如,如果您正在运行容器,则Ocelot可能会在URL上运行http://123.12.1.1:6543但在其前面有类似nginx的响应在https://api.yilezhu.cn。在这种情况下,Ocelot基本网址应为https://api.yilezhu.cn。 如果由于某种原因你正在使用容器并且希望Ocelot在http://123.12.1.1:6543上响应客户端的请求, 那么你可以这样做但是如果要部署多个Ocelot,你可能希望在命令行中传递它某种脚本。希望您使用的任何调度程序都可以传递IP。 特别需要注意的是,这里的Ocelot.json配置文件需要在VS中右键修改为“始终复制”属性。 Program配置方法 官方文档是按照下面进行配置的。不过个人还是习惯在Sartup.cs文件中进行相关的配置。博主就先贴出官方文档给出的配置方法。然后在你的Program.cs你将按照如何代码进行配置。这里最主要的是AddOcelot() 添加 ocelot 服务), UseOcelot().Wait() (使用 Ocelot中间件). public class Program { public static void Main(string[] args) { new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { config .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { s.AddOcelot(); }) .ConfigureLogging((hostingContext, logging) => { //add your logging }) .UseIISIntegration() .Configure(app => { app.UseOcelot().Wait(); }) .Build() .Run(); } Startup配置方法 我个人也比较习惯在Startup.cs中进行配置,不习惯在Program.cs中配置。下面是我配置的一种方式,当然你也可以自由发挥。 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddOcelot(new ConfigurationBuilder() .AddJsonFile("ocelot.json") .Build()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public async void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } await app.UseOcelot(); app.UseMvc(); } 总结 今天只是给大家介绍Ocelot的非常非常简单地使用,可以说零配置,并介绍了官方的使用方法以及我平时的使用方式,只为了快速开始Ocelot,让项目能够跑起来。接下来我们会详细的介绍Ocelot的配置。
Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 Ocelot简易教程(四)之请求聚合以及服务发现 Ocelot简易教程(五)之集成IdentityServer认证以及授权 Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据 Ocelot简易教程(七)之配置文件数据库存储插件源码解析 Ocelot是为.net core量身定做的,目前是基于 netstandard2.0进行构建的。 .NET Core 2.1中如何使用呢? 安装NuGet package 使用nuget安装Ocelot及其依赖项。您需要创建一个netstandard2.0项目并将其Package安装到项目中。然后按照下面的“启动”和“ 配置”节点启动并运行。 安装命令 Install-Package Ocelot 你可以通过下面的链接查看Ocelot的历史版本https://www.nuget.org/packages/Ocelot/ 目前最新版是10.0.4。最新版最近正在进行重构,更新比较频繁。 配置 以下配置是一个非常基础的Ocelot.json配置,他不会做任何事情,但却可以让ocelot正常运行。 { "ReRoutes": [], "GlobalConfiguration": { "BaseUrl": "https://api.yilezhu.cn" } } 这个配置里面最重要的是BaseUrl。Ocelot需要知道它正在运行的URL,以便执行Header查找和替换以及某些管理配置。设置此URL时,它应该是客户端将看到Ocelot运行的外部URL,例如,如果您正在运行容器,则Ocelot可能会在URL上运行http://123.12.1.1:6543但在其前面有类似nginx的响应在https://api.yilezhu.cn。在这种情况下,Ocelot基本网址应为https://api.yilezhu.cn。 如果由于某种原因你正在使用容器并且希望Ocelot在http://123.12.1.1:6543上响应客户端的请求, 那么你可以这样做但是如果要部署多个Ocelot,你可能希望在命令行中传递它某种脚本。希望您使用的任何调度程序都可以传递IP。 特别需要注意的是,这里的Ocelot.json配置文件需要在VS中右键修改为“始终复制”属性。 Program配置方法 官方文档是按照下面进行配置的。不过个人还是习惯在Sartup.cs文件中进行相关的配置。博主就先贴出官方文档给出的配置方法。 然后在你的Program.cs你将按照如何代码进行配置。这里最主要的是AddOcelot() 添加 ocelot 服务), UseOcelot().Wait() (使用 Ocelot中间件). public class Program { public static void Main(string[] args) { new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureAppConfiguration((hostingContext, config) => { config .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) .AddJsonFile("ocelot.json") .AddEnvironmentVariables(); }) .ConfigureServices(s => { s.AddOcelot(); }) .ConfigureLogging((hostingContext, logging) => { //add your logging }) .UseIISIntegration() .Configure(app => { app.UseOcelot().Wait(); }) .Build() .Run(); } Startup配置方法 我个人也比较习惯在Startup.cs中进行配置,不习惯在Program.cs中配置。下面是我配置的一种方式,当然你也可以自由发挥。 public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddOcelot(new ConfigurationBuilder() .AddJsonFile("ocelot.json") .Build()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public async void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } await app.UseOcelot(); app.UseMvc(); } 总结 今天只是给大家介绍Ocelot的非常非常简单地使用,可以说零配置,并介绍了官方的使用方法以及我平时的使用方式,只为了快速开始Ocelot,让项目能够跑起来。接下来我们会详细的介绍Ocelot的配置。 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者:yilezhu(依乐祝).NET Core实战项目交流群:637326624 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!另如需修改文章内容请与本人联系。微信账号:jkingzhu
作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9557375.html 简单的说Ocelot是一个用.NET Core实现并且开源的API网关技术。可能你又要问了,什么是API网关技术呢?Ocelot又有什么特别呢?我们又该如何集成到我们的asp.net core程序中呢?下面我会通过一些列通俗易懂的教程来为大家讲解。今天的这篇文档先给大家简述下什么是API网关技术,以及Ocelot是什么,一个Ocelot的整体架构。 API网关是什么? API网关是系统暴露在外部的一个访问入口。就像一个公司的门卫承担着寻址、限制进入、安全检查、位置引导、等等功能。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理等等。API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。 Ocelot在API网关实现上有什么优点呢? 首先,上面已经讲述了Ocelot是一个用.NET Core技术实现并且开源的API网关技术。除此之外还有什么优点呢?那就是它强大的功能以及使用上的简单了。它的功能包括了:路由、请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器、Service Fabric、Butterfly Tracing等的集成。而且这些功能都只需要简单的配置即可完成。目前,腾讯和微软是Ocelot在官网贴出来的客户。另外,附上Ocelot的开源地址:https://github.com/ThreeMammals/Ocelot Ocelot工作流程是怎样的呢? 实际上Ocelot就是一系列按特定顺序排列的中间件。Ocelot首先通过配置将HttpRequest对象保存到一个指定的状态直到它到达用来创建HttpRequestMessage对象并将创建的HttpRequestMessage对象发送到下游服务中的请求构造中间件。通过中间件来发出请求是Ocelot管道中做的最后一件事。它不会再调用下一个中间件。下游服务的响应会存储在每个请求 scoped repository中,并作为一个请求返回到Ocelot管道中。有一个中间件将HttpResponseMessage映射到HttpResponse对象并返回给客户端。接下来是你使用Ocelot是可能会使用的配置。 基本集成 用一台web service来host Ocelot,在这里有一个json配置文件,里面设置了所有对当前这个网关的配置。它会接收所有的客户端请求,并路由到对应的下游服务器进行处理,再将请求结果返回。而这个上下游请求的对应关系也被称之为路由。 集成 IdentityServer 当我们涉及到认证和鉴权的时候,我们可以跟Identity Server进行结合。当网关需要请求认证信息的时候会与Identity Server服务器进行交互来完成。 网关集群配置 只有一个网关是很危险的,也就是我们通常所讲的单点,只要它挂了,所有的服务全挂。这显然无法达到高可用,所以我们也可以部署多台Ocelot网关。当然这个时候在多台网关前,你还需要一台负载均衡器。 结合Consul服务发现 在Ocelot已经支持简单的负载功能,也就是当下游服务存在多个结点的时候,Ocelot能够承担起负载均衡的作用。但是它不提供健康检查,服务的注册也只能通过手动在配置文件里面添加完成。这不够灵活并且在一定程度下会有风险。这个时候我们就可以用Consul来做服务发现,它能与Ocelot完美结合。 结合Service Fabric 总结 本文首先介绍了API网关的概念,进而引出asp.net core中的一个开源的API网关技术Ocelot。并介绍了Ocelot的优点以及工作原理及架构图。接下来会详细介绍Ocelot如何通过简单地配置实现路由、请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器、Service Fabric、Butterfly Tracing等等功能。
作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9557375.html Ocelot简易教程目录 Ocelot简易教程(一)之Ocelot是什么 Ocelot简易教程(二)之快速开始1 Ocelot简易教程(二)之快速开始2 Ocelot简易教程(三)之主要特性及路由详解 Ocelot简易教程(四)之请求聚合以及服务发现 Ocelot简易教程(五)之集成IdentityServer认证以及授权 Ocelot简易教程(六)之重写配置文件存储方式并优化响应数据 Ocelot简易教程(七)之配置文件数据库存储插件源码解析 简单的说Ocelot是一个用.NET Core实现并且开源的API网关技术。 可能你又要问了,什么是API网关技术呢?Ocelot又有什么特别呢?我们又该如何集成到我们的asp.net core程序中呢? 下面我会通过一些列通俗易懂的教程来为大家讲解。今天的这篇文档先给大家简述下什么是API网关技术,以及Ocelot是什么,一个Ocelot的整体架构。 API网关是什么? API网关是系统暴露在外部的一个访问入口。就像一个公司的门卫承担着寻址、限制进入、安全检查、位置引导、等等功能。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理等等。 API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。 Ocelot在API网关实现上有什么优点呢? 首先,上面已经讲述了Ocelot是一个用.NET Core技术实现并且开源的API网关技术。除此之外还有什么优点呢?那就是它强大的功能以及使用上的简单了。它的功能包括了:路由、请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器、Service Fabric、Skywalking等的集成。而且这些功能都只需要简单的配置即可完成。 目前,腾讯和微软是Ocelot在官网贴出来的客户。 另外,附上Ocelot的开源地址:https://github.com/ThreeMammals/Ocelot Ocelot工作流程是怎样的呢? 实际上Ocelot就是一系列按特定顺序排列的中间件。 Ocelot首先通过配置将HttpRequest对象保存到一个指定的状态直到它到达用来创建HttpRequestMessage对象并将创建的HttpRequestMessage对象发送到下游服务中的请求构造中间件。通过中间件来发出请求是Ocelot管道中做的最后一件事。它不会再调用下一个中间件。下游服务的响应会存储在每个请求 scoped repository中,并作为一个请求返回到Ocelot管道中。有一个中间件将HttpResponseMessage映射到HttpResponse对象并返回给客户端。 接下来是你使用Ocelot是可能会使用的配置。 基本集成 用一台web service来host Ocelot,在这里有一个json配置文件,里面设置了所有对当前这个网关的配置。它会接收所有的客户端请求,并路由到对应的下游服务器进行处理,再将请求结果返回。而这个上下游请求的对应关系也被称之为路由。 集成 IdentityServer 当我们涉及到认证和鉴权的时候,我们可以跟Identity Server进行结合。当网关需要请求认证信息的时候会与Identity Server服务器进行交互来完成。 网关集群配置 只有一个网关是很危险的,也就是我们通常所讲的单点,只要它挂了,所有的服务全挂。这显然无法达到高可用,所以我们也可以部署多台Ocelot网关。当然这个时候在多台网关前,你还需要一台负载均衡器。 结合Consul服务发现 在Ocelot已经支持简单的负载功能,也就是当下游服务存在多个结点的时候,Ocelot能够承担起负载均衡的作用。但是它不提供健康检查,服务的注册也只能通过手动在配置文件里面添加完成。这不够灵活并且在一定程度下会有风险。这个时候我们就可以用Consul来做服务发现,它能与Ocelot完美结合。 结合Service Fabric 总结 本文首先介绍了API网关的概念,进而引出asp.net core中的一个开源的API网关技术Ocelot。并介绍了Ocelot的优点以及工作原理及架构图。接下来会详细介绍Ocelot如何通过简单地配置实现路由、请求聚合、服务发现、认证、鉴权、限流熔断、并内置了负载均衡器、Service Fabric、Skywalking等等功能。 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者:yilezhu(依乐祝).NET Core实战项目交流群:637326624 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!另如需修改文章内容请与本人联系。微信账号:jkingzhu
如何测量并报告ASP.NET Core Web API请求的响应时间 介绍 大家都知道性能是API的流行语。而相应时间则是API性能的一个重要并且可测量的参数。在本文中,我们将了解如何使用代码来测量API的响应时间,然后将响应时间数据返回到客户端。 作者:依乐祝原文地址:https://www.cnblogs.com/yilezhu/p/9520808.html 我们为什么需要测量响应时间 首先,让我们先花一点时间思考下为什么我们需要这么一个特性来测量API的响应时间。下面是编写代码来捕获响应时间的一些场景。 您需要为您的客户定义API的SLA(服务水平协议)。客户需要了解API响应的时间。响应时间数据可以帮助我们确定API的SLA。 管理层对报告应用程序的速度快慢感兴趣。您需要有数据来证实您的报告的声明。报告应用程序的性能并与利益相关者进行分享时值得的。 客户端需要具有API的响应时间的信息,以便它们可以跟踪在客户端和服务器上花费了多少时间。您可能在项目中也遇到过类似的请求,因此研究一种捕获API响应时间的方法是值得的。 在哪里添加测量代码? 让我们探索一些方法来捕获API的响应时间,主要集中在捕获API中花费的时间。我们的目标是计算从Asp.net Core运行时接收请求到处理响应并从服务器返回结果所经过的时间(以毫秒为单位)。 我们需要忽略哪些因素? 重要的是要理解这个讨论不包括花在N/W上的时间,以及在IIS和应用程序池启动中花费的时间。如果应用程序池未启动并运行,则第一个请求可能会影响API的总体响应时间。我们可以使用一个应用程序初始化模块,但这超出了本文的范围。 第一次尝试 捕获API响应时间的一种非常异想天开的方法是在开始和结束时向每个API方法添加如下代码,然后测量增量以计算响应时间,如下所示。 // GET api/values/5 [HttpGet] public IActionResult Get() { // Start the watch var watch = new Stopwatch(); watch.Start(); // Your actual Business logic // End the watch watch.Stop(); var responseTimeForCompleteRequest = watch.ElapsedMilliseconds; } 此代码应该能够计算操作所花费的时间。但由于以下原因,这似乎不是正确的方法。 如果API有很多操作,那么我们需要将这个代码添加到多个不利于可维护性的地方。 此代码仅测量在方法中花费的时间,它不测量在中间件,过滤器,控制器选择,Action选择,模型绑定等其他活动上花费的时间。 第二次尝试 让我们尝试通过将代码集中在一个地方来改进上面的代码,以便更容易维护。我们需要在执行方法之前和之后执行响应时间的计算代码。如果您使用过早期版本的Asp.net Web API,那么您将熟悉Filter的概念。过滤器允许您在请求处理管道中的特定阶段之前或之后运行代码。 我们将实现一个用于计算响应时间的过滤器,如下所示。我们将创建一个Filter并使用OnActionExecuting启动计时器,然后在方法OnActionExecuted中停止计时器,从而计算API的响应时间。 public class ResponseTimeActionFilter: IActionFilter { private const string ResponseTimeKey = "ResponseTimeKey"; public void OnActionExecuting(ActionExecutingContext context) { // Start the timer context.HttpContext.Items[ResponseTimeKey] = Stopwatch.StartNew(); } public void OnActionExecuted(ActionExecutedContext context) { Stopwatch stopwatch = (Stopwatch) context.HttpContext.Items[ResponseTimeKey]; // Calculate the time elapsed var timeElapsed = stopwatch.Elapsed; } } 此代码不是计算响应时间的可靠技术,因为它没有解决计算执行中间件,控制器选择,操作方法选择,模型绑定等所花费的时间的问题。过滤器管道在MVC选择Action后执行。因此,它实际上无法检测在其他Asp.net管道中花费的时间。 第三次尝试 我们将使用Asp.net Core中间件来计算API的响应时间 所以,什么是中间件呢? 基本上,中间件是处理请求/响应的软件组件。中间件被组装到应用程序管道中并在传入请求中提供服务。每个组件执行以下操作。 选择是否将请求传递给管道中的下一个组件。 可以在调用管道中的下一个组件之前和之后执行工作。 如果您在ASP.NET中使用过HTTPModules或HTTPHandler,那么您可以将中间件视为ASP.NET Core中的替代品。一些中间件的例子是 : MVC中间件 Authentication中间件 Static File Serving Caching CORS 等等 我们希望在请求进入ASP.NET Core管道后添加代码以启动计时器,并在管道处理响应后停止计时器。请求管道开始时的自定义中间件似乎是访问请求最早访问并在管道中执行最后一步之前进行访问的最佳方法。 我们将构建一个响应时间中间件,我们将其作为第一个中间件添加到请求管道中,以便我们可以在请求进入Asp.net Core管道后立即启动计时器。 ### 如何处理响应时间数据呢? 一旦我们捕获到响应时间数据,我们就可以通过以下方式来进行数据的处理。 将响应时间数据添加到报告数据库或分析解决方案。 将响应时间数据写入日志文件。 将响应时间数据传递到消息队列,该消息队列可以由另一个应用程序进一步处理以进行报告和分析。 使用响应头将响应时间信息发送到使用我们的Rest API的客户端应用程序。可能还有其他有用的方法来使用响应时间数据。您可以在评论区进行留言,并告诉我您是如何处理应用程序中的响应时间数据的。 ### 我们开始写代码吧 我们将按照下面的处理步骤来进行代码的编写。 计算API的响应时间数据 通过在响应头中传递数据将数据报告回客户端应用程序。 ResponseTimeMiddleware的完整代码片段如下所示: public class ResponseTimeMiddleware { // Name of the Response Header, Custom Headers starts with "X-" private const string RESPONSE_HEADER_RESPONSE_TIME = "X-Response-Time-ms"; // Handle to the next Middleware in the pipeline private readonly RequestDelegate _next; public ResponseTimeMiddleware(RequestDelegate next) { _next = next; } public Task InvokeAsync(HttpContext context) { // Start the Timer using Stopwatch var watch = new Stopwatch(); watch.Start(); context.Response.OnStarting(() => { // Stop the timer information and calculate the time watch.Stop(); var responseTimeForCompleteRequest = watch.ElapsedMilliseconds; // Add the Response time information in the Response headers. context.Response.Headers[RESPONSE_HEADER_RESPONSE_TIME] = responseTimeForCompleteRequest.ToString(); return Task.CompletedTask; }); // Call the next delegate/middleware in the pipeline return this._next(context); } } 代码说明 主要的代码是在InvokeAsync方法中,一旦请求进入到第一个中间件,我们使用秒表类来启动秒表,然后在处理请求完成后并且响应准备好返回给客户端的Response后停止秒表。OnStarting方法提供了编写自定义代码的机会,以便在将响应头发送到客户端之前添加要调用的委托中。最后,我们在自定义标题中添加响应时间信息。我们使用X-Response-Time-ms标头作为响应标头。作为惯例,自定义标题以X开头。 总结 在本文中,我们了解了如何利用ASP.NET中间件来管理跨领域问题,例如测量API的响应时间。使用中间件还有其他各种有用的用例,可以帮助重用代码并提高应用程序的可维护性。
# ASP.NET Core 2.1中基于角色的授权 授权是来描述用户能够做什么的过程。例如,只允许管理员用户可以在电脑上进行软件的安装以及卸载。而非管理员用户只能使用软件而不能进行软件的安装以及卸载。它是独立的而又与验证配合使用,需要身份验证机制。对于应用程序来说,首先需要进行身份验证,然后进行进行授权。 作者:依乐祝原文链接:https://www.cnblogs.com/yilezhu/p/9508267.html Identity是一个会员资格系统,它允许我们将登录功能添加到我们的应用程序中,身份可能属于一个或多个角色。例如,“User1”属于“Admin”角色,“User2”属于“HR”的角色。我们可以在我们的MVC或者Web API应用程序中的控制器上使用AuthorizeFilter特性来控制用户的访问。基于角色的授权可以检查登陆的用户是否有访问页面的权限。这里开发人员可以在他们的代码中加入角色。下面我们使用一个例子来进行说明,我们将创建三个角色,对应的我们将建立三个用户。代码如下: public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider) { .... .... app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); CreateRoles(serviceProvider).Wait(); } private async Task CreateRoles(IServiceProvider serviceProvider) { //initializing custom roles var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>(); var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>(); string[] roleNames = { "Admin", "User", "HR" }; IdentityResult roleResult; foreach (var roleName in roleNames) { var roleExist = await RoleManager.RoleExistsAsync(roleName); if (!roleExist) { //create the roles and seed them to the database: Question 1 roleResult = await RoleManager.CreateAsync(new IdentityRole(roleName)); } } IdentityUser user = await UserManager.FindByEmailAsync("jignesh@gmail.com"); if (user == null) { user = new IdentityUser() { UserName = "jignesh@gmail.com", Email = "jignesh@gmail.com", }; await UserManager.CreateAsync(user, "Test@123"); } await UserManager.AddToRoleAsync(user, "Admin"); IdentityUser user1 = await UserManager.FindByEmailAsync("tejas@gmail.com"); if (user1 == null) { user1 = new IdentityUser() { UserName = "tejas@gmail.com", Email = "tejas@gmail.com", }; await UserManager.CreateAsync(user1, "Test@123"); } await UserManager.AddToRoleAsync(user1, "User"); IdentityUser user2 = await UserManager.FindByEmailAsync("rakesh@gmail.com"); if (user2 == null) { user2 = new IdentityUser() { UserName = "rakesh@gmail.com", Email = "rakesh@gmail.com", }; await UserManager.CreateAsync(user2, "Test@123"); } await UserManager.AddToRoleAsync(user2, "HR"); } 我们可以使用Authorize属性的Roles属性指定有权访问所请求资源的角色。例如,以下代码允许分配了“Admin”角色用户进行访问的操作方法。 [Authorize(Roles = "Admin")] public IActionResult OnlyAdminAccess() { ViewData["role"] = "Admin"; return View("MyPage"); } 我们可以使用英文的逗号分割的角色列表来允许多个角色访问的方法。例如,在以下代码段中,操作方法只能由“Admin”或“User”角色的用户访问。 [Authorize(Roles = "Admin,User")] public IActionResult MultipleAccess() { ViewData["role"] = "Admin"; return View("MyPage"); } 我们也可以使用如下的代码来进行多角色的访问控制 [Authorize(Roles = "Admin")] [Authorize(Roles = "User")] public IActionResult MultipleAccess() { ViewData["role"] = "Admin"; return View("MyPage"); } 基于策略的角色检查 我们还可以创建基于策略的访问控制。我们可以使用授权服务进行策略的添加以及注册。在下面的代码中,我们创建了一个只允许具有“Admin”角色的用户才能进行访问的策略。 public void ConfigureServices(IServiceCollection services) { .... .... services.AddAuthorization(options => { options.AddPolicy("OnlyAdminAccess", policy => policy.RequireRole("Admin")); }); } 我们可以使用Authorize 特性的“Policy ”属性进行策略的应用 [Authorize(Policy = "OnlyAdminAccess")] public IActionResult PolicyExample() { ViewData["role"] = "Admin"; return View("MyPage"); } 使用这种策略方法我们也可以在Razor页面中应用基于角色的授权。例如,如果我们有一个"Test1.cshtml"的Razor页面,而且这个页面只允许具有"Admin"角色的用户访问,我们就可以使用下面的代码进行Razor页面的授权访问控制。 public void ConfigureServices(IServiceCollection services) { ... ... services.AddMvc().AddRazorPagesOptions(options => { options.Conventions.AuthorizePage("/test1", "OnlyAdminAccess"); }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddAuthorization(options => { options.AddPolicy("OnlyAdminAccess", policy => policy.RequireRole("Admin")); }); } 总结 本文是对https://www.c-sharpcorner.com/article/role-base-authorization-in-asp-net-core-2-1/ 这篇文章的翻译,讲述了ASP.NET Core 2.1中基于角色的授权,内容都很简单,浅显易懂!
# Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验 前几天分享的"Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验" 反响还行。有网友就说有了NLog+MySql的组合,那如果我是用SqlServer怎么使用NLog呢?于是乎,这篇“Net Core平台灵活简单的日志记录框架NLog+SqlServer初体验”就诞生了!关于记录到文本文件里面的方法上篇文章也已经说明了。而且NLog+SqlServer的组合跟NLog+MySql的组合使用方法很类似知识配置不一样。因此这篇文章会很精简,直接讲使用了!作者:依乐祝本文地址:https://www.cnblogs.com/yilezhu/p/9451282.html NLog+SqlServer的组合在Net Core中怎么用啊? 关于怎么安装,使用,请看我的上篇文章“Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验”。用法一样,只是如果你需要把MySql的程序集改成“System.Data.SqlClient”.依赖项截图如下所示: 打开Nlog.config文件,把NLog的配置修改成如下所示。我写的只是参考,大家可以自由发挥: <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="true" internalLogLevel="warn" internalLogFile="logfiles/internal-nlog.txt"> <targets> <target xsi:type="Null" name="blackhole" /> <target name="database" xsi:type="Database" dbProvider="System.Data.SqlClient" connectionString="Data Source=127.0.0.1;Initial Catalog=MiddleData;User ID=lzhu;Password=bl123456;" > <!-- create table NLog ( Id int identity, Application nvarchar(50) null, Logged datetime null, Level nvarchar(50) null, Message nvarchar(512) null, Logger nvarchar(250) null, Callsite nvarchar(512) null, Exception nvarchar(512) null, constraint PK_NLOG primary key (Id) ) --> <commandText> insert into nlog ( Application, Logged, Level, Message, Logger, CallSite, Exception ) values ( @Application, @Logged, @Level, @Message, @Logger, @Callsite, @Exception ); </commandText> <parameter name="@application" layout="NLogTestDemo" /> <parameter name="@logged" layout="${date}" /> <parameter name="@level" layout="${level}" /> <parameter name="@message" layout="${message}" /> <parameter name="@logger" layout="${logger}" /> <parameter name="@callSite" layout="${callsite:filename=true}" /> <parameter name="@exception" layout="${exception:tostring}" /> </target> </targets> <rules> <!--Skip Microsoft logs and so log only own logs--> <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" /> <logger name="NLogTestDemo.*" minlevel="Info" writeTo="database" /> </rules> </nlog> 上面的代码中我是以写入SqlServer为例进行的NLog配置。下面就可以进行简单地使用了。首先需要在。首先在Startup中的Configure中来加入中间件: public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //使用NLog作为日志记录工具 loggerFactory.AddNLog(); //引入Nlog配置文件 env.ConfigureNLog("Nlog.config"); //app.AddNLogWeb(); app.UseMvc(); } 在Program中进行如下配置: public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseNLog() .UseStartup<Startup>(); } 下面就可以在代码中愉快的玩耍了, private readonly Logger nlog = LogManager.GetCurrentClassLogger(); //获得日志实; // GET api/values [HttpGet] public ActionResult<string> Get() { nlog.Log(NLog.LogLevel.Debug, $"yilezhu测试Debug日志"); nlog.Log(NLog.LogLevel.Info, $"yilezhu测试Info日志"); try { throw new Exception($"yilezhu故意抛出的异常"); } catch (Exception ex) { nlog.Log(NLog.LogLevel.Error, ex, $"yilezhu异常的额外信息"); } return "yilezhu的返回信息"; } 下面运行起来项目,然到数据库里面就可以看到记录的日志信息如下所示: 这里大家可能会问,为什么没有Debug信息输出呢,这是因为我们上面NLog配置设置的记录日志的最低级别为Info.所以比Info级别小的Debug信息不会记录。如果想记录的话就把这个级别设置成Debug或者比Debug小的Trace就可以记录了。如下图所示: 源码下载 https://download.csdn.net/download/qin_yu_2010/10594141 总结 本文开头讲述了上篇关于“Net Core平台灵活简单的日志记录框架NLog+Mysql组合初体验”说起,然后引出轻量级简单易用的NLog+SqlServer组合,并通过一个简单地api项目讲述了NLog+SqlServer组合如何在Net Core中使用。以及SqlServer的建表语句。实例代码都跟上篇文章很相似。希望能对大家有所参考!
# Net Core平台灵活简单的日志记录框架NLog初体验 前几天分享的"Net Core集成Exceptionless分布式日志功能以及全局异常过滤" 有人说比较重量,生产环境部署也比较麻烦。因此就有了今天的这篇文章。如果你的项目(网站或者中小型项目)不是很大,日志量也不多的话可以考虑NLog+Mysql的组合。因为NLog具有高性能,易于使用,易于扩展和灵活配置的特点能够让你快速集成日志记录功能。作者:yilezhu本文链接 :https://www.cnblogs.com/yilezhu/p/9416439.html NLog是什么? 这里还是简单介绍一下吧,为了让小白也知道。NLog是一个灵活的免费日志记录平台,适用于各种.NET平台,包括.NET Core。NLog可以通过简单地配置就可以可以很方便的写入多个日志仓库中(数据库,文件,控制台)。 NLog在Net Core中怎么用啊? 用之前你得新建一个asp.net core项目吧。这里以net core api为例吧。如下图所示是博主刚刚创建的net core api项目。 建好项目之后干什么呢、当然得添加引用了。你可以随心所欲的使用Nuget或者命令进行安装 Install-Package NLog -Version 4.5.7 Install-Package NLog.Web.AspNetCore -Version 4.5.4 上面说了,NLog只需要简单地修改配置就可以使用,那接下来就是新建一个NLog配置文件了。你可以通过Nuget或者程序包控制台进行安装,也可以自己新建一个NLog.config文件。这里还是通过程序包控制台进行安装吧 Install-Package NLog -Version 4.5.7 安装后看到项目目录多了一个NLog.config文件。这里需要注意,右键设置一下这个NLog.config的属性为“始终复制” 打开Nlog.config文件,看看里面的结构,发现有两个重要节点,一个是声明目标 一个是声明规则。如下图所示,我配置了一个写入文件,一个写入mysql的target.并定义了不同的写入规则,大家可以根据实际需要参照着自定义规则以及target. <?xml version="1.0" encoding="utf-8" ?> <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" throwExceptions="true" internalLogLevel="Off"> <targets> <target xsi:type="Null" name="blackhole" /> <target name="database" xsi:type="Database" dbProvider="MySql.Data.MySqlClient.MySqlConnection, MySql.Data" connectionString="server=127.0.0.1;Database=nlog;user id=root;password=123456;SslMode=none" > <!-- CREATE TABLE `log` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, `Application` varchar(50) DEFAULT NULL, `Logged` datetime DEFAULT NULL, `Level` varchar(50) DEFAULT NULL, `Message` varchar(512) DEFAULT NULL, `Logger` varchar(250) DEFAULT NULL, `Callsite` varchar(512) DEFAULT NULL, `Exception` varchar(512) DEFAULT NULL, PRIMARY KEY (`Id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; --> <commandText> insert into nlog.log ( Application, Logged, Level, Message, Logger, CallSite, Exception ) values ( @Application, @Logged, @Level, @Message, @Logger, @Callsite, @Exception ); </commandText> <parameter name="@application" layout="NLogTestDemo" /> <parameter name="@logged" layout="${date}" /> <parameter name="@level" layout="${level}" /> <parameter name="@message" layout="${message}" /> <parameter name="@logger" layout="${logger}" /> <parameter name="@callSite" layout="${callsite:filename=true}" /> <parameter name="@exception" layout="${exception:tostring}" /> </target> </targets> <rules> <!--Skip Microsoft logs and so log only own logs--> <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" /> <logger name="NLogTestDemo.*" minlevel="Info" writeTo="database" /> </rules> </nlog> 上面的代码中我是以写入mysql为例进行的NLog配置。下面就可以进行简单地使用了。首先需要在。首先在Startup中的Configure中来加入中间件: public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //使用NLog作为日志记录工具 loggerFactory.AddNLog(); //引入Nlog配置文件 env.ConfigureNLog("Nlog.config"); //app.AddNLogWeb(); app.UseMvc(); } 在Program中进行如下配置: public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseNLog() .UseStartup<Startup>(); } 下面就可以在代码中愉快的玩耍了, private readonly Logger nlog = LogManager.GetCurrentClassLogger(); //获得日志实; // GET api/values [HttpGet] public ActionResult<string> Get() { nlog.Log(NLog.LogLevel.Debug, $"yilezhu测试Debug日志"); nlog.Log(NLog.LogLevel.Info, $"yilezhu测试Info日志"); try { throw new Exception($"yilezhu故意抛出的异常"); } catch (Exception ex) { nlog.Log(NLog.LogLevel.Error, ex, $"yilezhu异常的额外信息"); } return "yilezhu的返回信息"; } 下面运行起来项目,然到数据库里面就可以看到记录的日志信息如下所示: 这里大家可能会问,为什么没有Debug信息输出呢,这是因为我们上面NLog配置设置的记录日志的最低级别为Info.所以比Info级别小的Debug信息不会记录。如果想记录的话就把这个级别设置成Debug或者比Debug小的Trace就可以记录了。如下图所示: 总结 本文开头讲述了分布式日志记录框架Exceptionless部署困难说起,然后引出轻量级简单易用的NLog日志框架,并通过一个简单地api项目讲述了NLog如何在Net Core中使用。并且给出了NLog日志记录在mysql中的使用配置。以及mysql的建表语句。希望能对大家有所参考!
# Net Core集成Exceptionless分布式日志功能以及全局异常过滤 相信很多朋友都看过我的上篇关于Exceptionless的简单入门教程asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 上篇文章只是简单的介绍了Exceptionless是什么?能做什么呢?以及怎么进行本地部署和异常提交的简单用法,而这篇文章将带你探讨一下Exceptionless的异常收集高级用法以及你熟悉的类似NLog的日志用法。 这篇文章有一部分内容翻译自官方文档,点我阅读 英语好的可以自行阅读 。当然中间很多代码我都进行了重构,还有参考周旭龙的代码,进行了简单地封装,同时加入了为webapi加入异常全局过滤器进行异常日志的记录。希望对大家有所帮助。 本文地址:https://www.cnblogs.com/yilezhu/p/9339017.html作者:依乐祝 手动发送错误 上篇文章介绍了,导入命名空间后,并使用如下代码就可以简单地提交异常日志: try { throw new ApplicationException(Guid.NewGuid().ToString()); } catch (Exception ex) { ex.ToExceptionless().Submit(); } 发送附加信息 当然你还可以为发送的事件添加额外的标记信息,比如坐标,标签,以及其他的用户相关的信息等等 try { throw new ApplicationException("Unable to create order from quote."); } catch (Exception ex) { ex.ToExceptionless() // 设置一个ReferenceId方便查找 .SetReferenceId(Guid.NewGuid().ToString("N")) // 添加一个不包含CreditCardNumber属性的对象信息 .AddObject(order, "Order", excludedPropertyNames: new [] { "CreditCardNumber" }, maxDepth: 2) // 设置一个名为"Quote"的编号 .SetProperty("Quote", 123) // 添加一个名为“Order”的标签 .AddTags("Order") // 标记为关键异常 .MarkAsCritical() // 设置一个位置坐标 .SetGeo(43.595089, -88.444602) // 在你的系统中设置userid并提供一个有好的名字,俗称昵称 .SetUserIdentity(user.Id, user.FullName) // 为异常信息添加一些用户描述信息. .SetUserDescription(user.EmailAddress, "I tried creating an order from my saved quote.") // 提交. .Submit(); } 统一修改未处理的异常报告 你可以在通过SubmittingEvent 事件设置全局的忽略异常信息添加一些自定义信息等等 #region Exceptionless配置 ExceptionlessClient.Default.Configuration.ApiKey = exceptionlessOptions.Value.ApiKey; ExceptionlessClient.Default.Configuration.ServerUrl = exceptionlessOptions.Value.ServerUrl; ExceptionlessClient.Default.SubmittingEvent += OnSubmittingEvent; app.UseExceptionless(); #endregion /// <summary> /// 全局配置Exceptionless /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnSubmittingEvent(object sender, EventSubmittingEventArgs e) { // 只处理未处理的异常 if (!e.IsUnhandledError) return; // 忽略404错误 if (e.Event.IsNotFound()) { e.Cancel = true; return; } // 忽略没有错误体的错误 var error = e.Event.GetError(); if (error == null) return; // 忽略 401 (Unauthorized) 和 请求验证的错误. if (error.Code == "401" || error.Type == "System.Web.HttpRequestValidationException") { e.Cancel = true; return; } // Ignore any exceptions that were not thrown by our code. var handledNamespaces = new List<string> { "Exceptionless" }; if (!error.StackTrace.Select(s => s.DeclaringNamespace).Distinct().Any(ns => handledNamespaces.Any(ns.Contains))) { e.Cancel = true; return; } // 添加附加信息. //e.Event.AddObject(order, "Order", excludedPropertyNames: new[] { "CreditCardNumber" }, maxDepth: 2); e.Event.Tags.Add("MunicipalPublicCenter.BusinessApi"); e.Event.MarkAsCritical(); //e.Event.SetUserIdentity(); } 配合使用 NLog 或 Log4Net 有时候,程序中需要对日志信息做非常详细的记录,比如在开发阶段。这个时候可以配合 log4net 或者 nlog 来联合使用 exceptionless,详细可以查看这个官方的 示例。 如果你的程序中有在短时间内生成大量日志的情况,比如一分钟产生上千的日志。这个时候你需要使用内存存储(in-memory store)事件,这样客户端就不会将事件系列化的磁盘,所以会快很多。这样就可以使用Log4net 或者 Nlog来将一些事件存储到磁盘,另外 Exceptionless 事件存储到内存当中。 Exceptionless 日志记录的封装 首先简单地封装一个ILoggerHelper接口 /// <summary> /// lzhu /// 2018.7.19 /// 日志接口 /// </summary> public interface ILoggerHelper { /// <summary> /// 记录trace日志 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> void Trace(string source, string message, params string[] args); /// <summary> /// 记录debug信息 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> void Debug(string source, string message, params string[] args); /// <summary> /// 记录信息 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> void Info(string source, string message, params string[] args); /// <summary> /// 记录警告日志 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> void Warn(string source, string message, params string[] args); /// <summary> /// 记录错误日志 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> void Error(string source, string message, params string[] args); } 既然有了接口,那么当然得实现它了 /// <summary> /// lzhu /// 2018.7.19 /// Exceptionless日志实现 /// </summary> public class ExceptionlessLogger : ILoggerHelper { /// <summary> /// 记录trace日志 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">添加标记</param> public void Trace(string source,string message, params string[] args) { if (args != null && args.Length > 0) { ExceptionlessClient.Default.CreateLog(source, message, LogLevel.Trace).AddTags(args).Submit(); } else { ExceptionlessClient.Default.SubmitLog(source, message, LogLevel.Trace); } } /// <summary> /// 记录debug信息 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> public void Debug(string source, string message, params string[] args) { if (args != null && args.Length > 0) { ExceptionlessClient.Default.CreateLog(source, message, LogLevel.Debug).AddTags(args).Submit(); } else { ExceptionlessClient.Default.SubmitLog(source, message, LogLevel.Debug); } } /// <summary> /// 记录信息 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> public void Info(string source, string message, params string[] args) { if (args != null && args.Length > 0) { ExceptionlessClient.Default.CreateLog(source, message, LogLevel.Info).AddTags(args).Submit(); } else { ExceptionlessClient.Default.SubmitLog(source, message, LogLevel.Info); } } /// <summary> /// 记录警告日志 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> public void Warn(string source, string message, params string[] args) { if (args != null && args.Length > 0) { ExceptionlessClient.Default.CreateLog(source, message, LogLevel.Warn).AddTags(args).Submit(); } else { ExceptionlessClient.Default.SubmitLog(source, message, LogLevel.Warn); } } /// <summary> /// 记录错误日志 /// </summary> /// <param name="source">信息来源</param> /// <param name="message">日志内容</param> /// <param name="args">标记</param> public void Error(string source, string message, params string[] args) { if (args != null && args.Length > 0) { ExceptionlessClient.Default.CreateLog(source, message, LogLevel.Error).AddTags(args).Submit(); } else { ExceptionlessClient.Default.SubmitLog(source, message, LogLevel.Error); } } } 当然实现好了,可别忘了依赖注入哦 //注入ExceptionlessLogger服务 services.AddSingleton<ILoggerHelper, ExceptionlessLogger>(); 这时候该写一个全局异常过滤器了 /// <summary> /// lzhu /// 2018.7.19 /// 定义全局过滤器 /// </summary> public class GlobalExceptionFilter : IExceptionFilter { private readonly ILoggerHelper _loggerHelper; //构造函数注入ILoggerHelper public GlobalExceptionFilter(ILoggerHelper loggerHelper) { _loggerHelper = loggerHelper; } public void OnException(ExceptionContext filterContext) { _loggerHelper.Error(filterContext.Exception.TargetSite.GetType().FullName, filterContext.Exception.ToString(), MpcKeys.GlobalExceptionCommonTags, filterContext.Exception.GetType().FullName); var result = new BaseResult() { errcode = ResultCodeAddMsgKeys.CommonExceptionCode,//系统异常代码 errmsg= ResultCodeAddMsgKeys.CommonExceptionMsg,//系统异常信息 }; filterContext.Result = new ObjectResult(result); filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; filterContext.ExceptionHandled = true; } } 全局过滤器写好了,怎么让它生效呢,客观别急啊,上正菜 //添加验证 services.AddMvc(options=> { options.Filters.Add<GlobalExceptionFilter>(); }).AddFluentValidation(); 哈哈,没什么说的了,代码都已经写好了,剩下的就是上代码测试结果了。我这里只是简单地api测试下,万能的ValuesController登场: // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { //try //{ throw new Exception($"测试抛出的异常{id}"); //} //catch (Exception ex) //{ // ex.ToExceptionless().Submit(); //} //return "Unknown Error!"; } 这里是直接抛出异常,不进行trycatch,这时候异常会被全局过滤器捕获,然后放到Exceptionless的Log里面,别问我为什么会在log里面,因为我全局过滤器代码里面已经写明了,不明白的回去看代码,然后看接口调用的实现方法。下面上结果: 点进去,看看详细信息: 再测试下使用try catch捕获的异常处理,这时候异常信息会被提交到Exception这个里面。直接上代码吧 // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { try { throw new Exception($"测试抛出的异常{id}"); } catch (Exception ex) { ex.ToExceptionless().Submit(); } return "Unknown Error!"; } 到exceptionless里面看看不活的异常吧。打字很累直接上图吧 点进去看看详细信息,有三个tab,下面之粘贴一个图片了: ### 最后,源码就不上了,因为上面代码很清楚了 ### 总结 本文没有对Exceptionless进行过多地介绍,因为博主的上篇文章 已经进行了详细的介绍。直接切入正题,先对官方高级用法进行了简单地翻译。然后对Exceptionless Log这个eventtype进行了简单地封装,让你可以像使用NLog一样很爽的使用Exceptionless。最后通过一个asp.net core web api的项目进行了演示,在全局过滤器中利用封装的Log方法进行全局异常的捕获。希望对大家使用Exceptionless有所帮助。
win10很完美,用的也很舒服!当然人无完人,也总有不尽如人意的时候。比如说我们经常用的远程mstsc,就出现了一个坑,既然出现坑了,我们就得把坑解决掉吧!下面就记录一下这个坑的解决方法。 本文地址:https://yq.aliyun.com/articles/614058作者:依乐祝 查看微软CredSSP更新日志 查看win10系统升级日志,果然找到了原因,是因为CVE-2018-0886 的 CredSSP 2018 年 5 月 8 日更新默认设置从“易受攻击”更改为“缓解”的更新。相关的 Microsoft 知识库编号已在 CVE-2018-0886 中列出。默认情况下,安装此更新后,修补的客户端无法与未修补的服务器进行通信。 使用本文中描述的互操作性矩阵和组策略设置来启用“允许的”配置。具体的内容请自行阅读:https://support.microsoft.com/zh-cn/help/4093492/credssp-updates-for-cve-2018-0886-march-13-2018 微软官方给出的解决“这可能是由于CredSSP 加密Oracle修正”的方法 组策略解决方法及步骤 win+R 打开运行运行,然后输入“gpedit.msc”打开 如下图所示,在左侧窗口依次找到策略路径:“计算机配置”->“管理模板”->“系统”->“凭据分配” 然后右侧窗口设置名称中找到: 加密 Oracle 修正 然后双击“加密 Oracle 修正”打开如下的窗口,对着设置即可。 最后,微软建议对加密 Oracle 修正的任何更改都需要重启。所以这里最好还是重启一下电脑。不过博主本人试了,不需要重启远程连接也正常了! 注册表解决方法及步骤 这里建议先使用上面组策略的方式进行解决,如果解决不了,再使用注册表方式,因为注册表方式没有组策略这种图形化界面的简单方便。同时,微软也给出了警告: 如果使用注册表编辑器或其他方法修改注册表不当,可能会出现严重问题。 这些问题可能需要您重新安装操作系统。 Microsoft 不能保证可解决这些问题。 请自行承担修改注册表的风险。 win+R 打开运行菜单,然后输入“regedit” 按Enter键即可打开如下的注册表,按照图示,找到此路径“ HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionPoliciesSystemCredSSPParameters” 然后在右侧窗口双击“AllowEncryptionOracle” 并把值设置成“2” (跟上面组策略设置效果一样)然后确定。如下图所示 0表示强制更新的客户端1表示缓解2表示易受攻击 (跟上面组策略设置效果一样,有木有) 总结 今天主要是介绍下win10 mstsc远程遇到的坑“这可能是由于CredSSP 加密Oracle修正”的两种解决方法。从微软官方更新日志入手,然后引出组策略以及注册表的解决方法。图文也更容易让新手朋友也能按照步骤进行解决!最后还是提醒下,新手朋友最好通过组策略的方式进行解决,因为微软也给出了警告: 如果使用注册表编辑器或其他方法修改注册表不当,可能会出现严重问题。 这些问题可能需要您重新安装操作系统。 Microsoft 不能保证可解决这些问题。 请自行承担修改注册表的风险。
写在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html作者:yilezhu上一篇关于Asp.Net Core Web Api图片上传的文章使用的是mongoDB进行图片的存储,文章发布后,张队就来了一句,说没有使用GridFS。的确博主只是进行了简单的图片上传以及mongoDB存储操作,目的是提供思路。具体的图片存储,有条件的还是基于阿里云OSS或者七牛吧,如果实在想用MongDB进行存储的话,建议采用GridFS的方式!又有人说,GridFS大于16M的时候才适合使用,图片上传已经控制小于1M了,就没必要使用GridFS了吧。这里可以指定chunksize的大小。这样性能上就没有什么问题了。而且在性能差不多的时候使用GridFS可以更方便的管理。因此建议如果采用MongDB进行文件存储的话,建议采用GridFS的方式。 这里特别感谢张队的耐心指导! 为什么使用IdentityServer4? 上一篇文章中,给大家讲解了如何通过 Asp.Net Core Web Api实现图片上传的接口,具体的可以点这里查看 。这个接口是一个公开的接口,如何发布的话,任何知道调用方法的"任何人"都能任意的调用这个接口,俗称“裸奔”。这时候我们就应该给接口加入认证以及访问控制机制,来加强安全性!那么我们怎么来实现接口的认证以及访问控制呢?这时候部分人就会很懵逼了,还有一部分人就会联想到 OpenID Connect 和 OAuth 2.0了!可是怎么实现呢?从到到位搭一个这样的框架,会累死我滴,可能还要经过很长时间的测试呢!别担心,这时候就体现出Asp.Net Core社区的强大了,我们的主角IdentityServer4闪亮登场! IdentityServer4是什么?能帮我们做什么呢? IdentityServer4是一套为 ASP.NET Core 2.0开发的基于OpenID Connect 和 OAuth 2.0 的框架,他能让我们的系统很轻松的就能很多认证以及授权相关的功能,比如:单点登录,api访问控制等等!其他的我就不介绍了,社区里面介绍的太多太多了!如果有想了解的OAuth 2.0的可以看看阮一峰的这篇文章理解OAuth 2.0 。最后 IdentityServer4最最最大好处是开源的,用的人也多,而且比较成熟。想想是不是有点小激动,迫不及待的想试试了。在开始之前,附上开原地址 以及详细文档 。想了解更多自行阅读官方文档吧! 为了演示的方便,本文采用的是客户端认证模式,至于其他的几种验证模式,大家可以看下上面给出的阮一峰的文章。还有大家用之前要理解下身份认证服务器(IdentityServer),用户(User),客户端(Client),资源(Resources),身份令牌(Identity Token),访问令牌(Access Token)这些概念。如果不清楚的话可以参考晓晨Master的这篇“ASP.NET Core的身份认证框架IdentityServer4(3)-术语的解释”文章。 Asp.Net Core Web Api中如何使用IdentityServer4呢? 创建IdentityServer4服务端即“身份认证服务器(IdentityServer)” 新建一个空的Asp.Net Core Web Api项目,名称为IdentityServer端口为5001,如下图所示 通过Nuget安装IdentityServer4命令如下,记得程序包管理控制套,上面的项目选择刚刚创建的IdentityServer项目 Install-Package IdentityServer4 这里因为采用OAuth 2.0的客户端模式,所以简单地使用一个类来硬编码一些资源(Resources) 以及客户端(Client),代码如下: /// <summary> /// yilezhu /// 2018.7.15 /// 因为此处采用in-memory,所以硬编码一些api,以及client /// </summary> public class ApiConfig { /// <summary> /// 定义ApiResource 这里的资源(Resources)指的就是我们的API /// </summary> /// <returns>ApiResource枚举</returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("PictureApi", "图片上传的APi") }; } /// <summary> /// 定义受信任的客户端 Client /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "MobileUploadPicture",//客户端的标识,要是惟一的 ClientSecrets = new [] { new Secret("yilezhu123".Sha256()) },//客户端密码,进行了加密 AllowedGrantTypes = GrantTypes.ClientCredentials,//授权方式,这里采用的是客户端认证模式,只要ClientId,以及ClientSecrets正确即可访问对应的AllowedScopes里面的api资源 AllowedScopes = new [] { "PictureApi" }//定义这个客户端可以访问的APi资源数组,上面只有一个api } }; } } 在Startup.cs中注入IdentityServer服务并使用中间件,代码如下: // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //注入IdentityServer服务 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryClients(ApiConfig.GetClients()) .AddInMemoryApiResources(ApiConfig.GetApiResources()); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加认证中间件 app.UseIdentityServer(); app.UseMvc(); } 用Postman测试并获取AccessToken吧!如下图所示,在Post请求中传入,认证类型,client_id以及client_secret即可获取AccessToken: 当传入错误的Client_id或者密码将出现下面的结果 至此IdentityServer服务已经简单地完成了!下面改造下我们的图片上传服务。 改造图片上传接口,加入授权认证 在图片上传api项目中添加IdentityServer nuget包,这里只需要加入AccessTokenValidation包即可,注意选择api项目: Install-Package IdentityServer4.AccessTokenValidation appsettings.json中加入IdentityServerOptions,进行IdentityServer的一些配置 "IdentityServerOptions": { "ServerIP": "localhost", "ServerPort": 5001, "IdentityScheme": "Bearer", "ResourceName": "PictureApi" } 新建一个类用来匹配这个options,这样可以爽爽的使用: /// <summary> /// yilezhu /// 2018.7.15 /// IdentityServer的配置选项 /// </summary> public class IdentityServerOptions { /// <summary> /// 授权服务器的Ip地址 /// </summary> public string ServerIP { get; set; } /// <summary> /// 授权服务器的端口号 /// </summary> public int ServerPort { get; set; } /// <summary> /// access_token的类型,获取access_token的时候返回参数中的token_type一致 /// </summary> public string IdentityScheme { get; set; } /// <summary> /// 资源名称,认证服务注册的资源列表名称一致, /// </summary> public string ResourceName { get; set; } } 在Startup.cs中加入identityServer验证 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //注入Options OptionsConfigure(services); var identityServerOptions = new IdentityServerOptions(); Configuration.Bind("IdentityServerOptions", identityServerOptions); services.AddAuthentication(identityServerOptions.IdentityScheme) .AddIdentityServerAuthentication(options => { options.RequireHttpsMetadata = false; //是否启用https options.Authority = $"http://{identityServerOptions.ServerIP}:{identityServerOptions.ServerPort}";//配置授权认证的地址 options.ApiName = identityServerOptions.ResourceName; //资源名称,跟认证服务中注册的资源列表名称中的apiResource一致 } ); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); } /// <summary> /// yilezhu /// 2018.7.10 /// 注册Options /// </summary> /// <param name="services">服务容器</param> private void OptionsConfigure(IServiceCollection services) { //MongodbHost信息 services.Configure<MongodbHostOptions>(Configuration.GetSection("MongodbHost")); //图片选项 services.Configure<PictureOptions>(Configuration.GetSection("PictureOptions")); } 为需要说全访问的图片上传接口添加[Authorize]特性,当然要引用下命名空间: using Microsoft.AspNetCore.Authorization; /// <summary> /// 接口上传图片方法 /// </summary> /// <param name="fileDtos">文件传输对象,传过来的json数据</param> /// <returns>上传结果</returns> [HttpPost] [Authorize] public async Task<UploadResult> Post([FromBody] FileDtos fileDtos) { ………… } 把授权服务以及图片上传接口同时启动下,然后Postman再次进行下图片上传的测试: 在请求头上加入我们获取的token信息,来再次访问下: Asp.Net Core Web Api图片上传接口集成Identity Server 4安全认证实例教程到此结束了。 示例代码 点我下载 总结 本文通过图片上传这个Asp.Net Core Web Api做引子,然后引入Identity Server 4。然后通过一个简单地实例教程阐述了如何创建Identity Server 以及接口中如何进行授权认证访问。博主尽量采用通俗易懂的语言进行阐述,步骤也尽量详细,目的就是为了让初学者也能按照步骤一步一步的实现Identity Server 4的认证。下一篇我会加入SwaggerUI生成接口文档,当然大家也可以看下我的这篇关于SwaggerUI的文章ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了 。这个系列的教程源码,我已经放在github上了,大家可以点这里进行访问源代码。https://github.com/yilezhu/ImageUploadApiDemo
写在前面 本文地址:http://www.cnblogs.com/yilezhu/p/9315644.html作者:yilezhu上一篇关于Asp.Net Core Web Api图片上传的文章使用的是mongoDB进行图片的存储,文章发布后,张队就来了一句,说没有使用GridFS。的确博主只是进行了简单的图片上传以及mongoDB存储操作,目的是提供思路。具体的图片存储,有条件的还是基于阿里云OSS或者七牛吧,如果实在想用MongDB进行存储的话,建议采用GridFS的方式!又有人说,GridFS大于16M的时候才适合使用,图片上传已经控制小于1M了,就没必要使用GridFS了吧。这里可以指定chunksize的大小。这样性能上就没有什么问题了。而且在性能差不多的时候使用GridFS可以更方便的管理。因此建议如果采用MongDB进行文件存储的话,建议采用GridFS的方式。 这里特别感谢张队的耐心指导! 为什么使用IdentityServer4? 上一篇文章中,给大家讲解了如何通过 Asp.Net Core Web Api实现图片上传的接口,具体的可以点这里查看 。这个接口是一个公开的接口,如何发布的话,任何知道调用方法的"任何人"都能任意的调用这个接口,俗称“裸奔”。这时候我们就应该给接口加入认证以及访问控制机制,来加强安全性!那么我们怎么来实现接口的认证以及访问控制呢?这时候部分人就会很懵逼了,还有一部分人就会联想到 OpenID Connect 和 OAuth 2.0了!可是怎么实现呢?从到到位搭一个这样的框架,会累死我滴,可能还要经过很长时间的测试呢!别担心,这时候就体现出Asp.Net Core社区的强大了,我们的主角IdentityServer4闪亮登场! IdentityServer4是什么?能帮我们做什么呢? IdentityServer4是一套为 ASP.NET Core 2.0开发的基于OpenID Connect 和 OAuth 2.0 的框架,他能让我们的系统很轻松的就能很多认证以及授权相关的功能,比如:单点登录,api访问控制等等!其他的我就不介绍了,社区里面介绍的太多太多了!如果有想了解的OAuth 2.0的可以看看阮一峰的这篇文章理解OAuth 2.0 。最后 IdentityServer4最最最大好处是开源的,用的人也多,而且比较成熟。想想是不是有点小激动,迫不及待的想试试了。在开始之前,附上开原地址 以及详细文档 。想了解更多自行阅读官方文档吧! 为了演示的方便,本文采用的是客户端认证模式,至于其他的几种验证模式,大家可以看下上面给出的阮一峰的文章。还有大家用之前要理解下身份认证服务器(IdentityServer),用户(User),客户端(Client),资源(Resources),身份令牌(Identity Token),访问令牌(Access Token)这些概念。如果不清楚的话可以参考晓晨Master的这篇“ASP.NET Core的身份认证框架IdentityServer4(3)-术语的解释”文章。 Asp.Net Core Web Api中如何使用IdentityServer4呢? 创建IdentityServer4服务端即“身份认证服务器(IdentityServer)” 新建一个空的Asp.Net Core Web Api项目,名称为IdentityServer端口为5001,如下图所示 通过Nuget安装IdentityServer4命令如下,记得程序包管理控制套,上面的项目选择刚刚创建的IdentityServer项目 Install-Package IdentityServer4 这里因为采用OAuth 2.0的客户端模式,所以简单地使用一个类来硬编码一些资源(Resources) 以及客户端(Client),代码如下: /// <summary> /// yilezhu /// 2018.7.15 /// 因为此处采用in-memory,所以硬编码一些api,以及client /// </summary> public class ApiConfig { /// <summary> /// 定义ApiResource 这里的资源(Resources)指的就是我们的API /// </summary> /// <returns>ApiResource枚举</returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("PictureApi", "图片上传的APi") }; } /// <summary> /// 定义受信任的客户端 Client /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "MobileUploadPicture",//客户端的标识,要是惟一的 ClientSecrets = new [] { new Secret("yilezhu123".Sha256()) },//客户端密码,进行了加密 AllowedGrantTypes = GrantTypes.ClientCredentials,//授权方式,这里采用的是客户端认证模式,只要ClientId,以及ClientSecrets正确即可访问对应的AllowedScopes里面的api资源 AllowedScopes = new [] { "PictureApi" }//定义这个客户端可以访问的APi资源数组,上面只有一个api } }; } } 在Startup.cs中注入IdentityServer服务并使用中间件,代码如下: // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //注入IdentityServer服务 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryClients(ApiConfig.GetClients()) .AddInMemoryApiResources(ApiConfig.GetApiResources()); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加认证中间件 app.UseIdentityServer(); app.UseMvc(); } 用Postman测试并获取AccessToken吧!如下图所示,在Post请求中传入,认证类型,client_id以及client_secret即可获取AccessToken: 当传入错误的Client_id或者密码将出现下面的结果 至此IdentityServer服务已经简单地完成了!下面改造下我们的图片上传服务。 改造图片上传接口,加入授权认证 在图片上传api项目中添加IdentityServer nuget包,这里只需要加入AccessTokenValidation包即可,注意选择api项目: Install-Package IdentityServer4.AccessTokenValidation appsettings.json中加入IdentityServerOptions,进行IdentityServer的一些配置 "IdentityServerOptions": { "ServerIP": "localhost", "ServerPort": 5001, "IdentityScheme": "Bearer", "ResourceName": "PictureApi" } 新建一个类用来匹配这个options,这样可以爽爽的使用: /// <summary> /// yilezhu /// 2018.7.15 /// IdentityServer的配置选项 /// </summary> public class IdentityServerOptions { /// <summary> /// 授权服务器的Ip地址 /// </summary> public string ServerIP { get; set; } /// <summary> /// 授权服务器的端口号 /// </summary> public int ServerPort { get; set; } /// <summary> /// access_token的类型,获取access_token的时候返回参数中的token_type一致 /// </summary> public string IdentityScheme { get; set; } /// <summary> /// 资源名称,认证服务注册的资源列表名称一致, /// </summary> public string ResourceName { get; set; } } 在Startup.cs中加入identityServer验证 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //注入Options OptionsConfigure(services); var identityServerOptions = new IdentityServerOptions(); Configuration.Bind("IdentityServerOptions", identityServerOptions); services.AddAuthentication(identityServerOptions.IdentityScheme) .AddIdentityServerAuthentication(options => { options.RequireHttpsMetadata = false; //是否启用https options.Authority = $"http://{identityServerOptions.ServerIP}:{identityServerOptions.ServerPort}";//配置授权认证的地址 options.ApiName = identityServerOptions.ResourceName; //资源名称,跟认证服务中注册的资源列表名称中的apiResource一致 } ); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); } /// <summary> /// yilezhu /// 2018.7.10 /// 注册Options /// </summary> /// <param name="services">服务容器</param> private void OptionsConfigure(IServiceCollection services) { //MongodbHost信息 services.Configure<MongodbHostOptions>(Configuration.GetSection("MongodbHost")); //图片选项 services.Configure<PictureOptions>(Configuration.GetSection("PictureOptions")); } 为需要说全访问的图片上传接口添加[Authorize]特性,当然要引用下命名空间: using Microsoft.AspNetCore.Authorization; /// <summary> /// 接口上传图片方法 /// </summary> /// <param name="fileDtos">文件传输对象,传过来的json数据</param> /// <returns>上传结果</returns> [HttpPost] [Authorize] public async Task<UploadResult> Post([FromBody] FileDtos fileDtos) { ………… } 把授权服务以及图片上传接口同时启动下,然后Postman再次进行下图片上传的测试: 在请求头上加入我们获取的token信息,来再次访问下: Asp.Net Core Web Api图片上传接口集成Identity Server 4安全认证实例教程到此结束了。 示例代码 点我下载 总结 本文通过图片上传这个Asp.Net Core Web Api做引子,然后引入Identity Server 4。然后通过一个简单地实例教程阐述了如何创建Identity Server 以及接口中如何进行授权认证访问。博主尽量采用通俗易懂的语言进行阐述,步骤也尽量详细,目的就是为了让初学者也能按照步骤一步一步的实现Identity Server 4的认证。下一篇我会加入SwaggerUI生成接口文档,当然大家也可以看下我的这篇关于SwaggerUI的文章ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了 。这个系列的教程源码,我已经放在github上了,大家可以点这里进行访问源代码。https://github.com/yilezhu/ImageUploadApiDemo
Asp.Net Core Web Api图片上传及MongoDB存储实例教程(一) 图片或者文件上传相信大家在开发中应该都会用到吧,有的时候还要对图片生成缩略图。那么如何在Asp.Net Core Web Api实现图片上传存储以及生成缩略图呢?今天我就使用MongoDB作为图片存储,然后使用SixLabors作为图片处理,通过一个Asp.Net Core Web Api实例来给大家讲解下!本文有点长,可以先收藏推荐然后再看! 写在前面 阅读本文章,需要你具备asp.net core的基础知识,至少能够创建一个Asp.Net Core Web Api项目吧!其次,我不会跟你说MongoDB是什么以及为什么选择MongoDB作为文件存储这样的问题,因为百度百科已经给你说了, MongoDB 是一个基于分布式文件存储的数据库。实在不清楚的话自己去看百度百科吧! MongoDB在Windows下的下载安装以及配置 你可以从MongoDB官网下载安装。下载地址:https://www.mongodb.com/download-center#community 。如下图所示: 下载之后一路next基本就能完成。当然还是给新手朋友一些图文操作吧。已经会安装的朋友可以跳过这一节往下看。 一路next,然后安装出现最后安装成功的界面,点击Finish即可。 然后,Win+R 运行,输入services.msc 然后输入Enter键。打开如下的服务窗口 双击上面圈起来的MongoDB服务,可以看到如下参数命令 "C:\Program Files\MongoDB\Server\4.0\bin\mongod.exe" --config "C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg" --service 上面表示,MongoDB作为服务的方式进行启动,并且按照 --Config后面的路径里面的配置文件里的配置进行启动。 我们找到这个配置文件,并打开看下吧! 你可以重新设置参数后,然后重新启动服务即可生效!最后,让我们浏览器打开上面设置的IP以及端口号查看一下,如下图所示,表示MongoDB安装成功! Asp.Net Core Web Api图片上传的代码实现 新建Asp.Net Core Web Api项目这里我不会教你怎么创建一个Asp.Net Core Web Api项目了。创建好后会出现如下图所示的结构 安装MongoDB的nuget包以及SixLabors图片处理的包 Install-Package MongoDB.Bson -Version 2.7.0 Install-Package MongoDB.Driver -Version 2.7.0 Install-Package SixLabors.ImageSharp -Version 1.0.0-beta0004 Install-Package SixLabors.ImageSharp.Drawing -Version 1.0.0-beta0004 在appsettings.json中加入MongoDBHost的配置,如下所示,端口以及ip上面我们已经配置过了 { "Logging": { "LogLevel": { "Default": "Warning" } }, "MongodbHost": { "Connection": "mongodb://127.0.0.1:27017", "DataBase": "File_Server", "Table": "" }, "PictureOptions": { "FileTypes": ".gif,.jpg,.jpeg,.png,.bmp,.GIF,.JPG,.JPEG,.PNG,.BMP", "MaxSize": 1048576, "ThumsizeW": 200, "ThumsizeH": 140, "MakeThumbnail": true, "ThumbnailGuidKeys": "yilezhu", "ImageBaseUrl": "http://localhost:5002/api/Picture/Show/" } } 既然有了配置,肯定要创建对应的Options了。MongodbHostOptions以及PictureOptions的代码如下所示: public class MongodbHostOptions { /// <summary> /// 连接字符串 /// </summary> public string Connection { get; set; } /// <summary> /// 库 /// </summary> public string DataBase { get; set; } /// <summary> /// 表 /// </summary> public string Table { get; set; } } public class PictureOptions { /// <summary> /// 允许的文件类型 /// </summary> public string FileTypes { get; set; } /// <summary> /// 最大文件大小 /// </summary> public int MaxSize { get; set; } /// <summary> /// 缩略图宽度 /// </summary> public int ThumsizeW { get; set; } /// <summary> /// 缩略图高度 /// </summary> public int ThumsizeH { get; set; } /// <summary> /// 是否缩略图 /// </summary> public bool MakeThumbnail { get; set; } /// <summary> /// 图片的基地址 /// </summary> public string ImageBaseUrl { get; set; } } 服务注册中进行注入 //MongodbHost信息 services.Configure<MongodbHostOptions>(Configuration.GetSection("MongodbHost")); //图片选项 services.Configure<PictureOptions>(Configuration.GetSection("PictureOptions")); 新建一个PictureController用来作为图片上传的api,里面包含图片上传接口,删除接口,以及显示接口,上传接受一个base64的图片字符串,然后生成缩略图,然后存储到MongoDB数据库中,全部代码如下所示: using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using MongoDBDemo.Dtos; using MongoDBDemo.Helper; using MongoDBDemo.Models; using MongoDBDemo.Options; using MongoDBDemo.Result; namespace MongoDBDemo.Controllers { /// <summary> /// yilezhu /// 2018.7.10 /// 图片操作相关接口 /// </summary> public class PictureController : BaseController { // MongodbHost信息 private readonly MongodbHostOptions _mongodbHostOptions; // 图片选项 private readonly PictureOptions _pictureOptions; /// <summary> /// 构造函数注入 /// </summary> /// <param name="mongodbHostOptions">MongodbHost信息</param> /// <param name="pictureOptions">图片选项</param> public PictureController(IOptions<MongodbHostOptions> mongodbHostOptions, IOptions<PictureOptions> pictureOptions) { _mongodbHostOptions = mongodbHostOptions.Value; _pictureOptions = pictureOptions.Value; } /// <summary> /// 接口上传图片方法 /// </summary> /// <param name="fileDtos">文件传输对象,传过来的json数据</param> /// <returns>上传结果</returns> [HttpPost] public async Task<UploadResult> Post([FromBody] FileDtos fileDtos) { UploadResult result = new UploadResult(); if (ModelState.IsValid) { #region 验证通过 //首先根据api参数判断是否为图片类型,是则处理,不是则返回对应的结果 if (!string.IsNullOrEmpty(fileDtos.Type) && fileDtos.Type.ToLower() == "image") { //文件类型 string FileEextension = Path.GetExtension(fileDtos.Filename).ToLower();//获取文件的后缀 //判断文件类型是否是允许的类型 if (_pictureOptions.FileTypes.Split(',').Contains(FileEextension)) { //图片类型是允许的类型 Images_Mes fmster = new Images_Mes();//图片存储信息类,跟MongoDB里面表名一致 string fguid = Guid.NewGuid().ToString().Replace("-",""); //文件名称 fmster.AddTime = DateTimeOffset.Now;//添加时间为当前时间 fmster.AddUser = "server";//具体根据你的业务来获取 if (Base64Helper.IsBase64String(fileDtos.Base64String, out byte[] fmsterByte)) { //判断是否是base64字符串,如果是则转换为字节数组,用来保存 fmster.FileCon = fmsterByte; } fmster.FileName = Path.GetFileName(fileDtos.Filename);//文件名称 fmster.FileSize = fmster.FileCon.Length;//文件大小 fmster.FileType = FileEextension;//文件扩展名 fmster.GuidID = fguid;//唯一主键,通过此来获取图片数据 await MongodbHelper<Images_Mes>.AddAsync(_mongodbHostOptions, fmster);//上传文件到mongodb服务器 //检查是否需要生产缩略图 if (_pictureOptions.MakeThumbnail) { //生成缩略图 Images_Mes fthum = new Images_Mes(); fthum.AddTime = DateTimeOffset.Now; fthum.AddUser = "server";//具体根据你的业务来获取 fthum.FileCon = ImageHelper.GetReducedImage(fmster.FileCon, _pictureOptions.ThumsizeW, _pictureOptions.ThumsizeH); fthum.FileName = Path.GetFileNameWithoutExtension(fileDtos.Filename) + "_thumbnail" + Path.GetExtension(fileDtos.Filename);//生成缩略图的名称 fthum.FileSize = fthum.FileCon.Length;//缩略图大小 fthum.FileType = FileEextension;//缩略图扩展名 fthum.GuidID = fguid + _pictureOptions.ThumbnailGuidKeys;//为了方面,缩略图的主键为主图主键+一个字符yilezhu作为主键 await MongodbHelper<Images_Mes>.AddAsync(_mongodbHostOptions, fthum);//上传缩略图到mongodb服务器 } result.Errcode = ResultCodeAddMsgKeys.CommonObjectSuccessCode; result.Errmsg = ResultCodeAddMsgKeys.CommonObjectSuccessMsg; UploadEntity entity = new UploadEntity(); entity.Picguid = fguid; entity.Originalurl = _pictureOptions.ImageBaseUrl + fguid; entity.Thumburl = _pictureOptions.ImageBaseUrl + fguid + _pictureOptions.ThumbnailGuidKeys; result.Data = entity; return result; } else { //图片类型不是允许的类型 result.Errcode = ResultCodeAddMsgKeys.HttpFileInvalidCode;//对应的编码 result.Errmsg = ResultCodeAddMsgKeys.HttpFileInvalidMsg;//对应的错误信息 result.Data = null;//数据为null return result; } } else { result.Errcode = ResultCodeAddMsgKeys.HttpFileNotFoundCode; result.Errmsg = ResultCodeAddMsgKeys.HttpFileNotFoundMsg; result.Data = null; return result; } #endregion } else { #region 验证不通过 StringBuilder errinfo = new StringBuilder(); foreach (var s in ModelState.Values) { foreach (var p in s.Errors) { errinfo.AppendFormat("{0}||", p.ErrorMessage); } } result.Errcode = ResultCodeAddMsgKeys.CommonModelStateInvalidCode; result.Errmsg = errinfo.ToString(); result.Data = null; return result; #endregion } } /// <summary> /// 删除图片 /// </summary> /// <param name="guid">原始图片主键</param> /// <returns>执行结果</returns> [HttpDelete("{guid}")] public async Task<BaseResult> Delete(string guid) { await MongodbHelper<Images_Mes>.DeleteAsync(_mongodbHostOptions, guid);//删除mongodb服务器上对应的文件 await MongodbHelper<Images_Mes>.DeleteAsync(_mongodbHostOptions, guid + _pictureOptions.ThumbnailGuidKeys);//删除mongodb服务器上对应的文件 return new BaseResult(ResultCodeAddMsgKeys.CommonObjectSuccessCode, ResultCodeAddMsgKeys.CommonObjectSuccessMsg); } /// <summary> /// 返回图片对象 /// </summary> /// <param name="guid">图片的主键</param> /// <returns>图片对象</returns> [Route("Show/{guid}")] [HttpGet] public async Task<FileResult> ShowAsync(string guid) { if (string.IsNullOrEmpty(guid)) { return null; } FilterDefinition<Images_Mes> filter = Builders<Images_Mes>.Filter.Eq("GuidID", guid); var result= await MongodbHelper<Images_Mes>.FindListAsync(_mongodbHostOptions, filter); if (result != null && result.Count > 0) { return File(result[0].FileCon, "image/jpeg", result[0].FileName); } else { return null; } } } } 下面我们先连接下MongoDB看下里面内容如下: win7系统MongoDB操作软件显示有点问题,将就将就吧! 打开Postman进行图片上传操作,方法选择“Post”,参数格式选择json,然后输入对应的参数格式,如下图所示,都有标注,相信你都能看懂: 参数输入完毕之后,最后点击右侧的“Send”按钮进行测试,看到如下所示的返回结果: 我们接下来利用工具查询下MongoDB,看看有没有生成对应的数据库以及Collections如下图所示,可以看到生成了我们定义的File_Server数据库以及Images_Mes集合 查询下看看里面的数据可以看到里面的数据正是结果返回的数据,说明我们的结果是正确的,如下所示: 最后应该打开浏览器,然后输入图片地址,浏览器就会自动下载图片了,效果如下所示: 总结 教程已经写完了,细心地朋友应该能够看到,大部分代码都是昨天晚上写的,今天晚上又稍加修改完成的!一篇文章足足消耗了两个晚上,希望大家能多多支持!总结中说了一句废话!莫怪啊!本篇文章首先介绍了MongoDB的安装,然后创建了一个新的Asp.Net Core Web Api项目,然后通过一个图片上传的实例来讲述了Asp.Net Core中图片上传的操作,以及MongoDB作为图片存储的实现!当然中间用到了图片缩略图的生成,最后写了一个简单地图片展示。希望本篇关于Asp.Net Core Web Api图片上传及MongoDB存储的实例教程能对大家使用Asp.Net Core Web Api进行图片上传以及MongoDB的使用有多帮助!最后,还是希望大家多多推荐,多多支持!下面我会继续完善下代码,加入IdentityServer认证,以及SwaggerUI实现接口文档,但是Ocelot网关技术以及consul实现服务发现以及故障的邮件发送等等功能就不在此项目代码中应用了!
Asp.Net Core Web Api图片上传及MongoDB存储实例教程(一) 图片或者文件上传相信大家在开发中应该都会用到吧,有的时候还要对图片生成缩略图。那么如何在Asp.Net Core Web Api实现图片上传存储以及生成缩略图呢?今天我就使用MongoDB作为图片存储,然后使用SixLabors作为图片处理,通过一个Asp.Net Core Web Api实例来给大家讲解下!本文有点长,可以先收藏推荐然后再看! 本文地址:https://www.cnblogs.com/yilezhu/p/9297009.html 作者:yilezhu 写在前面 阅读本文章,需要你具备asp.net core的基础知识,至少能够创建一个Asp.Net Core Web Api项目吧!其次,我不会跟你说MongoDB是什么以及为什么选择MongoDB作为文件存储这样的问题,因为百度百科已经给你说了, MongoDB 是一个基于分布式文件存储的数据库。实在不清楚的话自己去看百度百科吧! MongoDB在Windows下的下载安装以及配置 你可以从MongoDB官网下载安装。下载地址:https://www.mongodb.com/download-center#community 。如下图所示: 下载之后一路next基本就能完成。当然还是给新手朋友一些图文操作吧。已经会安装的朋友可以跳过这一节往下看。 一路next,然后安装出现最后安装成功的界面,点击Finish即可。 然后,Win+R 运行,输入services.msc 然后输入Enter键。打开如下的服务窗口 双击上面圈起来的MongoDB服务,可以看到如下参数命令 "C:\Program Files\MongoDB\Server\4.0\bin\mongod.exe" --config "C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg" --service 上面表示,MongoDB作为服务的方式进行启动,并且按照 --Config后面的路径里面的配置文件里的配置进行启动。 我们找到这个配置文件,并打开看下吧! 你可以重新设置参数后,然后重新启动服务即可生效! 最后,让我们浏览器打开上面设置的IP以及端口号查看一下,如下图所示,表示MongoDB安装成功! Asp.Net Core Web Api图片上传的代码实现 新建Asp.Net Core Web Api项目 这里我不会教你怎么创建一个Asp.Net Core Web Api项目了。创建好后会出现如下图所示的结构 安装MongoDB的nuget包以及SixLabors图片处理的包 Install-Package MongoDB.Bson -Version 2.7.0 Install-Package MongoDB.Driver -Version 2.7.0 Install-Package SixLabors.ImageSharp -Version 1.0.0-beta0004 Install-Package SixLabors.ImageSharp.Drawing -Version 1.0.0-beta0004 在appsettings.json中加入MongoDBHost的配置,如下所示,端口以及ip上面我们已经配置过了 { "Logging": { "LogLevel": { "Default": "Warning" } }, "MongodbHost": { "Connection": "mongodb://127.0.0.1:27017", "DataBase": "File_Server", "Table": "" }, "PictureOptions": { "FileTypes": ".gif,.jpg,.jpeg,.png,.bmp,.GIF,.JPG,.JPEG,.PNG,.BMP", "MaxSize": 1048576, "ThumsizeW": 200, "ThumsizeH": 140, "MakeThumbnail": true, "ThumbnailGuidKeys": "yilezhu", "ImageBaseUrl": "http://localhost:5002/api/Picture/Show/" } } 既然有了配置,肯定要创建对应的Options了。MongodbHostOptions以及PictureOptions的代码如下所示: public class MongodbHostOptions { /// <summary> /// 连接字符串 /// </summary> public string Connection { get; set; } /// <summary> /// 库 /// </summary> public string DataBase { get; set; } /// <summary> /// 表 /// </summary> public string Table { get; set; } } public class PictureOptions { /// <summary> /// 允许的文件类型 /// </summary> public string FileTypes { get; set; } /// <summary> /// 最大文件大小 /// </summary> public int MaxSize { get; set; } /// <summary> /// 缩略图宽度 /// </summary> public int ThumsizeW { get; set; } /// <summary> /// 缩略图高度 /// </summary> public int ThumsizeH { get; set; } /// <summary> /// 是否缩略图 /// </summary> public bool MakeThumbnail { get; set; } /// <summary> /// 图片的基地址 /// </summary> public string ImageBaseUrl { get; set; } } 服务注册中进行注入 //MongodbHost信息 services.Configure<MongodbHostOptions>(Configuration.GetSection("MongodbHost")); //图片选项 services.Configure<PictureOptions>(Configuration.GetSection("PictureOptions")); 新建一个PictureController用来作为图片上传的api,里面包含图片上传接口,删除接口,以及显示接口,上传接受一个base64的图片字符串,然后生成缩略图,然后存储到MongoDB数据库中,全部代码如下所示: using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using MongoDBDemo.Dtos; using MongoDBDemo.Helper; using MongoDBDemo.Models; using MongoDBDemo.Options; using MongoDBDemo.Result; namespace MongoDBDemo.Controllers { /// <summary> /// yilezhu /// 2018.7.10 /// 图片操作相关接口 /// </summary> public class PictureController : BaseController { // MongodbHost信息 private readonly MongodbHostOptions _mongodbHostOptions; // 图片选项 private readonly PictureOptions _pictureOptions; /// <summary> /// 构造函数注入 /// </summary> /// <param name="mongodbHostOptions">MongodbHost信息</param> /// <param name="pictureOptions">图片选项</param> public PictureController(IOptions<MongodbHostOptions> mongodbHostOptions, IOptions<PictureOptions> pictureOptions) { _mongodbHostOptions = mongodbHostOptions.Value; _pictureOptions = pictureOptions.Value; } /// <summary> /// 接口上传图片方法 /// </summary> /// <param name="fileDtos">文件传输对象,传过来的json数据</param> /// <returns>上传结果</returns> [HttpPost] public async Task<UploadResult> Post([FromBody] FileDtos fileDtos) { UploadResult result = new UploadResult(); if (ModelState.IsValid) { #region 验证通过 //首先根据api参数判断是否为图片类型,是则处理,不是则返回对应的结果 if (!string.IsNullOrEmpty(fileDtos.Type) && fileDtos.Type.ToLower() == "image") { //文件类型 string FileEextension = Path.GetExtension(fileDtos.Filename).ToLower();//获取文件的后缀 //判断文件类型是否是允许的类型 if (_pictureOptions.FileTypes.Split(',').Contains(FileEextension)) { //图片类型是允许的类型 Images_Mes fmster = new Images_Mes();//图片存储信息类,跟MongoDB里面表名一致 string fguid = Guid.NewGuid().ToString().Replace("-",""); //文件名称 fmster.AddTime = DateTimeOffset.Now;//添加时间为当前时间 fmster.AddUser = "server";//具体根据你的业务来获取 if (Base64Helper.IsBase64String(fileDtos.Base64String, out byte[] fmsterByte)) { //判断是否是base64字符串,如果是则转换为字节数组,用来保存 fmster.FileCon = fmsterByte; } fmster.FileName = Path.GetFileName(fileDtos.Filename);//文件名称 fmster.FileSize = fmster.FileCon.Length;//文件大小 fmster.FileType = FileEextension;//文件扩展名 fmster.GuidID = fguid;//唯一主键,通过此来获取图片数据 await MongodbHelper<Images_Mes>.AddAsync(_mongodbHostOptions, fmster);//上传文件到mongodb服务器 //检查是否需要生产缩略图 if (_pictureOptions.MakeThumbnail) { //生成缩略图 Images_Mes fthum = new Images_Mes(); fthum.AddTime = DateTimeOffset.Now; fthum.AddUser = "server";//具体根据你的业务来获取 fthum.FileCon = ImageHelper.GetReducedImage(fmster.FileCon, _pictureOptions.ThumsizeW, _pictureOptions.ThumsizeH); fthum.FileName = Path.GetFileNameWithoutExtension(fileDtos.Filename) + "_thumbnail" + Path.GetExtension(fileDtos.Filename);//生成缩略图的名称 fthum.FileSize = fthum.FileCon.Length;//缩略图大小 fthum.FileType = FileEextension;//缩略图扩展名 fthum.GuidID = fguid + _pictureOptions.ThumbnailGuidKeys;//为了方面,缩略图的主键为主图主键+一个字符yilezhu作为主键 await MongodbHelper<Images_Mes>.AddAsync(_mongodbHostOptions, fthum);//上传缩略图到mongodb服务器 } result.Errcode = ResultCodeAddMsgKeys.CommonObjectSuccessCode; result.Errmsg = ResultCodeAddMsgKeys.CommonObjectSuccessMsg; UploadEntity entity = new UploadEntity(); entity.Picguid = fguid; entity.Originalurl = _pictureOptions.ImageBaseUrl + fguid; entity.Thumburl = _pictureOptions.ImageBaseUrl + fguid + _pictureOptions.ThumbnailGuidKeys; result.Data = entity; return result; } else { //图片类型不是允许的类型 result.Errcode = ResultCodeAddMsgKeys.HttpFileInvalidCode;//对应的编码 result.Errmsg = ResultCodeAddMsgKeys.HttpFileInvalidMsg;//对应的错误信息 result.Data = null;//数据为null return result; } } else { result.Errcode = ResultCodeAddMsgKeys.HttpFileNotFoundCode; result.Errmsg = ResultCodeAddMsgKeys.HttpFileNotFoundMsg; result.Data = null; return result; } #endregion } else { #region 验证不通过 StringBuilder errinfo = new StringBuilder(); foreach (var s in ModelState.Values) { foreach (var p in s.Errors) { errinfo.AppendFormat("{0}||", p.ErrorMessage); } } result.Errcode = ResultCodeAddMsgKeys.CommonModelStateInvalidCode; result.Errmsg = errinfo.ToString(); result.Data = null; return result; #endregion } } /// <summary> /// 删除图片 /// </summary> /// <param name="guid">原始图片主键</param> /// <returns>执行结果</returns> [HttpDelete("{guid}")] public async Task<BaseResult> Delete(string guid) { await MongodbHelper<Images_Mes>.DeleteAsync(_mongodbHostOptions, guid);//删除mongodb服务器上对应的文件 await MongodbHelper<Images_Mes>.DeleteAsync(_mongodbHostOptions, guid + _pictureOptions.ThumbnailGuidKeys);//删除mongodb服务器上对应的文件 return new BaseResult(ResultCodeAddMsgKeys.CommonObjectSuccessCode, ResultCodeAddMsgKeys.CommonObjectSuccessMsg); } /// <summary> /// 返回图片对象 /// </summary> /// <param name="guid">图片的主键</param> /// <returns>图片对象</returns> [Route("Show/{guid}")] [HttpGet] public async Task<FileResult> ShowAsync(string guid) { if (string.IsNullOrEmpty(guid)) { return null; } FilterDefinition<Images_Mes> filter = Builders<Images_Mes>.Filter.Eq("GuidID", guid); var result= await MongodbHelper<Images_Mes>.FindListAsync(_mongodbHostOptions, filter); if (result != null && result.Count > 0) { return File(result[0].FileCon, "image/jpeg", result[0].FileName); } else { return null; } } } } 下面我们先连接下MongoDB看下里面内容如下: win7系统MongoDB操作软件显示有点问题,将就将就吧! 打开Postman进行图片上传操作,方法选择“Post”,参数格式选择json,然后输入对应的参数格式,如下图所示,都有标注,相信你都能看懂: 参数输入完毕之后,最后点击右侧的“Send”按钮进行测试,看到如下所示的返回结果: 我们接下来利用工具查询下MongoDB,看看有没有生成对应的数据库以及Collections如下图所示,可以看到生成了我们定义的File_Server数据库以及Images_Mes集合 查询下看看里面的数据可以看到里面的数据正是结果返回的数据,说明我们的结果是正确的,如下所示: 最后应该打开浏览器,然后输入图片地址,浏览器就会自动下载图片了,效果如下所示: 示例代码 [点我下载][https://github.com/yilezhu/ImageUploadApiDemo] 总结 教程已经写完了,细心地朋友应该能够看到,大部分代码都是昨天晚上写的,今天晚上又稍加修改完成的!一篇文章足足消耗了两个晚上,希望大家能多多支持! 总结中说了一句废话!莫怪啊!本篇文章首先介绍了MongoDB的安装,然后创建了一个新的Asp.Net Core Web Api项目,然后通过一个图片上传的实例来讲述了Asp.Net Core中图片上传的操作,以及MongoDB作为图片存储的实现!当然中间用到了图片缩略图的生成,最后写了一个简单地图片展示。希望本篇关于Asp.Net Core Web Api图片上传及MongoDB存储的实例教程能对大家使用Asp.Net Core Web Api进行图片上传以及MongoDB的使用有多帮助! 最后,还是希望大家多多推荐,多多支持!下面我会继续完善下代码,加入IdentityServer认证,以及SwaggerUI实现接口文档,但是Ocelot网关技术以及consul实现服务发现以及故障的邮件发送等等功能就不在此项目代码中应用了! 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者:yilezhu(依乐祝).NET Core实战项目交流群:637326624 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!另如需修改文章内容请与本人联系。微信账号:jkingzhu
[译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 本文首发自:博客园 文章地址: https://www.cnblogs.com/yilezhu/p/9276565.html 园子里关于ASP.NET Core Web API的教程很多,但大多都是使用EF+Mysql或者EF+MSSQL的文章。甚至关于ASP.NET Core Web API中使用Dapper+Mysql组合的文章都很少,更别提Oracel+Dapper组合的文章了,那么今天就带着大家一起翻译一篇国外大牛写的关于ASP.NET Core Web API 开发中使用Oracle+Dapper的组合的文章吧。 注:虽然本文内容是翻译,但是楼主刚在2.1环境是使用成功,中间也没有任何阻碍,只是鉴于本人电脑配置太差无法安装Oracle数据库,所以无法进行演示,再者是表示对原作者的尊重,所以在这里只是对原作内容进行翻译然后加上自己的理解稍作改动。应该能对大家使用Oracle+Dapper组合开发ASP.NET Core Web API 有所帮助。 本文的重点是介绍如何使用Dapper ORM+Oracle数据库的组合来创建ASP.NET Core Web API。首先,在这里,我们不使用SQL ,因为互联网上已有很多文章都是使用SQL Server进行演示的。所以,我想写一篇使用Oracle作为数据库的文章。为了降低数据库访问逻辑的复杂性,我们使用Dapper ORM。那么,让我们赶紧开始实战演练吧。 创建一个ASP.NET Core Web API 项目 如果要创建一个新的ASP.NET Core Web API项目的话,只需要打开Visual Studio 2017版本15.3及以上,然后按照以下步骤操作。 打开文件菜单,点击新建>>项目 在新打开的新建项目窗口,首先你需要选择 .NET Framework 4.6及以上版本,然后在左侧面板选择C# ,然后选择 .NET Core 在右侧面板中选择“.NET Core Web 应用程序” 并且选择项目位置,最后点击“确定” 在下一个窗口,在众多模板中选择Web API模板 写如何新建ASP.NET Core Web API 的这些步骤的时候我都嫌累,我想大家应该都知道怎么创建吧!就不上图片了。 设置Oracle表和存储过程 首先要为演示创建数据库以及表,我们这里使用Oracle Developer Tools。因为它非常小巧灵活,可以帮助我们顺利的处理Oracle数据库。 Oracle SQL Developer是一个免费的集成开发环境,可简化传统和云部署中Oracle数据库的开发和管理。SQL Developer提供完整的PL / SQL应用程序端到端开发,运行查询和脚本的工作表,用于管理数据库的DBA控制台,报告界面,完整的数据建模解决方案以及用于迁移第三方数据到Oracle的平台。 创建一个名为“TEST_DB”的数据库名称,并在其中创建一个表名为“EMPLOYEE”。您可以使用以下语法在“TEST_DB”数据库中创建表。 CREATE TABLE "TEST_DB"."EMPLOYEE" ( "ID" NUMBER(10,0) GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 100 CACHE 20 NOORDER NOCYCLE , "NAME" VARCHAR2(255 BYTE), "SALARY" NUMBER(10,0), "ADDRESS" VARCHAR2(500 BYTE) ) SEGMENT CREATION IMMEDIATE PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) TABLESPACE "TEST_DATA" ; 我们需要在表中添加一些虚拟数据,以便我们可以直接从PostMan获取数据。所以,我们在这里添加四条记录如下。 Insert into TEST_DB.EMPLOYEE (ID,NAME,SALARY,ADDRESS) values (100,'Mukesh',20000,'India'); Insert into TEST_DB.EMPLOYEE (ID,NAME,SALARY,ADDRESS) values (101,'Rion',28000,'US'); Insert into TEST_DB.EMPLOYEE (ID,NAME,SALARY,ADDRESS) values (102,'Mahesh',10000,'India'); Insert into TEST_DB.EMPLOYEE (ID,NAME,SALARY,ADDRESS) values (103,'Banky',20000,'India'); 现在我们来创建一个存储过程,用来获取员工记录列表。这里我们使用Cursor返回数据列表作为输出参数。 CREATE OR REPLACE PROCEDURE "TEST_DB"."USP_GETEMPLOYEES" ( EMPCURSOR OUT SYS_REFCURSOR ) AS Begin Open EMPCURSOR For SELECT ID, NAME, SALARY,ADDRESS FROM Employee; End; 下面我们再创建一个存储过程,它根据员工ID获取员工的个人记录 CREATE OR REPLACE PROCEDURE "TEST_DB"."USP_GETEMPLOYEEDETAILS" ( EMP_ID IN INT, EMP_DETAIL_CURSOR OUT SYS_REFCURSOR ) AS BEGIN OPEN EMP_DETAIL_CURSOR FOR SELECT ID, NAME, SALARY,ADDRESS FROM Employee WHERE ID = EMP_ID; END; 安装Dapper ORM 从“工具”菜单的“Nuget包管理器”中打开“包管理器控制台”,然后输入以下命令并按Enter键以安装dapper及其依赖项(如果有) Install-Package Dapper -Version 1.50.5 当然还有另一个安装方式,具体可以看 [ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了][http://www.cnblogs.com/yilezhu/p/9241261.html] 中关于安装Swashbuckle.AspNetCore的步骤 安装完成后,你可以查看下项目大的引用中,是否有“Dapper”的引用,如果有的话表示安装正确 为项目安装Oracle Manage Data Access 我们在Asp.Net Core Web API应用程序中使用Oracle,需要从Core应用程序访问Oracle数据库。要将Oracle数据库与.Net Core应用程序一起使用,我们有Oracle库,它将帮助我们管理数据库访问的逻辑。因此,我们必须安装以下bata的软件包。 Install-Package Oracle.ManagedDataAccess.Core -Version 2.12.0-beta2 添加 Oracle 数据库连接 现在我们已准备好与数据库相关的所有内容,如数据库,表和SP等。要从Web API访问数据库,我们必须像往常一样在“appsettings.json”文件中创建连接字符串。 { "Logging": { "IncludeScopes": false, "Debug": { "LogLevel": { "Default": "Warning" } }, "Console": { "LogLevel": { "Default": "Warning" } } }, "ConnectionStrings": { "EmployeeConnection": "data source=mukesh:1531;password=**********;user id=mukesh;Incr Pool Size=5;Decr Pool Size=2;" } } 创建一个仓储 为了保持关注点的分离,我们在这里使用Repository。在Web API项目中创建一个新文件夹作为“仓储库”,并创建一个“IEmployeeRepository”接口和一个它的实现类“EmployeeRepository”,它将实现到IEmployeeRepository。 namespace Core2API.Repositories { public interface IEmployeeRepository { object GetEmployeeList(); object GetEmployeeDetails(int empId); } } 以下是实现了IEmployeeRepository的EmployeeRepository类。它需要访问配置中的数据库连接串,因此我们在构造函数中注入IConfiguration。所以,我们已经准备好使用配置对象了。除此之外,我们还有GetConnection()方法,该方法将从appsettings.json获取连接字符串,并将其提供给OracleConnection以创建连接并最终返回连接。我们已经实现了“IEmployeeRepository”,它有两个方法,如GetEmployeeDetails和GetEmployeeList。 using Core2API.Oracle; using Dapper; using Microsoft.Extensions.Configuration; using Oracle.ManagedDataAccess.Client; using System; using System.Data; namespace Core2API.Repositories { public class EmployeeRepository : IEmployeeRepository { IConfiguration configuration; public EmployeeRepository(IConfiguration _configuration) { configuration = _configuration; } public object GetEmployeeDetails(int empId) { object result = null; try { var dyParam = new OracleDynamicParameters(); dyParam.Add("EMP_ID", OracleDbType.Int32, ParameterDirection.Input, empId); dyParam.Add("EMP_DETAIL_CURSOR", OracleDbType.RefCursor, ParameterDirection.Output); var conn = this.GetConnection(); if (conn.State == ConnectionState.Closed) { conn.Open(); } if (conn.State == ConnectionState.Open) { var query = "USP_GETEMPLOYEEDETAILS"; result = SqlMapper.Query(conn, query, param: dyParam, commandType: CommandType.StoredProcedure); } } catch (Exception ex) { throw ex; } return result; } public object GetEmployeeList() { object result = null; try { var dyParam = new OracleDynamicParameters(); dyParam.Add("EMPCURSOR", OracleDbType.RefCursor, ParameterDirection.Output); var conn = this.GetConnection(); if(conn.State == ConnectionState.Closed) { conn.Open(); } if (conn.State == ConnectionState.Open) { var query = "USP_GETEMPLOYEES"; result = SqlMapper.Query(conn, query, param: dyParam, commandType: CommandType.StoredProcedure); } } catch (Exception ex) { throw ex; } return result; } public IDbConnection GetConnection() { var connectionString = configuration.GetSection("ConnectionStrings").GetSection("EmployeeConnection").Value; var conn = new OracleConnection(connectionString); return conn; } } } public IDbConnection GetConnection() { var connectionString = configuration.GetSection("ConnectionStrings").GetSection("EmployeeConnection").Value; var conn = new OracleConnection(connectionString); return conn; } 为了在.Net Core中使用Oracle的数据类型,我们使用的是OracleDyamicParameters类,它将提供管理Oracle参数行为的一系列方法。 using Dapper; using Oracle.ManagedDataAccess.Client; using System.Collections.Generic; using System.Data; namespace Core2API.Oracle { public class OracleDynamicParameters : SqlMapper.IDynamicParameters { private readonly DynamicParameters dynamicParameters = new DynamicParameters(); private readonly List<OracleParameter> oracleParameters = new List<OracleParameter>(); public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction, object value = null, int? size = null) { OracleParameter oracleParameter; if (size.HasValue) { oracleParameter = new OracleParameter(name, oracleDbType, size.Value, value, direction); } else { oracleParameter = new OracleParameter(name, oracleDbType, value, direction); } oracleParameters.Add(oracleParameter); } public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction) { var oracleParameter = new OracleParameter(name, oracleDbType, direction); oracleParameters.Add(oracleParameter); } public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { ((SqlMapper.IDynamicParameters)dynamicParameters).AddParameters(command, identity); var oracleCommand = command as OracleCommand; if (oracleCommand != null) { oracleCommand.Parameters.AddRange(oracleParameters.ToArray()); } } } } 在Startup.cs中配置依赖 如果要在控制器或仓储类中使用依赖项的话,我们必须配置或者说在Startup类的ConfigureServices方法中为我们的接口注册我们的依赖项类。 (翻译的好拗口,楼主四级没过,希望不被喷) using Core2API.Repositories; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Core2API { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddTransient<IEmployeeRepository, EmployeeRepository>(); services.AddSingleton<IConfiguration>(Configuration); services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); } } } 添加 EmployeeController 控制器 现在是时候在EmployeeControler中创建API调用了。首先,我们在构造函数中添加了IEmployeeRepository以使用依赖项。其次,我们必须为两个方法创建带有Route属性的API调用。 using Core2API.Repositories; using Microsoft.AspNetCore.Mvc; namespace CoreAPI.Controllers { [Produces("application/json")] public class EmployeeController : Controller { IEmployeeRepository employeeRepository; public EmployeeController(IEmployeeRepository _employeeRepository) { employeeRepository = _employeeRepository; } [Route("api/GetEmployeeList")] public ActionResult GetEmployeeList() { var result = employeeRepository.GetEmployeeList(); if (result == null) { return NotFound(); } return Ok(result); } [Route("api/GetEmployeeDetails/{empId}")] public ActionResult GetEmployeeDetails(int empId) { var result = employeeRepository.GetEmployeeDetails(empId); if (result == null) { return NotFound(); } return Ok(result); } } } 现在我们已准备就绪,就像存储库已准备好,与Oracle数据库的连接已准备就绪,最后,API调用也在控制器内部就绪。因此,是时候在PostMan中运行API来查看结果了。只需按F5即可运行Web API然后打开PostMan进行测试。 要在PostMan中进行测试,首先选择“Get”作为方法,并提供URL以获取员工记录列表,然后单击“发送”按钮,该按钮将向我们的API发出请求并使用我们文章开始时创建的数据库脚本来获取我们在此处添加的员工列表数据。 要获取单个员工记录,只需传递以下URL,如图中所示。您可以在此处看到,我们希望查看员工ID 103的记录。发送请求后,您可以看到如下所示的输出。 最后 所以,今天,我们已经学会了如何创建ASP.NET Core Web API项目并使用Dapper与Oracle数据库一起使用。 我希望这篇文章能对你有所帮助。请使用评论来进行反馈,这有助于我提高自己的下一篇文章。如果您有任何疑问,请在评论部分发表你的疑问,如果您喜欢这篇文章,请与您的朋友分享。并记得点下推荐哦! 原文地址:https://www.c-sharpcorner.com/article/asp-net-core-web-api-with-oracle-database-and-dapper/ 翻译人:依乐祝 总结 今天主要是翻译了一篇国外的使用Dapper以及Oracle的组合来开发asp.net core web api的教程!目的就是填补园子里使用Dapper以及Oracle的组合来开发asp.net core web api的空白!还有就是最近连续出差都没有更新文章了!接下来我会为大家介绍更多asp.net core 微服务相关的技术,希望大家持续关注!如果感觉博主写的还不错的话希望给个推荐!谢谢! 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者: yilezhu(依乐祝) 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
引言 在使用asp.net core 进行api开发完成后,书写api说明文档对于程序员来说想必是件很痛苦的事情吧,但文档又必须写,而且文档的格式如果没有具体要求的话,最终完成的文档则完全取决于开发者的心情。或者详细点,或者简单点。那么有没有一种快速有效的方法来构建api说明文档呢?答案是肯定的, Swagger就是最受欢迎的REST APIs文档生成工具之一! 为什么使用Swagger作为REST APIs文档生成工具 Swagger 可以生成一个具有互动性的API控制台,开发者可以用来快速学习和尝试API。 Swagger 可以生成客户端SDK代码用于各种不同的平台上的实现。 Swagger 文件可以在许多不同的平台上从代码注释中自动生成。 Swagger 有一个强大的社区,里面有许多强悍的贡献者。 asp.net core中如何使用Swagger生成api说明文档呢 Swashbuckle.AspNetCore 是一个开源项目,用于生成 ASP.NET Core Web API 的 Swagger 文档。 NSwag 是另一个用于将 Swagger UI 或 ReDoc 集成到 ASP.NET Core Web API 中的开源项目。 它提供了为 API 生成 C# 和 TypeScript 客户端代码的方法。 下面以Swashbuckle.AspNetCore为例为大家进行展示 Swashbuckle由哪些组成部分呢? Swashbuckle.AspNetCore.Swagger:将 SwaggerDocument 对象公开为 JSON 终结点的 Swagger 对象模型和中间件。 Swashbuckle.AspNetCore.SwaggerGen:从路由、控制器和模型直接生成 SwaggerDocument 对象的 Swagger 生成器。 它通常与 Swagger 终结点中间件结合,以自动公开 Swagger JSON。 Swashbuckle.AspNetCore.SwaggerUI:Swagger UI 工具的嵌入式版本。 它解释 Swagger JSON 以构建描述 Web API 功能的可自定义的丰富体验。 它包括针对公共方法的内置测试工具。 如何使用vs2017安装Swashbuckle呢? 从“程序包管理器控制台”窗口进行安装 转到“视图” > “其他窗口” > “程序包管理器控制台” 导航到包含 TodoApi.csproj 文件的目录 请执行以下命令 ·Install-Package Swashbuckle.AspNetCore 从“管理 NuGet 程序包”对话框中: 右键单击“解决方案资源管理器” > “管理 NuGet 包”中的项目 将“包源”设置为“nuget.org” 在搜索框中输入“Swashbuckle.AspNetCore” 从“浏览”选项卡中选择“Swashbuckle.AspNetCore”包,然后单击“安装” 添加并配置 Swagger 中间件 首先引入命名空间: using Swashbuckle.AspNetCore.Swagger; 将 Swagger 生成器添加到 Startup.ConfigureServices 方法中的服务集合中: //注册Swagger生成器,定义一个和多个Swagger 文档 services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" }); }); 在 Startup.Configure 方法中,启用中间件为生成的 JSON 文档和 Swagger UI 提供服务: //启用中间件服务生成Swagger作为JSON终结点 app.UseSwagger(); //启用中间件服务对swagger-ui,指定Swagger JSON终结点 app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); 启动应用,并导航到 http://localhost:<port>/swagger/v1/swagger.json。 生成的描述终结点的文档显示如下json格式。 可在 http://localhost:<port>/swagger 找到 Swagger UI。 通过 Swagger UI 浏览 API文档,如下所示。 要在应用的根 (http://localhost:<port>/) 处提供 Swagger UI,请将 RoutePrefix 属性设置为空字符串: app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); c.RoutePrefix = string.Empty; }); Swagger的高级用法(自定义以及扩展) 使用Swagger为API文档增加说明信息 在 AddSwaggerGen 方法的进行如下的配置操作会添加诸如作者、许可证和说明信息等: //注册Swagger生成器,定义一个和多个Swagger 文档 services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Version = "v1", Title = "yilezhu's API", Description = "A simple example ASP.NET Core Web API", TermsOfService = "None", Contact = new Contact { Name = "依乐祝", Email = string.Empty, Url = "http://www.cnblogs.com/yilezhu/" }, License = new License { Name = "许可证名字", Url = "http://www.cnblogs.com/yilezhu/" } }); }); wagger UI 显示版本的信息如下图所示: 为了防止博客被转载后,不保留本文的链接,特意在此加入本文的链接:https://www.cnblogs.com/yilezhu/p/9241261.html 为接口方法添加注释 大家先点击下api,展开如下图所示,可以没有注释啊,怎么来添加注释呢? 按照下图所示用三个/添加文档注释,如下所示 /// <summary> /// 这是一个api方法的注释 /// </summary> /// <returns></returns> [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; } 然后运行项目,回到swaggerUI中去查看注释是否出现了呢 还是没有出现,别急,往下看! 启用XML 注释 可使用以下方法启用 XML 注释: 右键单击“解决方案资源管理器”中的项目,然后选择“属性” 查看“生成”选项卡的“输出”部分下的“XML 文档文件”框 启用 XML 注释后会为未记录的公共类型和成员提供调试信息。如果出现很多警告信息 例如,以下消息指示违反警告代码 1591: warning CS1591: Missing XML comment for publicly visible type or member 'TodoController.GetAll()' 如果你有强迫症,想取消警告怎么办呢?可以按照下图所示进行取消 注意上面生成的xml文档文件的路径, 注意: 1.对于 Linux 或非 Windows 操作系统,文件名和路径区分大小写。 例如,“SwaggerDemo.xml”文件在 Windows 上有效,但在 CentOS 上无效。 2.获取应用程序路径,建议采用Path.GetDirectoryName(typeof(Program).Assembly.Location)这种方式或者·AppContext.BaseDirectory这样来获取 //注册Swagger生成器,定义一个和多个Swagger 文档 services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new Info { Version = "v1", Title = "yilezhu's API", Description = "A simple example ASP.NET Core Web API", TermsOfService = "None", Contact = new Contact { Name = "依乐祝", Email = string.Empty, Url = "http://www.cnblogs.com/yilezhu/" }, License = new License { Name = "许可证名字", Url = "http://www.cnblogs.com/yilezhu/" } }); // 为 Swagger JSON and UI设置xml文档注释路径 var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);//获取应用程序所在目录(绝对,不受工作目录影响,建议采用此方法获取路径) var xmlPath = Path.Combine(basePath, "SwaggerDemo.xml"); c.IncludeXmlComments(xmlPath); }); 重新生成并运行项目查看一下注释出现了没有 通过上面的操作可以总结出,Swagger UI 显示上述注释代码的 <summary> 元素的内部文本作为api大的注释! 当然你还可以将 remarks 元素添加到 Get 操作方法文档。 它可以补充 <summary> 元素中指定的信息,并提供更可靠的 Swagger UI。 <remarks> 元素内容可包含文本、JSON 或 XML。 代码如下: /// <summary> /// 这是一个带参数的get请求 /// </summary> /// <remarks> /// 例子: /// Get api/Values/1 /// </remarks> /// <param name="id">主键</param> /// <returns>测试字符串</returns> [HttpGet("{id}")] public ActionResult<string> Get(int id) { return $"你请求的 id 是 {id}"; } 重新生成下项目,当好到SwaggerUI看到如下所示: 描述响应类型 摘录自:https://www.cnblogs.com/yanbigfeg/p/9232844.html 接口使用者最关心的就是接口的返回内容和响应类型啦。下面展示一下201和400状态码的一个简单例子: 我们需要在我们的方法上添加:[ProducesResponseType(201)][ProducesResponseType(400)] 然后添加相应的状态说明: 最终代码应该是这个样子: /// <summary> /// 这是一个带参数的get请求 /// </summary> /// <remarks> /// 例子: /// Get api/Values/1 /// </remarks> /// <param name="id">主键</param> /// <returns>测试字符串</returns> /// <response code="201">返回value字符串</response> /// <response code="400">如果id为空</response> // GET api/values/2 [HttpGet("{id}")] [ProducesResponseType(201)] [ProducesResponseType(400)] public ActionResult<string> Get(int id) { return $"你请求的 id 是 {id}"; } 效果如下所示 使用SwaggerUI测试api接口 下面我们通过一个小例子通过SwaggerUI调试下接口吧 点击一个需要测试的API接口,然后点击Parameters左右边的“Try it out ” 按钮 在出现的参数文本框中输入参数,如下图所示的,输入参数2 点击执行按钮,会出现下面所示的格式化后的Response,如下图所示 好了,今天的在ASP.NET Core WebApi使用Swagger生成api说明文档看这篇就够了的教程就到这里了。希望能够对大家学习在ASP.NET Core中使用Swagger生成api文档有所帮助! 总结 本文从手工书写api文档的痛处说起,进而引出Swagger这款自动生成api说明文档的工具!然后通过通俗易懂的文字结合图片为大家演示了如何在一个ASP.NET Core WebApi中使用SwaggerUI生成api说明文档。最后又为大家介绍了一些ASP.NET Core 中Swagger的一些高级用法!希望对大家在ASP.NET Core中使用Swagger有所帮助! 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者: yilezhu(依乐祝) 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
最近在学习张善友老师的NanoFabric 框架的时了解到Exceptionless : https://exceptionless.com/ !因此学习了一下这个开源框架!下面对Exceptionless的学习做下笔记! Exceptionless是什么?能做什么呢? “Exceptionless”这个词的定义是:没有异常。Exceptionless可以为您的ASP.NET、Web API、WebFrm、WPF、控制台和MVC应用程序提供实时错误、特性和日志报告。它将收集的信息组织成简单的可操作的数据,这些数据将帮助你很方便的查看异常信息。还有最重要的是,它是开源的! Exceptionless的使用方式有哪些? 1.官网创建帐号,并新建应用程序以及项目,然后生成apikey(数据存储在Exceptionless) 2.自己搭建Exceptionless的环境,部署在本地(数据存储在本地) Exceptionless的运行环境有哪些要求?需要安装哪些软件,进行什么配置呢? .NET 4.6.1 (安装了.net core 或者vs2017的话环境应该都没问题,不需要额外安装) Java JDK 1.8+(如果使用windows系统的话需要配置环境变量,这个使用过java的人应该都知道吧!相信对于你来说应该不是难事).下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html IIS Express 8+(win 7以上环境应该都没问题,不需要额外安装) PowerShell 3+(win 7以上环境应该都没问题,不需要额外安装) 这里分win7(管理员身份运行cmd ,然后复制下面这条命令,按回车就行了 powershell Set-ExecutionPolicy Unrestricted) 以及 win10(管理员身份运行powershell,然后执行powershell Set-ExecutionPolicy Unrestricted) Elasticsearch 5.6 官方推荐这个版本,(当然你也可以不进行安装,因为后面会教你如何自动安装这个软件)需要在历史版本中找 ,下载地址:https://www.elastic.co/downloads/past-releases Exceptionless下载以及配置 1.打包下载地址:https://github.com/exceptionless/Exceptionless/releases 如下图所示进行下载就可以了!,别看只有15M有的人下载可能需要半个小时,别问为什么,因为~~~~~ 2.下载完成之后,右键解压 3.看到如下的文件目录结构,有几点需要说明,如果你比较懒,嫌部署到iis比较麻烦,安装Elasticsearch也比较麻烦,那么,你可以双击“Start.bat”这个脚本,它会自动帮你安装Elasticsearch,以及(当然,生产环境,还是建议自己搭建Elasticsearch的好) 4.如果出现下图所示,那么你就耐心的等等就行了,运行结束后会自动为您打开Exceptionless的管理页面 ,如果不幸,cmd里面出现红色字体,而且一闪就自动退出的话,那就执行下powershell Set-ExecutionPolicy Unrestricted 这个命令,然后再双击“Start.bat”这个脚本运行吧! 这里分win7(管理员身份运行cmd ,然后复制下面这条命令,按回车就行了 powershell Set-ExecutionPolicy Unrestricted) 以及 win10(管理员身份运行powershell,然后执行powershell Set-ExecutionPolicy Unrestricted) 5.如果全部安装成功后,会自动为你打开几个页面。还是先来看下目录结构吧,如下图所示,默认安装Elasticsearch是5.5.2 同时安装了kibana版本也是5.5.2 6.打开的几个页面如下图所示,然后在Exceptionless的页面,点击注册按钮注册一个账号,然后进行登录 7.注册成功后,进入如下的界面,在两个文本框输入,组织机构名称以及项目名称,用来对我们的项目的异常进行分类吧 8.下面进入项目类型配置界面,在1.select your project type下拉框选择asp.net core 9.出现下面的界面,说明配置完成,并且给出使用说明。到此Exceptionless的安装配置已经完成。 接下来我们通过一个实例项目进行使用说明吧 1.新建一个 netcore api项目,这一步应该难不倒你吧,我就不上图了。 2.在程序包管理器中,选中你的项目,然后输入“ Install-Package Exceptionless.AspNetCore”安装nuget包吧,当然也可以通过其他方式安装,就不介绍了 3.在startup.cs中添加 引用 using Exceptionless; 然后在Configure方法中添加Exceptionless管道信息 ExceptionlessClient.Default.Configuration.ApiKey = Configuration.GetSection("Exceptionless:ApiKey").Value; ExceptionlessClient.Default.Configuration.ServerUrl = Configuration.GetSection("Exceptionless:ServerUrl").Value; app.UseExceptionless(); 然后在appsettings.json中添加apikey以及serverurl的配置 "Exceptionless": { "ApiKey": "OvzcKg8V7bPcWU8yAYBVe6uCEKIAQm3xfEzW5yxp", "ServerUrl": "http://localhost:50000" } 好了,exceptionless的配置以及完成,接下来就是代码中使用了! 4.代码中使用异常,直接上代码吧!就是在ValuesController中修改下get方法进行下测试,代码很简单 // GET api/values [HttpGet] public ActionResult Get() { try { throw new Exception("ExceptionDemo 的异常"); } catch (Exception ex) { ex.ToExceptionless().Submit(); } return Ok(); } 5.运行起来吧。然后浏览器切换到exceptionless的面板进行查看吧,会自动刷新出现异常信息,如下图 http://localhost:50000/#!/project/5b2663e4e6c0b51dd015bdab/dashboard 6.点击进入可以查看详细信息 总结: 本文从Exceptionless是什么入手,然后介绍了Exceptionless的安装环境以及要求,接下来通过图文详细的介绍了Exceptionless的安装以及配置。最后通过一个Demo演示了如何在代码中使用Exceptionless,当然只是简单地一些使用!今天的关于asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程的介绍就到这里了! 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者: yilezhu(依乐祝) 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
今天使用Navicat 连接Oracle时晕倒了一些坑,特此记录一下! 楼主就是64位win10系统,安装的Navicat是64位的,刚开始配置32位的oci。配置后连接还是提示“Connot load OCI DLL,87:Instant Client package is required for Basic and TNS connetion. ” 然后按照上图重新打开OCI配置,发现刚刚选择的OCI路径那个文本框居然变成空的了!然后楼主去官网下载64位的InstantClient 然后解压到本地的一个目录,重新配置一下OCI,居然奇迹般的成功了! 下面附上详细的步骤! 1.连接前需要配置OCI路径,如下图所示!这里需要注意一下,电脑环境是64位的话需要下载64位的,切不可64位使用32位的客户端! 楼主就是64位win10系统,安装的Navicat是64位的,刚开始配置32位的oci。配置后连接还是提示“Connot load OCI DLL,87:Instant Client package is required for Basic and TNS connetion. ” 然后按照上图重新打开OCI配置,发现刚刚选择的OCI路径那个文本框居然变成空的了!然后楼主去官网下载64位的InstantClient 然后解压到本地的一个目录,重新配置一下OCI,居然奇迹般的成功了! 2.去官网下载InstantClient,解压后放到一个文件夹下面,再回到1的步骤配置OCI的路径 下载地址:http://www.oracle.com/technetwork/database/database-technologies/instant-client/downloads/index.html 根据你的Navicat位数选择你的对应版本,楼主选择64位的,点击后进入下一个页面 选择“Accept License Agressment” 然后下面选择版本进行下载,楼主选择的是最新版12.2版本,当然,你也可以往下拉选择11.2的版本进行下载 此处下载有可能需要你注册Oracle账号,然后登陆后才能下载!当然,你也可以点击这里下载楼主已经下载好了11.2的版本!其他版本的请自行下载吧! 链接:https://pan.baidu.com/s/19pvMN9zWExpoLp28R5r_pA 密码:k6vh 总结:安装的Navicat是64位的 那么就安装InstantClient 64位的,位数一定要一致才行! 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者: yilezhu(依乐祝) 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
之前碰到asp.net core异步进行新增操作并且需要判断某些字段是否重复的问题,进行插入操作的话会导致数据库中插入重复的字段!下面把我的解决方法记录一下,如果对您有所帮助,欢迎拍砖! 场景:EFCore操作MySql数据库的项目,进行高并发插入操作 需求:消息队列,最后进行新增数据的操作,插入前判断某些字段是否重复 问题:采用await db.SaveChangesAsync()进行提交操作前,FirstOrDefault判断数据库中是否有重复数据。测试100条一样的数据进行并发插入,结果数据库中插入成功四条重复数据! 原因分析:有可能是await db.SaveChangesAsync异步进行操作导致的时差问题! 解决方案: 第一种方案: 数据库中对表设置复合主键,即把需要判断不能重复的字段组合起来设置主键(不建议这种方式); 第二种方案:数据库插入操作采用同步的方式进行插入,即:await db.SaveChangesAsync() 改为 db.SaveChanges(); 第三种方案:数据库查询操作FirstOrDefault 以及数据库提交插入操作 await db.SaveChangesAsync() 放在一个数据库事务中即用 using (var tran = dBContext.Database.BeginTransaction())包裹下代码即可! 以上就是asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案!希望对您有所帮助! 如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力! 作者: yilezhu(依乐祝) 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!
-------------------------
-------------------------