65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET Core 跨平台数据库开发

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2017 年 9 月 11 日

CPOL

5分钟阅读

viewsIcon

14980

downloadIcon

116

使用跨平台数据库开发 ASP.NET Core NES 游戏网站,并部署到未安装 .NET Core 运行时的 Ubuntu 和 Windows。

引言

Linux 是一个随处可见的操作系统,在互联网上运行良好。在本文中,我们将尝试使用 ASP.NET Core 构建一个小型在线游戏网站。最后,我们将把 Release 版本复制到一个未安装 .NET Core 运行时的全新 Windows 系统。

你好 .NET Core

在开始 .NET Core 网站开发之前,我们首先在 Ubuntu Linux 上体验一下。没有 IDE,我们使用命令行来创建一个控制台项目。

$dotnet new console

它创建了两个文件:项目文件 game.csproj,C# 文件 Program.cs。如果您添加了依赖项但出错,可以删除 obj 文件夹。

运行项目

$dotnet run

完成。感觉如何?这就是在没有大型 IDE 的情况下,只用键盘而不是鼠标创建项目的方法。

但是…没有一个集成开发环境,如何创建数据库应用程序?在本文中,我们将使用 Visual Studio Code 编辑代码,表的记录和 C# 对象对数据库引擎来说是相同的,编辑代码就是编辑数据库,我们将在数据库 稍后讨论。

创建 ASP.NET Core WebAPI

我们将使用 WebAPI + JavaScript 来构建游戏网站,创建 WebAPI 项目就像创建控制台项目一样。

$dotnet new webapi

与大多数网站一样,我们需要两个组件:数据库和服务器。

首先,向项目中添加数据库,运行

 $dotnet add package iBoxDB --version 2.15

它会将程序包信息添加到 game.csproj 中,您可以手动编写 PackageReference。以下代码段中加粗的部分是代码。

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="iBoxDB" Version="2.15" />
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
  </ItemGroup>

某些项目无法直接连接到 Internet。我们可以从网上下载程序集并复制到项目中,然后修改 game.csproj。

  <ItemGroup>
    <Reference Include="iBoxDB">
      <HintPath>/home/user/Downloads/iBoxDBv21500_27/NETDB/iBoxDB.dll</HintPath>
    </Reference>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
  </ItemGroup>

设置好数据库 iBoxDB 后,我们编写九行代码来测试它,并看看编辑器。请做好准备,Visual Studio Code 只继承了 Visual Studio 的名字。

using iBoxDB.LocalServer;

public class User  //Class as Table, Object as Record
{
   public long ID;
   public string Name;
}    
        
DB db = new DB(DB.CacheOnlyArg); // Create DB in Memory
db.GetConfig().EnsureTable<User>("User", "ID"); // Create Table
AutoBox auto = db.Open();
auto.Insert("User", new User()   //Insert Object
            {
                ID = 1L,
                Name = "Hello .NET Core"
            });
Console.WriteLine(auto.Get("User", 1L)); //Select Object

ASP.NET Core 是自托管的,数据库 iBoxDB 是嵌入式的。控制台显示 DB 正在运行,Web 服务器正在监听 "https://:5000"。一切正常。

此网站将提供 NES 游戏在线服务,玩家可以在线玩 NES 游戏。我们将 NES 模拟器 nesnes 复制到 ./wwwroot/nesnes 文件夹,它由 JavaScript 编写。并为游戏 ROM 创建一个新文件夹 ./wwwroot/roms。您可以在 Internet 上找到这些 ROM(*.nes),下载并复制到此文件夹。有些游戏不被模拟器支持,请尽可能多地将 NES ROM(*.nes)复制到该文件夹。

设置完成。

上线

目前,自托管的 Web 服务器只能通过 localhost 访问,并且拒绝用于获取 *.nes 的 HTTP 请求。

修改 Program.BuildWebHost() 以监听任何 IP。

public static IWebHost BuildWebHost(string[] args) =>
   WebHost.CreateDefaultBuilder(args)
      .UseStartup<Startup>()
      .UseKestrel((opt) =>
      {
          opt.Listen(IPAddress.Any, 5000);
      })
      .ConfigureLogging((logging) =>
      {
         logging.SetMinimumLevel(LogLevel.Warning);
      })
   .Build();
}

修改 Startup.Configure() 以接受所有文件。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
   app.UseDeveloperExceptionPage();
   var fs = new FileServerOptions();
   fs.EnableDefaultFiles = true;
   fs.EnableDirectoryBrowsing = true;
   fs.StaticFileOptions.ServeUnknownFileTypes = true;
   app.UseFileServer(fs);
   app.UseMvc();
}

网站开发

大多数网站都有会员系统,我们创建一个 User 类来表示玩家。

//C#
public class User
{
    public long ID;
    public string GameName;

    public string ImageURL;

    public DateTime Time;
    public long Ver;
}

GameName 是玩家要玩的游戏,对应的 JavaScript 代码如下。

//JavaScript
var emulator = new NesNes(nesnes);

var xhr = new XMLHttpRequest();
xhr.open("GET", "./api/user", true); 
...
var romPath = "./roms/" + user.gameName 
emulator.load(romPath, true);

客户端(玩家浏览器)发送 HTTP 请求到服务器以获取他可玩的游戏,然后从服务器加载游戏。

"./api/user" 是一个 ASP.NET Core WebAPI,下面的 C# 代码,它会随机设置 GameName。

//C#
[HttpGet()]
public User Get()
{
   User u = new User();
   u.ID = App.Auto.NewId();
   u.GameName = App.Roms[u.ID % App.Roms.Length];
   App.Auto.Insert("User", u);
   return u;
}

我们将有一个管理员来监控在线玩家。他需要客户端将游戏屏幕(作为图像)发送回服务器,并通过使用 User.ImageURL 属性将其存储在数据库中。

//JavaScript   
var xhr = new XMLHttpRequest();
xhr.open("PUT", "./api/user/" + user.id, true);
xhr.setRequestHeader("Content-Type", "application/json");

xhr.send(JSON.stringify(user.imageURL));

玩家来来往往,管理员想要看到的只是在线玩家。我们如何知道哪个是激活的?一种简单的方法是为 User 类添加一个 Time 属性,当更新屏幕时,更新 Time,然后使用 Select<User>( "from User where Time>?", lastTime) 从上次开始获取最新数据。

我们知道会有很多玩家在线,两个玩家在更新屏幕时可能会获得相同的时间,让我们看看下面的场景。

LastTime = 0 
...
Thread-1::Player-1: GetTime() //Time=1
Thread-2::Player-2: GetTime() //Time=1 
Thread-1::Player-1: Update()
Thread-2::Player-2: Update() 
Select Time > LastTime
Set LastTime = 1
...
Thread-1::Player-1: GetTime() //Time=2
Thread-2::Player-2: GetTime() //Time=2
Thread-1::Player-1: Update()
Thread-2::Player-2: OS Suspends this Thread for some background tasks
Select Time > LastTime
Set LastTime = 2
...
Thread-1::Player-1: GetTime() //Time=3
Thread-2::Player-2: OS Resumes this Thread //Time=2
Thread-1::Player-1: Update() //Time=3
Thread-2::Player-2: Update() //Time=2
Select Time > LastTime  //Who is missing?

99.99% 的准确性对于这个小型项目来说已经足够了,但我们能做得更好吗?iBoxDB 支持 UpdateIncrement,这意味着当对象更新时,字段的值(long 类型)可以增加,并且是线程安全的。要启用此功能,请在配置中添加一行代码。

//C#
DB db = new DB();
db.GetConfig().EnsureTable<User>("User", "ID")
  .EnsureUpdateIncrementIndex<User>("User", "Ver");

现在,我们可以将玩家的游戏屏幕存储在服务器上,我们不需要设置 User.Ver,它将由数据库设置。

//C#
[HttpPut("{id}")]
public string Put(long id, [FromBody]string value)
{
   using (var box = App.Auto.Cube())
   {
       User u = box["User", id].Select<User>();
       u.ImageURL = value;
       u.Time = DateTime.Now;
       box["User"].Update(u);
       CommitResult cr = box.Commit();
       return cr.ToString();
   }
}

因为每个对象都有一个 Version,我们可以像这样获取更新的对象。

while(true){
   objects = Select<User>("from User where Ver > ?", lastVersion)
   if ( objects.Length > 0 )
     lastVersion = objects[0].Ver; //descending order
}

最初获取最新的版本。

lastVersion = App.Auto.NewId(byte.MaxValue, 0);

在本文开头,我们使用 Time 属性,以获得更好的外观,因为最初更新的对象是空的。

//C#
public IEnumerable<User> Get(long last)
{
   int count = 4 * 5; 
   if (last == 0)
   {
      DateTime dt = DateTime.Now;
      dt = dt.Subtract(TimeSpan.FromSeconds(60 * 1));
      return App.Auto.Select<User>("from User where Ver>? & Time>? limit 0,?", last, dt, count);
   }
   else
   {
      return App.Auto.Select<User>("from User where Ver>?  limit 0,?", last, count);
   }
}

尽管我们最初使用了 Time,但 Ver 属性仍然是必需的,它告诉数据库使用 Ver 索引进行搜索,否则数据库将使用 ID 索引进行搜索。

附注:上面使用 "limit 0,count" 语句来调试 Javascript 和 CSS,返回我们想要测试页面布局的大小。发布前删除 "limit"。

对应的 JavaScript 代码。

//JavaScript  
last = users[0].ver;     
var divs = document.getElementsByTagName("div");

//Update
for (index = 0; index < divs.length; index++) {
  for (var i = 0; i < users.length; i++) {
     if ((!users[i])) continue;
     if (divs[index].id == "u" + users[i].id) {
        var img = divs[index].childNodes[0];
        img.src = users[i].imageURL;
        img.ver = users[i].ver;
        ...
        break;
     }
  }
} 
//Create
for (var i = users.length - 1; i >= 0; i--) {
   var div = document.createElement("div");
   ...
   document.body.insertBefore(div, document.body.firstChild);
}
//Remove
for (index = 0; index < divs.length; index++) {
  var img = divs[index].childNodes[0];
  if (img.ver < (last - 100)) {
    divs[index].parentNode.removeChild(divs[index]);
  }
}

最终的 admin.html 页面。

$dotnet run

Publish (发布)

我们在 Linux 上开发了网站,如果想在 Windows 上部署,请编辑 game.csproj,添加 win10-64

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <RuntimeIdentifiers>win10-x64;ubuntu.16.04-x64</RuntimeIdentifiers>
  </PropertyGroup>

然后运行

$dotnet publish -c release -r win10-x64

您将在 project/bin/release/netcoreapp2.0/win10-x64/publish 中获得一个 Windows Release 版本,不要忘记 /publish 文件夹。

这是一个 EXE 文件,意味着您可以直接在 Windows 上运行。所有 .net core 运行时都包含在包中。

摘要

跨平台越来越受到开发者的关注,一次编写,随处部署,无需寻找他人来设置系统。许多编程语言、IDE、工具和数据库都支持 Windows、Linux、Mac 和移动设备,并且运行良好。本文介绍了使用 .NET Core 运行时和 iBoxDB 数据库,加上一个 JavaScript 模拟器 Nesnes,一个简单干净的解决方案。您可以使用与业务相关的模拟器代替游戏模拟器,以保持产品的卓越!

资源

   iBoxDB - 数据库

   Nesnes - NES 模拟器

历史

   版本 1.0

   版本 1.1

 

© . All rights reserved.