zoukankan      html  css  js  c++  java
  • Asp.Net Core快速入门(二)视图、模型、持久化、文件、错误处理、日志

    TagHelper

    入门

    优点:根据参数自动生成,不需要手写超链接,类似Django模板里面的url命令。

    在ViewImport中添加TagHelper

    @addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers
    

    比如,链接TagHelper使用

    <a class="btn btn-outline-primary" 
       asp-controller="student" asp-action="get" 
       asp-route-id="@student.Id">
        查看
    </a>
    

    缓存破坏的TagHelper

    <img src="~/images/banner.jpg" alt="Alternate Text" asp-append-version="true" />
    

    环境 TagHelper

    在开发环境中使用本地css文件,在非开发环境下使用的是CDN的css文件。

    注:integrity是用来做完整性检查的,保证CDN提供文件的完整和安全。

    <environment include="Development">
        <link href="~/lib/twitter-bootstrap/css/bootstrap.css" rel="stylesheet" />
    </environment>
    
    <environment exclude="Development">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
    </environment>
    

    为了防止CDN加载失败页面无法显示,可以加上fallback相关属性,第一个是失败时加载的文件,第二个是不检查这个文件的完整性

    asp-fallback-href="~/lib/twitter-bootstrap/css/bootstrap.css"
    asp-suppress-fallback-integrity="true"
    

    表单 Tag Helper

    直接贴上一个布局的代码,把class样式都去掉了,保留最基本代码。

    确实是很方便的,和Django、jinja2之类的模板比完全不输。

    @model Student
    
    <form asp-controller="student" asp-action="create">
        <label asp-for="Name"></label>
        <input asp-for="Name" />
    
        <label asp-for="Email"></label>
        <input asp-for="Email" />
    
        <label asp-for="ClassName"></label>
        <select asp-for="ClassName" asp-items="Html.GetEnumSelectList<ClassNameEnum>()"></select>
    
        <button type="submit">提交</button>
    </form>
    

    模型绑定

    将Http请求中的数据绑定到控制器方法上对应参数的顺序:

    • Form Values (Post表单数据)
    • Route Values (路由中的值)
    • Query String (Get的查询字符串)

    模型验证

    1.设置模型

    首先在Model中加入验证属性,如:

    public class Student
    {
        public int Id { get; set; }
    
        [Display(Name = "姓名"), MaxLength(4, ErrorMessage = "名字长度不能超过四个字")]
        [Required(ErrorMessage = "请输入名字!")]
        public string Name { get; set; }
    
        [Display(Name = "班级")]
        public ClassNameEnum ClassName { get; set; }
    
        [Required(ErrorMessage = "请输入邮箱!")]
        [Display(Name = "邮箱")]
        [RegularExpression(@"^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$", ErrorMessage = "邮箱格式不正确")]
        public string Email { get; set; }
    }
    
    常用的模型验证方法
    • Required
    • Range:指定允许的最小值和最大值
    • MinLength
    • MaxLength
    • Compare:比较两个属性,比如密码和确认密码
    • RegularExpression:正则匹配

    2.在控制器中加入验证代码

    使用ModelState.IsValid来验证模型属性是否正确

    [HttpPost]
    public IActionResult Create(Student student)
    {
        if (ModelState.IsValid)
        {
            var newStudent = _studentRepository.Add(student);
            return RedirectToAction("details", new { id = newStudent.Id });
        }
    
        return View();
    }
    

    3.使用TagHelper在网页上显示错误信息

    例子如下:

    <div class="text-danger" asp-validation-summary="All"></div>
    
    <div class="form-group row">
    <label asp-for="Name" class="col-sm-2 col-form-label"></label>
    <div class="col-sm-10">
    <input asp-for="Name" class="form-control" />
    <span class="text-danger" asp-validation-for="Name"></span>
    </div>
    </div>
    

    EF Core入门

    首先实现DbContext

    public class AppDbContext:DbContext
    {
        // 将应用程序的配置传递给DbContext
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    
        // 对要使用到的每个实体都添加 DbSet<TEntity> 属性
        // 通过DbSet属性来进行增删改查操作
        // 对DbSet采用Linq查询的时候,EFCore自动将其转换为SQL语句
        public DbSet<Student> Students { get; set; }
    }
    

    注册DbContext连接池

    services.AddDbContextPool<AppDbContext>(options => options.UseSqlServer(_configuration.GetConnectionString("StudentDBConnection")));
    

    其中,本地SqlServer数据库的配置,在appserttings.json中:

    "ConnectionStrings": {
        "StudentDBConnection": "server=(localdb)\MSSQLLocalDB;database=StudentDB;Trusted_Connection=true"
    }
    

    实现仓储

    public class SqlStudentRepository : IStudentRepository
    {
        private readonly AppDbContext _context;
    
        public SqlStudentRepository(AppDbContext context)
        {
            _context = context;
        }
        public Student Add(Student student)
        {
            _context.Students.Add(student);
            _context.SaveChanges();
            return student;
        }
        public Student Delete(int id)
        {
            var student = _context.Students.Find(id);
            if (student != null)
            {
                _context.Students.Remove(student);
                _context.SaveChanges();
            }
            return student;
        }
        public IEnumerable<Student> GetAll() => _context.Students;
        public Student GetById(int id) => _context.Students.Find(id);
        public Student Update(Student updatedStudent)
        {
            var student = _context.Students.Attach(updatedStudent);
            student.State = EntityState.Modified;
            _context.SaveChanges();
            return updatedStudent;
        }
    }
    

    EF Core 常用命令

    在nuget控制台中,不区分大小写

    • Get-Help about_enti:显示帮助,about_enti全名很长可以只写前面的
    • Add-Migration:添加迁移记录
    • Update-Database:更新数据库

    添加种子数据

    重写DbContextOnModelCreating方法

    protected override void OnModelCreating(ModelBuilder modelBuilder) { 
        modelBuilder.Entity<Student>().HasData(
            new Student { Id = 1, Name = "小米" },
        );
    }
    

    为了避免DbContext代码太乱,也可以使用扩展方法的方式:

    public static class ModelBuilderExtensions
    {
        public static void InsertSeedData(this ModelBuilder mBuilder)
        {
            mBuilder.Entity<Student>().HasData(
                new Student { Id = 1, Name = "小米" },
            );
        }
    }
    

    领域模型与数据库架构

    • 使用迁移功能同步领域模型和数据库架构
    • 使用 add-migration 添加迁移记录
    • 使用 remove-migration 删除最近一条记录
    • 使用 update-database 迁移记录名称 可以回滚至任意一次迁移

    文件上传

    定义ViewModel

    要上传的字段采用 IFormFile 类型

    public class StudentCreateViewModel
    {
        public int Id { get; set; }
    	// 省略无关代码...
        [Display(Name = "图片")]
        public IFormFile Photo { get; set; }
    }
    

    编写视图

    修改cshtml视图文件,修改模型绑定:

    @model StudentCreateViewModel
    

    加入上传文件的表单项

    <div class="form-group row">
        <label asp-for="Photo" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <div class="custom-file">
            <input asp-for="Photo" class="form-control custom-file-input" />
            <label class="custom-file-label">请选择图片</label>
            </div>
        </div>
    </div>
    

    为了选择文件后能显示出文件名还要编写js:

    $(document).ready(function () {
        $('.custom-file-input').on('change', function () {
            var fileName = $(this).val().split('\').pop();
            $(this).next('.custom-file-label').html(fileName);
        });
    });
    

    编写控制器

    通过构造函数注入 HostingEnvironment

    public StudentController(IStudentRepository studentRepository, HostingEnvironment hostingEnvironment)
    {
        _studentRepository = studentRepository;
        _hostingEnvironment = hostingEnvironment;
    }
    

    处理文件上传和保存的逻辑

    [HttpPost]
    public IActionResult Create(StudentCreateViewModel model)
    {
        if (ModelState.IsValid)
        {
            var uniqueFileName = "";
            if (model.Photo != null)
            {
                var uploadDir = Path.Combine(
                    _hostingEnvironment.WebRootPath, 
                    "uploads", "images");
                uniqueFileName = Guid.NewGuid().ToString() + 
                    "_" + model.Photo.FileName;
                model.Photo.CopyTo(new FileStream(
                    Path.Combine(uploadDir, uniqueFileName), 
                    FileMode.Create));
            }
            var student = new Student
            {
                Name = model.Name,
                Email = model.Email,
                ClassName = model.ClassName,
                PhotoPath = uniqueFileName,
            };
    
            var newStudent = _studentRepository.Add(student);
            return RedirectToAction("details", 
                                    new { id = newStudent.Id });
        }
    
        return View();
    }
    

    多文件上传

    和单文件差不多

    ViewModel

    增加 List<IFormFile> 类型字段

    [Display(Name = "图库")]
    public List<IFormFile> Gallery { get; set; }
    

    修改视图

    <div class="form-group row">
        <label asp-for="Gallery" class="col-sm-2 col-form-label"></label>
        <div class="col-sm-10">
            <div class="custom-file">
                <input asp-for="Gallery" multiple id="gallery-input" class="form-control custom-file-input" />
                <label id="gallery-label" class="custom-file-label">请选择图片 可以一次选择多张</label>
            </div>
        </div>
    </div>
    

    js代码:

    $('#gallery-input').on('change', function () {
        var label = $(this).next('#gallery-label');
        var files = $(this)[0].files;
        if (files.length > 1) {
            label.html(`你已经选择了${files.length}个文件`);
        } else if (files.length == 1) {
            label.html(files[0].name);
        }
    });
    

    修改控制器代码

    // 处理多文件上传
    if (model.Gallery != null && model.Gallery.Count > 0)
    {
        foreach(var photo in model.Gallery)
        {
            uniqueFileName = Guid.NewGuid().ToString() + "_" 
                + photo.FileName;
            photo.CopyTo(new FileStream(Path.Combine(uploadDir, 
            uniqueFileName),FileMode.Create));
        }
    }
    

    错误处理

    添加错误处理页面

    Startup.cs 中设置中间件:

    app.UseStatusCodePagesWithReExecute("/error/{0}");
    

    推荐用 UseStatusCodePagesWithReExecute 而不是 UseStatusCodePagesWithRedirects,前者在管道内执行执行错误跳转url,后者会重定向到该url,导致http错误状态码变成新页面的正常执行的200码了。

    接着编写错误控制器:

    public class ErrorController : Controller
    {
        [Route("Error/{statusCode}")]
        public IActionResult Index(int statusCode)
        {
            var statusCodeResult = HttpContext.Features.
                Get<IStatusCodeReExecuteFeature>();
            var viewModel = new ErrorViewModel
            {
                Path = statusCodeResult.OriginalPath,
                QueryString = statusCodeResult.
                    OriginalQueryString,
            };
            switch (statusCode)
            {
                case 404:
                    viewModel.Message = "页面未找到";
                    break;
            }
            return View("Error", viewModel);
        }
    }
    

    对了,我还定义了ViewModel:

    public class ErrorViewModel
    {
        public int Code { get; set; }
        public string Message { get; set; }
        public string Path { get; set; }
        public string QueryString { get; set; }
    }
    

    视图代码就不贴了,无非就是显示ViewModel里的这些错误信息~

    设置全局异常跳转

    添加中间件

    app.UseExceptionHandler("/exception");
    

    编写处理用的控制器,这里需要添加AllowAnonymous注解,允许用户在未登录的时候访问到这个异常页面,保证无论如何可以显示出异常页面。

    [AllowAnonymous]
    [Route("exception")]
    public IActionResult ExceptionHandler()
    {
        var exception = HttpContext.Features.
            Get<IExceptionHandlerPathFeature>();
        var viewModel = new ExceptionViewModel
        {
            Path = exception.Path,
            Message = exception.Error.Message,
            StackTrace = exception.Error.StackTrace,
        };
        return View("Exception", viewModel);
    }
    

    另外,ViewModel定义如下:

    public class ExceptionViewModel
    {
        public string Path { get; set; }
        public string Message { get; set; }
        public string StackTrace { get; set; }
    }
    

    日志记录

    AspNetCore里面自带了一套日志系统,默认已经注册到了服务容器里了,只要在控制器的构造函数里注入就可以使用了,比如:

    public class ErrorController : Controller
    {
        private ILogger<ErrorController> _logger;
    
        public ErrorController(ILogger<ErrorController> logger)
        {
            this._logger = logger;
        }
    }
    

    默认的日志只会记录到控制台或者调试输出,不过我们为了实现更多功能,比如记录到文件或者推送到日志服务器,我们需要使用第三方的日志组件。这里我用的是NLog。

    首先要安装NLog.Web.AspNetCore这个包。

    之后在Program.cs里引入nlog服务:

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
        .ConfigureLogging((hostingContext, logging) =>
         {
             // 保留官方的代码中的默认日志程序
     logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
             logging.AddConsole();
             logging.AddDebug();
             logging.AddEventSourceLogger();
             // 引入 nlog
             logging.AddNLog();
          }).UseStartup<Startup>();
    

    保留官方默认日志程序那里,要看AspNetCore的源代码,本文用的是2.2版本,在github看,地址如下:

    https://github.com/dotnet/aspnetcore/blob/v2.2.8/src/DefaultBuilder/src/WebHost.cs

    然后,为了使用nlog,需要创建一个配置文件,在项目根目录创建 NLog.config

    关于配置文件的说明可以参考:https://github.com/NLog/NLog/wiki

    <?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"
          throwConfigExceptions="true">
      <targets>
        <target name="f1" xsi:type="File" fileName="Logs
    log-all-${shortdate}.log"/>
        <target name="n1" xsi:type="Network" address="tcp://localhost:4001"/>
        <target name="c1" xsi:type="Console" encoding="utf-8"
                error="true"
                detectConsoleAvailable="true" />
        <target name="c2" xsi:type="ColoredConsole" encoding="utf-8"
              useDefaultRowHighlightingRules="true"
              errorStream="true"
              enableAnsiOutput="true"
              detectConsoleAvailable="true"
              DetectOutputRedirected="true">
        </target>
      </targets>
      <rules>
        <logger name="*" maxLevel="Debug" writeTo="c2" />
        <logger name="*" minLevel="Info" writeTo="f1" />
      </rules>
    </nlog>
    

    之后在程序中就可以正常使用日志功能了。比如:

    var viewModel = new StatusCodeViewModel
    {
        Code = statusCode,
        Path = statusCodeResult.OriginalPath,
        QueryString = statusCodeResult.OriginalQueryString,
    };
    _logger.LogWarning(viewModel.ToString());
    

    还有可以在appsettings.json里面配置日志等级和命名空间过滤,跟在NLog.conf里面配置效果是一样的。例如:

    "Logging": {
        "LogLevel": {
            "Default": "Warning",
            "StudyManagement.Controllers.ErrorController": 
            "Warning"
        }
    },
    

    欢迎交流

    程序设计实验室专注于互联网热门新技术探索与团队敏捷开发实践,在公众号「程序设计实验室」后台回复 linux、flutter、c#、netcore、android、kotlin、java、python 等可获取相关技术文章和资料,同时有任何问题都可以在公众号后台留言~

    It never rains but it pours. 欢迎关注我的公众号:DealiAxy 提供更多技术文章
  • 相关阅读:
    LOJ164 高精度除法
    CQOI2013 新Nim游戏 和 BZOJ1299 巧克力棒
    UOJ514 通用测评号 和 CF891E Lust
    CF526F Pudding Monsters 和 CF997E Good Subsegments
    UOJ513 清扫银河
    SNOI2020 水池
    NOI2015 品酒大会 和 SNOI2020 字符串
    SNOI2020 生成树
    BJOI2020 封印
    UOJ523 半前缀计数
  • 原文地址:https://www.cnblogs.com/deali/p/14436250.html
Copyright © 2011-2022 走看看