VS2022

vs2022

下载及安装

一、下载
  1. 版本:Visual Studio 2022 版本:社区版、专业版、企业版;其中社区版是免费版本

  2. 下载地址:https://visualstudio.microsoft.com/zh-hans/vs/

  3. 下载加速:Visual Studio 2022 报错 "无法下载安装文件。请检查Internet连接,然后重试"的解决方案

    1. 解决方案1:
      1. 使用站长工具(网址:http://tool.chinaz.com/dns/ )查询域名:download.visualstudio.microsoft.com,TTL值越小响应越快。
      2. 根据查询结果,直接改本地host文件:如:183.131.119.137 download.visualstudio.microsoft.com
    2. 解决方案2:
      1. 网络设置中,修改首选DNS服务器:223.5.5.5 和备用DNS服务器:223.6.6.6。
      2. 然后在cmd中输入命令ipconfig/flushdns,刷新DNS解析缓存,下载完成后将DNS服务器修改过来
二、安装
  1. 安装
    全新安装:勾选相应工具包,选择安装位置,等待安装

  2. 增加功能
    工具 >> 获取工具与功能 >> 点击打开模块安装器:installer

三、常见问题
  1. VS2022登陆窗口一直空白的解决方案

    1. 打开VS2022,点击工具,打开选项。

    2. 找到账户,将使用以下方式添加账户改为系统Web浏览器,并将第一个选项取消勾选,如下图所示。

    3. 重新进行登陆操作,VS2022会自动切换到浏览器界面登陆,登陆成功后,重新打开VS2022就会发现成功登陆。

  2. VS2022 Nuget找不到包的问题处理

    1. 点击右侧的设置按钮

    2. 弹出窗中左侧树形结构选择“程序包源”,再点击右上方的添加按钮:输入:

    名称:nuget.org
    源:https://api.nuget.org/v3/index.json     
    

    3.确定之后返回到Nuget主页面,这时我们将右上方的程序包源选择为“全部”或者刚添加的“nuget.org”,再次进行搜索

  3. 解决VS停止调试时浏览器自动关闭&关闭浏览器时自动停止调试

    1. 选择 工具->选项->项目和解决方案->Web项目 取消勾选”浏览器窗口关闭时停止调试程序“

  4. VisualStudio2022 Razor 视图调试时前端修改后刷新无效的解决方法

    1. 安装 Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation 包。

    2. NuGet 程序包控制台命令 PM> Install-Package Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation

    3. 修改startup,在ConfigureServices 中新增 services.AddControllersWithViews().AddRazorRuntimeCompilation(); 注入。

    4. 从Vs2019 Asp.net Core项目中已经支持编码热重载

新建开发程序

一、新建解决方案,命名App

路径:文件/新建/项目

二、添加类库:CommonLib,
  1. 目标框架选net 6.0

  2. 在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
    
  3. 命令方式安装:工具->NuGet包管理器->程序包管理器控制台

  4. 安装后效果如下(版本号会有不同),这些包会在后面操作中用到

三、新建项目
  1. 添加net Core空模版,命名Web_DEMO,

  1. net 6.0,开发模式下,可以取消配置HTTPS复选框
四、设置启动项,添加项目间引用
  1. 将Web_DEMO设置为启动项,添加项目之间的引用,

  2. 在Web_DEMO应用程序中引用CommonLib类库

五、启动项目
  1. 项目创建完成之后,点击IIS Express是否可以运行,浏览器显示Hello World,说明项目正常
六、dotnet new cli 以及Abp-cli命令的简单使用

配置MVC

一、配置M-V-C文件结构

二、新建控制器
  1. Controllers文件夹下,右键添加控制器,选择空控制器,命名:HomeController.cs

  1. HomeController.cs代码如下:

    using Microsoft.AspNetCore.Mvc;
    namespace Web_DEMO.Controllers
    {
        public class HomeController : Controller
        {
            public IActionResult Index()
            {
                return View();
            }
        }
    }
    
三、 添加视图,
  1. 右键Index方法,选择添加视图,Razor空视图

  1. View文件夹下自动生成Home文件夹,Home文件夹下自动生成Index.cshtml文件,

  2. 在Index.cshtml文件中,添加<h1>标签

@{
}
<h1>我是mvc视图</h1>
四、添加模型,
  1. 右键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选项

  1. builder.Services.AddMvcCore(); //只运行Controller/RazorPages必要的核心服务
  2. builder.Services.AddControllers(); //包含AddMvcCore()所做的动作外,再加身份验证服务
  3. builder.Services.AddRazorPages(); //要用Controller但不用View,例如WebAPI,可以选这个
  4. builder.Services.AddControllersWithViews(); //包含AddControllers()的所有项目,再加上:cshtml Razor View和Cache Tag Helper
  5. builder.Services.AddMvc(); //等于AddControllersWithViews() 加 AddRazorPages(),不想漏掉功能,发挥ASP.NET Core 最大威力
  1. 添加静态文件,注意添加位置,在路由app.UseRouting();上面
app.UseStaticFiles();    //使用默认文件夹wwwroot
  1. 修改路由
//app.MapGet("/", () => "Hello World!");  //注释原路由,使用控制器路由
app.UseRouting();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
  1. 配置好的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视图开始和视图导入
  1. View文件夹右键,添加Razor视图,类型选择“Razor视图-空”

  1. 依次添加“Razor视图开始”和“Razor视图导入”,文件名系统默认:_ViewStart.cshtml、_ViewImports.cshtml

  1. _ViewStart.cshtml在View开始执行之前执行,而且是每一个View,一般在里面设置“母版页”值,当作_layout.cshtml文件的布局属性来使用,内容只有一行:
@{
    Layout = "_Layout";
}
  1. _ViewImports.cshtml是放一些要引用的命名空间,预设的ASP.NET Core MVC专案就有一个/Views/_ViewImports.cshtml,其内容为:
@using Web_DEMO;
@using Web_DEMO.Models;   @*//如果Models文件夹下不存在模型,会提示错误*@)
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
  1. 之后的每一个View 就不用再引用这些命名空间,与_ViewStart.cshtml 一样,可以为不同的Controller、不同的Area 增加_ViewImports.cshtml
二、添加Razor视图布局页,即"母版页"
  1. View->Shared文件夹右键,添加Razor视图,类型选择“Razor视图-空”,添加”Razor布局“,文件名系统默认:_Layout.cshtml

  1. 在_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添加客户端库
  1. 客户端库
  • 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

  1. 在lib文件夹下添加客户端库

  2. 安装bootstrap

    1. 可以根据需要进行勾选安装

    2. 安装完成后,lib文件夹下添加客户端库;并在根目录下自动添加libman.json文件,记录已安装的客户端库信息

    3. 其他客户端库添加相同

  3. 安装jquery-easyui

    1. 选@hirohe这项,这个包含相应版本的jquery文件,目前是v1.12

    1. 自定义的选择特定文件如下:
    |-- locale
    |  |-- easyui-lang-zh_CN.js
    |-- themes 
    |  |-- color.css
    |  |-- icon.css
    |  |-- bootstrap
    |  |  |-- easyui.css
    |  |-- icons
    |  |  |-- ***.png
    |-- jquery.easyui.min.js   
    |-- jquery.min.js       // jquery
    

    1. 在home的index.cshtml中配置easyui框架

      1. 修改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>
      
      1. F5运行,运行结果如下

      1. 修改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进行迁移

一、添加数据上下文
  1. 创建数据库上下文
  • 在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");
        }
    }
}
  1. 注入服务

在Program.cs中注入DemoDbContext,同时添加添加数据库异常筛选器:

builder.Services.AddDbContext<DemoDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DemoConnection")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); //数据库异常筛选器
  1. 在系统配置文件appsettings.json中设置连接字符串
  "ConnectionStrings": {
    "DemoConnection": "server=127.0.0.1;database=demo;uid=sa;pwd=1234"
  },

如何读取appsettings.json配置

  1. 实体类的构造函数中使用IConfiguration,如在Home控制器中
  private IConfiguration _configuration { get; }
  public HomeController(IConfiguration configuration)
  {
      _configuration = configuration;
  }
  1. 使用方法
//一级目录
_configuration["xxxx"];
//二级目录下的文件
_configuration["xxxx:yyyy"];
//获取并转为某某类型
_configuration.GetValue<int>("xxx");
//获取并序列化为某某类型
_configuration.GetSection("xxx").Get<某某类型>(;)
  1. 可以直接将连接字符串写入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
  1. 在程序包管理器控制台中,运行
PM> add-migration init
  • 初始化成功后,控制台返回“Build succeeded”信息。同时,项目中生成一个Migrations文件夹,里面是迁移记录。

  • 其中:xxx_init.cs是主迁移文件,包含应用迁移所需的操作 Up() 和还原迁移所需的操作Down()

  • xxx_init.Designe.cs迁移元数据文件。 包含 EF 所用的信息,如给实体类型构建属性、主键、外键、索引、映射到数据表,主体和依赖关系等等。

  1. 在程序包管理器控制台中,运行
PM> update-database
  • 数据库创建成功后,控制台返回“Done.”信息。

  • 可以查看SQL server数据库,已经生成数据库和数据表

  1. 常见错误及解决:
  • 错误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

  1. 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)]
三、添加控制器
  1. 创建Login控制器
using Microsoft.AspNetCore.Mvc;
namespace Web_DEMO.Controllers
{
    public class LoginController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}
  1. 生成构造函数LoginController(),快捷键(ctor+两次Tab)

  1. 定义context,将数据上下文DemoDbContext 作为构造函数参数,依赖关系注入将 DemoDbContext 实例传递到控制器,添加using引用

  1. 完整代码如下
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();
        }
    }
}
  1. 修改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());
        }
    }
}

使用基架自动创建控制器和视图

一、添加控制器
  1. 右键Controllers文件夹,选择添加使用EF的MVC控制器

  2. 选择模型类和数据上下文

自动生成的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);
        }
    }
自动生成的对应的视图文件
  1. 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>
  1. 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");}
}
  1. 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");}
}
  1. 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>
  1. 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项目
  1. 右键解决方案,依次选择:添加、新建项目

  2. 语言:C#,项目类型:Web或WebAPI,模版:ASP.NET Core Web API,命名WebApi_DEMO

  1. 选择目标框架

    配置HTTPS默认是勾选的,也可取消

  2. 创建后,将WebApi_DEMO设为默认启动项,添加对CommonLib的引用

  3. 直接F5运行,如果是新系统首次运行会提示安装证书

  4. 运行界面如下

  1. 依次点击:GET、Try it out、Execute,显示明细信息,API项目创建完成

二、配置跨域,为前端提供服务
  1. 在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();
  1. 测试跨域,前端使用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>
  1. 前端成功获取数据

三、Swagger配置
  1. 【解决方法资源管理器】--【右击项目名称】-【属性】-【生成】-【输出】
  • 选中文档文件-生成包含API文档的文件,

  • 默认地址为bin\Debug\net6.0\xxx.xml,也可以自定义地址,在指定地址新建个空的xml文件即可。

  1. 在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
});
  1. 在WeatherForecast控制器的Get()方法上,添加注释:天气预报

  1. F5运行

Web API服务端添加jwtToken验证

一、token验证机制
  1. 用户使用用户名密码来请求服务器

  2. 服务器进行验证用户的信息

  3. 服务器通过验证发送给用户一个token

  4. 客户端存储token,并在每次请求时附送这个token值,附带在http请求的header里

  5. 服务端验证token值,并返回数据

二、CommonLib库
  1. 添加工具包
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
Install-Package Microsoft.IdentityModel.Tokens
Install-Package System.IdentityModel.Tokens.Jwt
Install-Package Newtonsoft.Json
  1. 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项目下
  1. 在appsettings.json中配置jwt参数的值
{
  "Jwt": {
    "Issuer": "jwtIssuer", //颁发者
    "Audience": "jwtAudience", //可以给哪些客户端使用
    "SecurityKey": "abcdefghijklmnopqrstuvwxyz" //加密的Key,大于16位的字符串
  },
}
  1. 在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);
            }
        };
});
  1. 在Program.cs中使用jwt鉴权
//使用鉴权授权
app.UseAuthentication();
app.UseAuthorization();
四、使用EF构建登录信息,获取token
  1. 新建数据库表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
  1. 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; }

}
  1. 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");
        }
    }
}
  1. WebApi_DEMO项目下,Program.cs文件注入服务
builder.Services.AddDbContext<DemoDbContext>();
builder.Services.AddDatabaseDeveloperPageExceptionFilter(); //数据库异常筛选器
  1. 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);
        }
    }
}

  1. 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>
posted @ 2018-09-22 15:59  生之韵  阅读(220)  评论(0编辑  收藏  举报