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





4.00/5 (2投票s)
使用跨平台数据库开发 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,一个简单干净的解决方案。您可以使用与业务相关的模拟器代替游戏模拟器,以保持产品的卓越!
资源
历史
版本 1.0
版本 1.1