[ Web Proxy ]
URL:
Viewing: https://shinylib.net/httpserver [Back]  [Original]

Getting Started | Shiny.NETSkip to content
Search
Shiny Controls v1.0 - The Ultra Control Suite for .NET MAUI & BlazorO...M...G!

Getting Started

ASP.NET Core is heavyweight and does not run on .NET MAUI or in several embedded server scenarios. This is a dependency-light, AOT- and trim-clean HTTP/1.1, HTTP/2 and HTTP/3 server that runs anywhere .NET runs plus tunnelling, so a server embedded in a phone app is reachable from the public internet.

Only Microsoft.Extensions.* abstractions are taken as dependencies. Everything else JSON, crypto, JWT, OpenAPI, HPACK, QPACK is built on what is in the box.

GitHub GitHub stars for shinyorg/httpserver [GitHub stars for shinyorg/httpserver]
Downloads NuGet downloads for Shiny.Net.HttpServer [NuGet downloads for Shiny.Net.HttpServer]
Frameworks
.NET
.NET MAUI
Operating Systems
Android
iOS
macOS
Windows
Linux
Package Description
NuGet package Shiny.Net.HttpServer [NuGet package Shiny.Net.HttpServer] The server: HTTP/1.1, HTTP/2 & HTTP/3, routing, middleware, DI scopes, static files, WebSockets, SSE, sessions, OpenAPI, CORS, rate limiting, IP filtering, tunnelling. Includes the typed-endpoint source generator
NuGet package Shiny.Net.HttpServer.Jwt [NuGet package Shiny.Net.HttpServer.Jwt] JWT authentication on in-box crypto no Microsoft.IdentityModel dependency
NuGet package Shiny.Net.HttpServer.AzureRelay [NuGet package Shiny.Net.HttpServer.AzureRelay] Azure Relay tunnel provider
NuGet package Shiny.Net.HttpServer.Ssh [NuGet package Shiny.Net.HttpServer.Ssh] SSH remote-forwarding tunnel provider, including zero-account quick tunnels
NuGet package Shiny.Net.HttpServer.Tunnels [NuGet package Shiny.Net.HttpServer.Tunnels] cloudflared, ngrok and Tailscale agents, supervised. Desktop and CLI, not mobile
NuGet package Shiny.Net.HttpServer.Mcp [NuGet package Shiny.Net.HttpServer.Mcp] Model Context Protocol (Streamable HTTP) transport host an MCP server without ASP.NET Core, including inside a MAUI app
NuGet package Shiny.Net.HttpServer.Mediator [NuGet package Shiny.Net.HttpServer.Mediator] Shiny.Mediator requests, commands and streams published as endpoints generated at compile time. Generator included
NuGet package Shiny.Net.HttpServer.DocumentDb [NuGet package Shiny.Net.HttpServer.DocumentDb] Shiny.DocumentDb types as REST resources list, by-id, count, CRUD, merge-patch and a live SSE tail
NuGet package Shiny.Net.HttpServer.WebDav [NuGet package Shiny.Net.HttpServer.WebDav] WebDAV (RFC 4918) class 1 & 2 over a directory mount an apps storage in Finder or Explorer, and open the same URL in a browser for a file manager
NuGet package Shiny.Net.HttpServer.Grpc [NuGet package Shiny.Net.HttpServer.Grpc] gRPC and gRPC-Web unary, streaming and bidirectional methods over the same HTTP/2 stack, with serialization you supply
NuGet package Shiny.Net.HttpServer.Discovery [NuGet package Shiny.Net.HttpServer.Discovery] mDNS/Bonjour advertise the server on the local link, and find the ones other devices advertise
NuGet package Shiny.Net.HttpServer.Mobile [NuGet package Shiny.Net.HttpServer.Mobile] Mobile lifecycle background and resume, an Android foreground service, rebinding when the device moves
NuGet package Shiny.Net.HttpServer.Testing [NuGet package Shiny.Net.HttpServer.Testing] An in-memory HttpClient endpoint tests with no port and no listener
NuGet package Shiny.Net.HttpServer.CommandLine [NuGet package Shiny.Net.HttpServer.CommandLine] A .NET tool shinyhttpserver that serves a directory over WebDAV: a browser file manager and a mountable drive at one address, with a QR code in the banner

Everything shipping targets net10.0 and has the trim, AOT and single-file analyzers turned on, so AOT-clean is enforced by the build rather than claimed in a readme.

Changed files
  • MyApp/
    • Platforms
      • iOS
        • Info.plist
    • MauiProgram.cs
    • MyApp.csproj
Platforms/iOS/Info.plist
Info.plistCopy
1<?xml version="1.0" encoding="UTF-8"?>2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">3<plist version="1.0">4<dict>5	<key>ITSAppUsesNonExemptEncryption</key>6	<false/>7	<key>UIDeviceFamily</key>8	<array>9		<integer>1</integer>10		<integer>2</integer>11	</array>12	<key>UIRequiredDeviceCapabilities</key>13	<array>14		<string>arm64</string>15	</array>16	<key>UISupportedInterfaceOrientations</key>17	<array>18		<string>UIInterfaceOrientationPortrait</string>19		<string>UIInterfaceOrientationLandscapeLeft</string>20		<string>UIInterfaceOrientationLandscapeRight</string>21	</array>22	<key>UISupportedInterfaceOrientations~ipad</key>23	<array>24		<string>UIInterfaceOrientationPortrait</string>25		<string>UIInterfaceOrientationPortraitUpsideDown</string>26		<string>UIInterfaceOrientationLandscapeLeft</string>27		<string>UIInterfaceOrientationLandscapeRight</string>28	</array>29	<key>XSAppIconAssets</key>30	<string>Assets.xcassets/appicon.appiconset</string>	31	<!-- iOS 14+ gates anything touching the local network, and that includes SERVING on it.32	     Without this key the app is denied without ever being asked. -->33	<key>NSLocalNetworkUsageDescription</key>34	<string>Say something useful here that your users will understand</string>35</dict>36</plist>
Terminal window
dotnet add package Shiny.Net.HttpServer

That one package is also what tier 3 below needs: the typed-endpoint generator ships inside it under analyzers/, so there is no second reference to add. It runs inside the compiler and never lands in your output.

In the test project, add the in-memory harness it is the only other package most apps ever need:

Terminal window
dotnet add package Shiny.Net.HttpServer.Testing
using Shiny.Net.HttpServer;
var server = new HttpServer(new HttpServerOptions { Port = 8080 });
server.MapGet("/ping", ctx => ctx.Response.WriteAsync("pong"));
await server.RunAsync();

That is the whole ceremony. No container, no host builder, no configuration file. The server binds loopback by default a server embedded in a mobile app should not be reachable from the local network unless its author says so.

The point of the API is a gentle ramp: trivial to start, strongly typed when you want it. Each tier is built on the one below and they compose in the same app.

  1. Tier 0 one delegate, no routing.

    server.OnRequest(ctx => ctx.Response.WriteAsync("hello"));
  2. Tier 1 raw routing. MapGet/MapPost/, the ASP.NET Core spelling.

    server.MapGet("/ping", ctx => ctx.Response.WriteAsync("pong"));
    server.MapGet("/users/{id:int}", ctx => ctx.Response.WriteAsync(ctx.Request.RouteValues["id"]!));

    See Routing.

  3. Tier 2 middleware. The same shape as ASP.NET Core middleware, as a lambda or as a class.

    server.Use(async (ctx, next) =>
    {
    var sw = Stopwatch.StartNew();
    await next(ctx);
    logger.LogInformation("{Path} took {Elapsed}ms", ctx.Request.Path, sw.ElapsedMilliseconds);
    });

    See Middleware.

  4. Tier 3 source-generated typed endpoints. Route registration, parameter binding and OpenAPI metadata are emitted at compile time no reflection, which is what keeps the whole thing trim- and AOT-clean.

    [Route("/api/users")]
    public class UserEndpoints(IUserService users, ILogger<UserEndpoints> logger)
    {
    [Get("/{id:int}")]
    public async Task<IActionResult> GetUser(int id, CancellationToken ct)
    => await users.FindAsync(id, ct) is { } u ? new OkObjectResult(u) : new NotFoundResult();
    }
    app.MapMyAppEndpoints(); // emitted for every [Route] class in the assembly

    See Typed Endpoints.

Dependency injection is available, never mandatory

Section titled Dependency injection is available, never mandatory
// No container. RequestServices resolves nothing; everything else works.
var server = new HttpServer(new HttpServerOptions { Port = 8080 });
// With a container. Scoped services behave exactly as in ASP.NET Core.
var builder = HttpServer.CreateBuilder();
builder.Services.AddSingleton<IClock, SystemClock>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
var app = builder.Build();

A real IServiceScope is created per request/response exchange and disposed when the request ends, including IAsyncDisposable. Endpoint classes are resolved from that scope, so a Scoped dependency is one instance shared by everything handling the request the same contract as ASP.NET Core. ctx.RequestServices is the accessor at every tier.

If the app already has a container a MAUI app, a generic host use AddShinyHttpServer() instead. See Hosting & Lifecycle.

Shiny.Net.HttpServer.Testing wires an HttpClient straight to the server through a pair of pipes, so a test costs no port, binds no listener and leaves nothing behind when it fails half way through:

await using var app = TestHttpServer.Create(server =>
server.MapGet("/ping", ctx => ctx.Response.WriteTextAsync("pong")));
Assert.Equal("pong", await app.Client.GetStringAsync("/ping"));

Only the socket is replaced the request still goes through the real parser, router, middleware and response framing. TestHttpServer.Create takes the same builder the app uses, so substituting a dependency in a test is the ordinary builder.Services.AddSingleton(...). See Testing.

If you want to Read
Start, stop and restart the server at runtime Hosting & Lifecycle
Bind several ports, set limits, turn on forwarded headers Configuration
Understand templates, constraints and match precedence Routing
Return JSON without reflection Results & JSON
Serve a web app out of the assembly Static Files or Blazor WebAssembly
Authenticate callers Authentication, JWT
Reach the device from the internet Tunnelling
Run this inside a .NET MAUI or mobile app Mobile
Host an MCP server Model Context Protocol
Publish Shiny.Mediator handlers or a DocumentDb type Shiny.Mediator, Shiny.DocumentDb
Test endpoints without a port Testing

This is the constraint the whole design answers to, so it is worth stating plainly: nothing in the server discovers anything by reflection. Routes are registered by generated code, parameters are bound by generated code, and JSON goes through JsonTypeInfo from a JsonSerializerContext rather than through Type.GetProperties().

The one thing you have to bring is that context:

[JsonSerializable(typeof(Widget))]
[JsonSerializable(typeof(IReadOnlyList<Widget>))]
public partial class AppJson : JsonSerializerContext;

The endpoint generator emits a module initializer that registers it for you, and warns at build time (SWS006) about any type crossing an endpoint boundary that the context does not cover turning a runtime failure into a build warning. See Results & JSON.

The reflection-based JSON overloads still exist for apps that are not published trimmed, but they are annotated [RequiresUnreferencedCode]/[RequiresDynamicCode], so an AOT build has to opt into them deliberately.


Web Proxy Viewer  |  New URL  |  Original Page