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 提供更多技术文章
  • 相关阅读:
    nginx-1.8.1的安装
    ElasticSearch 在3节点集群的启动
    The type java.lang.CharSequence cannot be resolved. It is indirectly referenced from required .class files
    sqoop导入导出对mysql再带数据库test能跑通用户自己建立的数据库则不行
    LeetCode 501. Find Mode in Binary Search Tree (找到二叉搜索树的众数)
    LeetCode 437. Path Sum III (路径之和之三)
    LeetCode 404. Sum of Left Leaves (左子叶之和)
    LeetCode 257. Binary Tree Paths (二叉树路径)
    LeetCode Questions List (LeetCode 问题列表)- Java Solutions
    LeetCode 561. Array Partition I (数组分隔之一)
  • 原文地址:https://www.cnblogs.com/deali/p/14436250.html
Copyright © 2011-2022 走看看