Removed Immediate.Apis, Added AOT Testing Scripts.

This commit is contained in:
2026-05-05 18:47:11 +05:00
parent ec7ab8aadc
commit 22577fe3fb
27 changed files with 623 additions and 422 deletions
-28
View File
@@ -1,5 +1,3 @@
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -18,29 +16,3 @@ public sealed class Greeting : GreetingBase
protected override void RenderGreetingId(HtmxRenderContext context) => context.Writer.WriteUtf8(_greetingIdData);
protected override void RenderUser(HtmxRenderContext context) => context.Writer.WriteUtf8(_userData);
}
[Handler]
[MapGet("/greet/{username}/{count?}/{id?}")]
public static partial class GetGreetingHandler
{
public class Query
{
[FromRoute] public string Username { get; set; } = default!;
[FromRoute] public string? Count { get; set; }
[FromRoute] public string? Id { get; set; }
}
private static ValueTask<IResult> HandleAsync(
Query query,
IHttpContextAccessor httpContextAccessor,
CancellationToken token)
{
var context = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var count = int.TryParse(query.Count, out var parsedCount) ? parsedCount + 1 : 0;
var greetingId = Guid.TryParse(query.Id, out var parsedId) ? parsedId : Guid.NewGuid();
var template = new Greeting { Username = query.Username, Count = count, GreetingId = greetingId };
return ValueTask.FromResult<IResult>(context.WriteHtmxBody(template));
}
}
@@ -0,0 +1,23 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
using Htmx.ApiDemo.Templates;
namespace Htmx.ApiDemo;
public static partial class RouteMap
{
public static void MapGetIndex(WebApplication app)
=> app.MapGet("/", (IHttpContextAccessor contextAccessor) =>
{
var context = contextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var greet = new Greeting { Username = "Enciphered", Count = 0, GreetingId = Guid.NewGuid() };
greet.HtmxAwareWriteToBody(
context: context,
title: "Home",
appName: "HtmxApp",
pageTitle: "Home"
);
});
}
+1 -58
View File
@@ -1,6 +1,4 @@
using Htmx.ApiDemo.Data;
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -25,59 +23,4 @@ public sealed class Login : LoginBase
protected override void RenderErrorMessage(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_errorData);
protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afTokenData);
}
[Handler]
[MapGet("/login")]
public static partial class GetLoginHandler
{
public class Query;
private static ValueTask<IResult> HandleAsync(
Query _,
IHttpContextAccessor httpContextAccessor,
IAntiforgery antiforgery,
CancellationToken token)
{
var ctx = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
if (ctx.User.Identity?.IsAuthenticated == true)
return ValueTask.FromResult<IResult>(new HtmxResult("/"));
var afTokens = antiforgery.GetAndStoreTokens(ctx);
return ValueTask.FromResult<IResult>(ctx.WriteHtmxPage(new Login(afToken: afTokens.RequestToken), title: "Sign in", appName: "HtmxApp", pageTitle: "Sign in"));
}
}
[Handler]
[MapPost("/login")]
public static partial class PostLoginHandler
{
public class Command
{
[FromForm] public string Email { get; set; } = default!;
[FromForm] public string Password { get; set; } = default!;
}
private static async ValueTask<IResult> HandleAsync(
[AsParameters] Command command,
IHttpContextAccessor httpContextAccessor,
IAntiforgery antiforgery,
AuthService authService,
CancellationToken token)
{
var ctx = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var (success, error) = await authService.LoginAsync(command.Email, command.Password);
if (success)
return new HtmxResult("/");
var afTokens = antiforgery.GetAndStoreTokens(ctx);
return ctx.WriteHtmxPage(new Login(error, afToken: afTokens.RequestToken), title: "Sign in", appName: "HtmxApp", pageTitle: "Sign in");
}
}
}
@@ -0,0 +1,65 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Antiforgery;
using Htmx.ApiDemo.Templates;
using Htmx.ApiDemo.Data;
using Microsoft.AspNetCore.Mvc;
namespace Htmx.ApiDemo;
public static partial class RouteMap
{
private static void GetLogin(WebApplication app)
=> app.MapGet("/login", (IHttpContextAccessor contextAccessor, IAntiforgery antiforgery) =>
{
var context = contextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
if (context.User.Identity?.IsAuthenticated == true)
{
context.Response.Redirect("/");
return;
}
var afToken = antiforgery.GetAndStoreTokens(context).RequestToken;
var loginComponent = new Login(afToken: afToken);
loginComponent.HtmxAwareWriteToBody(
context: context,
title: "Login",
appName: "HtmxApp",
pageTitle: "Welcome back"
);
});
private static void PostLogin(WebApplication app)
=> app.MapPost("/login", async ValueTask
(
[FromForm] string email,
[FromForm] string password,
[FromServices] IHttpContextAccessor httpContextAccessor,
[FromServices] IAntiforgery antiforgery,
[FromServices] AppAuthService authService
) =>
{
var context = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var afToken = antiforgery.GetAndStoreTokens(context).RequestToken;
var (success, error) = await authService.LoginAsync(email, password);
if (success)
{
context.Response.Redirect("/");
return;
}
var loginComponent = new Login(error, afToken: afToken);
loginComponent.HtmxAwareWriteToBody(
context: context,
title: "Login",
appName: "HtmxApp",
pageTitle: "Welcome back"
);
});
}
-27
View File
@@ -1,27 +0,0 @@
using Htmx.ApiDemo;
using Htmx.ApiDemo.Data;
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace Htmx.ApiDemo.Templates;
[Handler]
[MapPost("/logout")]
public static partial class PostLogoutHandler
{
// Empty command — [AsParameters] ensures form content-type is accepted
// and antiforgery token in the form is validated by the middleware.
public class Command;
private static async ValueTask<IResult> HandleAsync(
[AsParameters] Command _,
AuthService authService,
IHttpContextAccessor httpContextAccessor,
CancellationToken token)
{
await authService.SignOutAsync();
return new HtmxResult("/login");
}
}
+2 -24
View File
@@ -1,12 +1,10 @@
using Htmx.ApiDemo;
using Htmx.ApiDemo.Templates.Components;
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Http;
namespace Htmx.ApiDemo.Templates;
namespace Htmx.ApiDemo;
public sealed class MainLayout : MainLayoutBase
public sealed class MainLayout : Templates.MainLayoutBase
{
private byte[] _titleData = [];
private byte[] _appNameData = [];
@@ -67,24 +65,4 @@ public sealed class MainLayout : MainLayoutBase
protected override void RenderAppName(HtmxRenderContext context) => context.Writer.WriteUtf8(_appNameData);
protected override void RenderPageTitle(HtmxRenderContext context) => context.Writer.WriteUtf8(_pageTitleData);
protected override void RenderUserSection(HtmxRenderContext context) => context.Writer.WriteUtf8(_userSectionData);
}
[Handler]
[MapGet("/")]
public static partial class GetIndexHandler
{
public class Command;
private static ValueTask<IResult> HandleAsync(
Command command,
IHttpContextAccessor httpContextAccessor,
CancellationToken token)
{
var context = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var greet = new Greeting { Username = "Enciphered", Count = 0, GreetingId = Guid.NewGuid() };
return ValueTask.FromResult<IResult>(context.WriteHtmxPage(greet, title: "Home", appName: "HtmxApp", pageTitle: "Home"));
}
}
+1 -68
View File
@@ -1,6 +1,4 @@
using Htmx.ApiDemo.Data;
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
@@ -25,69 +23,4 @@ public sealed class Register : RegisterBase
protected override void RenderErrorMessage(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_errorData);
protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afTokenData);
}
[Handler]
[MapGet("/register")]
public static partial class GetRegisterHandler
{
public class Query;
private static ValueTask<IResult> HandleAsync(
Query _,
IHttpContextAccessor httpContextAccessor,
IAntiforgery antiforgery,
CancellationToken token)
{
var ctx = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
if (ctx.User.Identity?.IsAuthenticated == true)
return ValueTask.FromResult<IResult>(new HtmxResult("/"));
var afTokens = antiforgery.GetAndStoreTokens(ctx);
return ValueTask.FromResult<IResult>(ctx.WriteHtmxPage(new Register(afToken: afTokens.RequestToken), title: "Register", appName: "HtmxApp", pageTitle: "Create account"));
}
}
[Handler]
[MapPost("/register")]
public static partial class PostRegisterHandler
{
public class Command
{
[FromForm] public string Email { get; set; } = default!;
[FromForm] public string Password { get; set; } = default!;
[FromForm] public string ConfirmPassword { get; set; } = default!;
[FromForm] public string? DisplayName { get; set; }
}
private static async ValueTask<IResult> HandleAsync(
[AsParameters] Command command,
IHttpContextAccessor httpContextAccessor,
IAntiforgery antiforgery,
AuthService authService,
CancellationToken token)
{
var ctx = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
if (command.Password != command.ConfirmPassword)
{
var afTokens1 = antiforgery.GetAndStoreTokens(ctx);
return ctx.WriteHtmxPage(new Register("Passwords do not match.", afToken: afTokens1.RequestToken),
title: "Register", appName: "HtmxApp", pageTitle: "Create account");
}
var (success, error) = await authService.RegisterAsync(command.Email, command.Password, command.DisplayName);
if (success)
return new HtmxResult("/");
var afTokens2 = antiforgery.GetAndStoreTokens(ctx);
return ctx.WriteHtmxPage(new Register(error, afToken: afTokens2.RequestToken),
title: "Register", appName: "HtmxApp", pageTitle: "Create account");
}
}
}
@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
using Htmx.ApiDemo.Templates;
using Microsoft.AspNetCore.Antiforgery;
using Htmx.ApiDemo.Data;
using Microsoft.AspNetCore.Mvc;
namespace Htmx.ApiDemo;
public static partial class RouteMap
{
private static void GetRegister(WebApplication app)
=> app.MapGet("/register", (IHttpContextAccessor contextAccessor, IAntiforgery antiforgery) =>
{
var context = contextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
if (context.User.Identity?.IsAuthenticated == true)
{
context.Response.Redirect("/");
return;
}
var afTokens = antiforgery.GetAndStoreTokens(context);
var registerComponent = new Register(afToken: afTokens.RequestToken);
registerComponent.HtmxAwareWriteToBody(
context: context,
title: "Register",
appName: "HtmxApp",
pageTitle: "Create account"
);
});
private static void PostRegister(WebApplication app)
=> app.MapPost("/register", async ValueTask
(
[FromForm] string email,
[FromForm] string password,
[FromForm] string confirmPassword,
[FromForm] string? displayName,
[FromServices] IHttpContextAccessor httpContextAccessor,
[FromServices] IAntiforgery antiforgery,
[FromServices] AppAuthService authService
) =>
{
var context = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var afToken = antiforgery.GetAndStoreTokens(context).RequestToken;
if (password != confirmPassword)
{
var errorComponent = new Register("Passwords do not match.", afToken: afToken);
errorComponent.HtmxAwareWriteToBody(
context: context,
title: "Register",
appName: "HtmxApp",
pageTitle: "Create account"
);
}
var (success, error) = await authService.RegisterAsync(email, password, displayName);
if (success)
{
context.Response.Redirect("/");
return;
}
var registerComponent = new Register(error, afToken: afToken);
});
}
+1 -23
View File
@@ -1,6 +1,4 @@
using Htmx.ApiDemo.Templates.Components;
using Immediate.Apis.Shared;
using Immediate.Handlers.Shared;
using Microsoft.AspNetCore.Http;
namespace Htmx.ApiDemo.Templates;
@@ -364,24 +362,4 @@ public sealed class UiDemo : UiDemoBase
protected override void RenderDropdownDemo(HtmxRenderContext ctx) => DropdownDemo.Render(ctx);
protected override void RenderToastViewportDemo(HtmxRenderContext ctx) => ToastViewportDemo.Render(ctx);
}
[Handler]
[MapGet("/ui-demo")]
public static partial class GetUiDemoHandler
{
public class Query;
private static ValueTask<IResult> HandleAsync(
Query query,
IHttpContextAccessor httpContextAccessor,
CancellationToken token)
{
var context = httpContextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
var page = new UiDemo();
return ValueTask.FromResult<IResult>(context.WriteHtmxPage(page, title: "UI Demo", appName: "HtmxApp", pageTitle: "UI Components"));
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Builder;
using Htmx.ApiDemo.Templates;
namespace Htmx.ApiDemo;
public static partial class RouteMap
{
private static void GetUiDemo(WebApplication app)
=> app.MapGet("/ui-demo", (IHttpContextAccessor contextAccessor) =>
{
var context = contextAccessor.HttpContext
?? throw new InvalidOperationException("HttpContext is not available.");
if (context.User.Identity?.IsAuthenticated == false)
{
context.Response.Redirect("/login");
return;
}
var uiDemoComponent = new UiDemo();
uiDemoComponent.HtmxAwareWriteToBody(
context: context,
title: "UI Demo",
appName: "HtmxApp",
pageTitle: "Htmx UI Demo"
);
});
}