Linux下运行ASP.NET Core并使用MySql简明教程

作者:czp 分类: C# 发布于:2016-12-29 11:59 ė1445次浏览 60条评论

环境: ubuntu 16.04 | Intellij Rider 1.0 EAP | dotnet core 1.0.0-preview2-003131


近日需要完成 .net 大作业,同时又懒得安装 windows 和臃肿的 MSSQL 。而 LocalDB 就更奇怪了,其在微软文档上的说明页中的”下载中心“链接是个 404 的页面,这甚至令我感到闻所未闻见所未见,随即放弃使用微软工具链。


什么是 .NET Core

.net core 是微软在 .net 开源计划中的产物,对之前的 .net 4.x 进行了重写,并且尽可能去除平台相关部分。底层对四大平台(Windows,Unix,Linux,Mac)的本地API(native method)做接入,使得 .net core 程序可以跨平台运行。 .net core 没有实现全部的 .net api,一些旧库和旧程序无法在 .net core 正常运行,并且具体的 api 受到平台限制,例如 windows 平台的注册表相关库在 unix 不存在。对于大多数不涉及底层的开发来说,他和旧的 .net 并没有什么显著区别。


安装 .NET Core

现在,我们可以通过源安装 .net core ,详见 https://www.microsoft.com/net/core#linuxubuntu

.net core 提供一个命令行工具,我们可以使用他来查看其安装情况

dotnet --version

同时,这个命令行工具还包括创建 dotnet core 项目,处理依赖,构建项目等多种功能。由于提供了命令行,因此真正做到了 IDE independent ,开发者可以通过任何编辑器或 IDE 简单的使用 dotnet core 而不需要在自己的开发设备上有 Microsoft Visual Studio 。更多命令通过以下命令查看

dotnet --help


什么是 ASP.NET Core

aspdotnet core 是为 dotnet core 全新设计的下一代 asp.net 开发框架。实际上,aspdotnet mvc core 被称为 aspdotnet mvc 6 。包括其他框架,例如 EntityFramework ,现在属于 Microsoft.EntityFrameworkCore 包,并且被称为 EF 7。

这些带有 core 尾缀的包是全新编写并专为 dotnet core 设计的,可以充分利用 dotnet core 特性,并且旧版库不保证一定可以在 dotnet core 正常运行。同时,带有 core 尾缀的包也不一定保证可以在旧版 dotnet 表现正常,选择你需要的库时请注意这一点。

aspdotnet core 加入了一些全新的特性和功能,详见 https://docs.microsoft.com/zh-cn/aspnet/core/

其中最令人兴奋的特性是,由于 dotnet core 自身已支持依赖注入,因此在 aspdotnet core 中,微软强烈推荐全部使用工厂模式。以及 aspdotnet core 现在和 java 的 spring boot 一样,自带一个嵌入式 iis ,可以只输出一个文件并在任何平台不做任何其他部署处理直接运行并监听端口,极大的方便了开发者和小型应用的极速部署。

基于 dotnet core 进行开发的另一个好处是,现在 dotnet core 项目不会再像以前那样,将本项目所需的依赖全部放到本项目目录,使得项目目录非常庞大。现在和 java 一样,依赖只存储在系统中的依赖缓存目录,只在编译时调取它们,而不会污染项目目录。同时这也方便了使用各种版本控制工具例如 git ,例如不会再出现团队中有人把依赖文件夹传了上去。


创建 ASP.NET Core 项目

aspdotnet core 项目的创建比较复杂,需要首先初始化 dotnet core 项目再导入依赖项并生成初始文件。具体的手动命令行操作在此不做介绍,此处我们通过 JetBrains 公司的 Rider 来初始化我们的项目。

Rider 是一款跨平台 C# IDE ,详见此 https://www.jetbrains.com/rider/

首先运行 Rider 并创建一个 dotnet core web application

2016-12-29 20-43-10屏幕截图.png

Rider 将代替我们自动完成全部的初始化工作,最终的项目目录结构和 Microsoft Visual Studio 生成的一致,并可被 vs 读取。

以默认选项运行一次项目,以确认环境是否正常。默认将监听于 5000 端口。

2016-12-29 20-48-02屏幕截图.png


使用 MySql 存取数据

aspdotnet core 中使用一种全新编写的用户管理系统,名为 Identity 。它在底层使用 EntityFramework Core 对用户数据进行存取,因此aspdotnet core 项目将自带有 Microsoft.EntityFrameworkCore 依赖。关于 Identity 将在接下来的分节讲到。

由于项目已经自带有 EntityFramework Core ,因此这里将以在 EntityFramework Core 中使用 MySql 进行示例。

EntityFramework Core 即 EF7 ,以下将直接使用此简称。EF7 主要用法和 EF6 一致,但是增加了完全的 Code based Configuration ,可以像 Java 的 Spring boot 框架一样完全不用手动编写任何配置文件,通过纯粹的面向对象设置方法完成设置。实际上,目前的 aspdotnet core 自身也已经可以完全基于代码进行设置,这极大的方便了开发者检查设置错误。

为了使用 MySql ,我们首先需要加入 EF7 对 MySql 的支持依赖。

dotnet core 的项目依赖记录于项目目录下的 project.json 文件中,并且这份文件只用于手动编写,根据这份文件自动生成的实际配置文件是项目目录下的 project.lock.json 。在此编写项目依赖后,并在项目目录运行命令行 dotnet restore ,将自动调用 nuget 满足依赖,并且依赖配置不会写入 dotnet core 项目的 package.json 。

传统的 dotnet 项目中,一个令人非常头疼的问题就是 package.json 文件手动写入一项依赖后,一旦依赖满足完成,将被自动写入所需的这个依赖的全部父依赖。这导致 package.json 文件几乎无法用于人类阅读,并且难以表达哪些依赖是人为加入的,而哪些是为了依赖而加入的。现在,这个问题被解决了, project.json 文件将永远不会被自动写入内容。与之相对的,为了避免项目出错,请勿手动修改 project.lock.json 中的内容。

project.json 中,项目依赖记录于 ”dependencies“ 标签下,依赖可能是这样的


"dependencies": {
    "Microsoft.NETCore.App": {
        "version": "1.0.1",
        "type": "platform"
    },
    "Microsoft.AspNetCore.Authentication.Cookies": "1.0.0",
    "Microsoft.AspNetCore.Diagnostics": "1.0.0",
    "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0",
    "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0",
    "Microsoft.AspNetCore.Mvc": "1.0.1",
    "Microsoft.AspNetCore.Razor.Tools": {
        "version": "1.0.0-preview2-final",
        "type": "build"
    },
    "Microsoft.AspNetCore.Routing": "1.0.1",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
    "Microsoft.AspNetCore.StaticFiles": "1.0.0",
    "Microsoft.EntityFrameworkCore.Sqlite": "1.0.1",
    "Microsoft.EntityFrameworkCore.Tools": {
        "version": "1.0.0-preview2-final",
        "type": "build"
    },
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",
    "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
        "version": "1.0.0-preview2-update1",
        "type": "build"
    },
    "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
        "version": "1.0.0-preview2-update1",
        "type": "build"
    },
    "MySql.Data.EntityFrameworkCore": "7.0.6-IR31"
},


我们需要的包是 MySql.Data.EntityFrameworkCore ,他是 EF7 对 MySql 的支持包,具体的版本号,我们可以在 nuget 网站上搜索并查看到,这里我们用 7.0.6-IR31 版本作为演示。

插入完毕这项依赖后,保存当前文件。此时 Rider 将自动重新载入项目依赖。如果 Rider 没有自动重载,请至项目目录下手动执行命令行

dotnet restore

项目目录是 project.json 所在的目录,而 .sln 文件所在目录是解决方案目录,这可能是不同的。

依赖满足完毕后,我们来创建 DbContext 类,这个类用来存储我们需要的 DbSet ,有 EF6 使用经验的同学对此不会陌生。

与 EF6 不同的是,在 aspdotnet core 中,EF7 的这些类,存放在项目目录下的 Data 文件夹中。

2016-12-29 21-22-10屏幕截图.png

我们在这个目录中首先创建自己的 DbContext 类,另一个自带的 ApplicationDbContext 将在接下来的分节讲到。假设我们的项目是一个社团管理系统,通常,我们将给其命名为 SocietyManagementSystemDbContext.cs 

这个类需要继承于 DbContext 类,而用于 Identity 的数据库上下文需要继承于 IdentityDbContext 类,这里我们不使用这份数据库上下文作为 Identity 提供源,因此只需要简单的继承于 DbContext 。

通常,一个自己编写的 DbContext 类可能是这样的


using DotnetHomework.Data.SocietyManagementSystemEntities;
using Microsoft.EntityFrameworkCore;

namespace DotnetHomework.Data
{
    public class SocietyManagementSystemDbContext : DbContext
    {
        public SocietyManagementSystemDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<SocietyEntity> Society { get; set; }
    }
}


其中,Society 是我们自定义的 DbSet 。

对 SocietyEntity 的定义可以是这样的


using System.ComponentModel.DataAnnotations.Schema;

namespace DotnetHomework.Data.SocietyManagementSystemEntities
{
    [Table("society")]
    public class SocietyEntity
    {
        [Column("id")]
        public int Id { get; set; }

        [Column("name")]
        public string Name { get; set; }

        [Column("category")]
        public int Category { get; set; }

        [Column("description")]
        public string Description { get; set; }

        [Column("creator")]
        public int Creator { get; set; }

        [Column("status")]
        public string Status { get; set; }
    }
}


Table 这个 Attribute 表示这个实体具体对应的表名,如果没有这个 Attribute ,那么 EF7 将直接取 DbSet 变量名作为表名进行查询。

Column 表示对应的实际字段名,与表名类似,如果不定义它们, EF7 将直接按照实体模型中的变量名作为字段名进行查询。

这些 Attribute 的设计是为了解决一些数据库的表名和字段名可能不符合 C# 命名法而导致的不规范问题。这和 Java 中 Hibernate 的设计思想如出一辙。

这里需要提及的是,dotnet 命令行工具有子命令,例如 dotnet ef ,他实际上是调用项目目录下的 EF7 ,因此必须在项目目录中执行这条命令,这将在接下来的分节中将到。这个子命令的一个选项是 dbcontext ,可以用它自动生成 DbContext 和与之相关的实体模型。

但是似乎 Oracle 没有在 MySql.Data.EntityFrameworkCore 中实现生成 DbContext 的部分,因此我们目前无法在 EF7 中自动生成 ADO.NET 实体模型,这非常遗憾,我们必须手动编写实体模型了。感兴趣的同学可以执行一次相关命令并查看其错误提示,此处不赘述。

由于 dotnet core 原生支持依赖注入,因此我们也将使用依赖注入的形式将 DbContext 的实例注入到我们需要他的地方。

在依赖注入前,我们首先要将 DbContext 注册为一个 Service ,aspdotnet core 没有类似 Java 中的 Spring boot 框架的自动扫描和自动注册机制。

Service 注册写在 Startup.cs 中的 ConfigureServices 方法中,这个方法可能是这样的。


// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
    services.AddDbContext<SocietyManagementSystemDbContext>(options =>
        options.UseMySQL(Configuration.GetConnectionString("MySqlConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}


其中,MySqlConnection 是在配置文件中设置的 ConnectionString ,配置文件是项目目录下的 appsettings.json 。这份文件可能是这样的


{
    "ConnectionStrings": {
        "DefaultConnection": "Data Source=DotnetHomework.db",
        "MySqlConnection": "Server=localhost;Username=root;Password=123456789;Database=dotnet_homework;SslMode=None"
    },
    "Logging": {
        "IncludeScopes": false,
        "LogLevel": {
            "Default": "Debug",
            "System": "Information",
            "Microsoft": "Information"
        }
    }
}


注意其中的 SslMode ,目前版本的 EF7 不支持 SSL。

dotnet core 的依赖注入和 Java 中的 Spring 框架不同,并不是使用注解(在 c# 中即 Attribute ),而是将需要的实例写在类的构造器中。

例如一个类可能是这样的


public class MyClass
{
    private SocietyManagementSystemDbContext _societyManagementSystemDbContext;

    public MyClass(SocietyManagementSystemDbContext societyManagementSystemDbContext)
    {
        _societyManagementSystemDbContext = societyManagementSystemDbContext;
    }
}



这样,这个类中的其他方法就可以调用到这个数据库上下文。

之后的调用和 EF6 一致,DbSet 中相关的自带方法也一致。例如取出所有 society 表中的数据


_societyManagementSystemDbContext.Society.ToList();


需要注意的是, MySql.Data.EntityFrameworkCore 在 dotnet 4.x 中运行,读取操作不会有任何问题,但是任何写入操作后进行 SaveChanges() ,都将抛出一个空指针异常,且数据没有正常写入。此问题是由于 MySql.Data.EntityFrameworkCore 不兼容传统 dotnet 导致的,没有解决方案,在传统 dotnet 请选择使用 EF6 ,不要使用 EF Core 即 EF7 。


使用 Identity

关于 Identity 的详情见此 https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/identity

我翻阅了大量微软文档,没有找到如何使用自定义登录服务类的设置(类似 Java 中的 Spring security),即我不清楚如何使用自己编写的类完成用户登录的逻辑并分发令牌。因此只好使用 Identity 的自带登录逻辑处理类(实际上我并不知道是哪个)。

由于使用 Identity 自带的登录服务类,因此不可避免的问题是数据库中必须有 Identity 需要的表。

在默认的配置文件中,默认的 ApplicationDbContext 这个数据库上下文将使用一份 SQLite 文件作为 Identity 的数据库。此时如果我们在自带的注册页面上进行注册,会提示数据表可以进行 Migrations 操作,点击网页上的这个按钮后,将在 Debug 目录出现一份 db 文件,里面就是Identity 所需的表的 SQLite 实现。

我们这里不使用 SQLite ,而使用 MySql 作为我们的数据库。

于是问题就变成了如何将这些数据表导入到 MySql 。

在项目的 Data/Migrations 文件夹中,我们可以看到一些文件,他们就是 dotnet ef 的数据库迁移文件,dotnet ef 使用他们来完成数据库的自动导入,这和 Java 中的 Hibernate 提供的数据库反向生成功能类似。

现在,我们要使用 dotnet ef 工具来自动生成这些数据表到 MySql ,详细文档可以参见 https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/migrations

在生成之前,我们首先要让 ApplicationDbContext 对应的连接指向我们的 MySql ,我们修改 Startup.cs 中的 ConfigureServices 方法,现在,它可能是这样的


// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseMySQL(Configuration.GetConnectionString("MySqlConnection")));
    services.AddDbContext<SocietyManagementSystemDbContext>(options =>
        options.UseMySQL(Configuration.GetConnectionString("MySqlConnection")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
}


现在,我们打开一个命令行,并 cd 至项目目录执行以下命令

dotnet ef database update -c ApplicationDbContext

随后会提示没有 __efmigrationshistory 表。

这张表在生成到 SQLite 时会被自动生成,用于记录 Migration 操作记录,但是在 MySql 中,我们需要首先手动创建它。

我们由生成出的 SQlite 文件导出的 sql 文件得到这张表的结构,并导入到 MySql,表结构大概是这样的


CREATE TABLE `__EFMigrationsHistory` (
    `MigrationId` varchar(45) NOT NULL PRIMARY KEY,
    `ProductVersion` varchar(45) NOT NULL
);


现在我们再次执行 database update 操作,提示 Done 。

我们回到我们的 MySql 查看有哪些表,发现多生成了7张以 asp 开头的数据表,它们就是我们需要的。

由于我们已经修改默认的 ApplicationDbContext 的连接,此时我们重启项目,并进行注册和登录测试,如果不出意外,此时已经可以正常调用 MySql 进行 Identity 操作了。


关于 dotnet ef migration 工具,我们也可以使用他由 MySql 生成数据库迁移文件。但是可能无法对自己生成的一部分 Migration 进行反向生成,会被提示 MySql 相关包内的一些方法没有正确 implemented ,恐怕 Oracle 仍然没有完全实现他们,毕竟 MySql.Data.EntityFrameworkCore 发布至今才不到3个月。



至此,我们已经可以正常使用 MySql 并直接使用 ASP.NET Core 提供的 Identity 用以管理用户了。接下去的开发请参见微软相关文档 https://docs.microsoft.com/en-us/aspnet/core/

小伙伴们一把眼泪一把鼻涕的对 C# 说再见!

本文出自 czp的装逼站,转载时请注明出处及相应链接。

0

发表评论

电子邮件地址不会被公开。必填项已用*标注


Ɣ回顶部