Skip to main content

Posts

Enabling diagnostics in a YAML pipeline in Azure DevOps

When a pipeline run fails and the regular logs don't tell you enough, Azure DevOps has a built-in way to get more detail: system diagnostics. The classic way to turn it on is the "Enable system diagnostics" checkbox you get when you manually queue a run. But if your pipeline is defined in YAML and runs automatically on every push, there's no checkbox to click. The checkbox approach When you queue a pipeline manually, click Run pipeline and you'll see an  Advanced options section with an Enable system diagnostics checkbox. Tick it, hit Run , and that single run gets verbose logging: purple-colored debug lines, extra detail on what each task is doing under the hood. Remark: this only affects the run you're queuing. It doesn't persist, and it's useless for CI-triggered runs where nobody is manually clicking Run. The YAML equivalent: system.debug For YAML pipelines, the checkbox maps directly to a variable: system.debug . Set it to true a...
Recent posts

EF Core savepoints: rolling back part of a transaction

There's a moment when a transaction fails halfway through, and you realize rolling back everything is overkill. You did five inserts, the sixth one violates a constraint, and you'd rather undo just that last operation than start the whole transaction over. That's what savepoints are for, and EF Core has supported them for a while now but until recently I didn't know this feature existed. What's the problem with plain transactions? A regular database transaction is all-or-nothing. Something like this: using var transaction = await context.Database.BeginTransactionAsync(); try { context.Blogs.Add(new Blog { Url = "https://blog1.com" }); await context.SaveChangesAsync(); context.Blogs.Add(new Blog { Url = "https://blog2.com" }); await context.SaveChangesAsync(); // this one fails context.Blogs.Add(new Blog { Url = null }); await context.SaveChangesAsync(); await transaction.CommitAsync(); } catch { a...

Passing custom parameters through OpenIddict's client authorization request

In my passthrough authentication post , the login endpoint set a provider value on AuthenticationProperties so the OpenIddict server would know whether to challenge GitHub or ADFS: app.MapGet("login/{provider}", (string provider) => Results.Challenge(new AuthenticationProperties { RedirectUri = "/", Items = { ["provider"] = provider } }, [OpenIddictClientAspNetCoreDefaults.AuthenticationScheme])); Run that as-is and the server never sees it. Items round-trips through your own app's correlation cookie — it was never meant to become part of the outgoing OAuth2 request. Why this doesn't just work AuthenticationProperties.Items is an ASP.NET Core concept. It's how the authentication middleware remembers things like the redirect URI across the round trip to an external provider and back. The OpenIddict client builds the actual /connect/authorize request from a completely different object — an OpenIddictReq...

Passthrough authentication with OpenIddict

In most enterprise applications authentication is externalized to an external identity provider. For our applications we had to support multiple identity providers. How can we do this without tight coupling to each of these providers? In this post we'll bring OpenIddict into the picture and show how it helps to solve this nicely. What is OpenIddict? OpenIddict is an open-source OAuth2/OpenID Connect server and client stack for .NET. It plugs into ASP.NET Core Identity (or your own user store) and lets you run your own authorization server instead of depending fully on an external one. You get the standard endpoints — /connect/authorize , /connect/token , /connect/userinfo — backed by your own database and your own claims. That's exactly what makes the passthrough pattern possible: OpenIddict is the piece in the middle that can defer to external providers for the actual authentication, then still be the one issuing the token your APIs trust. The naive approach The ...

Why "AI is just the next compiler" doesn't hold up

At a recent talk I compared AI adoption to the introduction of compilers: a new layer of abstraction that lets us work at a higher level, the same way compilers let us stop writing assembly by hand. In the hallway afterwards, a few people pushed back on that comparison. They were right to. Here's the point I should have made from the stage: agents are not deterministic, and that single difference breaks the analogy. Compilers follow rules A compiler is not a magical black box. You give it code, and it gives you a binary, following a fixed set of rules. Given the same input, a compiler will reliably produce the same output, every time. You don't hope the binary does what you wrote. You trust the compiler, because the transformation is deterministic. That determinism is exactly what let us move up the abstraction ladder in the first place. We stopped worrying about registers and memory addresses because we could trust the layer below us to behave the same way twice....

Reading a Profiler trace without guessing

Note: this is part 3 of a series on the Azure Monitor Profiler. Part 1 covered what the Profiler is, part 2 covered enabling it. This post is about the part that actually matters: making sense of a trace once you have one. Having profiler traces is only half the job. I've seen people enable the profiler, open a trace, stare at a wall of unfamiliar method names, and close the tab. The trace explorer isn't self-explanatory the first time - it took me a few real incidents before the views clicked. This post is the walkthrough I wish I'd had. Getting to a trace From your Application Insights resource: Go to the Performance tab Pick an operation from the list or leave Overall selected Click on Profiler traces   Pick one of the captured requests, ideally one with a longer duration than usual - that's where you'll actually find something. Once you're in a trace, you get two views of the same call stack data: Flame graph - the full...

Enabling the Application Insights Profiler

Note: this is part 2 of a series on the Azure Monitor Profiler. Part 1 covered what the Profiler is and where to find the results - this post covers actually turning it on. There are two ways to enable the profiler: through app settings on App Service, or by wiring it into your code directly. Which one you need depends on where your app runs and how much control you want over the setup. Option 1: codeless enablement on App Service If your app runs on App Service (Windows) and your Application Insights resource is in the same subscription, this is the easiest path - no code changes, no redeploy. From the portal: In your App Service instance, select Monitoring > Application Insights Select Turn on Application Insights , then Enable Scroll down to the .NET or .NET Core tab Set Collection level to Recommended Under Profiler and Code Optimizations , select On Apply , then confirm with Yes Or skip the portal entirely and set the app settings directly...