Rocky Lhotka
@rocky.lhotka.net
950 followers 460 following 360 posts
🧑 he/him 🧑‍💻 #oss #cslanet 🤵 VP @ Xebia; Chief Architect @ Marimer LLC 🎗️ #MicrosoftMVP and RD Program member 🙏 #a11y, #BlackLivesMatter 🏓 #rock, #metal, #LiveMusic, #ttrpg, #travel, #outdoors, #scifi, #fishing https://linktr.ee/rockylhotka
Posts Media Videos Starter Packs
rocky.lhotka.net
Downloading #battlefield6 - I can't wait to get back into that type of game, it has been far too long!!

#bf6
rocky.lhotka.net
Earlier today I had #sonnet write some code. Then I switched to #gpt5codex to do some more work - and it _rewrote_ the original code.

I think #ai suffers from "not written here" syndrome just like humans!! 😀
rocky.lhotka.net
That too is a good question!
rocky.lhotka.net
Man, #m365 #copilot is nearly useless when working with #powerpoint - they really need an equivalent to "agent mode" for it to be helpful.
rocky.lhotka.net
One thing that really annoys me about the #linkedin #windows app is that once you launch it, it runs in the system tray, consuming a bunch of memory for nothing. There doesn't appear to be an option to prevent this annoying behavior.
Reposted by Rocky Lhotka
iwriteok.bsky.social
as a kid growing up right-wing i regularly heard people express their desire for "a government so small I can drown it in the bathtub". imagine my surprise growing up to realize i was the only one who actually believed that.
Reposted by Rocky Lhotka
blog.lhotka.net.web.brid.gy
Accessing User Identity on a Blazor Wasm Client
On the server, Blazor authentication is fairly straightforward because it uses the underlying ASP.NET Core authentication mechanism. I’ll quickly review server authentication before getting to the WebAssembly part so you have an end-to-end understanding. I should note that this post is all about a Blazor 8 app that uses per-component rendering, so there is an ASP.NET Core server hosting Blazor server pages, and there may also be pages using `InterativeAuto` or `InteractiveWebAssembly` that run in WebAssembly on the client device. ## Blazor Server Authentication Blazor Server components are running in an ASP.NET Core hosted web server environment. This means that they can have access to all that ASP.NET Core has to offer. For example, a server-static rendered Blazor server page can use HttpContext, and therefore can use the standard ASP.NET Core `SignInAsync` and `SignOutAsync` methods like you’d use in MVC or Razor Pages. ### Blazor Login Page Here’s the razor markup for a simple `Login.razor` page from a Blazor 8 server project with per-component rendering: @page "/login" @using BlazorHolWasmAuthentication.Services @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Authentication.Cookies @using System.Security.Claims @inject UserValidation UserValidation @inject IHttpContextAccessor httpContextAccessor @inject NavigationManager NavigationManager <PageTitle>Login</PageTitle> <h1>Login</h1> <div> <EditForm Model="userInfo" OnSubmit="LoginUser" FormName="loginform"> <div> <label>Username</label> <InputText @bind-Value="userInfo.Username" /> </div> <div> <label>Password</label> <InputText type="password" @bind-Value="userInfo.Password" /> </div> <button>Login</button> </EditForm> </div> <div style="background-color:lightgray"> <p>User identities:</p> <p>admin, admin</p> <p>user, user</p> </div> <div><p class="alert-danger">@Message</p></div> This form uses the server-static form of the `EditForm` component, which does a standard postback to the server. Blazor uses the `FormName` and `OnSubmit` attributes to route the postback to a `LoginUser` method in the code block: @code { [SupplyParameterFromForm] public UserInfo userInfo { get; set; } = new(); public string Message { get; set; } = ""; private async Task LoginUser() { Message = ""; ClaimsPrincipal principal; if (UserValidation.ValidateUser(userInfo.Username, userInfo.Password)) { // create authenticated principal var identity = new ClaimsIdentity("custom"); var claims = new List<Claim>(); claims.Add(new Claim(ClaimTypes.Name, userInfo.Username)); var roles = UserValidation.GetRoles(userInfo.Username); foreach (var item in roles) claims.Add(new Claim(ClaimTypes.Role, item)); identity.AddClaims(claims); principal = new ClaimsPrincipal(identity); var httpContext = httpContextAccessor.HttpContext; if (httpContext is null) { Message = "HttpContext is null"; return; } AuthenticationProperties authProperties = new AuthenticationProperties(); await httpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, principal, authProperties); NavigationManager.NavigateTo("/"); } else { Message = "Invalid credentials"; } } public class UserInfo { public string Username { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; } } The username and password are validated by a `UserValidation` service. That service returns whether the credentials were valid, and if they were valid, it returns the user’s claims. The code then uses that list of claims to create a `ClaimsIdentity` and `ClaimsPrincpal`. That pair of objects represents the user’s identity in .NET. The `SignInAsync` method is then called on the `HttpContext` object to create a cookie for the user’s identity (or whatever storage option was configured in `Program.cs`). From this point forward, ASP.NET Core code (such as a web API endpoint) and Blazor server components (via the Blazor `AuthenticationStateProvider` and `CascadingAuthenticationState`) all have consistent access to the current user identity. ### Blazor Logout Page The `Logout.razor` page is simpler still, since it doesn’t require any input from the user:  @page "/logout" @using Microsoft.AspNetCore.Authentication @using Microsoft.AspNetCore.Authentication.Cookies @inject IHttpContextAccessor httpContextAccessor @inject NavigationManager NavigationManager <h3>Logout</h3> @code { protected override async Task OnInitializedAsync() { var httpContext = httpContextAccessor.HttpContext; if (httpContext != null) { var principal = httpContext.User; if (principal.Identity is not null && principal.Identity.IsAuthenticated) { await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } } NavigationManager.NavigateTo("/"); } } The important part of this code is the call to `SignOutAsync`, which removes the ASP.NET Core user token, thus ensuring the current user has been “logged out” from all ASP.NET Core and Blazor server app elements. ### Configuring the Server For the `Login.razor` and `Logout.razor` pages to work, they must be server-static (which is the default for per-component rendering), and `Program.cs` must contain some important configuration. First, some services must be registered: builder.Services.AddHttpContextAccessor(); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddTransient<UserValidation>(); The `AddHttpContextAccessor` registration makes it possible to inject an `IHttpContextAccessor` service so your code can access the `HttpContext` instance. > ⚠️ Generally speaking, you should only access `HttpContext` from within a server-static rendered page. The `AddAuthentication` method registers and configures ASP.NET Core authentication. In this case to store the user token in a cookie. The `AddCascadingAuthenticationState` method enables Blazor server components to make use of cascading authentication state. Finally, the `UserValidation` service is registered. This service is implemented by you to verify the user credentials, and to return the user’s claims if the credentials are valid. Some further configuration is required after the services have been registered: app.UseAuthentication(); app.UseAuthorization(); ### Enabling Cascading Authentication State The `Routes.razor` component is where the user authentication state is made available to all Blazor components on the server: <CascadingAuthenticationState> <Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }"> <Found Context="routeData"> <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> <FocusOnNavigate RouteData="routeData" Selector="h1" /> </Found> </Router> </CascadingAuthenticationState> Notice the addition of the `CascadingAuthenticationState` element, which cascades an `AuthenticationState` instance to all Blazor server components. Also notice the use of `AuthorizeRouteView`, which enables the use of the authorization attribute in Blazor pages, so only an authorized user can access those pages. ### Adding the Login/Logout Links The final step to making authentication work on the server is to enhance the `MainLayout.razor` component to add links for the login and logout pages: @using Microsoft.AspNetCore.Components.Authorization @inherits LayoutComponentBase <div class="page"> <div class="sidebar"> <NavMenu /> </div> <main> <div class="top-row px-4"> <AuthorizeView> <Authorized> Hello, @context!.User!.Identity!.Name <a href="logout">Logout</a> </Authorized> <NotAuthorized> <a href="login">Login</a> </NotAuthorized> </AuthorizeView> </div> <article class="content px-4"> @Body </article> </main> </div> <div id="blazor-error-ui"> An unhandled error has occurred. <a href="" class="reload">Reload</a> <a class="dismiss">🗙</a> </div> The `AuthorizeView` component is used, with the `Authorized` block providing content for a logged in user, and the `NotAuthorized` block providing content for an anonymous user. In both cases, the user is directed to the appropriate page to login or logout. At this point, all _server-side_ Blazor components can use authorization, because they have access to the user identity via the cascading `AuthenticationState` object. This doesn’t automatically extend to pages or components running in WebAssembly on the browser. That takes some extra work. ## Blazor WebAssembly User Identity There is nothing built in to Blazor that automatically makes the user identity available to pages or components running in WebAssembly on the client device. You should also be aware that there are possible security implications to making the user identity available on the client device. This is because any client device can be hacked, and so a bad actor could gain access to any `ClaimsIdentity` object that exists on the client device. As a result, a bad actor could get a list of the user’s claims, if those claims are on the client device. In my experience, if developers are using client-side technologies such as WebAssembly, Angular, React, WPF, etc. they’ve already reconciled the security implications of running code on a client device, and so it is probably not an issue to have the user’s roles or other claims on the client. I will, however, call out where you can filter the user’s claims to prevent a sensitive claim from flowing to a client device. The basic process of making the user identity available on a WebAssembly client is to copy the user’s claims from the server, and to use that claims data to create a copy of the `ClaimsIdentity` and `ClaimsPrincipal` on the WebAssembly client. ### A Web API for ClaimsPrincipal The first step is to create a web API endpoint on the ASP.NET Core (and Blazor) server that exposes a copy of the user’s claims so they can be retrieved by the WebAssembly client. For example, here is a controller that provides this functionality: using Microsoft.AspNetCore.Mvc; using System.Security.Claims; namespace BlazorHolWasmAuthentication.Controllers; [ApiController] [Route("[controller]")] public class AuthController(IHttpContextAccessor httpContextAccessor) { [HttpGet] public User GetUser() { ClaimsPrincipal principal = httpContextAccessor!.HttpContext!.User; if (principal != null && principal.Identity != null && principal.Identity.IsAuthenticated) { // Return a user object with the username and claims var claims = principal.Claims.Select(c => new Claim { Type = c.Type, Value = c.Value }).ToList(); return new User { Username = principal.Identity!.Name, Claims = claims }; } else { // Return an empty user object return new User(); } } } public class Credentials { public string Username { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; } public class User { public string Username { get; set; } = string.Empty; public List<Claim> Claims { get; set; } = []; } public class Claim { public string Type { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; } This code uses an `IHttpContextAccessor` to access `HttpContext` to get the current `ClaimsPrincipal` from ASP.NET Core. It then copies the data from the `ClaimsIdentity` into simple types that can be serialized into JSON for return to the caller. Notice how the code doesn’t have to do any work to determine the identity of the current user. This is because ASP.NET Core has already authenticated the user, and the user identity token cookie has been unpacked by ASP.NET Core before the controller is invoked. The line of code where you could filter sensitive user claims is this: var claims = principal.Claims.Select(c => new Claim { Type = c.Type, Value = c.Value }).ToList(); This line copies _all_ claims for serialization to the client. You could filter out claims considered sensitive so they don’t flow to the WebAssembly client. Keep in mind that any code that relies on such claims won’t work in WebAssembly pages or components. In the server `Program.cs` it is necessary to register and map controllers. builder.Services.AddControllers(); and app.MapControllers(); At this point the web API endpoint exists for use by the Blazor WebAssembly client. ### Getting the User Identity in WebAssembly Blazor always maintains the current user identity as a `ClaimsPrincpal` in an `AuthenticationState` object. Behind the scenes, there is an `AuthenticationStateProvider` service that provides access to the `AuthenticationState` object. On the Blazor server we generally don’t need to worry about the `AuthenticationStateProvider` because a default one is provided for our use. On the Blazor WebAssembly client however, we must implement a custom `AuthenticationStateProvider`. For example: using Microsoft.AspNetCore.Components.Authorization; using System.Net.Http.Json; using System.Security.Claims; namespace BlazorHolWasmAuthentication.Client; public class CustomAuthenticationStateProvider(HttpClient HttpClient) : AuthenticationStateProvider { private AuthenticationState AuthenticationState { get; set; } = new AuthenticationState(new ClaimsPrincipal()); private DateTimeOffset? CacheExpire; public override async Task<AuthenticationState> GetAuthenticationStateAsync() { if (!CacheExpire.HasValue || DateTimeOffset.Now > CacheExpire) { var previousUser = AuthenticationState.User; var user = await HttpClient.GetFromJsonAsync<User>("auth"); if (user != null && !string.IsNullOrEmpty(user.Username)) { var claims = new List<System.Security.Claims.Claim>(); foreach (var claim in user.Claims) { claims.Add(new System.Security.Claims.Claim(claim.Type, claim.Value)); } var identity = new ClaimsIdentity(claims, "auth_api"); var principal = new ClaimsPrincipal(identity); AuthenticationState = new AuthenticationState(principal); } else { AuthenticationState = new AuthenticationState(new ClaimsPrincipal()); } if (!ComparePrincipals(previousUser, AuthenticationState.User)) { NotifyAuthenticationStateChanged(Task.FromResult(AuthenticationState)); } CacheExpire = DateTimeOffset.Now + TimeSpan.FromSeconds(30); } return AuthenticationState; } private static bool ComparePrincipals(ClaimsPrincipal principal1, ClaimsPrincipal principal2) { if (principal1.Identity == null || principal2.Identity == null) return false; if (principal1.Identity.Name != principal2.Identity.Name) return false; if (principal1.Claims.Count() != principal2.Claims.Count()) return false; foreach (var claim in principal1.Claims) { if (!principal2.HasClaim(claim.Type, claim.Value)) return false; } return true; } private class User { public string Username { get; set; } = string.Empty; public List<Claim> Claims { get; set; } = []; } private class Claim { public string Type { get; set; } = string.Empty; public string Value { get; set; } = string.Empty; } } This is a subclass of `AuthenticationStateProvider`, and it provides an implementation of the `GetAuthenticationStateAsync` method. This method invokes the server-side web API controller to get the user’s claims, and then uses them to create a `ClaimsIdentity` and `ClaimsPrincipal` for the current user. This value is then returned within an `AuthenticationState` object for use by Blazor and any other code that requires the user identity on the client device. One key detail in this code is that the `NotifyAuthenticationStateChanged` method is only called in the case that the user identity has changed. The `ComparePrincipals` method compares the existing principal with the one just retrieved from the web API to see if there’s been a change. It is quite common for Blazor and other code to request the `AuthenticationState` very frequently, and that can result in a lot of calls to the web API. Even a cache that lasts a few seconds will reduce the volume of repetitive calls significantly. This code uses a 30 second cache. ### Configuring the WebAssembly Client To make Blazor use our custom provider, and to enable authentication on the client, it is necessary to add some code to `Program.cs` _in the client project_ : using BlazorHolWasmAuthentication.Client; using Marimer.Blazor.RenderMode.WebAssembly; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddAuthorizationCore(); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>(); builder.Services.AddCascadingAuthenticationState(); await builder.Build().RunAsync(); The `CustomAuthenticationStateProvider` requires an `HttpClient` service, and relies on the `AddAuthorizationCore` and `AddCascadingAuthenticationState` to properly function. ## Summary The preexisting integration between ASP.NET Core and Blazor on the server make server-side user authentication fairly straightforward. Extending the authenticated user identity to WebAssembly hosted pages and components requires a little extra work: creating a controller on the server and custom `AuthenticationStateProvider` on the client.
blog.lhotka.net
Reposted by Rocky Lhotka
handle.invalid
Excited to see MSTest.TestFramework v.4.0.0 finally out. This one has a quite a few breaking changes, but can finally correctly to async tests without hacks that were causing several instability issues. www.nuget.org/packages/MST...
MSTest.TestFramework 4.0.0
MSTest is Microsoft supported Test Framework. This package includes the libraries for writing tests with MSTest. To ensure discovery and execution of your tests, install the MSTest.TestAdapter ...
www.nuget.org
Reposted by Rocky Lhotka
adamkinzinger.substack.com
MAGA: “why didn’t Biden release the files and why didn’t you guys call for it?!”

America: “Because we assumed the justice dept was doing their job. But you all demanded it and now Trump is panicking…. So uh, ya now we def want to know why he’s panicking.”

See? Easy!
rocky.lhotka.net
I'm finding that #sonnet45 and #visualstudio2026 #copilot agent mode don't work great together. It seems like the AI keeps trying to find files that don't exist or something? it gets in long loops of failed tool calls. #sonnet4 and other models work better - in terms of tool calls anyway
Reposted by Rocky Lhotka
visualstudio.com
Visual Studio now supports rendering Mermaid charts in both the Markdown editor and Copilot Chat. This lets you visualize complex data structures, workflows, and relationships directly within your code editor. See it in action...
rocky.lhotka.net
Usually due to necessary #nuget packages. If they are in local cache AND your wifi is turned off, building should work offline.
Reposted by Rocky Lhotka
visualstudio.com
Select Visual Studio subscriptions include monthly Azure credits for development and testing purposes. No credit card is required to get started.
Explore more at my.visualstudio.com
Reposted by Rocky Lhotka
thebulwark.com
To the extent that it is true that federal money covers healthcare for illegal immigrants, it's because Congress and Ronald Reagan back in the 1980s understood it would be monstrous for it not to. www.thebulwark.com/p/government...
Illegal Immigrants Are Already Getting Health Care Under Donald Trump
The program financing ER care for illegal immigrants has been around for four decades, and not even Trump wants to take it away.
www.thebulwark.com
rocky.lhotka.net
What does Shawn mean about choco being a source for winget then?
rocky.lhotka.net
I just set up a computer with only winget - and it is pretty good so far, but sometimes I wish I knew how to make it pull in choco packages - what's the trick with that?
rocky.lhotka.net
I "love" how the #AI often speaks with such certainty, when really it is taking a random guess about what might work.
rocky.lhotka.net
interesting that #claudesonnet in #vscode #github #copilot is unaware of information I was able to get from the #claude app directly - both using 4.5!

So I manually fed the info from Claude back to Claude-copilot so it could resolve my issue with #dotnet #dotnetaspire.
rocky.lhotka.net
How do you put that stuff in a choco image?
Reposted by Rocky Lhotka
erikej.bsky.social
New release of SQLite Toolbox for Visual Studio with support for Visual Studio 2026 Insiders - if you use SQLite in Visual Studio you need this (like 1.5 million current users!!)
#dotnet #sqlite #visualstudio
SQLite and SQL Server Compact Toolbox - Visual Studio Marketplace
Extension for Visual Studio - SQLite / SQL Server Compact Toolbox extension for Visual Studio. This extension adds several features to help your embedded database development efforts: Scripting of…
buff.ly
Reposted by Rocky Lhotka
teresalhotka.bsky.social
Don’t let them make you forget that state violence is political violence.
Don’t let them make you forget that crime committed by the government is still crime.
Even though the government and the media does not count these things, it’s important that WE do.
Reposted by Rocky Lhotka
alvinashcraft.com
GitMCP – Instantly create a Remote MCP server for any GitHub repository

gitmcp.io

#ai #mcp #github #apis #aiagents
GitMCP
Instantly create an MCP server for any GitHub project
gitmcp.io