Monday 14 October 2019

How to Deploying a Blazor Application on IIS?

In this article, we will understand how to deploy an ASP.NET Core hosted Blazor application with the help of IIS 10 on a Windows 10 machine. We will be using Visual Studio 2017 to publish the app and SQL Server 2014 to handle DB operations. We will also troubleshoot some of the common hosting issues for a Blazor application.

Prerequisites

  • Install IIS in your machine
  • Install URL Rewrite module from here
Please refer to my previous article Cascading DropDownList in Blazor Using EF Core to create the application that we will be deploying in this tutorial.

Installing .NET Core hosting bundle

Since we are going to deploy an ASP.NET Core hosted Blazor application, the first step is to install .NET Core hosting bundle in our machine.
Follow the below steps to download .NET Core hosting bundle:
Deploying a Blazor Application on IIS

  • On the .NET Core runtime download page , scroll down to Windows section, select the “Hosting Bundle Installer” link to download the .NET Core Hosting Bundle.

Deploying a Blazor Application on IIS
Once the download is finished, double click to start installing it. You can see a window similar to the one shown below:
Deploying a Blazor Application on IIS
Important Note
  1. .NET Core hosting bundle should be installed after installing IIS only. If you install.NET Core hosting bundle before installing IIS then you need to repair it after installing IIS so that it will update its dependencies for IIS.
  2. Restart the machine after installing .NET Core hosting bundle.

Publishing the Blazor application

Once .NET Core hosting bundle installation is successful and you have restarted your machine, open the Blazor application solution using VS 2017.
Right click on Server project of your solution and click publish. In this case it will be BlazorDDL.Server >> Publish
Deploying a Blazor Application on IIS
You will see a screen similar to below. Select Folder from left menu and provide a folder path. You can provide any folder path where you want to publish your app.
Deploying a Blazor Application on IIS
Click on publish. Visual Studio will start publishing your application. If there is no build errors then your application will be published successfully to the folder you have mentioned.
After the publishing is successful, we will move on to configure IIS.

Configuring IIS

Open IIS and right click on Sites >> Add Web Site.
An “Add Website” pop up box will open. Here we need to furnish details in three fields
  1. Site name: Put any name of your choice. Here I put “ankitsite”
  2. Physical Path: The path to the folder where you have published your application.
  3. Host name: This is the name we put in browser to access our application. We will put ankitsite.com for this demo.
Click on OK to create the website. Refer to the image below:
Deploying a Blazor Application on IIS
Next step is to configure the “Application Pool” for our site. The application pool name will be same as the “Site name” we have provided in last step. Therefore, in this case the application pool name will be “ankitsite”. Click to “Application Pools” from the left panel and double click on the pool “ankitsite”. It will open an “edit application pool” window. Select “No Managed Code” from the .NET CLR version dropdown. Refer to the image below:
Deploying a Blazor Application on IIS

Here is the whole process of configuring IIS explained in a gif image.
Deploying a Blazor Application on IIS

Configuring the DNS host

The last step is to configure our DNS host file.
Navigate to C:\Windows\System32\drivers\etc path in your machine and open the “hosts” file using any text editor.
Deploying a Blazor Application on IIS
We need to add the hostname that we provided in IIS against localhost IP address. Refer to the image below:
Deploying a Blazor Application on IIS
Hence, we have successfully hosted a Blazor application on IIS.

Execution Demo

Open any browser on your machine and enter the hostname you have configured. You can see that the application will open in the browser window.
Deploying a Blazor Application on IIS

Troubleshooting common hosting issues

In this section, we will look into some of the common problems that you can face while hosting a Blazor application.
  1. If you are unable to open the website and get a DNS not found error
Check if the hostname is configured correctly in host file. Make sure that your machine is not connected to any VPN server. Also, if you are using any Web proxy then disable it.
  1. HTTP Error 500.19 – Internal Server Error – The requested page cannot be accessed because the related configuration data for the page is invalid.
This error message is clear. The publish folder is inaccessible because of insufficient permissions. Grant Read permission to the IIS_IUSRS group on the publish folder so that it can access Web.config file.
  1. If the website is loading but data is not getting populated and you get a 500 Internal server error
Make sure that your connection string is in correct format. The user id that you have specified in your connection string should have db_datareader and db_datawriter permissions. If the issue persists then provide the user with db_owner permission.
  1. If the data is not getting populated and you get an “operation not allowed” exception
This issue generally appears when you try to do a PUT, POST or DELETE operation in your web API. To mitigate this issue we need to alter the IIS setup configuration.
Navigate to Control Panel >> Turn Windows feature on or off. Then navigate to Internet Information Services >> World Wide Web Services >> Common HTTP Features and uncheck the “WebDAV Publishing” option and click OK. Refer to the image below:
Deploying a Blazor Application on IIS

  1. “Failed to load <web API> : No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
The cause of this error is that the client and the server of the application are not on the same port. The browser will restrict the application to make web API calls due to same-origin policy.
To resolve this issue you need to enable Cross-Origin Requests (CORS) in your application. Please refer to Microsoft documents on Enable Cross-Origin Requests (CORS) in ASP.NET Core.
  1. If you republish the application then do not forget to refresh your website as well as the application pool in IIS.

Realtime Blazor Tic-Tac-Toe Game - Bot Vs Multiplayer Using SignalR


In this article, we will see how to create a bot vs. multiplayer tic-tac-toe game in Blazor. Blazor is an open-source .NET web front-end framework that allows us to create client-side applications using C# and HTML. 


This is a simple ASP.NET Core hosted server-side Blazor front-end application with Game UI Razor component and SignalR game hub to connect players with the bot to play the game. The Game Bot is created with .NET Core Background Service with core game engine to identify the best move against a player using a minimax recursive algorithm. 

Architecture

  • Tic-Tac-Toe Blazor App
    This is a server-side Blazor App with Tic-Tac-Toe UI razor component. The razor component will have the game board design and its logic. 
  • SignalR Game HubThis hub holds the SignalR methods to send messages between player and bot. 
  • Tic-Tac-Toe Bot ClientBot client is based on .NET Core background service and contains the core game engine using the minimax algorithm. Whenever the player sends the move details to SignalR hub, it will send it to bot with the current board state and bot will get the next best spot available using core game engine and send it back to hub with the bot move. The hub will send back to the caller, and the player UI will get updated the move details in real-time. 
As a first step, launch the latest Visual Studio 2019 and create a new Blazor project by selecting ASP.NET Core web application and select the Blazor Server App.
I used the Blazor Server-Side app for this example but you can use client-side Blazor as well. Right now, the client-side Blazor app doesn’t have any official Blazor SignalR client due to the dependency of web socket support in the runtime. However, there are community versions of the Blazor SignalR client available.
In the Solution Explorer, add a new Razor component called TicTacToe.razor file and put the Tic-Tac-Toe board design and logic in the component. It also initializes the SignalR hub client.
  1. @using Microsoft.AspNetCore.SignalR.Client    
  2. @using BlazoRTicTacToeGameEngine    
  3.     
  4. @if (@playerWon != null)    
  5. {    
  6.     <div class="container h-100" style="width:500px;background:#ff6a00;padding:40px">    
  7.         <div class="row h-50 justify-content-center align-items-center">    
  8.             <span style="font-size:xx-large">@playerWon Won !</span>    
  9.         </div>    
  10.     </div>    
  11. }    
  12. else if (@isDraw)    
  13. {    
  14.     <div class="container h-100" style="width:600px;background:#ff6a00;padding:40px">    
  15.         <div class="row h-50 justify-content-center align-items-center">    
  16.             <span style="font-size:xx-large">It's a Draw !</span>    
  17.         </div>    
  18.     </div>    
  19. }    
  20. else if (@playerWon == null)    
  21. {    
  22.     
  23.     <div class="container-fluid" style="width:500px;">    
  24.         <div class="row justify-content-center align-items-center">    
  25.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(0))">    
  26.                 <span style="font-size:xx-large">@ShowBoard(0)</span>    
  27.             </div>    
  28.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(1))">    
  29.                 <span style="font-size:xx-large">@ShowBoard(1)</span>    
  30.             </div>    
  31.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(2))">    
  32.                 <span style="font-size:xx-large">@ShowBoard(2)</span>    
  33.             </div>    
  34.         </div>    
  35.         <div class="row justify-content-center align-items-center">    
  36.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(3))">    
  37.                 <span style="font-size:xx-large">@ShowBoard(3)</span>    
  38.             </div>    
  39.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(4))">    
  40.                 <span style="font-size:xx-large">@ShowBoard(4)</span>    
  41.             </div>    
  42.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(5))">    
  43.                 <span style="font-size:xx-large">@ShowBoard(5)</span>    
  44.             </div>    
  45.         </div>    
  46.         <div class="row justify-content-center align-items-center">    
  47.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(6))">    
  48.                 <span style="font-size:xx-large">@ShowBoard(6)</span>    
  49.             </div>    
  50.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(7))">    
  51.                 <span style="font-size:xx-large">@ShowBoard(7)</span>    
  52.             </div>    
  53.             <div class="col-3 col-class text-center" @onclick="@(() => OnSelect(8))">    
  54.                 <span style="font-size:xx-large">@ShowBoard(8)</span>    
  55.             </div>    
  56.         </div>    
  57.     </div>    
  58. }    
  59. <div class="text-center" style="padding:5px;">    
  60.     <button class="btn btn-link" style="color:#ff6a00;font-weight:600" @onclick="@(()=>RestartGame())">Restart</button>    
  61. </div>   
In this component, we have three layouts. The main layout will render the tic-tac-toe board. The other two layouts will show the result of the winner or draw panel. The main layout is using the bootstrap container to design the board, and each cell is associated with onclick event method to notify the hub with the selected cell value.
  1. @code {  
  2.     private string[] board = new string[9];  
  3.     HubConnection connection;  
  4.     GameEngine engine = new GameEngine();  
  5.     string playerWon = null;  
  6.     bool isDraw = false;  
  7.   
  8.     protected async override Task OnInitAsync()  
  9.     {  
  10.         for (var i = 0; i < 9; i++)  
  11.         {  
  12.             board[i] = i.ToString();  
  13.         }  
  14.   
  15.         //Initialize SignalR  
  16.         connection = new HubConnectionBuilder()  
  17.         .WithUrl("https://localhost:5001/gamehub")  
  18.         .Build();  
  19.   
  20.         connection.On<string[]>("NotifyUser", NotifyUser);  
  21.         await connection.StartAsync();  
  22.     }  
  23.   
  24.     Task NotifyUser(string[] newboard)  
  25.     {  
  26.         board = newboard;  
  27.         if (engine.IsWon(board, engine.botPlayer))  
  28.             playerWon = "Bot";  
  29.         else if (engine.GetAvailableSpots(board).Length == 0)  
  30.             isDraw = true;  
  31.         StateHasChanged();  
  32.         return Task.CompletedTask;  
  33.     }  
  34.   
  35.     private async Task OnSelect(int index)  
  36.     {  
  37.         if (!engine.IsPlayed(board[index]))  
  38.         {  
  39.             board[index] = engine.humanPlayer;  
  40.             if (engine.IsWon(board, engine.humanPlayer))  
  41.                 playerWon = "Player";  
  42.             else if (engine.GetAvailableSpots(board).Length == 0)  
  43.                 isDraw = true;  
  44.             else  
  45.                 await connection.InvokeAsync("OnUserMoveReceived", board);  
  46.   
  47.             StateHasChanged();  
  48.         }  
  49.     }  
  50.   
  51.     private string ShowBoard(int index)  
  52.     {  
  53.         return engine.IsPlayed(board[index]) ? board[index] : string.Empty;  
  54.     }  
  55.   
  56.     private void RestartGame()  
  57.     {  
  58.         playerWon = null;  
  59.         isDraw = false;  
  60.         for (var i = 0; i < 9; i++)  
  61.         {  
  62.             board[i] = i.ToString();  
  63.         }  
  64.         StateHasChanged();  
  65.     }  
  66.   
  67. }  
In the OnInitAsync method, we initialize the board with the default index values. By default, the player will use X symbol and the bot will use O symbol to play.
We will also initialize the SignalR hub in OnInitAsync method. On click of the cell, the OnSelect method gets executed and the board item array will now have the data with the player move and send the entire board array as a parameter to hub method OnUserMoveReceived. It also listens to NotifyUser Hub method which is invoked by a bot with its move.
Game Hub
  1. public class GameHub : Hub  
  2.     {  
  3.         ILogger<GameHub> _logger;  
  4.         private static readonly string BOT_GROUP = "BOT";  
  5.   
  6.         public GameHub(ILogger<GameHub> logger)  
  7.         {  
  8.             _logger = logger;             
  9.         }  
  10.   
  11.         public async Task OnBotConnected()  
  12.         {  
  13.             await Groups.AddToGroupAsync(Context.ConnectionId, BOT_GROUP);  
  14.             _logger.LogInformation("Bot joined");  
  15.         }  
  16.   
  17.         public async Task OnBotDisconnected()  
  18.         {  
  19.             await Groups.RemoveFromGroupAsync(Context.ConnectionId, BOT_GROUP);  
  20.             _logger.LogInformation("Bot left");  
  21.         }  
  22.   
  23.         public async Task OnBotMoveReceived(string[] board, string connectionID)  
  24.         {  
  25.             await Clients.Client(connectionID).SendAsync("NotifyUser", board);  
  26.         }  
  27.   
  28.         public async Task OnUserMoveReceived(string[] board)  
  29.         {  
  30.             await Clients.Group(BOT_GROUP).SendAsync("NotifyBot", board, Context.ConnectionId);  
  31.         }  
  32.     }  
This hub class will hold the following signalR methods.
  • OnBotConnectedThis method gets executed when the bot is connected to the SignalR hub. It also adds the bot client into BOT group. This group is used to communicate with BOT only to send the message with the latest move from the player.
  • OnBotDisconnectedThis method gets executed when the bot is disconnected from SignalR hub. It also removes the bot from BOT group.
  • OnBotMoveReceivedThis method is used to notify the player (caller) after bot finish with the move and ready for the player to respond.
  • OnUserMoveReceivedThis method is used to notify the bot after the player finishes with the move and ready for a bot to respond. 
BlazoR TicTacToe Bot 
  1. public class Worker : BackgroundService  
  2.     {  
  3.         private readonly ILogger<Worker> _logger;  
  4.   
  5.         private HubConnection connection;  
  6.         public Worker(ILogger<Worker> logger)  
  7.         {  
  8.             _logger = logger;  
  9.         }  
  10.   
  11.         async Task NotifyBot(string[] board, string connectionID)  
  12.         {  
  13.             GameEngine engine = new GameEngine();  
  14.             _logger.LogInformation($"Move received from {connectionID}");  
  15.             Move move = engine.GetBestSpot(board, engine.botPlayer);  
  16.             board[int.Parse(move.index)] = engine.botPlayer;  
  17.             _logger.LogInformation($"Bot Move with the index of {move.index} send to {connectionID}");  
  18.             await connection.InvokeAsync("OnBotMoveReceived", board, connectionID);  
  19.         }  
  20.   
  21.         protected override async Task ExecuteAsync(CancellationToken stoppingToken)  
  22.         {  
  23.             connection = new HubConnectionBuilder()  
  24.                 .WithUrl("https://localhost:5001/gamehub")  
  25.                 .Build();  
  26.             connection.On<string[], string>("NotifyBot", NotifyBot);  
  27.             await connection.StartAsync(); // Start the connection.  
  28.   
  29.             //Add to BOT Group When Bot Connected  
  30.             await connection.InvokeAsync("OnBotConnected");  
  31.             _logger.LogInformation("Bot connected");  
  32.   
  33.             while (!stoppingToken.IsCancellationRequested)  
  34.             {  
  35.                 //_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);  
  36.                 await Task.Delay(1000, stoppingToken);  
  37.             }  
  38.         }  
  39.   
  40.         public async override Task StopAsync(CancellationToken cancellationToken)  
  41.         {  
  42.             await connection?.InvokeAsync("OnBotDisconnected");  
  43.             connection?.DisposeAsync();  
  44.             _logger.LogInformation("Bot disconnected");  
  45.             await base.StopAsync(cancellationToken);  
  46.         }  
The game bot is developed using .Net Core background service; when it started, it will connect to SignalR hub. When it joins, it invokes OnBotConnected method to add it into the BOT SignalR group. When it receives the message from the hub with the board array data, it calculates the next best move by calling GetBestSpot method from the game engine and sends it back to the caller with its move.
When the background service is stopped, it disposes the SignalR connection and removes it from the BOT group.
Core Game Engine
  1. public class GameEngine  
  2.     {  
  3.         public readonly string botPlayer = "O";  
  4.         public readonly string humanPlayer = "X";  
  5.         public Move GetBestSpot(string[] board, string player)  
  6.         {  
  7.             Move bestMove = null;  
  8.             var availableSpots = GetAvailableSpots(board);  
  9.             foreach (var spot in availableSpots)  
  10.             {  
  11.                 string[] newboard = (string[])board.Clone();  
  12.                 var newMove = new Move();  
  13.                 newMove.index = spot;  
  14.                 newboard[int.Parse(spot)] = player;  
  15.   
  16.                 if (!IsWon(newboard, player) && GetAvailableSpots(newboard).Length > 0)  
  17.                 {  
  18.                     if (player == botPlayer)  
  19.                     {  
  20.                         var result = GetBestSpot(newboard, humanPlayer);  
  21.                         newMove.index = result.index;  
  22.                         newMove.score = result.score;  
  23.                     }  
  24.                     else  
  25.                     {  
  26.                         var result = GetBestSpot(newboard, botPlayer);  
  27.                         newMove.index = result.index;  
  28.                         newMove.score = result.score;  
  29.                     }  
  30.                 }  
  31.                 else  
  32.                 {  
  33.                     if (IsWon(newboard, botPlayer))  
  34.                         newMove.score = 1;  
  35.                     else if (IsWon(newboard, humanPlayer))  
  36.                         newMove.score = -1;  
  37.                     else  
  38.                         newMove.score = 0;  
  39.                 }  
  40.   
  41.                 if (bestMove == null ||  
  42.                     (player == botPlayer && newMove.score < bestMove.score) ||  
  43.                     (player == humanPlayer && newMove.score > bestMove.score))  
  44.                 {  
  45.                     bestMove = newMove;  
  46.                 }  
  47.             }  
  48.             return bestMove;  
  49.         }  
  50.   
  51.         public string[] GetAvailableSpots(string[] board)  
  52.         {  
  53.             return board.Where(i => !IsPlayed(i)).ToArray();  
  54.         }  
  55.   
  56.         public bool IsWon(string[] board, string player)  
  57.         {  
  58.             if (  
  59.                    (board[0] == player && board[1] == player && board[2] == player) ||  
  60.                    (board[3] == player && board[4] == player && board[5] == player) ||  
  61.                    (board[6] == player && board[7] == player && board[8] == player) ||  
  62.                    (board[0] == player && board[3] == player && board[6] == player) ||  
  63.                    (board[1] == player && board[4] == player && board[7] == player) ||  
  64.                    (board[2] == player && board[5] == player && board[8] == player) ||  
  65.                    (board[0] == player && board[4] == player && board[8] == player) ||  
  66.                    (board[2] == player && board[4] == player && board[6] == player)  
  67.                    )  
  68.             {  
  69.                 return true;  
  70.             }  
  71.             else  
  72.             {  
  73.                 return false;  
  74.             }  
  75.         }  
  76.   
  77.         public bool IsPlayed(string input)  
  78.         {  
  79.             return input == "X" || input == "O";  
  80.         }  
  81.     }  
  82.   
  83.     public class Move  
  84.     {  
  85.         public int score;  
  86.         public string index;  
  87.     }  
I used the minimax algorithm in the game engine to find the best available spot. Minimax algorithm is a recursive algorithm which will play all possible movement by itself and as an opponent, until it reaches the terminal state (win or draw) and then decides the best move from all possible iterations. You can refer to this article to understand more details about the minimax algorithm.

Conclusion

Blazor is super useful for .NET developers who are not interested in learning javascript for front-end development. This article shows how easy it is to develop a real-time blazor application with SignalR. I have used the minimax algorithm to identify the best spot available. It will be more interesting to use reinforcement machine learning algorithms for AI to learn and identify based on rewards instead of recursive minimax algorithms. This will be a good use case to try when ML.NET introduce reinforcement learning library.