Click here to Skip to main content
15,795,331 members
Articles / Containers / Docker

ASP.NET Core and Blazor on Mac: Dockerizing Applications

Rate me:
Please Sign up or sign in to vote.
4.79/5 (5 votes)
6 Oct 2018CPOL38 min read 25.6K   159   6   6
A step-by-step demo on how to build and dockerize ASP.NET Core and Blazor apps on MacOS


I’ve been exploring .NET Core since it was still vNext to ASP.NET 5 until it officially came a .NET Core / ASP.NET Core version 1. I was really happy and amazed that Microsoft came into a realization to rebuild .NET and make it cross-platform and run everywhere. To me, I think it’s one of the greatest milestones that Microsoft ever had as this opens up .NET to an entirely new audience of developers and designers.

Technologies are constantly evolving and as developers, we need to cope up with what’s the latest or at least popular nowadays. As a beginner, you might find yourself having a hard-time catching up with latest technologies because it will creates more confusion for you as to what sets of technologies to use and where to start. We know that there are tons of resources out there that you can use as a reference to learn but you still find it hard to connect the dots in the picture. Sometimes, you might think of losing the interest to learn and give up. If you are confused and have no idea about how to start building an ASP.NET Core and Blazor apps and running them on Docker, then this article is for you.


I was researching the concept of “microservices” since the past years and given that .NET Core is open-source, cross-platform and runs everywhere, I had this curiosity of running it in a new set of environments to get a feel of how great it is. And then, I started out exploring .NET Core on Mac and running it on Docker when version 1 came out and a year after, I started trying out dockerizing ASP.NET Core 2.0 on Mac environment. It was a fun experiment but at the same time hard because I am used to Windows environment. Building apps in Mac environment is a bit different because we will be dealing with the new commands, tools, and the file structure. In other words, I was not that familiar with the Mac environment and putting the pieces together was really a P.I.T.A.

Early this year, Microsoft announced a new experimental project from the ASP.NET team called Blazor. Blazor is an experimental web UI SPA framework based on C#, Razor, and HTML that runs in the browser via WebAssembly without JavaScript. Yes, you heard that right – without JavaScript! That being said. Blazor is still in its experimental stage and we can't guarantee anything until it will be officially released.

I personally find the framework very interesting. I think Blazor is going to be incredibly a hit and that's because I see WebAssembly as actually superseding JavaScript. Sure, JavaScript and its frameworks aren't going anywhere, but why would you teach a new programmer JavaScript when you can just teach them C#, Python, etc. and have them work with simpler tools in a more performant environment?

It was on my to-do list to try out Blazor but I just didn’t get the chance to make time for it because of priorities and I’ve been very busy at work. Fortunately, I was able to find some time this week and gave Blazor a shot. My first attempt was running it on Windows, but I also wanted to test out Blazor on Mac and running it on Docker.

It took me a few days to put the pieces together and try to figure out how to make them work. With a lot of research, some patience and head scratching, I was able to connect the dots in the picture. So that’s why here, I am trying to share the fun and experience with those who are also interested.

Who Is This For?

While there are a bunch of resources on the web that demonstrate how to build and run .NET Core apps in Docker, it seems to me that there are only limited resources on doing it on a Mac. This article will walk you through on building your first Blazor app with ASP.NET Core Web API, Entity Framework Core, SQL Server and running them on a Docker container.

This article is targeted for beginner to intermediate .NET developers who want to jump on ASP.NET Core, Blazor and Docker in a Mac environment and get their hands dirty with a practical example.

I've written this article in such a way that it’s easy to follow and understand by providing step-by-step process with as detailed code explanation as possible. As you go along and until such time you finish following the article, you will learn the basic concepts and fundamentals of each of the technologies used for building the whole application and how each of them connects to each other.

What You Will Learn

Here’s what you can learn from the article:

  • The goal of what we are trying to achieve
  • Setting up the development environment
  • Configuring a database using SQL Server for Linux
  • Using Valentina Studio for managing database
  • Creating an ASP.NET Core Web API application
  • Dockerizing the Web API application
  • Creating your first Blazor application
  • Connecting all applications together from a Docker container

Setup the Development Environment

Let’s go ahead and install the required tools and SDKs for us to build our application in MAC environment. If you already have installed the tools mentioned in the list below, then you may skip this step but just make sure you update them to the latest version possible.


macOS 10.12 “Sierra” and later versions

You need to have at least macOS 10.12 “Sierra” version on your MAC machine as we are going to use .NET Core 2.1 as the target framework for building the apps.

Visual Studio Community

The Community edition of Visual Studio is a free and full-featured solution that enables developers to build applications for Android, iOS, macOS, Cloud and Web. We will be using this editor to build a Blazor and ASP.NET Core Web API app using C# code. For more information about the features of Visual Studio Community edition, see this link.

.NET Core (optional)

.NET Core gives us the dotnet command line tools for us to build and run .NET Core apps without any IDE. I tagged it as optional because .NET Core SDK should be included when you install Visual Studio for MAC. For more information about .NET Core, see this link.

Docker Engine

Docker creates simple tooling and a universal packaging approach that bundles up all application dependencies inside a container. Docker Engine enables containerized applications to run anywhere consistently on any infrastructure. We will see how we can use Docker to deploy and run .NET Core apps inside a container. For more information about the Docker Engine, see this link.

Valentina Studio

As far as I know, Microsoft SSMS team has no plan as of this time of writing to make a cross-platform version of SQL Server Management Studio. As a result, if you are planning to use SQL Server database in your MAC and wanted to use a tool (GUI) for managing your database, then you may end up using a third party tool such as Valentina Studio, SQLPro, Navicat, TablePlus, etc.

For this demo, I’m going to use Valentina Studio because it’s free and it doesn’t require you to run a Windows Virtual Machine (VM) on your MAC. Adding to that, it also supports a few database engines such as PosgreSQL, MySQL and SQLite.

Download Source


Make sure to download and install the perquisites before you go any further.

Five Players, One Goal

As you can see from the prerequisites section, we are going to use various technologies to build this whole application to fulfill a goal. At this point, you should already have the needed frameworks installed in your machine.

Our main goal is to build a simple data-driven web application using cutting-edge technologies: Blazor, ASP.NET Core Web API, Entity Framework Core, SQL Server and Docker.

Before we talk about the high-level application flow on how each technology connects together, let’s take a look at first the brief overview of them.


The ASP.NET Core Web API is an extensible framework for building HTTP based services that can be accessed in different applications on different platforms. It works more or less the same way as ASP.NET Core MVC web application except that it sends data as a response instead of HTML View. It is like a webservice or WCF service, but the exception is that it only supports HTTP protocol. Here’s the definition taken from the official documentation.


ASP.NET Web API is a framework that makes it easy to build HTTP services that reach a broad range of clients, including browsers and mobile devices. ASP.NET Web API is an ideal platform for building RESTful applications on the .NET Framework.

Entity Framework Core

Entity Framework (EF) Core is a lightweight, extensible, and cross-platform version of the popular Entity Framework data access technology. EF Core is an object-relational mapper (O/RM) that enables .NET developers to work with a database using .NET objects. It eliminates the need for most of the data-access code that developers usually need to write. According to the official documentation:


As an O/RM, EF reduces the impedance mismatch between the relational and object-oriented worlds, enabling developers to write applications that interact with data stored in relational databases using strongly-typed .NET objects that represent the application's domain, and eliminating the need for a large portion of the data access "plumbing" code that they usually need to write.


Blazor is a single-page web app (SPA) framework built on top .NET Core that runs in the browser with WebAssembly. For more information about Blazor, see this link.


Warning! Blazor is an unsupported experimental web framework that shouldn't be used for production workloads at this time.

SQL Server

Microsoft SQL Server is a relational database management system developed by Microsoft. As a database server, it is a software product with the primary function of storing and retrieving data as requested by other software applications (Desktop, Service, Mobile or Web) — which may run either on the same computer or on another computer across a network or internet.


Docker provides container software that is ideal for developers and teams looking to get started and experimenting with container-based applications.

Application Flow

Image 1

Figure 1: Application flow

The diagram above shows what we are trying to achieve in this article. We basically need three main Docker containers: a Blazor app for UI, Web API for serving JSON data and SQL Server for storing the actual data.

Let’s take a look at how we can achieve the goal. Let’s start by setting up a database.

Configure a Database

In order for us to work with real data, we need to have a persistent storage called “database”. In Windows environment, setting up a new database is very easy and straight forward. However, in the context of macOS, it is different as there is no SQL Server version for Mac yet as of this time of writing.

The only way to run SQL Server on Mac is to install a Windows VM and run it from there. However, with the release of SQL Server 2017, Microsoft has made it available for macOS and Linux environments. This enables us to use Docker container to run SQL Server which acts as if the server is running on your Mac. Thanks to Docker!

SQL Server for Linux Prerequisites

According to the documentation, SQL Server for Linux requires at least a minimum of 2 GB of disk space to run. However, Docker only allocates 2 GB by default. Therefore, we should increase this allocation to ensure that SQL Server will be able to run. To do that, follow the steps below:

  1. Open the Docker application.
  2. Logon using your Docker credentials.
  3. On the Docker main menu, select Preferences.
  4. Click on the Advance tab and adjust the Memory allocation to something like 4 GB as shown in the figure below:

    Image 2

    Figure 2: Docker memory allocation
  5. Click Apply & Restart.

Now let’s download the SQL Server for Linux Docker container images from the Docker Hub by running the following command in the Terminal console:

docker pull

The docker pull command downloads a particular image or sets of images from the Docker registry. The figure below shows when your pull is successful:

Image 3

Figure 3: Downloading the SQL Server for Linux Docker container

To confirm the image that we pulled, run:

docker images

The command above should result to something like this:

Image 4

Figure 4: List of Docker images

We can then start running the SQL Server container image within Docker by running the following command:

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=SuperSecret1!' 
       -p 1433:1433 --name sql17_linux -d

Take note of the SA_PASSWORD value as we are going to use that in setting up a ConnectionString to enable us to communicate with the database.

The following table provides a description of the parameters in the previous docker run example taken from the official documentation:




Set the ACCEPT_EULA variable to any value to confirm your acceptance of the End-User Licensing Agreement. Required setting for the SQL Server image.

-e 'SA_PASSWORD=SuperSecret1!'

Specify your own strong password that is at least 8 characters and meets the SQL Server password requirements. Required setting for the SQL Server image.

-p 1433:1433

Map a TCP port on the host environment (first value) with a TCP port in the container (second value). In this example, SQL Server is listening on TCP 1433 in the container and this is exposed to the port, 1433, on the host.

--name sql17_linux

Specify a custom name for the container rather than a randomly generated one. If you run more than one container, you cannot reuse this same name.

-d microsoft/mssql-server-linux:2017-latest

Run the SQL Server 2017 Linux container image on detach mode.

To verify if the Docker container is running, run:

docker ps

and it should give you the following result:

Image 5

Figure 5: List of Docker containers

If the SQL Server for Linux Docker container exits, you can run it again using the following command:

docker run -e 'SA_PASSWORD=SuperSecret1!' -p 1433:1433 

Connect to SQL Server Inside a Docker Container

Open Valentina Studio. You may be prompted to enter a key the very first time you open it as shown in the figure below:

Image 6

Figure 6: Valentina Studio registration - screen 1

Select Get a FREE serial and enter it, then click Continue and it should take you to the next screen below:

Image 7

Figure 7: Valentina Studio registration - screen 2

Enter all the required information and then click Continue. When your registration is successful, you should be able to receive an email from Valentina support containing the free serial keys for Mac, Linux and Windows.

After you’ve configured Valentina to use a free serial key, you should be able to see something like this:

Image 8

Figure 8: Valentina Studio start page

At the bottom of the “Servers” panel, click Add Bookmark and it should present you the following screen:

Image 9

Figure 9: Valentina Studio connecting to SQL Server for Linux container

Enter a Bookmark Name, Port and the Password for connecting the SQL Server for Linux hosted in Docker container in the previous step. It's good practice to click “Test Connection” first to make sure you entered your information correctly, and if the test succeeds, click OK to save the bookmark.

Once you’ve saved your bookmark, you will see a new item under “Servers” panel which allows you to directly access your Docker SQL instance as shown in the figure below:

Image 10

Figure 10: Valentina Studio SQL Server for Linux running

Note that you can also use the SQL Server command-line tool, sqlcmd, inside the container to connect to SQL Server. For more information, see:

Create an Empty Database

Now it’s time for us to create a new database. Double click the database instance that we’ve created earlier. It should display something like this:

Image 11

Figure 11: Valentina Studio SQL Server for Linux running

Click the Open SQL Editor and then execute the following script below:


The script above should create a blank database called “Band” as shown in the figure below:

Image 12

Figure 12: Created the Band database

Note that you can also use the Valentina Studio UI to create and manage database by clicking on the Create Database button.

At this point, we are now ready to implement a basic Data Access.

Create an ASP.NET Core Web API Project

Open Visual Studio for Mac and then create a New Project. Under .NET Core > App, select ASP.NET Core Web API just like in the figure below:

Image 13

Figure 13: New ASP.NET Core Web API project

Click Next and it should present you the following screen below:

Image 14

Figure 14: New ASP.NET Core Web API project

Enter the Project Name, Solution Name and Location. You can also configure version control but it’s optional. Click Create to let Visual Studio generates the default files needed for our application. Here’s what the Solution looks like:

Image 15

Figure 15: ASP.NET Core Web API default generated files

Let’s take a quick overview about each file generated.

If you already know the core significant changes of ASP.NET Core, then you may skip this part, but if you are new to ASP.NET Core, then I would like to highlight some of those changes. If you have worked with previous versions of ASP.NET before, then you will notice that the new project structure is totally different. The project now includes these files:

  • Dependencies: contains both NuGet and SDK dependencies needed for the application.
  • Controllers: This is where you put all your Web API or MVC controller classes. Note that the Web API project will by default generate the ValuesController.cs file.
  • Properties: contains the launchSettings.json file which manages application configuration settings associated with each debug profile such as IIS, IIS Express and the application itself. This is where you define profile-specific configuration (Compilation and Debug profiles) for frameworks used in the application.
  • wwwroot: is a folder in which all your static files will be placed. These are the assets that the web app will serve directly to the clients, including HTML, CSS, Image and JavaScript files.
  • appsettings.json: contains application settings.
  • Startup.cs: this is where you put your startup and configuration code.
  • Program.cs: this is where you initialize all services needed for your application.

First Run

To ensure that we got everything we need for our Web API project, let’s try to build and run the project. We can do this by pressing the Command + Return keys or by clicking the Play button located at the Visual Studio menu toolbar. This will compile, build and automatically launch the browser window. If you are getting the following result below, then we can safely assume that our project is good to go.

Image 16

Figure 16: First run

Cool! Now let’s move on to the next step.

Configure Data Access with Entity Framework Core

Let’s install Entity Framework Core packages into our project. From Visual Studio menu, go to Project > Add Nuget Packages. Alternatively, you can right-click on the Dependencies folder of the project and the select Add Packages.

The figure below shows the Nuget Package Manager window:

Image 17

Figure 17: Nuget Package Manager window

On the search bar, type in “Microsoft.EntityFrameworkCore.SqlServer” and then click Add Package.

Later in this demo, we will use some Entity Framework Tools to maintain the database. So install the following tools package as well:

  • Microsoft.EntityFrameworkCore.Tools

The versions of Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.Tools as of this time of writing is 2.1.3.

After successfully installing the packages above, make sure to check your project NuGet dependencies to ensure that we got them in the project. See the figure below:

Image 18

Figure 18: Nuget Package dependencies

These packages are used to provide Entity Framework Core migration. Migration enables us to generate database, sync and update tables based on our Entity Models.

For this demo, we are going to use Code-First approach, that is we create classes (POCO Entities) and generate a new database out from it. If you are new to Entity Framework Core, read my other article here: Getting Started with Entity Framework Core: Building an ASP.NET Core Application with Web API and Code First Development.

Create a Model

Create a new folder in your application and name it as “Models”. Under that folder, create a new class and name it as “Band” and then copy the following code below:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Band.API.Models
    public class Band
        public int Id { get; set; }
        public string Name { get; set; }
        public string Genre { get; set; }

The Band class is nothing but just a simple class that houses some properties. You may have noticed that we decorated the Id property with [Key] and [DatabaseGenerated] attributes. This is because we will be converting this class into a database schame and the Id property will serve as our primary key with auto-incremented identity. These attributes resides within System.ComponentModel.DataAnnotations. For more details about Data Annotations, see Using Data Annotations for Model Validation.

Define a DBContext

Entity Framework Core Code-First development approach requires us to create a data access context class that inherits from the DbContext class. Now let's add a new class under the "Models" folder. Name the class as "BandContext" and then copy the following code below:

using System;
using Microsoft.EntityFrameworkCore;

namespace Band.API.Models
    public class BandContext : DbContext
        public BandContext(DbContextOptions opts) : base(opts){}

        public DbSet<Band> Bands { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

The class above defines a context and an entity class that makes up our model. A DbContext must have an instance of DbContextOptions in order to execute. This can be configured by overriding OnConfiguring() method, or supplied externally via a constructor argument. In our example above, we opt to choose constructor argument for configuring our DbContext.

Add Database Migration

Our next step is to add Code-First migrations. Migrations automates the creation of database based from our Model. We will be using EF Core tools to scaffold our migration and updating the database. We’ll be using the command line (dotnet CLI) to run our migrations.

Setup a ConnectionString

Let’s go ahead and define the following database connection string under appsettings.json file:

"ConnectionString": "Server=; Database=Band; User Id=sa;Password=SuperSecret1!;"

The ConnectionString above defines a set of attributes to connect the application to the database we configured previously. These attributes are:

  • Server: We used an IP address to connect to the server using which is equivalent to localhost. Please note that putting the 1433 port is not necessary as that’s the default port number for connecting to SQL Server for Linux instance.
  • Database: is the name of the database that you want to connect. In this case, the Band database.
  • User Id: The user id for connecting to SQL server. In this case “SA”.
  • Password: The password for connecting to SQL Server for Linux in Docker container. In this case, it’s “SuperSecret1!”
Register DBConext as a Service

Next, we need to register the BandContext as a service and enable it to use SQL Server. To do that, follow the steps below:

  1. Open Startup.cs and declare the following namespaces below:
    using Microsoft.EntityFrameworkCore;
    using BandModel = Band.API.Models; 
  2. Next, add the following code within ConfigureServices() method to register the BandContext as a service:
    public void ConfigureServices(IServiceCollection services){
          services.AddDbContext<BandModel.BandContext>(opts => opts.UseSqlServer
  3. Clean and build the application to ensure there’s no error.
Execute Database Migrations

Now open the Terminal console to the location where your project Solution is located and then do:

dotnet ef migrations add InitialMigration

The dotnet ef migration is the command to add migrations. When the migration is successful, you should be able to see a new folder named "Migrations" that contains the migration files as shown in the figure below:

Image 19

Figure 19: EF Migration files

At this point, our Entity Model (Bands DbSet) isn’t added to the database yet. We need to apply the migration to the database to create the schema by running the following command below in the Terminal console:

dotnet ef database update

The command above should sync the Entity Model to the database. You can confirm that in Valentina Studio as shown in the figure below:

Image 20

Figure 20: Generated Database schema from EF migration process

You can also use the SQLCMD command tool to verify if the migration is reflected inside the SQL Server for Linux in Docker container by running the following command in the Terminal console:

docker exec -it sql17_linux /opt/mssql-tools/bin/sqlcmd 
            -S localhost -U sa -P SuperSecret1!

And then running the following command:

1> SELECT Name FROM sys.Databases
2> GO

This should result to this:

Image 21

Figure 21: Using SQLCMD

You can then drill-down to the schema by running the following:

1> USE band
2> GO
1> SELECT * FROM Bands
2> GO

It should display the Columns that we’ve migrated but of course it should yield an empty result for now.

Seed Test Data on Application Startup

There are certain scenarios where you don’t want to manually generate the database and schema after adding a migration. This can be accomplished by applying migrations at runtime. To do that, let’s setup a helper class for generating default test data.

Go ahead and create a new folder under the “Models” folder and create a new class called “DBSeeder” and replace the default generated code with the following:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Band.API.Models.DataManager
    public class DBSeeder
        public static void Seed(IApplicationBuilder appBuilder)
            using (var serviceScope = appBuilder.ApplicationServices.CreateScope())
                var context = serviceScope.ServiceProvider.GetService<BandContext>();

                using (context)

                    if (!context.Bands.Any())
                        var bands = new List<Band>()
                            new Band(){
                                Name = "Alice In Chains",
                                Genre="Heavy Metal"


The Database.Migrate() method piece is responsible for two things:

  1. The creation of database in SQL Server if it does not exist yet.
  2. Migrating the database schemas to the latest version.

The final step is to apply the migration when the application starts up. To do that, you can call the DBSeeder.Seed() method to the Configure() method in the Startup class of the Web API project:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            if (env.IsDevelopment())



Caution, this approach isn't for everyone. While it's great for apps with a local database, most applications will require more robust deployment strategy like generating SQL scripts.

Implement a Data Repository Interface

We don't want our API Controller to access directly our BandContext service, instead we will let other service handle the communication between our BandContext and API Controller. Having that said, we are going to implement a basic generic repository for handling data access within our application. Add a new folder under "Models" and name it as “Repository”. Create a new interface and name it as “IDataRepository”. Update the code within that file so it would look similar to the following code below:

using System.Collections.Generic;

namespace Band.API.Models.Repository
    public interface IDataRepository<TEntity, U> where TEntity : class
        IEnumerable<TEntity> GetAll();
        TEntity Get(U id);
        int Add(TEntity b);
        int Update(U id, TEntity b);
        int Delete(U id);

The code above defines our IDataRepository interface. An interface is just a skeleton of a method without the actual implementation. This interface will be injected into our API Controller so we will only need to talk to the interface rather than the actual implementation of our repository. One of the main benefits of using interface is to make our code reusable and easy to maintain.

Create a Data Manager

Next is we are going to create a concrete class that implements the IDataRepository interface. Add a new folder under "Models" and name it as “DataManager”. Create a new class and name it as “BandManager”. Update the code within that file so it would look similar to the following code below:

using Band.API.Models.Repository;
using System.Collections.Generic;
using System.Linq;

namespace Band.API.Models.DataManager
    public class BandManager: IDataRepository<Band, int>
        BandContext _context;
        public BandManager(BandContext context)
            _context = context;

        public Band Get(int id)
            return _context.Bands.FirstOrDefault(b => b.Id == id);

        public IEnumerable<Band> GetAll()
            return _context.Bands.ToList();

        public int Add(Band band)
            int id = _context.SaveChanges();
            return id;

        public int Delete(int id)
            var band = _context.Bands.FirstOrDefault(b => b.Id == id);
            if (band != null)
            return id;

        public int Update(int id, Band item)
            var band = _context.Bands.Find(id);
            if (band != null)
                band.Name = item.Name;
                band.Genre = item.Genre;

            return id;

The DataManager class handles all database operations for our application. The purpose of this class is to separate the actual data operation logic from our API Controller, and to have a central class for handling create, update, fetch and delete (CRUD) operations.

At the moment, the DataManager class contains five methods:

  • The Get() method gets a specific student record from the database by passing an Id. It uses a LINQ syntax to query the data and returns a Band object.
  • The GetAll() method gets all band records from the database as you could probably guess based on the method name.
  • The Add() method creates a new band record in the database.
  • The Delete() method removes a specific band record from database based on the Id.
  • Finally, the Update() method updates a specific student record in the database.

All methods above use LINQ to query the data from database.

Register IDataRespository Interface as a Service

Next, we will register the IDataRepository as a service. This service will be registered with Dependency Injection (DI) during application startup. This would enable our API Controller or other services gain access to the BandContext via constructor parameters or properties.

In order for other services to make use of the BandContext, we will register it as a service. To enable the service, do the following steps:

  1. Open Startup.cs and declare the following namespaces below:
    using Band.API.Models.Repository;
    using Band.API.Models.DataManager; 
  2. Add the following code within ConfigureServices() method between services.AddDbContext() and services.AddMvc() method:
    services.AddScoped(typeof(IDataRepository<BandModel.Band, int>), 

Create an API Controller

Now that our DataManager is all set and enabled Dependency Injection for IDataRepository, it's time for us to create the API Controller and expose some endpoints for serving data to the client.

Go ahead and right-click on the "Controllers" folder and then select Add > New File > ASP.NET Core > Web API Controller Class as shown in the figure below:

Image 22

Figure 22: New Web API Controller

Name the class as "BandsController" and click New.

Now replace the default generated code so it would look similar to the following code below:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Band.API.Models.Repository;
using BandModel = Band.API.Models; 

namespace Band.API.Controllers
    public class BandsController : Controller
        private IDataRepository<BandModel.Band, int> _iRepo;
        public BandsController(IDataRepository<BandModel.Band, int> repo)
            _iRepo = repo;

        // GET: api/bands
        public IEnumerable<BandModel.Band> Get()
            return _iRepo.GetAll();

        // GET api/bands/5
        public BandModel.Band Get(int id)
            return _iRepo.Get(id);

        // POST api/bands
        public int Post([FromBody]BandModel.Band band)
            return _iRepo.Add(band);

        // POST api/bands
        public int Put([FromBody]BandModel.Band band)
            return _iRepo.Update(band.Id, band);

        // DELETE api/bands/5
        public int Delete(int id)
            return _iRepo.Delete(id);

Let’s see what we just did there.

The BandController derives from the Controller base and by decorating it with the Route attribute enables this class to become a Web API Controller.

If you notice, we have used Controller Injection to subscribed to the IDataRepository interface. So instead of taking directly to the StudentManager class, we just simply talk to the interface and make use of the methods that are available.

The BandController has five (5) main methods/endpoints. The first Get() method class the GetAll()method from IDataRepository interface and basically returns all the list of Band from the database. The second Get() method returns a specific Band object based on the Id. Notice that the second Get() method is decorated with [HttpGet("{id}")], which means append the "id" to the route template. The "{id}" is a placeholder variable for the Id of the Band object. When the second Get() method is invoked, it assigns the value of "{id}" in the URL to the method's id parameter. The Post() method adds a new record to the database. The Put() method updates a specific record from the database. Notice that both Post() and Put() uses a [FromBody] tag. When a parameter has [FromBody], Web API uses the Content-Type header to select a formatter. Finally, the Delete() method removes a specific record from the database.

Enable CORS

Now that we have our API ready, the final step that we are going to do on this project is to enable Cross-Origin Resource Sharing (a.k.a CORS). We need this configuration so other clients that are hosted in a different domain/ports can access the API endpoints.

To enable CORS in ASP.NET Core Web API, follow the steps below:

  1. Open Startup.cs file and on ConfigureServices() method, add the following code below before services.AddMvc():
    services.AddCors(opts =>{
                    opts.AddPolicy("AllowAll", builder =>
  2. Then on Configure() method, copy the following code below before app.UseMvc():

That’s it.


Note that just for the purpose of this demo, we allow all clients to access the API. In real-world applications, it’s recommended to set the allowable origins, methods, headers and credentials for your APIs. For more information about CORS, see:

Testing the API Enpoints

To ensure that our API endpoints are working, it’s always recommended to do an initial test at the early stage of development. You can download Postman and use that as a tool to test out APIs.

Here are a few tests I did in Postman:


curl -X POST \
  https://localhost:5001/api/bands \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: a025f464-3501-4fbc-a556-51ed1f4abe0c' \
  -d '{
    "Name": "Alice in Chains",
    "Genre": "Heavy Metal"


Image 23

Figure 23: POST result


curl -X GET \
  https://localhost:5001/api/bands \
  -H 'Cache-Control: no-cache' \
  -H 'Postman-Token: 40bbcadd-c5fe-43da-8c4f-fb89bc63f9b8'

Image 24

Figure 24: GET result

Dockerize ASP.NET Core Web API Application

At this point, the Web API application is still running on the local machine. Our goal is to deploy it on a Docker container so other client application can access to it.

Enable Docker Support

To enable docker support, right-click on the Web API project and then select Add > Add Docker Support as shown in the figure below:

Image 25

Figure 25: Add Docker support

That should add the following file to the project:

  • Dockerfile - contains the instruction to build the docker image. For more information, see: Dockerfile Reference
  • Docker-Compose - tool for defining and running multi-container Docker applications. For more information, see: Docker Compose

Docker File

Here’s what the Docker file looks like for this demo:

FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base

FROM microsoft/dotnet:2.1-sdk AS build
COPY Band.API/Band.API.csproj Band.API/
RUN dotnet restore Band.API/Band.API.csproj
COPY . .
RUN dotnet build Band.API.csproj -c Release -o /app

FROM build AS publish
RUN dotnet publish Band.API.csproj -c Release -o /app

FROM base AS final
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Band.API.dll"] 

The instruction above contains the recipe for creating a final Docker image. What it does is it initializes multi-stage build images and sets the base image for subsequent commands. In this case, it tells Docker to base our project on aspnetcore-runtime image and run from there by exposing port 5555. The subsequent instructions tells the Docker to pull the required dependencies needed for this app to run, build it and publish as a container . For more information about the commands within it, see: Dockerfile reference.

Managing Containers

Remember that our SQL Server database is running on separate container. What we need right now is to enable the Web API app to connect with the database when they are both inside Docker containers. Unfortunately, connecting to the already running container using the ConnectionString Server value of,1433 won’t work.

While it may possible to link an existing container and run it in conjunction with your Web API container as demonstrated on my previous article here, still it’s not an ideal way to handle it especially if you are working with multi-container applications which depends on each other.

Docker-Compose File

Thankfully, Visual Studio 2017 versions 15.7 or earlier support Docker Compose as the sole container orchestration solution. The Docker Compose artifacts are automatically added for us when we enabled Docker Support as described in previous section.

The Docker Compose gives us a more convenient way manage multiple Docker containers in one place. Here’s what the docker-compose.yaml file looks like for this app:

version: '3.4'

    image: ${DOCKER_REGISTRY}band
    container_name: band.api
        - "5555:80"
      context: .
      dockerfile: Band.API/Dockerfile
        -;Database=Band;User Id=sa;
        - SA_PASSWORD=SuperSecret1!
        - ACCEPT_EULA=Y
        - "1433:1433" 

The root key in this file is “services”. Under that key, you define the services you want to deploy and run when you execute the docker-compose using a command or when you deploy from Visual Studio by using this docker-compose.yml file. In this case, the docker-compose.yml file has multiple services defined, as described in the following list.

  • band.api – defines the container for the ASP.NET Core Web API project. The ports attribute exposes 5555 for accessing the app externally and exposes 80 for accessing the app internally inside Docker. In other words, it provides the network plumbing so we can talk to services running in containers by mapping to ports on the host. The build attribute contains the path to the dockerfile to use as well as the context docker should use to build the container from. The environment attribute defines variable named ConnectionString with the connection string to be used by Entity Framework to access the SQL Server instance inside Docker container. This configuration overrides the ConnectionString value we defined in the appsettings.json in the Web API project. Also notice that it uses the value as the server value instead of the IP address. This is the beauty of the docker compose as it enables us to connect between containers without having to worry about internal IP and ports used to accessing between containers. Finally, the depends_on attribute defines a dependency to other containers. In this case the This tells Docker to run the service first before running the band.api service.
  • – defines how to run SQL Server for Linux docker container. This docker compose configuration is the same as running the image using the docker run command that we used previously using this command:
docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=SuperSecret1!' 
-p 1433:1433 --name sql17_linux -d

Execute the Docker Compose

To build and deploy the containers defined in docker-compose.yaml file, do the following steps:

  1. Clean and the Build the Web API project to release mode.
  2. Open the Terminal console and make sure you are running it in the location where your Solution file is located.
  3. Stop the existing running container for SQL Server for Linux using the command: docker stop sql17_linux
  4. Then run: docker ps to ensure that the container named “sql17_linux” will be stopped.
  5. Next, run: docker-compose up -d.

You should be able to see something like in the figure below when the build is successful:

Image 26

Figure 26: Composing containers

Testing the Dockerized Web API

At this point, the Web API app and database should be up and running inside the Docker container. We can access it by using the external port 5555 that we have defined in the dockerfile and docker-compose.yaml. Fire off a browser or Postman and then access the following URL:

That should output the following data:

Image 27

Figure 27: Dockerize Web API output

If you’ve made this far, congrats! You’ve just ran an ASP.NET Core and SQL Server inside Docker on your Mac machine. Pretty cool, eh?

But we’re not done yet! We are still going to need a UI to consume the API using a server-side SPA framework – Blazor!

Create Your First Blazor App on Docker


This demo is intended for Blazor 0.6.0 as this is the latest version of as of this time of writing. Any code, file structure and syntax may change on future versions. Check out the official repo for future release updates here:

Without further ado, let’s get cracking. As of this time of writing, Visual Studio 2017 Community v7.6 for MAC doesn’t include Blazor templates so you need to manually install them using the dotnet new CLI just like in the following:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::*

The command above should install the templates for building and running Blazor apps. You can verify if the installation is successful by running the following command:

dotnet new --list

which should give you the following results:

Image 28

Figure 28: ASP.NET Core templates with Blazor

Note that the Blazor templates will not be available in the list of .NET Core Apps when you create a new project in Visual Studio for Mac even if you’ve added them from the command line (CLI). This means that in order for us to create a new Blazor project, we need to use the command line too.

Create a Runtime Docker Container for Blazor

Since our goal is to run Blazor on a Docker container, then we are going to take a different route. This simply means that we are going to build a Docker Container runtimes for Blazor first since there’s no existing one that I am aware of as of this moment.

In order to build a Docker container runtime for Blazor, we need to make sure the exact supported version of .NET Core SDK that corresponds to the Blazor version that you are using. In this example, we are going to use Blazor 0.6.0 which requires .NET Core 2.1 SDK (2.1.402 or later). Once we’ve identified that, head over to the official repository of .NET Core Docker containers here.

The link should take you to page where it lists all the available official images for .NET Core and ASP.NET Core for Linux and Windows Nano Server. The figure below shows the latest versions of common tags:

Image 29

Figure 29: .NET Core docker images

Click on 2.1-sdk and it should take you to the GitHub repo for the corresponding Dockerfile.

Copy the content of the Dockerfile from the repo. Create a new Dockerfile on your local drive and paste the content there. Here’s what my Dockerfile looks like:

FROM buildpack-deps:stretch-scm

# Install .NET CLI dependencies
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        libc6 \
        libgcc1 \
        libgssapi-krb5-2 \
        libicu57 \
        liblttng-ust0 \
        libssl1.0.2 \
        libstdc++6 \
        zlib1g \
    && rm -rf /var/lib/apt/lists/*

# Install .NET Core SDK

RUN curl -SL --output dotnet.tar.gz$DOTNET_SDK_VERSION/
dotnet-sdk-$DOTNET_SDK_VERSION-linux-x64.tar.gz \
    && dotnet_sha512='903a8a633aea9211ba36232a2decb3b34a59bb62bc145a0e7a90c
    a46dd37bb6c2da02bcbe2c50c17e08cdff8e48605c0f990786faf1f06be1ea4a4d373beb8a9' \
    && sha512sum dotnet.tar.gz \
    && echo "$dotnet_sha512 dotnet.tar.gz" | sha512sum -c - \
    && mkdir -p /usr/share/dotnet \
    && tar -zxf dotnet.tar.gz -C /usr/share/dotnet \
    && rm dotnet.tar.gz \
    && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \
    && dotnet new -i Microsoft.AspNetCore.Blazor.Templates

# Configure Kestrel web server to bind to port 80 when present
    # Enable detection of running in a container
    # Enable correct mode for dotnet watch (only mode supported in a container)
    # Skip extraction of XML docs - generally not useful 
    # within an image/container - helps performance

# Trigger first run experience by running arbitrary cmd to populate local package cache
RUN dotnet help

The script above contains the instructions to install and configure .NET Core within Docker container. One important line there is I've added the (dotnet new -i Microsoft.AspNetCore.Blazor.Templates) command to get the Blazor templates so we don’t need to worry about manually installing them and instead just focus directly on building Blazor apps.

Now, save the Dockerfile and then open the Terminal console where you put the Docker file and then run the following command:

docker build -t blazor060:core2.1.402 .

The command above creates a local Docker image named blazor060:core2.1.402 . Note that the name is configurable using the following format <Your Image Name>:<Tag>.

Once the build is successful, then you can run the command docker images to verify if it’s created on your local machine.


If you don’t want to build and create your own Docker runtime container for Blazor, you can also use the existing Docker image that’ve created for this demo here.

You can then do:

docker pull proudmonkey30/blazor060

Create a Blazor App

At this point, we are now ready to create the Blazor app. Go ahead and create a new directory in your local drive to where you would want to store your Blazor app. In this example, I created a folder on my Mac at /<User>/repo/Blazor.Spa.

After that, open your Terminal console and run the following command:

docker run -it --rm --name blazorapp -p 5600:80 
-v /Users/vdurano_srg/repo/Blazor.Spa/:/BlazorApp -w /BlazorApp blazor060:core2.1.402

If you are using the Alternative Docker runtime that I've created, you can do:

docker run -it --rm --name blazorapp -p 5600:80 
-v /Users/vdurano_srg/repo/Blazor.Spa/:/BlazorApp -w /BlazorApp proudmonkey30/blazor060

The command above runs the Docker container in interactive mode. The –rm command tells Docker to remove the container after exiting. The –name specifies the container name. The -p command sets the port for the running container, in this case, we set an external port 5600 and internal port 80. The -v command attaches a volume to the container, in this case we set the volume to the local directory where we dump Blazor related files and then maps it to the internal Docker container location named “/BlazorApp”. The -w command specifies a working directory which in this case, we want to automatically set the working directory to “/Blazor” after executing the command. Finally, we based the container on the image we’ve created earlier to download and restore the dependencies.

Now open a new Terminal console window and run the command docker ps, and it should result to something like this:

Image 30

Figure 30: List of containers

Go back to the first Terminal console window where we execute the docker run command and then do:

dotnet new blazor -o .

The command above should pull the Blazor templates and create the default files to the local directory that we set as volume as shown in the figure below:

Image 31

Figure 31: Blazor app directory

Now, let’s try to run the application by running:

dotnet run

When successful, it should show something like this in the console:

Image 32

Figure 32: Running Blazor app inside docker

Remember that we are running the application within Docker container that’s why you see that’s it’s listening to port 80. However, we can test that out externally using port 5600. To see that action, go ahead and fire up a Chrome browser and the navigate to http://localhost:5600. It should display something like this in your browser:

Image 33

Figure 33: Blazor first run

Awesome! You just had your first Dockerized Blazor app running on Mac! Now let’s have some fun and modify the app to work with real data from our Dockerized Web API.

Now go back to the Terminal console and press CTRL + C to stop the running application.

Just to give you a quick heads-up that I’m not going to cover the details on how the app is implemented in Blazor here. I’m going to cover that in a separate article. The main goal of this article is to see how we can run, build and deploy apps in Docker containers and connect between them.

Okay let’s keep moving, open up the Blazor app that we’ve created previously. To open multiple instance of Visual Studio on Mac, see this link.

Add a New Component

Let’s add a new component by right-clicking on "Pages" folder and select Add > New Item. Select ASP.NET Core from the left panel, then select “Razor Page” from templates panel as shown in the figure below:

Image 34

Figure 34: Create new Blazor component

Name the file as “MyFavoriteBands” and then click New. Copy the following code below:

@page "/bands"
@using Microsoft.AspNetCore.Blazor;
@using Microsoft.JSInterop;
@using System.Threading.Tasks;
@inject HttpClient Http

<h1>All-time Favorite Bands</h1>
    <div class="row">
        <div class="col-sm-1">
        <div class="col-sm-4">
            <input id="txtBandName" 
            placeholder="Band Name" bind="@_bandName" />
    <br />
    <div class="row">
        <div class="col-sm-1">
        <div class="col-sm-4">
            <input id="txtBandGenre" 
            placeholder="Band Genre" bind="@_bandGenre" />
    <br />
    <div class="row">
        <div class="col-sm-1">
            <button class="btn btn-info" 
            id="btnAdd" onclick=@(async () => await Add())>Add</button>
    <br />
@if (bands == null)
    <p><em>Loading your favorite bands of all time...</em></p>
    @if (bands.Count > 0)
        <table class='table table-striped 
        table-bordered table-hover table-condensed' style="width:80%;">
                    <th style="width: 40%">Band Name</th>
                    <th style="width: 20%">Band Genre</th>
                    <th style="width: 20%">Edit</th>
                    <th style="width: 20%">Delete</th>
                @foreach (var band in bands)
                            <span id="spnName_@band.Id">@band.Name</span>
                            <input id="txtName_@band.Id" 
                            bind="@_bandNameUpdate" style="display:none;"></input>
                            <span id="spnGenre_@band.Id">@band.Genre</span>
                            <input id="txtGenre_@band.Id" 
                            bind="@_bandGenreUpdate" style="display:none;"></input>
                            <button id="btnEdit_@band.Id" class="btn btn-primary" 
                            onclick=@(async() => 
                            await Edit(band.Id, band.Name, band.Genre))>Edit</button>
                            <button id="btnUpdate_@band.Id" style="display:none;" 
                            class="btn btn-success" onclick=@(async () => 
                            await Update(band.Id))>Update</button>
                            <button id="btnCancel_@band.Id" style="display:none;" 
                            class="btn btn-primary" onclick=@(async () => 
                            await Cancel(band.Id))>Cancel</button>
                        <td><button class="btn btn-danger" onclick=@(async () => 
                        await Delete(band.Id))>Delete</button></td>

@functions {

    public class BandDTO
        public int Id { get; set; }
        public string Name { get; set; }
        public string Genre { get; set; }

    string _bandAPIUri = "http://localhost:5555/api/bands";
    string _bandName;
    string _bandGenre;
    string _bandNameUpdate;
    string _bandGenreUpdate;

    IList<BandDTO> bands = new List<BandDTO>();

    protected override async Task OnInitAsync()
        await RefreshView();

    private async Task RefreshView()
        bands = await Http.GetJsonAsync<BandDTO[]>(_bandAPIUri);

    public async Task Add()
        if (!string.IsNullOrEmpty(_bandName))
            await Http.SendJsonAsync(HttpMethod.Post, _bandAPIUri, new BandDTO
                Name = _bandName,
                Genre = _bandGenre

            _bandName = string.Empty;
            _bandGenre = string.Empty;

            await RefreshView();

    public async Task Update(int id)
        if (!string.IsNullOrEmpty(_bandNameUpdate))
            await Http.SendJsonAsync(HttpMethod.Put, _bandAPIUri, new BandDTO
                Id = id,
                Name = _bandNameUpdate,
                Genre = _bandGenreUpdate

            await RefreshView();
            await JSRuntime.Current.InvokeAsync<bool>("toggleUIView", 
            new object[] { id.ToString(), "", "", false });

    public async Task Delete(int id)
        await Http.DeleteAsync($"{_bandAPIUri}/{id}");
        await RefreshView();

    public async Task Edit(int id, string bandName, string bandGenre)
        await JSRuntime.Current.InvokeAsync<bool>("blazorAppJS.toggleUIView", 
        new object[] { id.ToString(), bandName, bandGenre, true });

    public async Task Cancel(int id)
        await JSRuntime.Current.InvokeAsync<bool>("blazorAppJS.toggleUIView", 
        new object[] { id.ToString(), "", "", false });

The component above defines the UI and the corresponding UI code logic using Razor and C# syntax to perform a basic CRUD operations in the page by utilizing REST API’s. Notice that the bandAPIUri contains the external/public facing URL endpoint of the Web API project that we deployed in Docker.

Modify the Index.html File

Next, navigate to wwwroot > index.html and then copy the following JavaScript function after blazor.webassemly.js script reference:

        window.blazorAppJS = {
            toggleUIView: function(id, name, genre, show){
                    var txtName = document.getElementById("txtName_" + id);
                    document.getElementById("spnName_" + id).style.display = "none";
           = "";
                    txtName.value = name;

                    var txtGenre = document.getElementById("txtGenre_" + id);
                    document.getElementById("spnGenre_" + id).style.display = "none";
           = "";
                    txtGenre.value = genre;
                    document.getElementById("btnEdit_" + id).style.display = "none";
                    document.getElementById("btnUpdate_" + id).style.display = "";
                    document.getElementById("btnCancel_" + id).style.display = "";
                else {
                    document.getElementById("spnName_" + id).style.display = "";
                    document.getElementById("txtName_" + id).style.display = "none";
                    document.getElementById("spnGenre_" + id).style.display = "";
                    document.getElementById("txtGenre_" + id).style.display = "none";
                    document.getElementById("btnEdit_" + id).style.display = "";
                    document.getElementById("btnUpdate_" + id).style.display = "none";
                    document.getElementById("btnCancel_" + id).style.display = "none";

The toggleUIView function contains the logic for toggling the Edit, Update and Cancel buttons in the UI.

For more information about Blazor, see this link.

Run the Blazor App

Before we start the test, be sure to run the command docker ps to ensure that all the containers that we need are up and running as shown in the figure below:

Image 35

Figure 35: List of Docker containers

As you can see, the blazorapp, band.api and Docker containers are up and running. It’s safe for us to test the application.

Now, open the Terminal console to the location where your BlazorApp Solution project is located and then do the following:

  1. dotnet build
  2. dotnet run

Here’s a screenshot of Blazor app running on Docker Container on my Mac:

Image 36

Figure 36: Blazor page output

That's it! goals achieved! woot!

GitHub Repo


In this article, we’ve learned a lot of things, starting from setting up the development environment, creating REST APIs and data-driven SPA app from scratch, down to deploying ASP.NET Core Web API and Blazor apps on a Docker containers. To summarize, here’s what we’ve learned:

  • The goal of what we are trying to achieve
  • Setting up the development environment
  • Configuring a database using SQL Server for Linux
  • Using Valentina Studio for managing database
  • Creating an ASP.NET Core Web API application
  • Dockerizing the Web API application
  • Creating your first Blazor application
  • Connecting all applications together from a Docker container

My apology for the long post. But I hope you've learned something from this article.



  • 6th October, 2018: Initial version


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Written By
United States United States
A code monkey who loves to drink beer, play guitar and listen to music.

My Tech Blog:
My Youtube Channel:

I currently work as a Solutions Architect and we build "cool things" to help people improve their health.

With over 14 years of professional experience working as a Sr. Software Engineer specializing mainly on Web and Mobile apps using Microsoft technologies. My exploration into programming began at the age of 15;Turbo PASCAL, C, C++, JAVA, VB6, Action Scripts and a variety of other equally obscure acronyms, mainly as a hobby. After several detours, I am here today on the VB.NET to C# channel. I have worked on Web Apps + Client-side technologies + Mobile Apps + Micro-services + REST APIs + Event Communication + Databases + Cloud + Containers , which go together like coffee crumble ice cream.

I have been awarded Microsoft MVP each year since 2009, awarded C# Corner MVP for 2015, 2016,2017 and 2018, CodeProject MVP, MVA, MVE, Microsoft Influencer, Dzone MVB, Microsoft ASP.NET Site Hall of Famer with All-Star level and a regular contributor at various technical community websites such as CSharpCorner, CodeProject, ASP.NET and TechNet.

Books written:
" Book: Understanding Game Application Development with Xamarin.Forms and ASP.NET
" Book (Technical Reviewer): ASP.NET Core and Angular 2
" EBook: Dockerizing ASP.NET Core and Blazor Applications on Mac
" EBook: ASP.NET MVC 5- A Beginner's Guide
" EBook: ASP.NET GridView Control Pocket Guide

Comments and Discussions

GeneralWow a genius explanation! Pin
Famma25-Aug-19 23:02
Famma25-Aug-19 23:02 
GeneralRe: Wow a genius explanation! Pin
Vincent Maverick Durano20-Oct-19 11:11
professionalVincent Maverick Durano20-Oct-19 11:11 
QuestionComment on Tools for MS SQL Pin
hmadrigal3-Apr-19 8:57
professionalhmadrigal3-Apr-19 8:57 
QuestionInteresting... but whybuse JS ? Pin
Waseem Anis8-Oct-18 2:40
Waseem Anis8-Oct-18 2:40 
AnswerRe: Interesting... but whybuse JS ? Pin
Vincent Maverick Durano8-Oct-18 6:53
professionalVincent Maverick Durano8-Oct-18 6:53 
GeneralRe: Interesting... but whybuse JS ? Pin
Famma25-Aug-19 23:14
Famma25-Aug-19 23:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.