Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Core cross platform development with Database

0.00/5 (No votes)
12 Sep 2017 1  
Develop an ASP.NET Core NES Game website on Linux with cross platform Database, and deploy to Ubuntu & Windows which have no .NET Core runtime installed.

Introduction

Linux is a OS you can get from everywhere, it works very well on Internet. In this article, we will try to build a small online game website using ASP.NET Core. At the end,  we will copy the Release to a new Windows system which has no .NET Core runtime installed. 

Hello .NET Core

Before beginning a .NET Core website,  first we are going to feel it on Ubuntu Linux.  Without IDE, we use Command-Line to create a Console Project.

$dotnet new console

It created two file, the project game.csproj, the CSharp file Program.cs. the obj folder you can delete it if you added a Dependency but got error.

Run the project

$dotnet run

Done. how do you feel, that is how to create a project without a Big IDE, using keyboard instead of using mouse. 

But... without an all-in-one IDE, how to create database application? In this article, we will use Visual Studio Code to edit code, the record of Table and the object of C# are the same for the database engine, editing code is editing database,  we will talk about database later.

Create ASP.NET Core WebAPI

We'll use WebAPI + JavaScript to build the game Website, creating WebAPI project is like creating Console project.

$dotnet new webapi

As most WebSites, we need two components, database and service.

First, add a database to the project, run

$dotnet add package iBoxDB --version 2.15

it adds the package information to game.csproj, you can write the PackageReference by hand. Code is written in bold in the following snippet.

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

Some projects cannot connect to Internet directly,  we can download the Assembly from Web and copy to the project, then modify the 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>

After setup the database iBoxDB, we write nine lines of code to test it, and take a look the Editor, be prepared, Visual Studio Code only inherits the name from 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 is self-hosted,  the database iBoxDB is embedded, the console shows the DB is running and the WebServer is listening on "http://localhost:5000". Everything works.

This WebSite is going to provide NES Game online Service, player can play NES game online. we copy the NES emulator nesnes to ./wwwroot/nesnes folder, it is written by javascript. And create a new folder ./wwwroot/roms for the Game's roms, you can find these roms(*.nes) on Internet, download and copy to this folder. Some games are not supported by the emulator, copy the NES Roms(*.nes) to the folder as many as possible.

Setup is finished.

Get it Online

For now, the self-hosted WebServer only can be accessed through localhost, and decline the HttpRequest to get *.nes

Modify Program.BuildWebHost() to listen any 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();
}

Modify Startup.Configure() to accept all files

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();
}

Web Site Development

Most websites have a member system,  we create a class User to represent the player.

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

    public string ImageURL;

    public DateTime Time;
    public long Ver;
}

the GameName is what game the player to play, the corresponding javascript code below.

//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);

the Client(player's Browser) sends a HttpRequest to Server to get what game he can play, then Load() the game from server.

"./api/user" is a ASP.NET Core WebAPI,  the C# code below, it sets the GameName randomly.

//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;
}

We will have an Admin to monitor online players, he needs the Client sends the game screen(as image) back to the Server, and stores the screen in Database by using User.ImageURL property.

//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));

Players are in and out, what the Admin wants to see, only the online players.  how did we know which is activated. An easy way is  adding a Time property to class User, when updating the Screen, updates the Time, then Select<User>( "from User where Time>?", lastTime) to get newest data from last time;

We know it will have many players online, two players may get the same Time when updating the Screen, let's take a look at the following scenario.

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% of accuracy is fine for this small project, but can we get it better? iBoxDB supports UpdateIncrement, it means the Value of Field(long type) can be increased when the object is updated, with thread safe. To enable this feature, add one line of code at Config

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

Now, we can store the player's game screen on Server, we don't need to set User.Ver, it will be set by the DB.

//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();
   }
}

Because every object has a Version,  we can get updated objects like this

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

Get the newest Version at the beginning.

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

In this article, we use Time Property at the beginning for better looking, because at beginning the updated objects is empty.

//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);
   }
}

Although we used Time at beginning, the Ver Property is still needed, it told the DB to use Ver-Index to search, otherwise, the DB will use ID-Index to search.

PS: The "limit 0,count" sentence above was used to debug Javascript & CSS, returning the Size we wanted to test Page Layout. Remove the "limit" before publishing.

The corresponded javascript code

//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]);
  }
}

The final admin.html Page

$dotnet run

Publish

We developed the Website on Linux, if we want to deploy it on Windows, edit game.csproj, add win10-64 

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

Then run

$dotnet publish -c release -r win10-x64

You will get a Windows Release in project/bin/release/netcoreapp2.0/win10-x64/publish,  don't forget the /publish folder.

It is a EXE file, means you can run on Windows directly. All .net core runtime are included in the package.

Summary

Cross platform is getting attention from developers, write once, deploy wherever customers like, no need to find someone to setup a system. Many programming languages, IDEs, tools and databases support Windows & Linux & Mac & Mobile devices, working very well. this article introduced a simple and clean solution by using .NET Core runtime with iBoxDB database, plus a javascript emulator Nesnes, you can use a business related emulator instead of game emulator to keep the product awesome!

Resources

   iBoxDB -Database

   Nesnes -NES Emulator

History

   Version 1.0

   Version 1.1

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here