{"id":46702181,"url":"https://github.com/russkyc/avalonia-blazor-server","last_synced_at":"2026-03-15T13:03:42.427Z","repository":{"id":343081870,"uuid":"1176151707","full_name":"russkyc/avalonia-blazor-server","owner":"russkyc","description":"Host a full blazor server app in a local network using only an android device, using Avalonia as a cross-platform host.","archived":false,"fork":false,"pushed_at":"2026-03-10T17:57:35.000Z","size":284,"stargazers_count":10,"open_issues_count":0,"forks_count":3,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-12T15:00:08.872Z","etag":null,"topics":["android","asp-net","avalonia","blazor","blazor-server","server"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit-0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/russkyc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-08T17:29:27.000Z","updated_at":"2026-03-11T21:53:37.000Z","dependencies_parsed_at":"2026-03-11T09:01:05.211Z","dependency_job_id":null,"html_url":"https://github.com/russkyc/avalonia-blazor-server","commit_stats":null,"previous_names":["russkyc/avalonia-blazor-server"],"tags_count":3,"template":true,"template_full_name":null,"purl":"pkg:github/russkyc/avalonia-blazor-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russkyc%2Favalonia-blazor-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russkyc%2Favalonia-blazor-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russkyc%2Favalonia-blazor-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russkyc%2Favalonia-blazor-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/russkyc","download_url":"https://codeload.github.com/russkyc/avalonia-blazor-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/russkyc%2Favalonia-blazor-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30466309,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-13T06:34:02.089Z","status":"ssl_error","status_checked_at":"2026-03-13T06:33:49.182Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","asp-net","avalonia","blazor","blazor-server","server"],"created_at":"2026-03-09T07:04:45.690Z","updated_at":"2026-03-13T11:00:39.890Z","avatar_url":"https://github.com/russkyc.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿## Avalonia Embedded Blazor Web App Host (Interactive Server)\nThis is a sample of a Blazor Web App Server running from an android device,\nwithout the need of a PC, and accessible from the ui(webview), in browser, or other devices on the same network.\nUsing wifi or the device hotspot, you can have other devices connect to the server.\n\n\u003cimg src=\"screenshot.png\"\u003e\n\n### Want to try it out?\nYou can do one of the following:\n- Clone the repo and run the `AvaloniaBlazorServer.Android` project, you can also run the desktop project `AvaloniaBlazorServer.Desktop` to see it working on desktop.\n- Download the apk from the [releases](https://github.com/russkyc/avalonia-blazor-server/releases) and install it on your android device.\n\n### Rationale\nWe can't run a full ASP.NET hosted app on net-android (see), atleast officially. It is not planned as noted on these issues:\n\n- [Github Issue: Run ASP.Net Core on Android+iOs Fully as if its a normal PC](https://github.com/dotnet/aspnetcore/issues/3204)\n- [Microsoft.Net.Sdk.Web on Android](https://github.com/dotnet/sdk/issues/29567)\n\nWhat if we really want to? Technically we can, and this project is the proof of concept.\nA cross-platform app capable of hosting a complete Blazor Web App, Accessible from the ui, in browser, or other devices on the same network.\n\n#### Example use cases (mobile-first, no PC required):\n\n- **Gaming LAN host (phone hotspot mode)**  \n  One Android phone runs the app server and creates a hotspot. Nearby players connect from their own devices for match lobbies, scoreboards, tournament brackets, or shared game tools.\n\n- **Pop-up office in low-connectivity areas**  \n  A tablet hosts internal forms, task boards, and status pages for a small team during travel or temporary setups, with everyone connecting over local Wi-Fi/hotspot instead of cloud services.\n\n- **Field operations and inspections**  \n  Teams in construction, logistics, utilities, or events run workflows directly on an Android device in the field. Data entry, checklists, and logs continue in a truly portable self-hosted manner.\n\n- **Portable team hub / command center**  \n  A single mobile device acts as a local hub for schedules, alerts, check-ins, and live updates that other nearby devices can open in a browser on the same local network.\n\n- **Classroom/training/demo environments**  \n  In workshops or classrooms, one phone/tablet hosts the experience and participants join from their own devices without needing lab PCs or preconfigured infrastructure.\n\n- **Kiosk + supervisor access**  \n  Keep the UI visible on a mounted Android kiosk while managers connect from another phone/tablet on the same network for monitoring and control.\n\n- **Emergency fallback mode**  \n  When WAN/cloud access fails, the mobile-hosted server provides a continuity layer for critical local operations until normal connectivity returns.\n\n#### Should we be doing this?\nProbably not mainly because android and mobile devices are not meant to be always-running server devices. But for these examples, we could arugue that it could be acceptable. So we are doing it anyway.\n\n### Overview\n\n- `Microsoft.NET.Sdk.Web` cannot be used on android, so after creating a blazor web app, we set it to use `Microsoft.NET.Sdk.Razor`.\n- The `_framework/blazor.web.js` cannot be produced, so we take a copy of it from a running web app or produced assets of a normal web app.\n- The wwwroot content is embedded to not require copying the wwwroot folder to the android project.\n\n### How this works and the changes made\n\nMultiple workarounds are used in order for this to run on android. Let's focus on the actual changes to the projects and the code:\n\n#### I. The Blazor Web App (ServerApp.csproj)\nThe blazor web app is a normal blazor web app, but the template used was from [Blazor Minimum Project Templates](https://github.com/jsakamoto/BlazorMinimumTemplates). You can use the normal blazor web app template, but you will need to make the same changes as below.\n\nNote that this is for interactive server mode, if you are using wasm or blazor webassembly the names might be different.\n\n##### Steps:\nRun the web app initially and get a copy of the `blazor.web.js` file from the running app `http://\u003chost\u003e:\u003cport\u003e/_framework/blazor.web.js`. This is required because the build process cannot produce it when using `Microsoft.NET.Sdk.Razor`. Stop the app after copying the file. Save this to the `wwwroot/framework/` folder of the web app project update the path in the root compnent:\n```razorhtmldialect\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"utf-8\"/\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/\u003e\n    \u003ctitle\u003eServerApp\u003c/title\u003e\n    \u003cbase href=\"/\"/\u003e\n    \u003cResourcePreloader/\u003e\n    \u003cImportMap/\u003e\n    \u003clink rel=\"stylesheet\" href=\"@Assets[\"css/blazor-ui.css\"]\"/\u003e\n    \u003cHeadOutlet @rendermode=\"@InteractiveServer\"/\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cRoutes @rendermode=\"@InteractiveServer\"/\u003e\n@*Use the extracted blazor.web.js*@\n\u003cscript src=\"@Assets[\"framework/blazor.web.js\"]\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\nReplace `Microsoft.NET.Sdk.Web` with `Microsoft.NET.Sdk.Razor`. And set the project to be a class library instead of an executable and update references in the project file.\n\n```xml\n\u003c!-- Change to Sdk.Razor from Sdk.Web --\u003e\n\u003cProject Sdk=\"Microsoft.NET.Sdk.Razor\"\u003e\n  \u003cPropertyGroup\u003e\n    \u003cTargetFramework\u003enet10.0\u003c/TargetFramework\u003e\n    \u003cNullable\u003eenable\u003c/Nullable\u003e\n    \u003cImplicitUsings\u003eenable\u003c/ImplicitUsings\u003e\n    \u003c!-- Set as class library insteaf of executable --\u003e\n    \u003cOutputType\u003eLibrary\u003c/OutputType\u003e\n  \u003c/PropertyGroup\u003e\n  \u003cItemGroup\u003e\n    \u003c!-- Include references to the AspNetCore dlls of the current framework, your exact version folder may vary --\u003e\n    \u003cReference Include=\"C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App\\10.0.1\\*.dll\"\n               Exclude=\"C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App\\10.0.1\\aspnetcorev2_inprocess.dll\" /\u003e\n  \u003c/ItemGroup\u003e\n  \u003cItemGroup\u003e\n    \u003c!-- Configure to embed the wwwroot folder --\u003e\n    \u003cContent Remove=\"wwwroot\\**\" /\u003e\n    \u003cEmbeddedResource Include=\"wwwroot\\**\\*\" /\u003e\n  \u003c/ItemGroup\u003e\n  \u003cItemGroup\u003e\n    \u003cPackageReference Include=\"Microsoft.Extensions.FileProviders.Embedded\" /\u003e\n  \u003c/ItemGroup\u003e\n  \u003cItemGroup\u003e\n    \u003c_ContentIncludedByDefault Remove=\"Properties\\launchSettings.json\" /\u003e\n  \u003c/ItemGroup\u003e\n\u003c/Project\u003e\n```\n- Reference the aspnetcore dll's\n- Embed the wwwroot content as embedded resources\n- Add the `Microsoft.Extensions.FileProviders.Embedded` package\n\n\nRemove `Program.cs` and move the startup code to a separate class with a `Start` method that can be called from the avalonia app. Note that it doesn't need to be static\nthis is just for simplicity.\n\nServerAppHost.cs\n```csharp\nusing System.Net;\nusing System.Net.NetworkInformation;\nusing System.Net.Sockets;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Hosting.Server;\nusing Microsoft.AspNetCore.Hosting.Server.Features;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.FileProviders;\nusing Microsoft.Extensions.Hosting;\nusing ServerApp.Components;\n\nnamespace ServerApp;\n\npublic static class ServerAppHost\n{\n    public static ICollection\u003cstring\u003e Hosts { get; } = new List\u003cstring\u003e();\n\n    public static Task Start(CancellationToken serverTokenToken = default, int port = 5000, bool broadcast = true)\n    {\n        // Configure to properly resolve the static assets from the embedded resources\n        var assemblyName = typeof(ServerAppHost).Assembly.GetName().Name;\n        var builder = WebApplication.CreateBuilder(new WebApplicationOptions\n        {\n            ApplicationName = assemblyName\n        });\n\n        // set what IP addresses Kestrel should listen on. If broadcast is true, listen on all IPs, otherwise only on localhost\n        builder.WebHost.ConfigureKestrel((_, serverOptions) =\u003e\n            serverOptions.Listen(broadcast ? IPAddress.Any : IPAddress.Loopback, port));\n        // Enable static web assets\n        builder.WebHost.UseStaticWebAssets();\n\n        builder.Services.AddRazorComponents()\n            .AddInteractiveServerComponents();\n\n        var app = builder.Build();\n\n        if (!app.Environment.IsDevelopment())\n        {\n            app.UseExceptionHandler(\"/Error\", createScopeForErrors: true);\n            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\n            app.UseHsts();\n        }\n\n        app.UseStatusCodePagesWithReExecute(\"/not-found\", createScopeForStatusCodePages: true);\n        \n        // Use an embedded file provider to serve static files from the assembly's wwwroot folder\n        var embeddedProvider = new EmbeddedFileProvider(\n            typeof(ServerAppHost).Assembly,\n            $\"{assemblyName}.wwwroot\"\n        );\n\n        app.UseStaticFiles(new StaticFileOptions\n        {\n            FileProvider = embeddedProvider\n        });\n\n        app.UseAntiforgery();\n\n        app.MapRazorComponents\u003cApp\u003e()\n            .AddInteractiveServerRenderMode();\n\n        var server = app.Services.GetRequiredService\u003cIServer\u003e();\n        var addressesFeature = server.Features.Get\u003cIServerAddressesFeature\u003e();\n        \n        // We could also use app.RunAsync() here, but StartAsync()\n        // allows us to do additional work (like listing the URLs)\n        app.StartAsync(serverTokenToken);\n\n        // this is just used to list the actual URLs the server is listening on,\n        // so we can display them in the UI and let the user know how to connect to the server from their mobile device.\n        if (addressesFeature != null)\n        {\n            // The Addresses collection contains all configured URLs\n            foreach (var url in addressesFeature.Addresses)\n            {\n                var uri = new Uri(url);\n                // If it's the wildcard, find the actual network IPs\n                if (url.Contains(\"0.0.0.0\") || url.Contains(\"[::]\"))\n                {\n                    // Iterate through all network interfaces (Wi-Fi, Hotspot, Ethernet)\n                    var interfaces = NetworkInterface.GetAllNetworkInterfaces()\n                        .Where(i =\u003e i.OperationalStatus == OperationalStatus.Up);\n\n                    foreach (var netInterface in interfaces)\n                    {\n                        var properties = netInterface.GetIPProperties();\n                        var ipv4 = properties.UnicastAddresses\n                            .FirstOrDefault(a =\u003e a.Address.AddressFamily == AddressFamily.InterNetwork);\n\n                        if (ipv4 is null) continue;\n\n                        var host = ipv4.Address.ToString() == \"127.0.0.1\" ? \"localhost\" : ipv4.Address.ToString();\n                        var portSuffix = uri.Port == 80 ? \"\" : $\":{uri.Port}\";\n                        Hosts?.Add($\"{uri.Scheme}://{host}{portSuffix}\");\n                    }\n                }\n                else\n                {\n                    Hosts?.Add(url);\n                }\n            }\n        }\n\n        return app.WaitForShutdownAsync(token: serverTokenToken);\n    }\n}\n```\n\n#### II. The Main Avalonia Project (AvaloniaBlazorServer.csproj)\nReference the server project\n\n```xml\n\u003cItemGroup\u003e\n    \u003c!-- Reference the server class library project --\u003e\n    \u003cProjectReference Include=\"..\\ServerApp\\ServerApp.csproj\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\nCall the `Start` method of the server from the avalonia app, for example in `App.axaml.cs`. We use a cancellation token to be able to stop the server when the app is closed.\n\n```csharp\n_server = ServerAppHost.Start(_serverTokenSource.Token);\n```\n\n#### III. The Android and Desktop projects\n\nWe need to add the references to the aspnetcore dll's in the android project `AvaloniaBlazorServer.Android.csproj`\n\n```xml\n\u003cItemGroup\u003e\n    \u003cProjectReference Include=\"..\\AvaloniaBlazorServer\\AvaloniaBlazorServer.csproj\"/\u003e\n    \u003c!-- Include references to the AspNetCore dlls of the current framework, your exact version folder may vary --\u003e\n    \u003cReference Include=\"C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App\\10.0.1\\*.dll\"\n               Exclude=\"C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App\\10.0.1\\aspnetcorev2_inprocess.dll\"/\u003e\n\u003c/ItemGroup\u003e\n```\n\nTo make it actually run and not just crash, we need to configure the android project to not use AOT.\n\n```xml\n\u003cPropertyGroup\u003e\n    \u003c!-- Both needed to be set to false in order for the Asp.Net dll references to work on android --\u003e\n    \u003cAndroidEnableProfiledAot\u003efalse\u003c/AndroidEnableProfiledAot\u003e\n    \u003cRunAOTCompilation\u003efalse\u003c/RunAOTCompilation\u003e\n\u003c/PropertyGroup\u003e\n```\n\nIn the desktop project (`AvaloniaBlazorServer.Desktop.csproj`) we could just reference `Aspnetcore.App` since it is supported in the desktop platform.\n\n```xml\n\u003cItemGroup\u003e\n    \u003cProjectReference Include=\"..\\AvaloniaBlazorServer\\AvaloniaBlazorServer.csproj\"/\u003e\n    \u003c!-- Used instead of manuall dll reference since AspNetCore dll's can actually target desktop platforms --\u003e\n    \u003cFrameworkReference Include=\"Microsoft.AspNetCore.App\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\nNow you have a fully working blazor web app running on android and desktop, accessible from the ui (with a webview), in browser, or other devices on the same network.\n\n### Thoughts and Next Steps\n\nThe current setup is hacky and has some downsides:\n- We have to manually copy the `blazor.web.js` file and reference it, which is not ideal. It would be better if we could produce it as part of the build process.\n- Css isolation is not working (since assets are not being produced as part of the build process), so we have to use global css for styling.\n- Which also means that ui libraries that rely on css isolation won't work without modification.\n\n##### Next steps:\n\n- Programmaticaly produce the `blazor.web.js` file as part of the build process, preferrably fix the build process to produce it and other static assets properly.\n- Find a way to better handle the AspNet Core dll references, maybe by using a nuget package or something similar instead of manually referencing the dll's from the framework folder.\n- Look for ios workarounds, since it should be possible to run on ios with the right configuration, but I don't have access to a mac to test and develop those workarounds.\n\n### Special thanks\n\nthis is heavily inspired from [ASP.NET Core in Maui](https://github.com/JamesNK/aspnetcore-maui). Huge credit to JamesNK for discovering the workarounds\nto run ASP.NET unofficially in unsupported platforms.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frusskyc%2Favalonia-blazor-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frusskyc%2Favalonia-blazor-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frusskyc%2Favalonia-blazor-server/lists"}