by Joche Ojeda | Mar 21, 2023 | Linux, netcore, Oqtane, Ubuntu, Uncategorized
Virtual hosting is a technique used by Apache (and other web servers) to host multiple websites on a single server. With virtual hosting, a single physical server can host multiple virtual servers, each with its own domain name, IP address, and content.
Virtual hosting can be implemented in two ways:
- Name-based virtual hosting: In this approach, the server uses the domain name provided in the HTTP request to determine which virtual host should serve the request. For example, if a user requests a page from “example.com”, the server will use the virtual host configured for that domain and serve the appropriate content.
- IP-based virtual hosting: In this approach, each virtual host is assigned a separate IP address, and the server uses the IP address in the HTTP request to determine which virtual host should serve the request. For example, if a user requests a page from the IP address assigned to “example.com”, the server will use the virtual host configured for that IP address and serve the appropriate content.
Virtual hosting allows a server to serve multiple websites, each with its own domain name and content, using a single physical server. This makes hosting more efficient and cost-effective, especially for smaller websites that don’t require dedicated servers.
The following diagram represents the most common virtual hosting setup
+-----------------------+
| Apache Web Server |
+-----------------------+
|
|
| +---------------------+
| | Virtual Host A |
+------| (example.com) |
| |
| Document Root: |
| /var/www/A/ |
| |
+---------------------+
|
|
|
| +---------------------+
| | Virtual Host B |
+------| (example.net) |
| |
| Document Root: |
| /var/www/B/ |
| |
+---------------------+
ASP.NET Core and Blazor applications have the capability to run their own in-process web server, Kestrel. Kestrel can be bound to a specific IP address or port number, enabling the applications to be hosted in virtual environments. To accomplish this, each application can be bound to a unique port number.
+-----------------------+
| Apache Web Server |
+-----------------------+
|
|
| +---------------------+
| | Virtual Host A |
+------| (example.com) |
| |
| Proxy to: |
|http://localhost:8016|
| |
+---------------------+
|
|
| +---------------------+
| | Virtual Host B |
+------| (example.net) |
| |
| Proxy to: |
|http://localhost:8017|
| |
+---------------------+
As shown in the diagram, physical folders for the document root are no longer utilized. Rather, a proxy is created to the Kestrel web server, which runs our ASP.NET Core applications
To bind our ASP.NET Core applications to a specific IP address or port number, there are multiple methods available. Detailed documentation on this subject can be found at the following link: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-7.0#configureiconfiguration
There are various approaches that can be used based on the specific use case. For the sake of simplicity in this example, we will be utilizing the configuration method. This involves appending the configuration JSON for the Kestrel web server, as shown in the following example.
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:8016"
}
}
}
}
So here is how our configuration files should look like
Example.com (Host A)
{
"Runtime": "Server",
"RenderMode": "ServerPrerendered",
"Database": {
"DefaultDBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL"
},
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;Port=5432;Database=example.com;User ID=example.com;Password=1234567890;"
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:8016"
}
}
},
"Installation": {
"DefaultAlias": "",
"HostPassword": "",
"HostEmail": "",
"SiteTemplate": "",
"DefaultTheme": "",
"DefaultContainer": ""
},
"Localization": {
"DefaultCulture": "en"
},
"AvailableDatabases": [
{
"Name": "LocalDB",
"ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client",
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
},
{
"Name": "SQL Server",
"ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client",
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
},
{
"Name": "SQLite",
"ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client",
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite"
},
{
"Name": "MySQL",
"ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client",
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL"
},
{
"Name": "PostgreSQL",
"ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client",
"DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL"
}
],
"Logging": {
"FileLogger": {
"LogLevel": {
"Default": "Error"
}
},
"LogLevel": {
"Default": "Information"
}
},
"InstallationId": "f5789fa4-895c-45d7-bc26-03eb166e008a"
}
Example.net (Host B)
{
"Runtime": "Server",
"RenderMode": "ServerPrerendered",
"Database": {
"DefaultDBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL"
},
"ConnectionStrings": {
"DefaultConnection": "Server=127.0.0.1;Port=5432;Database=example.net;User ID=example.net;Password=1234567890;"
},
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:8017"
}
}
}
},
"Installation": {
"DefaultAlias": "",
"HostPassword": "",
"HostEmail": "",
"SiteTemplate": "",
"DefaultTheme": "",
"DefaultContainer": ""
},
"Localization": {
"DefaultCulture": "en"
},
"AvailableDatabases": [
{
"Name": "LocalDB",
"ControlType": "Oqtane.Installer.Controls.LocalDBConfig, Oqtane.Client",
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
},
{
"Name": "SQL Server",
"ControlType": "Oqtane.Installer.Controls.SqlServerConfig, Oqtane.Client",
"DBType": "Oqtane.Database.SqlServer.SqlServerDatabase, Oqtane.Database.SqlServer"
},
{
"Name": "SQLite",
"ControlType": "Oqtane.Installer.Controls.SqliteConfig, Oqtane.Client",
"DBType": "Oqtane.Database.Sqlite.SqliteDatabase, Oqtane.Database.Sqlite"
},
{
"Name": "MySQL",
"ControlType": "Oqtane.Installer.Controls.MySQLConfig, Oqtane.Client",
"DBType": "Oqtane.Database.MySQL.MySQLDatabase, Oqtane.Database.MySQL"
},
{
"Name": "PostgreSQL",
"ControlType": "Oqtane.Installer.Controls.PostgreSQLConfig, Oqtane.Client",
"DBType": "Oqtane.Database.PostgreSQL.PostgreSQLDatabase, Oqtane.Database.PostgreSQL"
}
],
"Logging": {
"FileLogger": {
"LogLevel": {
"Default": "Error"
}
},
"LogLevel": {
"Default": "Information"
}
},
"InstallationId": "f5789fa4-895c-45d7-bc26-03eb166e008a"
}
As demonstrated, utilizing Oqtane in virtual hosting environments is a straightforward process. There is no need to recompile the source code, as configuring the application for virtual hosting can be easily accomplished through a single configuration section in the appsettings.json file.
by Joche Ojeda | Mar 20, 2023 | netcore, Oqtane, Uncategorized
In the preceding blog post, we explored the process of running Oqtane on Ubuntu Linux and successfully confirmed its functionality. Now, let us delve deeper and proceed to package the installation binaries specifically tailored for Ubuntu Linux deployment.
As a frequent publisher of Blazor applications to Linux servers, I find the process to be quite straightforward. Utilizing the .NET CLI, one can simply execute the following command to publish the app for Linux:
dotnet publish --runtime linux-x64
On this occasion, my customary approach to publishing encountered an obstacle, indicating additional steps were required. Consequently, I submitted an issue to the Oqtane GitHub repository, where Leigh Pointer from the Oqtane community kindly provided guidance in the correct direction. The GitHub issue can be viewed here.
The first file that I changed was
del "*.nupkg"
del "*.zip"
dotnet clean -c Release ..\Oqtane.Databases.sln
dotnet clean -c Release ..\Oqtane.sln
dotnet build -c Release ..\Oqtane.Databases.sln
dotnet build -c Release ..\Oqtane.sln
nuget.exe pack Oqtane.Client.nuspec
nuget.exe pack Oqtane.Server.nuspec
nuget.exe pack Oqtane.Shared.nuspec
nuget.exe pack Oqtane.Framework.nuspec
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish"
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Modules\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Themes\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json"
del "..\Oqtane.Server\bin\Release\net6.0\publish\web.config"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
dotnet clean -c Release ..\Oqtane.Updater.sln
dotnet build -c Release ..\Oqtane.Updater.sln
dotnet publish ..\Oqtane.Updater\Oqtane.Updater.csproj /p:Configuration=Release
nuget.exe pack Oqtane.Updater.nuspec
pause
and this is my modified version
del "*.nupkg"
del "*.zip"
dotnet clean -c Release ..\Oqtane.Databases.sln
dotnet clean -c Release ..\Oqtane.sln
dotnet build -c Release ..\Oqtane.Databases.sln
dotnet build -c Release ..\Oqtane.sln
nuget.exe pack Oqtane.Client.nuspec
nuget.exe pack Oqtane.Server.nuspec
nuget.exe pack Oqtane.Shared.nuspec
nuget.exe pack Oqtane.Framework.nuspec
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish"
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release --runtime linux-x64 --self-contained true
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Modules\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Themes\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json"
del "..\Oqtane.Server\bin\Release\net6.0\publish\web.config"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
dotnet clean -c Release ..\Oqtane.Updater.sln
dotnet build -c Release ..\Oqtane.Updater.sln
dotnet publish ..\Oqtane.Updater\Oqtane.Updater.csproj /p:Configuration=Release --runtime linux-x64 --self-contained true
pause
The script is basically the same I just appended the runtime target parameter and the self contained parameter, lie this: “-runtime linux-x64 –self-contained true” in the 2 lines specified below
line 13: dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release --runtime linux-x64 --self-contained true
line 41: dotnet publish ..\Oqtane.Updater\Oqtane.Updater.csproj /p:Configuration=Release --runtime linux-x64 --self-contained true
After I changed the publishing parameters the path of the published files changed, now they are in a folder that with the same name of the target runtime, that means that I need to update 2 more files “install.ps1” and “upgrade.ps1”
install.ps1
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\linux-x64\publish\*" -DestinationPath "Oqtane.Framework.3.4.0.Install.zip" -Force
upgrade.ps1
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\linux-x64\publish\*" -DestinationPath "Oqtane.Framework.3.4.0.Upgrade.zip" -Force
So after those little changes we are ready to compile oqtane to target Linux runtime as a self contained app
by Joche Ojeda | Nov 24, 2022 | Uncategorized
Modern AspNetCore applications use the built-in web server kestrel,this server is usually bound to the localhost address using the ports 5000 and 5001 for http and https.
But what if you want to run 2 applications in the same server? then you have a problem because if you use the default ports one of the applications will not start correctly.
This can easily be solved by changing the default ports in your WebHostBuilder as shown below
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
webBuilder.UseUrls("http://0.0.0.0:8016");
webBuilder.UseStartup<Startup>();
});
The problem with the example above is that the URLs are hardcoded, so here is a better solution
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => {
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("hosting.json", optional: true)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddCommandLine(args)
.AddEnvironmentVariables()
.Build();
webBuilder.UseUrls(config["server.urls"]);
webBuilder.UseStartup<Startup>();
});
the example above uses a configuration builder to merge the appsettings.json and the hosting.json in a single configuration object, then with use the value of the property “server.urls” as base URL/port for our application
Here is the content of the hosting.json file
{
"server.urls": "http://0.0.0.0:8016"
}
by Joche Ojeda | Nov 24, 2022 | Uncategorized
Sometime we want to reuse our Blazor components in another apps, the best way to do this is to create a razor library, this process of create a razor library is not different from create a normal class library to share code. There is only one exception, razor components might need to reference JavaScript or CSS files. This problem can be easily solve in 2 steps as shown below.
1) Create a class that inherits from TagHelperComponent,,this class should include the tags that you want to include in the html header section of your app
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Razor.TagHelpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyBlazorApp
{
[HtmlTargetElement("head")]
public class MyTagHelper: TagHelperComponent
{
private string Tags=
@"
<!-- ZXingBlazor -->
<script src=""_content/ZXingBlazor/lib/barcodereader/zxing.js""></script>
<script src = ""_content/ZXingBlazor/lib/barcodereader/barcode.js"" ></ script >
< !--ZXingBlazor-- >
< !--Signature Pad -->
<script src = ""_content/Mobsites.Blazor.SignaturePad/bundle.js"" ></ script >
< link href=""_content/Mobsites.Blazor.SignaturePad/bundle.css"" rel=""stylesheet"" />
< link href=""_content/Ultra.PropertyEditors.Module.Blazor/js/signaturepropertyeditor.js""/>
<!-- Signature Pad -->
<!-- HTML Editor -->
<link href = ""//cdn.quilljs.com/1.3.6/quill.snow.css"" rel=""stylesheet"">
<link href = ""//cdn.quilljs.com/1.3.6/quill.bubble.css"" rel=""stylesheet"">
<script src = ""https://cdn.quilljs.com/1.3.6/quill.js"" ></ script >
<script src=""_content/Blazored.TextEditor/quill-blot-formatter.min.js""></script>
<script src = ""_content/Blazored.TextEditor/Blazored-BlazorQuill.js"" ></ script >
< !--HTML Editor -->
";
public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (string.Equals(context.TagName, "head", StringComparison.OrdinalIgnoreCase))
{
output.PostContent.AppendHtml(Tags).AppendLine();
}
return Task.CompletedTask;
}
}
}
*Note: to reference JavaScript or CSS from any razor library you can use the following syntax,please notice the doble quotes.
<script src=""_content/MyAssemblyName/PathToMyJavaScript/MyJavaScriptFile.js""></script>
2) Create an extension method in the “Microsoft.Extensions.DependencyInjection” namespace so you can easily add your tag helper to the service collection
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.Extensions.DependencyInjection
{
public static class StartupExtensions
{
public static IServiceCollection AddMyHtmlTags(this IServiceCollection services)
{
services.AddTransient<ITagHelperComponent, MyTagHelper>();
return services;
}
}
}
Here is an example on how to use your new extension in your startup class
public void ConfigureServices(IServiceCollection services
{
services.AddMyHtmlTags();
}