VS2022
- vs2022
vs2022
下载及安装
一、下载
-
版本:Visual Studio 2022 版本:社区版、专业版、企业版;其中社区版是免费版本
-
下载加速:Visual Studio 2022 报错 "无法下载安装文件。请检查Internet连接,然后重试"的解决方案
- 解决方案1:
- 使用站长工具(网址:http://tool.chinaz.com/dns/ )查询域名:download.visualstudio.microsoft.com,TTL值越小响应越快。
- 根据查询结果,直接改本地host文件:如:183.131.119.137 download.visualstudio.microsoft.com
- 解决方案2:
- 网络设置中,修改首选DNS服务器:223.5.5.5 和备用DNS服务器:223.6.6.6。
- 然后在cmd中输入命令ipconfig/flushdns,刷新DNS解析缓存,下载完成后将DNS服务器修改过来
- 解决方案1:
二、安装
-
安装
全新安装:勾选相应工具包,选择安装位置,等待安装 -
增加功能
工具 >> 获取工具与功能 >> 点击打开模块安装器:installer
三、常见问题
-
VS2022登陆窗口一直空白的解决方案
-
打开VS2022,点击工具,打开选项。
-
找到账户,将使用以下方式添加账户改为系统Web浏览器,并将第一个选项取消勾选,如下图所示。
-
重新进行登陆操作,VS2022会自动切换到浏览器界面登陆,登陆成功后,重新打开VS2022就会发现成功登陆。
-
-
VS2022 Nuget找不到包的问题处理
-
点击右侧的设置按钮
-
弹出窗中左侧树形结构选择“程序包源”,再点击右上方的添加按钮:输入:
名称:nuget.org 源:https://api.nuget.org/v3/index.json
3.确定之后返回到Nuget主页面,这时我们将右上方的程序包源选择为“全部”或者刚添加的“nuget.org”,再次进行搜索
-
-
解决VS停止调试时浏览器自动关闭&关闭浏览器时自动停止调试
- 选择 工具->选项->项目和解决方案->Web项目 取消勾选”浏览器窗口关闭时停止调试程序“
-
VisualStudio2022 Razor 视图调试时前端修改后刷新无效的解决方法
-
安装 Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 包。
-
NuGet 程序包控制台命令 PM> Install-Package Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation
-
修改startup,在ConfigureServices 中新增 services.AddControllersWithViews().AddRazorRuntimeCompilation(); 注入。
-
从Vs2019 Asp.net Core项目中已经支持编码热重载
-
新建开发程序
一、新建解决方案,命名App
路径:文件/新建/项目
二、添加类库:CommonLib,
-
目标框架选net 6.0
-
在CommonLib类库中安装一下NuGet包(程序集)
Install-Package Microsoft.EntityFrameworkCore.SqlServer Install-Package Oracle.EntityFrameworkCore Install-Package Oracle.ManagedDataAccess.Core Install-Package ClosedXML Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson
-
命令方式安装:工具->NuGet包管理器->程序包管理器控制台
-
安装后效果如下(版本号会有不同),这些包会在后面操作中用到
三、新建项目
- 添加net Core空模版,命名Web_DEMO,
- net 6.0,开发模式下,可以取消配置HTTPS复选框
四、设置启动项,添加项目间引用
-
将Web_DEMO设置为启动项,添加项目之间的引用,
-
在Web_DEMO应用程序中引用CommonLib类库
五、启动项目
- 项目创建完成之后,点击IIS Express是否可以运行,浏览器显示Hello World,说明项目正常
六、dotnet new cli 以及Abp-cli命令的简单使用
配置MVC
一、配置M-V-C文件结构
- 在Web_DEMO下面新建Controllers、Models、Repository、Views、wwwroot 共5个文件夹;
- 在Views文件夹下,创建Shared文件夹;
- 在wwwroot文件夹下,创建css、img、js、lib共4个文件夹
- 文件结构下载地址:https://files.cnblogs.com/files/shiliumu/Web_DEMO.rar?t=1666436518
二、新建控制器
- Controllers文件夹下,右键添加控制器,选择空控制器,命名:HomeController.cs
-
HomeController.cs代码如下:
using Microsoft.AspNetCore.Mvc; namespace Web_DEMO.Controllers { public class HomeController : Controller { public IActionResult Index() { return View(); } } }
三、 添加视图,
- 右键Index方法,选择添加视图,Razor空视图
-
View文件夹下自动生成Home文件夹,Home文件夹下自动生成Index.cshtml文件,
-
在Index.cshtml文件中,添加
<h1>
标签
@{
}
<h1>我是mvc视图</h1>
四、添加模型,
- 右键Models文件夹,选择添加类,新建Login类,添加DataAnnotations引用
using System.ComponentModel.DataAnnotations;
namespace Web_DEMO.Models
{
public class Login
{
[Key]
public string loginId { get; set; }
public string loginPwd { get; set; }
}
}
五、配置项目启动文件Program.cs
1、注入服务
builder.Services.AddControllersWithViews(); //注入服务,控制器及视图
选择注入MVC选项
- builder.Services.AddMvcCore(); //只运行Controller/RazorPages必要的核心服务
- builder.Services.AddControllers(); //包含AddMvcCore()所做的动作外,再加身份验证服务
- builder.Services.AddRazorPages(); //要用Controller但不用View,例如WebAPI,可以选这个
- builder.Services.AddControllersWithViews(); //包含AddControllers()的所有项目,再加上:cshtml Razor View和Cache Tag Helper
- builder.Services.AddMvc(); //等于AddControllersWithViews() 加 AddRazorPages(),不想漏掉功能,发挥ASP.NET Core 最大威力
- 添加静态文件,注意添加位置,在路由app.UseRouting();上面
app.UseStaticFiles(); //使用默认文件夹wwwroot
- 修改路由
//app.MapGet("/", () => "Hello World!"); //注释原路由,使用控制器路由
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
- 配置好的Program.cs文件如下:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews(); //注入服务,控制器及视图
var app = builder.Build();
app.UseStaticFiles(); //使用默认文件夹wwwroot
//app.MapGet("/", () => "Hello World!"); //注释原路由,使用控制器路由
app.UseRouting();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
5、F5运行,显示“我是mvc视图”
配置View视图端
一、添加Razor视图开始和视图导入
- View文件夹右键,添加Razor视图,类型选择“Razor视图-空”
- 依次添加“Razor视图开始”和“Razor视图导入”,文件名系统默认:_ViewStart.cshtml、_ViewImports.cshtml
- _ViewStart.cshtml在View开始执行之前执行,而且是每一个View,一般在里面设置“母版页”值,当作_layout.cshtml文件的布局属性来使用,内容只有一行:
@{
Layout = "_Layout";
}
- _ViewImports.cshtml是放一些要引用的命名空间,预设的ASP.NET Core MVC专案就有一个/Views/_ViewImports.cshtml,其内容为:
@using Web_DEMO;
@using Web_DEMO.Models; @*//如果Models文件夹下不存在模型,会提示错误*@)
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
- 之后的每一个View 就不用再引用这些命名空间,与_ViewStart.cshtml 一样,可以为不同的Controller、不同的Area 增加_ViewImports.cshtml
二、添加Razor视图布局页,即"母版页"
- View->Shared文件夹右键,添加Razor视图,类型选择“Razor视图-空”,添加”Razor布局“,文件名系统默认:_Layout.cshtml
- 在_Layout.cshtml文件中,@RenderBody()代码下
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
@await RenderSectionAsync("Scripts", required: false) @*添加*@
</div>
</body>
</html>
三、View添加客户端库
- 客户端库
-
LibMan功能类似于Nuget,Nuget是包管理器(.NET),LibMan是库管理器(CSS/JS)
-
库管理器(LibMan) 是一个轻量型客户端库获取工具。 LibMan可从文件系统或从内容分发网络(CDN)下载库和框架。 支持的CDN包括 CDNJS、jsDelivr和unpkg。 将提取所选库文件,并将其置于ASP.NET Core项目中的相应位置。
-
从“提供程序”下拉列表中选择库提供程序。 CDNJS是默认提供程序。选unpkg的话实际上就是npm。
-
例如要安装bootstrap,输入bootstrap时,会提供以所提供文本开头的库的列表,选择后会显示bootstrap的最新版本。库名称后缀带有 @ 符号和所选提供程序的已知最新稳定版本。
-
以下下分别安装bootstrap、jquery、jquery-validation、jquery-easyui
-
在lib文件夹下添加客户端库
-
安装bootstrap
-
可以根据需要进行勾选安装
-
安装完成后,lib文件夹下添加客户端库;并在根目录下自动添加libman.json文件,记录已安装的客户端库信息
-
其他客户端库添加相同
-
-
安装jquery-easyui
- 选@hirohe这项,这个包含相应版本的jquery文件,目前是v1.12
- 自定义的选择特定文件如下:
|-- locale | |-- easyui-lang-zh_CN.js |-- themes | |-- color.css | |-- icon.css | |-- bootstrap | | |-- easyui.css | |-- icons | | |-- ***.png |-- jquery.easyui.min.js |-- jquery.min.js // jquery
-
在home的index.cshtml中配置easyui框架
- 修改Views/Home/Index.cshtml的内容,替换如下
@{ Layout = null; } <head> <meta name="viewport" content="width=device-width" /> <title>石榴木工作室</title> <link href="~/lib/hirohe/jquery-easyui/themes/color.css" rel="stylesheet" /> <link href="~/lib/hirohe/jquery-easyui/themes/icon.css" rel="stylesheet" /> <link href="~/lib/hirohe/jquery-easyui/themes/bootstrap/easyui.css" rel="stylesheet" /> <script src="~/lib/hirohe/jquery-easyui/jquery.min.js"></script> <script src="~/lib/hirohe/jquery-easyui/jquery.easyui.min.js"></script> </head> <body class="easyui-layout"> <div data-options="region:'north',border:false" style="height: 40px; background-color: #F5F5F5; padding: 10px;"> 石榴木工作室 </div> <div data-options="region:'west',title:'功能导航',split:true" style="width: 180px; padding-top: 2px; overflow:auto"> <div class="easyui-panel" style="padding:5px" data-options="border:false"> <ul class="easyui-tree" id="tree" data-options="border:false"> <li> <span>目录</span> <ul> <li>菜单一</li> <li>菜单二</li> <li>菜单三</li> </ul> </li> </ul> </div> </div> <div data-options="region:'center'"> <div id="mainTab" class="easyui-tabs" data-options="fit:true, tools:[ {iconCls:'icon-reload',text:'刷新', handler:refreshTab }, {iconCls:'icon-cancel',text:'关闭全部', handler:closeTab} ]"> <div id="home" title="我的主页" data-options="closable:false,fit:true" style="padding:3px"> <iframe scrolling="auto" frameborder="0" src="" style="width: 100%; height: 100%;"></iframe> </div> </div> </div> </body> <script> //菜单树 $('#tree').tree({ onClick: function (node) { //节点的点击事件 if (node.text == '菜单一') { var url = '/Login'; }; if (node.text == '菜单二') { var url = '/SysSampleMVC'; }; if (node.text == '菜单三') { var url = '/SysSampleEasyUI'; }; if ($('#tree').tree('isLeaf', node.target)) { //判断是否是叶子节点 addTab(node.text, url); } else { //如果是父节点,单击文本切换展开/折叠节点的状态 $(this).tree('toggle', node.target); } } }); //增加选项卡 function addTab(subtitle, url) { if (!$("#mainTab").tabs('exists', subtitle)) { //若选项卡不存在,生成新的选项卡 var con = '<iframe frameborder="0" scrolling="auto" style="width:99.5%; height:99%" src="' + url + '"></iframe>'; $("#mainTab").tabs('add', { title: subtitle, content: con, closable: true, loadingMessage: '加载中......' }); } else { //若选项卡已存在,选择该选项卡 $("#mainTab").tabs('select', subtitle); $("#tab_menu-tabrefresh").trigger("click"); } } //刷新选项卡 function refreshTab() { var index = $('#mainTab').tabs('getTabIndex', $('#mainTab').tabs('getSelected')); if (index != -1) { var tab = $('#mainTab').tabs('getTab', index); $('#mainTab').tabs('update', { tab: tab, options: { selected: true } }); } } //关闭选项卡 function closeTab() { $('.tabs-inner span').each(function (i, n) { var t = $(n).text(); if (t != '') { if (t != "我的主页") { $('#mainTab').tabs('close', t); } } }); } </script>
- F5运行,运行结果如下
- 修改Views/Shared/_Layout.cshtml的内容,替换如下
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <link href="~/lib/hirohe/jquery-easyui/themes/color.css" rel="stylesheet" /> <link href="~/lib/hirohe/jquery-easyui/themes/icon.css" rel="stylesheet" /> <link href="~/lib/hirohe/jquery-easyui/themes/bootstrap/easyui.css" rel="stylesheet" /> <link href="~/lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" /> <script src="~/lib/hirohe/jquery-easyui/jquery.min.js"></script> <script src="~/lib/hirohe/jquery-easyui/jquery.easyui.min.js"></script> <script src="~/lib/hirohe/jquery-easyui/locale/easyui-lang-zh_CN.js"></script> <script src="~/lib/hirohe/jquery-easyui/jquery.easyui-plus.js"></script> <script src="~/js/site.js" ></script> </head> <body> <div> @RenderBody() @await RenderSectionAsync("Scripts", required: false) @*添加*@ </div> </body> </html>
使用DB first创建数据库并使用Migration进行迁移
一、添加数据上下文
- 创建数据库上下文
-
在Repository文件夹中添加DemoDbContext类,继承DbContext,DemoDbContext中包括包含了所有逻辑,比如与数据库交互、数据变化追踪等。添加引用:using Microsoft.EntityFrameworkCore;
-
将实体类Login添加到Dbset,添加引用:using Web_DEMO.Models;
-
当数据库创建完成后, EF 创建一系列数据表,表名默认和 DbSet 属性名相同。 集合的属性名通常采用复数形式。 例如,使用 Students,而不使用 Student,OnModelCreating方法,在 DbContext 中指定单数形式的表名称会覆盖默认行为。
using Microsoft.EntityFrameworkCore;
using Web_DEMO.Models;
namespace Web_DEMO.Repository
{
public class DemoDbContext: DbContext
{
/// <summary>
/// 配置数据库连接
/// </summary>
/// <param name="options"></param>
public DemoDbContext(DbContextOptions<DemoDbContext> options) : base(options)
{
}
/// <summary>
/// 实体类创建Dbset属性,添加到DbSet
/// </summary>
public DbSet<Login> logins { get; set; }
/// <summary>
/// EF表名默认和DbSet属性名相同,覆盖默认行为,数据库中生成单数形式表名
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Login>().ToTable("Login");
}
}
}
- 注入服务
在Program.cs中注入DemoDbContext,同时添加添加数据库异常筛选器:
builder.Services.AddDbContext<DemoDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DemoConnection")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); //数据库异常筛选器
- 在系统配置文件appsettings.json中设置连接字符串
"ConnectionStrings": {
"DemoConnection": "server=127.0.0.1;database=demo;uid=sa;pwd=1234"
},
如何读取appsettings.json配置
- 实体类的构造函数中使用IConfiguration,如在Home控制器中
private IConfiguration _configuration { get; } public HomeController(IConfiguration configuration) { _configuration = configuration; }
- 使用方法
//一级目录 _configuration["xxxx"]; //二级目录下的文件 _configuration["xxxx:yyyy"]; //获取并转为某某类型 _configuration.GetValue<int>("xxx"); //获取并序列化为某某类型 _configuration.GetSection("xxx").Get<某某类型>(;)
- 可以直接将连接字符串写入DbContext,并注入到Program.cs中
public class DemoDbContext: DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(@"server=127.0.0.1;database=demo;uid=sa;pwd=1234");
}
public DbSet<Login> logins { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Login>().ToTable("Login");
}
}
二、创建数据库配置Migration
- 在程序包管理器控制台中,运行
PM> add-migration init
-
初始化成功后,控制台返回“Build succeeded”信息。同时,项目中生成一个Migrations文件夹,里面是迁移记录。
-
其中:xxx_init.cs是主迁移文件,包含应用迁移所需的操作 Up() 和还原迁移所需的操作Down()
-
xxx_init.Designe.cs迁移元数据文件。 包含 EF 所用的信息,如给实体类型构建属性、主键、外键、索引、映射到数据表,主体和依赖关系等等。
- 在程序包管理器控制台中,运行
PM> update-database
-
数据库创建成功后,控制台返回“Done.”信息。
-
可以查看SQL server数据库,已经生成数据库和数据表
- 常见错误及解决:
-
错误1:ScriptHalted。
解决:PowerShell版本低,在官网下载对应版本,网址:https://www.microsoft.com/en-us/download/details.aspx?id=54616 -
错误2:无法将“add-migration”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
解决:
step1:设置当前用户作用域具备cmd> powershell PS > Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
step2:找到EntityFrameworkCore.psd1的全路径,在程序包管理控制台中执行,执行命令:"import-module ...\EntityFrameworkCore.psd1的全路径 "
PM> import-module C:\Users\Administrator\.nuget\packages\microsoft.entityframeworkcore.tools\6.0.4\tools\EntityFrameworkCore.psd1 PM> Get-Verb
-
错误3:Your startup project 'Web_DEMO' doesn't reference Microsoft.EntityFrameworkCore.Design.
解决:缺少NuGet包,在在该项目中安装:Install-Package Microsoft.EntityFrameworkCore.Tools -
错误4:对象'PK_xxx' 依赖于 列'xxx'。由于一个或多个对象访问此列,ALTER TABLE ALTER COLUMN xxx 失败。
解决:sqlserve中直接删除箭的约束,或使用语句alter table TableName drop constraint PK_xxx
- Code First 数据注释
使用 DataAnnotations,可以将 Code First 类映射到预先存在的数据库,常用
[Key]
[Required]
[StringLength(50)]
[Display(Name = "First Name")]
[Column("FirstName")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
三、添加控制器
- 创建Login控制器
using Microsoft.AspNetCore.Mvc;
namespace Web_DEMO.Controllers
{
public class LoginController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
- 生成构造函数LoginController(),快捷键(ctor+两次Tab)
- 定义context,将数据上下文DemoDbContext 作为构造函数参数,依赖关系注入将 DemoDbContext 实例传递到控制器,添加using引用
- 完整代码如下
using Microsoft.AspNetCore.Mvc;
using Web_DEMO.Repository;
namespace Web_DEMO.Controllers
{
public class LoginController : Controller
{
private readonly DemoDbContext context;
public LoginController(DemoDbContext context)
{
_context = context;
}
public IActionResult Index()
{
return View();
}
}
}
- 修改Index()方法
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Web_DEMO.Repository;
namespace Web_DEMO.Controllers
{
public class LoginController : Controller
{
private readonly DemoDbContext context;
public LoginController(DemoDbContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.logins.ToListAsync());
}
}
}
使用基架自动创建控制器和视图
一、添加控制器
-
右键Controllers文件夹,选择添加使用EF的MVC控制器
-
选择模型类和数据上下文
自动生成的Controller,包含增删查改
public class LoginController : Controller
{
private readonly DemoDbContext _context;
public LoginController(DemoDbContext context)
{
_context = context;
}
public async Task<IActionResult> Index()
{
return View(await _context.logins.ToListAsync());
}
public async Task<IActionResult> Details(string id)
{
if (id == null || _context.logins == null)
{
return NotFound();
}
var login = await _context.logins.FirstOrDefaultAsync(m => m.loginId == id);
if (login == null)
{
return NotFound();
}
return View(login);
}
public IActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("loginId,loginPwd")] Login login)
{
if (ModelState.IsValid)
{
_context.Add(login);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(login);
}
public async Task<IActionResult> Edit(string id)
{
if (id == null || _context.logins == null)
{
return NotFound();
}
var login = await _context.logins.FindAsync(id);
if (login == null)
{
return NotFound();
}
return View(login);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("loginId,loginPwd")] Login login)
{
if (id != login.loginId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(login);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!LoginExists(login.loginId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(login);
}
public async Task<IActionResult> Delete(string id)
{
if (id == null || _context.logins == null)
{
return NotFound();
}
var login = await _context.logins
.FirstOrDefaultAsync(m => m.loginId == id);
if (login == null)
{
return NotFound();
}
return View(login);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(string id)
{
if (_context.logins == null)
{
return Problem("Entity set 'DemoDbContext.logins' is null.");
}
var login = await _context.logins.FindAsync(id);
if (login != null)
{
_context.logins.Remove(login);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool LoginExists(string id)
{
return _context.logins.Any(e => e.loginId == id);
}
}
自动生成的对应的视图文件
- Index.cshtml
@model IEnumerable<Web_DEMO.Models.Login>
@{
ViewData["Title"] = "Index";
}
<p>
<a asp-action="Create">创建</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.loginId)
</th>
<th>
@Html.DisplayNameFor(model => model.loginPwd)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.loginId)
</td>
<td>
@Html.DisplayFor(modelItem => item.loginPwd)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.loginId">修改</a> |
<a asp-action="Details" asp-route-id="@item.loginId">明细</a> |
<a asp-action="Delete" asp-route-id="@item.loginId">删除</a>
</td>
</tr>
}
</tbody>
</table>
- Create.cshtml
@model Web_DEMO.Models.Login
@{
ViewData["Title"] = "Create";
}
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="loginId" class="control-label"></label>
<input asp-for="loginId" class="form-control" />
<span asp-validation-for="loginId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="loginPwd" class="control-label"></label>
<input asp-for="loginPwd" class="form-control" />
<span asp-validation-for="loginPwd" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
- Edit.cshtml
@model Web_DEMO.Models.Login
@{
ViewData["Title"] = "Edit";
}
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<input type="hidden" asp-for="loginId" />
<div class="form-group">
<label asp-for="loginPwd" class="control-label"></label>
<input asp-for="loginPwd" class="form-control" />
<span asp-validation-for="loginPwd" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
- Details.cshtml
@model Web_DEMO.Models.Login
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Login</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.loginId)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.loginId)
</dd>
</dl>
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.loginPwd)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.loginPwd)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model?.loginId">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
- Delete.cshtml
@model Web_DEMO.Models.Login
@{
ViewData["Title"] = "Details";
}
<h1>Details</h1>
<div>
<h4>Login</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.loginId)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.loginId)
</dd>
</dl>
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.loginPwd)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.loginPwd)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model?.loginId">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>
添加Web API项目
一、添加ASP.NET Core Web API项目
-
右键解决方案,依次选择:添加、新建项目
-
语言:C#,项目类型:Web或WebAPI,模版:ASP.NET Core Web API,命名WebApi_DEMO
-
选择目标框架
配置HTTPS默认是勾选的,也可取消
-
创建后,将WebApi_DEMO设为默认启动项,添加对CommonLib的引用
-
直接F5运行,如果是新系统首次运行会提示安装证书
-
运行界面如下
- 依次点击:GET、Try it out、Execute,显示明细信息,API项目创建完成
二、配置跨域,为前端提供服务
- 在Program.cs文件中配置跨域,红色代码部分
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
//配置Swagger
builder.Services.AddSwaggerGen();
//配置跨域策略
builder.Services.AddCors(options =>{
options.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
});
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
//使用跨域策略
app.UseCors("CorsPolicy");
app.MapControllers();
app.Run();
- 测试跨域,前端使用VUE
<template>
<button type="button" @click="getWeather">天气预报</button>
</template>
<script setup>
// 安装axios,npm install axios
import axios from "axios";
const getWeather = async () => {
console.log("显示天气预报......");
const res = await axios.get("http://127.0.0.1:8888/WeatherForecast");
console.log(res.data);
};
</script>
- 前端成功获取数据
三、Swagger配置
- 【解决方法资源管理器】--【右击项目名称】-【属性】-【生成】-【输出】
-
选中文档文件-生成包含API文档的文件,
-
默认地址为bin\Debug\net6.0\xxx.xml,也可以自定义地址,在指定地址新建个空的xml文件即可。
- 在Program.cs文件中配置Swagger,修改AddSwaggerGen代码
//修改前
builder.Services.AddSwaggerGen();
//配置Swagger后
builder.Services.AddSwaggerGen(options => {
#region 设置API文档信息
var ApiName = "NetCore项目框架";
options.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Title = $"{ApiName} 接口文档——Net 6",
Description = $"{ApiName} HTTP API v1",
});
options.OrderActionsBy(o => o.RelativePath);
#endregion
#region 设置接口注释
//using System.Reflection;
//获取xml注释文件目录
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename);
//默认的第二个参数是false,这个是controller的注释,true时会显示注释,否则只显示方法注释
options.IncludeXmlComments(xmlPath, true);
var xmlModelname = "Medel.xml";//Model层的xml文件名
var xmlModelPath = Path.Combine(AppContext.BaseDirectory, xmlModelname);
#endregion
#region 设置token
//using Microsoft.OpenApi.Models;
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme(){
Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer {token}(注意两者之间是一个空格)",
Name = "Authorization", //jwt默认的参数名称
In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer"
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
#endregion
});
- 在WeatherForecast控制器的Get()方法上,添加注释:天气预报
- F5运行
Web API服务端添加jwtToken验证
一、token验证机制
-
用户使用用户名密码来请求服务器
-
服务器进行验证用户的信息
-
服务器通过验证发送给用户一个token
-
客户端存储token,并在每次请求时附送这个token值,附带在http请求的header里
-
服务端验证token值,并返回数据
二、CommonLib库
- 添加工具包
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.IdentityModel.Tokens
Install-Package System.IdentityModel.Tokens.Jwt
Install-Package Newtonsoft.Json
- CommonLib库下,添加ResultModel.cs和JWTService.cs
- ResultModel.cs
namespace CommonLib
{
public class ResultModel
{
public bool success { get; set; }
public string? message { get; set; }
public int count { get; set; }
public object data { get; set; }
}
}
- JWTService.cs
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace CommonLib
{
public static class JWTService
{
/// <summary>
/// 创建token字符串
/// </summary>
/// <param name="model"></param>
/// <returns></returns>
public static string CreateToken(TokenModel model)
{
//将用户的部分信息存到了Claim中,using System.Security.Claims;
var claims = new List<Claim> {
new Claim("UserId",model.UserId),
new Claim("UserName",model.UserName),
new Claim("NickName",model.NickName),
};
// 用户的多个角色(如:Admin,System)全部赋予
if (!string.IsNullOrWhiteSpace(model.Role))
{
claims.AddRange(model.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
claims.Add(new Claim("Role", model.Role));
}
//秘钥,using Microsoft.IdentityModel.Tokens;
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(model.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);//密钥和加密方式
//using System.IdentityModel.Tokens.Jwt;
var jwt = new JwtSecurityToken(
issuer: model.Issuer, //颁发者
audience: model.Audience, //受众
expires: DateTime.Now.AddMinutes(60), //有效期
signingCredentials: creds,
claims: claims
);
var jwtHandler = new JwtSecurityTokenHandler();
var token = jwtHandler.WriteToken(jwt);
return token;
}
/// <summary>
/// 解析token字符串,不检查有效性
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public static TokenModel SerializeToken(string token)
{
//不校验,直接解析token
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);
//using Newtonsoft.Json;
var tokenModel = JsonConvert.DeserializeObject<TokenModel>(jwtToken.Payload.SerializeToJson());
return tokenModel;
}
}
/// <summary>
/// 令牌
/// </summary>
public class TokenModel
{
public string UserId { get; set; }
public string UserName { get; set; }
public string NickName { get; set; }
public string Role { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public string SecurityKey { get; set; }
}
}
三、WebApi_DEMO项目下
- 在appsettings.json中配置jwt参数的值
{
"Jwt": {
"Issuer": "jwtIssuer", //颁发者
"Audience": "jwtAudience", //可以给哪些客户端使用
"SecurityKey": "abcdefghijklmnopqrstuvwxyz" //加密的Key,大于16位的字符串
},
}
- 在Program.cs中配置jwt鉴权
//配置Jwt鉴权
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
//using Microsoft.AspNetCore.Authentication.JwtBearer;
.AddJwtBearer(options =>{
var tokenModel = builder.Configuration.GetSection("Jwt").Get<TokenModel>();
var secretByte = Encoding.UTF8.GetBytes(tokenModel.SecurityKey);
//using Microsoft.IdentityModel.Tokens;
options.TokenValidationParameters = new TokenValidationParameters(){
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
ValidIssuer = tokenModel.Issuer,
ValidAudience = tokenModel.Audience, //这两项和前面签发jwt的设置一致
IssuerSigningKey = new SymmetricSecurityKey(secretByte) // 拿到SecurityKey
};
options.Events = new JwtBearerEvents{
OnChallenge = context =>{
return Task.FromResult(0);
},
//403
OnForbidden = context =>{
return Task.FromResult(0);
}
};
});
- 在Program.cs中使用jwt鉴权
//使用鉴权授权
app.UseAuthentication();
app.UseAuthorization();
四、使用EF构建登录信息,获取token
- 新建数据库表sys_User
create table sys_User(
id nvarchar(50) PRIMARY KEY NOT NULL,
name nvarchar(50) NULL,
nickname nvarchar(50) NULL,
password nvarchar(50) NOT NULL,
role_id nvarchar(50) NOT NULL
)
go
--添加测试数据
insert into sys_User values ('zhou','周金辉','生之韵','123','admin')
insert into sys_User values ('an','安安','安若子渔','456','system')
go
- WebApi_DEMO项目下,新建Model文件夹,添加sys_User.cs
public class sys_User
{
[Key]
public string id { get; set; }
public string name { get; set; }
public string nickname { get; set; }
public string password { get; set; }
public string role_id { get; set; }
}
- WebApi_DEMO项目下,新建Repository文件夹,添加DemoDbContext.cs
namespace WebApi_DEMO.Repository
{
public class DemoDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"server=127.0.0.1;database=demo;uid=sa;pwd=1234");
}
public DbSet<sys_User> users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<sys_User>().ToTable("sys_User");
}
}
}
- WebApi_DEMO项目下,Program.cs文件注入服务
builder.Services.AddDbContext<DemoDbContext>();
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); //数据库异常筛选器
- WebApi_DEMO项目下,新建API控制器,添加sys_LoginController
using CommonLib;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using WebApi_DEMO.Repository;
namespace WebApi_DEMO.Controllers
{
/// <summary>
/// 用户登录
/// </summary>
[Route("api/[controller]/[action]")]
[ApiController]
public class sys_LoginController : ControllerBase
{
private readonly DemoDbContext _context;
private IConfiguration _configuration;
public sys_LoginController(DemoDbContext context, IConfiguration configuration)
{
_context = context;
_configuration = configuration;
}
/// <summary>
/// 登陆获取token
/// </summary>
/// <param name="UId">用户ID</param>
/// <param name="Pwd">密码</param>
/// <returns></returns>
[HttpGet]
public async Task<ResultModel> LoginIn(string UId, string Pwd)
{
var user = await _context.users.Where(u => u.id == UId && u.password == Pwd).FirstOrDefaultAsync();
if (user != null)
{
//读取配置信息中的jwt公共数据
var tokenModel = _configuration.GetSection("Jwt").Get<TokenModel>();
//读取用户传入的信息
tokenModel.UserId = user.id;
tokenModel.UserName = user.name;
tokenModel.NickName = user.nickname;
tokenModel.Role = user.role_id;
var token = JWTService.CreateToken(tokenModel);
return new ResultModel
{
success = true,
message = "登陆成功",
count = token.Count(),
data = token
};
}
return new ResultModel
{
success = false,
message = "登陆失败",
count = 0,
data = ""
};
}
/// <summary>
/// 通过token解析用户信息
/// </summary>
/// <param name="Authorization"></param>
/// <returns></returns>
[HttpGet]
[Authorize]
public IActionResult DeToken([FromHeader] string Authorization)
{
var str = Authorization.Replace("Bearer ", "");
var token = JWTService.SerializeToken(str);
return Ok(token);
}
}
}
-
F5运行,运行结果如下
获取的token字符串,可以登录https://jwt.io/ 网站解析token信息
五、前端获取
<template>
<button type="button" @click="getToken">获取token</button>
</template>
<script setup>
// 安装axios,npm install axios
import axios from "axios";
// 安装jwt-decode,npm install jwt-decode --save
import jwttoken from "jwt-decode";
const url = "http://127.0.0.1:8888/api/sys_Login/LoginIn";
const getToken = async () => {
axios
.get(url, {
params: {
UId: "zhou",
Pwd: "123",
},
})
.then((res) => {
console.log(res.data);
sessionStorage.token = res.data.data;
sessionStorage.userInfo = jwttoken(res.data.data).NickName;
});
};
</script>