第一个ASP.NET Core网站:幻走SkyDrift服务器相关记录

  .NET Core不久前发布了1.0版本,这东西目前最有用的用途当属在Linux服务器上部署ASP.NET网站了。虽然我手头的小霸王不是Linux系统,但为今后更换和传播的普适性考虑,学学使用.NET Core还是很有用处的。因此我决定拿之前预留的幻走SkyDrift开刀,写一个幻走联机服务器。目前已经上线,至少运行还算正常。

  虽然连续搞了十多天已经头大了,但是如果不记录一下的话,怕是以后啥都不记得了,所以还是有了这篇流水账。

  首先介绍一下这个网站服务的基本信息。幻走SkyDrift联机服务器是为游戏《幻走SkyDrift》(基于Unity)提供联机服务的一套ASP.NET程序及其他配套设施。具体包括:

  • SkyDriftCoreWeb:就是服务器本身,基于ASP.NET Core开发,提供【可供用户注册、登录、查询战绩统计等信息的页面】和【游戏本体访问的全套Json API(包括用户、房间、匹配、序列号、记录等诸多功能)】。前端采用Razor、HTML、JS等,后端采用ASP.NET MVC & Web API等。
  • 幻走SkyDrift联机补丁Ex:通过修改游戏程序DLL,加入联机相关的功能(配置读取、API地址设置、NAT切换、启用Proxy等),此外对游戏的文本也进行了一定程度的汉化,基于代码修改技术。
  • ServerDriver:幻走SkyDrift服务器选择器,基于.NET Winform开发。搭配联机补丁Ex食用,可使游戏连接到位于不同地址的服务器。类似于网游模式,启动时从网络上下载服务器列表。
  • NAT服务器与代理服务器:这两个是Unity提供的服务器程序,基于RakNet。只需在服务器上运行此程序并配置适当的参数即可。

 

目前本服务器已经开源:http://git.oschina.net/Ulysses/SkyDriftCoreWeb

 

1. ASP.NET MVC基本概念

Model(模型):在ASP.NET MVC(以下简称MVC)中Model是代表数据的类(.cs)。比如在幻走SkyDrift(以下简称幻走)中,要联机游戏,那么Room(房间)就可以作为一个模型。其中应该定义的属性包括HostIp(房主的IP)、RoomName(房间名)等等。

View(视图):前端页面(.cshtml)。比如访问关于页面时,就会用到About.cshtml这个视图。MVC通常使用Razor语法来描述视图,这是一种可以很方便地混合HTML和C#代码的表示,相比每插入一句代码都要来个<% %>之类的某些语言来说绝对是方便的多的,另外丰富的HTML辅助方法(如@HTML.EditorFor方法直接生成对应的表单填写项目)和TagHelper使得这一过程更加方便。

Controller(控制器):后端逻辑(.cs)。当访问URL时,通过路由机制确定到某个控制器的一个方法并调用。控制器最后返回视图(或者在Web API控制器中,返回Json或XML等,姑且也叫做View)。

 

2. Entity Framework Core

使你不用接触一句SQL就能完成网站开发和上线的东西,EF面向.NET Core的版本。首先需要定义好DbContext(可以看做一个数据库),其中包括多个DbSet<T>(可以看做数据表),T为上面的Model。当然还需要连接方式。

出于跨平台考虑,目前(EF Core)数据库可以选择MySQL、PostgreSQL或SQLite;出于节省系统资源考虑,可选择SQLite或SQLCE。于是我的小霸王只能选择SQLite(但是我也测试了SQLCE)。

幻走的DbContext定义如下:

    public class SkyDriftDbContext : DbContext
    {
        public DbSet<Serial> Serials { get; set; }
        public DbSet<SkyRoom> Rooms { get; set; }
        public DbSet<Match> Matches { get; set; }
        public DbSet<Record> Records { get; set; }

        public SkyDriftDbContext() :base()
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder builder)
        {
#if SQLCE
            base.OnConfiguring(builder.UseSqlCe(Startup.ConnectionString));
#else
            base.OnConfiguring(builder.UseSqlite(Startup.ConnectionString));
#endif
        }

当一切准备就绪后,就可以使用dotnet命令行工具来建立和迁移(migration,此处的迁移可理解为更新数据库的结构到最新的代码定义的状态,同时保留数据)数据库了。

在项目根目录开一个命令行(推荐VS插件:Open Command Line,使用Alt+空格快捷键即可打开你想要的命令行):

dotnet ef migrations add first --context SkyDriftDbContext

其中,first是名字,怎么写都可以。--context用于指定context,在有多个context的时候必须指定(比如在幻走中有储存用户的默认的ApplicationDbContext和幻走专用的SkyDriftDbContext,虽然它们最终都在一个文件中存储,而且其实也可以在代码中合到一起)。

这样就添加了一个migration,如果要回退可以将add first替换为remove。

随后更新数据库:(这一步就建立了数据库)

dotnet ef database update --context SkyDriftDbContext

 

3. 依赖注入(DI)

创建一个新的ASP.NET Core项目,可以看到它的用户控制器的构造函数是这样的(有删改):

        public AccountController(
            UserManager<ApplicationUser> userManager,
            SignInManager<ApplicationUser> signInManager,
            IEmailSender emailSender,
            ILoggerFactory loggerFactory)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _emailSender = emailSender;
            _logger = loggerFactory.CreateLogger<AccountController>();
        }

这个构造函数看起来并没有被调用过,但是其中有很多传入的对象。这些对象都是通过依赖注入传入的。在Startup.cs中可以看到注册服务的过程:

           //添加了UserManager和SignInManager
           services.AddIdentity<ApplicationUser, IdentityRole<int>>(options =>
                {
                    options.Lockout.AllowedForNewUsers = false;
                })
                .AddEntityFrameworkStores<ApplicationDbContext, int>()
                .AddDefaultTokenProviders();

            services.AddMvc();

            // 添加了一个EmailSender
            services.AddTransient<IEmailSender, AuthMessageSender>();

自己定义的控制器也可以这样通过构造函数来取得需要的依赖(需要什么,构造函数就要求传入什么)。

 

4. Identity

Identity是ASP.NET提供的一套用于管理用户的机制(登录、登出、修改密码、锁定等等),省去自行开发用户系统的麻烦和我等新手由于疏漏导致的不安全(当然各位黑阔大佬自行开发的没准比这要安全很多)。Identity的使用在AccountController中展示的已经很详细了。

要在用户对象中增加额外的数据(比如幻走中可加入每个用户的等级、积分等),只需在Models.ApplicationUser类中加入即可。

如何将User ID改为别的数据类型?

这是一个很普遍的问题,Identity中,用户ID默认是string(一个GUID)。但是通常情况下我们可能需要int型的ID。在之前老版本的ASP.NET中这可能很复杂,不过目前已经很简单了:

在Startup.cs添加服务时使用.AddEntityFrameworkStores<ApplicationDbContext, int>() ;

让ApplicationUser继承自IdentityUser<int> ;

此外可能还要修改一些现有的调用,特别是在AccountController中。

如此即可实现int型的ID(和主键)。但是其实目前还有一个问题:

UserManager.FindByIdAsync这个方法所需要的参数永远是string,也就是说把ID改为int之后这个函数就没用了(我感觉这是一个历史遗留问题),需要自己写一个实现(其实也不难)。

 

5. 路由标签

例:在AuthController控制器上加入[Route("api/[controller]/[action]")]

那么访问xxx/api/auth/login时就会路由到AuthController.Login。

ASP.NET MVC5中有个[RoutePrefix()]标签,但是在ASP.NET Core中,Route标签已具备同样的功能,所以取消了。

如何让一个方法能通过两个不同的地址路由到?

    [Route("api/[controller]/[action]")]
    public class MatchingsController : Controller
    ...

        [HttpPost]
        [ActionName("delete")]
        [Route("")]
        [Route("~/api/matchings/delete_for_to")]
        public async Task<IActionResult> Delete(int matching_id, string access_token)

 

 

6. Web API

在ASP.NET MVC 5中,Web API控制器与MVC控制器已经有融合的趋势。在ASP.NET Core中两者真正实现了融合,在架构方面没有什么区别了。WebAPI是用于向程序提供信息的,所以返回的结果通常是Json或XML;而MVC一般指的是给人看的,所以返回的结果是View页面。要实现API,只需要return Json(...)就好。(MVC中是return View(...)。)

 

7. 杂项

怎么在不是Controller的类(没有DI)中使用UserManager?

可以用静态类保存一个UserManager,在Startup.Configure末尾:

Core.UserManager = app.ApplicationServices.GetService<UserManager<ApplicationUser>>();

 

序列化问题:

[Serializable]标签在.NET Core中不能用。所以统统改为使用Json进行序列化/反序列化,反正Web API也是要用的。

 

如何设置监听哪些本机地址?

在Program.cs中,WebHostBuilder中加入:.UseUrls(ListenUrls) 。

注意这可能对调试造成影响,所以最好搭配宏定义使用。

 

8. 部署及运行

在VS中选择“生成”——“发布 xxx”。创建一个配置,如果条件允许的话可以用Web Deploy。我的小霸王就简单粗暴地使用FTP就好了。但使用FTP需要注意:发布时得先把网站停机,不然文件是占用状态,发布肯定是失败的。

如果要使用IIS来运行.NET Core网站,还有些东西是需要装的,包括Microsoft HTTP Platform Handler(似乎是用来在Kestrel和IIS之间提供一个代理)和DotNetCore.1.0.0-WindowsHosting(包含了运行.NET Core程序所需要的东西,特别是dotnet CLI:dotnet.exe)。安装好后参照ASP.NET官网的教程配置即可,其中最关键的一条是用于.NET Core的应用程序池必须设为“无托管代码”(因为它用的不是完整版的.NET,.NET Core自成一套)。

官方推荐使用IIS部署,以获得更全面的功能和更高的安全性。但是……在我的小霸王上,其响应速度真的很慢,一时间我觉得不升级配置已经没救了。

但是,随后我还是尝试了Kestrel自托管。单从速度上说,效果好得很!

这个操作过程就很简单也很通用了,放到Linux上也是一样的。运行命令:

dotnet SkyDriftCoreWeb.dll

即可。(当然,需要把dotnet.exe加入环境变量。安装过前面提到的东西的话应该已经加入了)

 

需要注意的是,IIS和Kestrel的行为并不是完全一样的,比如对URL中多个/的处理。不过这个问题并不大。

 

9. 从.NET Core切换到.NET Framework

为了尝试只支持.NET Framework的SQLCE,我试了把目标平台改为.NET4.5。其实这一过程也不复杂,只需修改project.json:

在frameworks一节中把netcoreapp1.0注释掉,加入"net451": {} ;

在dependencies一节中把Microsoft.NETCore.App注释掉;

运行dotnet restore更新依赖包(VS可能自动运行)。

 

添加评论

Loading