Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
150186 stories
·
33 followers

Reading Notes #690

1 Share

AI keeps changing how we build, think, and even feel about software. This batch of posts & episodes mixes practical agent skills, vibe coding, and faster shipping with a bit of reflection on the old internet and why it still sticks with us.


AI


Podcasts

  • Your Images are Out of Date (probably) - The Silent Rebuilds problem (DevOps and Docker Talk: Cloud Native Interviews and Tooling) - Very interesting episode. I guess I never realized how true it is that as soon as you download your image, they are outdated. This episode talks about the concept of silent rebuilds and tools to help us solve that issue.

  • 503: Welcome to Tiny Tool Town (Merge Conflict) - With a name like Tiny Tool Town, my head always goes to Looney Tunes. No idea why, but this episode is not about that. It's about the collection of open source tools named: Tiny Tool Town, and they also talk about different models in GitHub Copilot.

  • Building Software using Squad with Brady Gaster (.NET Rocks!) - Turn your Coplot to 11 with Squad. Carl and Richard talk to Brady Gaster about Squad, a tool for creating an AI development team using GitHub Copilot.

  • Daniel Ward: AI Agents - Episode 393 (Azure & DevOps Podcast) - In this episode, they talk about the different AI tools used by developers and DevOps people, and the trends.

  • Everything Is a Graph (Even Your Dad Jokes) with Roi Lipman (Screaming in the Cloud) - Nice episode about different database and most obviously about graph databases. Very interesting to learn more about all that explosion of database types.


Miscellaneous


Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, podcasts and books that catch my interest during the week.

If you have interesting content, share it!

~frank


Read the whole story
alvinashcraft
14 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

SOLID Principles in C#: A Beginner-Friendly Guide with Modern .NET Examples

1 Share

SOLID principles in C# are five design rules that help you write code that is easy to change, grow, and test. Whether you are building a small console app or a large system with .NET 10, these rules will change how you think about your code. In this guide, you will learn what each rule means, see hands-on code using modern C#, and find out how SOLID ties into clean design.

Table of Contents

What Are SOLID Principles?

Robert C. Martin came up with these five rules in the early 2000s. Since then, they have been a key part of good object-oriented code. In fact, the name SOLID is short for five ideas: Single Task, Open/Closed, Liskov Swap, Interface Split, and Dependency Flip. Each one solves a real problem you will face as your code base grows.

Think of SOLID as guard rails on a road. They don’t tell you where to drive. Instead, they keep you from going off the edge. As a result, your code stays clean and easy to work with. Microsoft’s design rules for .NET apps share many of these same ideas.

For C# devs on modern .NET, these rules matter a lot. For example, the built-in DI system, interface patterns, and records all push you toward SOLID code. In fact, you may already use some of these rules and not know it. So let’s look at each one with real code.

Single Responsibility Principle (SRP)

This rule says a class should have just one reason to change. In other words, each class should do one thing and do it well. Consequently, this is the most natural of all the SOLID principles in C#. Yet it is the one devs break the most.

Why SRP Matters

When a class does too many things, a change to one part can break the other. For example, picture a class that handles orders and sends emails. If you tweak the email layout, you might break the order logic. On top of that, testing gets harder. You can’t test one job on its own when two jobs share the same class.

A Hands-On Example

For example, look at this class that breaks SRP. It does order math and saves data:

// Breaks SRP: two reasons to change
public class OrderService
{
    public decimal CalculateTotal(Order order)
    {
        decimal total = 0;
        foreach (var item in order.Items)
        {
            total += item.Price * item.Quantity;
        }
        return total;
    }

    public void SaveToDatabase(Order order)
    {
        using var context = new AppDbContext();
        context.Orders.Add(order);
        context.SaveChanges();
    }
}

Now let’s split this into two small classes, each with one job:

// Follows SRP: each class has one job
public class OrderCalculator
{
    public decimal CalculateTotal(Order order)
    {
        return order.Items.Sum(item => item.Price * item.Quantity);
    }
}

public class OrderRepository
{
    private readonly AppDbContext _context;

    public OrderRepository(AppDbContext context)
    {
        _context = context;
    }

    public void Save(Order order)
    {
        _context.Orders.Add(order);
        _context.SaveChanges();
    }
}

After this split, OrderCalculator only changes when math logic changes. Similarly, OrderRepository only changes when save logic changes. As a result, each class is focused and easy to test. If you are using new C# 14 features, you can likewise make these classes even shorter with body members and primary constructors (added in C# 12).

Open/Closed Principle (OCP)

This rule says your code should be open for new features but closed for edits. In short, you should be able to add new things to your app without changing code that already works. Indeed, this rule is the base of plugin designs and the strategy pattern.

Why OCP Matters

Each time you edit working code to add new needs, you risk bugs. Furthermore, if many features rely on the same class, one small edit can cause a chain of breaks. However, the Open/Closed rule guards against this. Instead, it pushes you to add new classes rather than edit old ones.

A Hands-On Example

Say you need to work out discounts for different buyer types. Here is a way that breaks OCP:

// Breaks OCP: must edit this method for every new buyer type
public class DiscountCalculator
{
    public decimal GetDiscount(string customerType, decimal orderTotal)
    {
        if (customerType == "Regular")
            return orderTotal * 0.05m;
        else if (customerType == "Premium")
            return orderTotal * 0.10m;
        else if (customerType == "VIP")
            return orderTotal * 0.20m;

        return 0;
    }
}

Adding a new buyer type means you must edit GetDiscount. Instead, let’s use an interface so we can follow OCP:

// Follows OCP: add new classes, don't edit old ones
public interface IDiscountStrategy
{
    decimal CalculateDiscount(decimal orderTotal);
}

public class RegularDiscount : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal orderTotal) => orderTotal * 0.05m;
}

public class PremiumDiscount : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal orderTotal) => orderTotal * 0.10m;
}

public class VipDiscount : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal orderTotal) => orderTotal * 0.20m;
}

public class DiscountCalculator
{
    private readonly IDiscountStrategy _strategy;

    public DiscountCalculator(IDiscountStrategy strategy)
    {
        _strategy = strategy;
    }

    public decimal GetDiscount(decimal orderTotal) => _strategy.CalculateDiscount(orderTotal);
}

Now, when you need a new buyer type, you just make a new class that uses IDiscountStrategy. As a result, the old DiscountCalculator stays the same. Moreover, this works great with .NET’s DI system, which can pick the right class at runtime.

Liskov Substitution Principle (LSP)

Barbara Liskov, a well-known computer scientist, gave this rule its name. It says that a child class should be able to stand in for its parent class. The app must still work the right way. In plain terms, if your code works with a base type, it must also work with any child of that type.

Why LSP Matters

When you break LSP, you get runtime bugs. Your code might build and pass basic tests. However, it could fail in the real world when a child class acts in a way the parent would not. As a result, devs stop trusting the type system. They then add if-checks all over, which makes a mess of the code.

The Classic Rectangle Problem

The go-to LSP example is a Square that extends Rectangle:

// Breaks LSP: Square changes how Rectangle works
public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }

    public int CalculateArea() => Width * Height;
}

public class Square : Rectangle
{
    public override int Width
    {
        set { base.Width = value; base.Height = value; }
    }

    public override int Height
    {
        set { base.Width = value; base.Height = value; }
    }
}

This breaks LSP. For instance, setting the width of a Square also sets the height. That is not how a Rectangle works. Therefore, code that expects width and height to be free of each other will get wrong results.

A better path is to use a shared interface:

// Follows LSP: each shape finds its own area
public interface IShape
{
    int CalculateArea();
}

public record Rectangle(int Width, int Height) : IShape
{
    public int CalculateArea() => Width * Height;
}

public record Square(int Side) : IShape
{
    public int CalculateArea() => Side * Side;
}

See how we use C# records here. Records can’t be changed once made, which stops the kind of setter-based LSP bugs shown above. On top of that, the IShape interface means any shape can find its area without knowing about the other shape’s state.

Interface Segregation Principle (ISP)

This rule says no class should be forced to use methods it does not need. Rather than making one big, fat interface, you should split it into small, focused ones. This rule is close to SRP, but it deals with how you design your interfaces.

Why ISP Matters

Big interfaces glue things together that don’t belong. For example, picture a class that has ten methods but only needs three. Those seven extra methods just sit there and add weight. Furthermore, a change to one of the extra methods forces a rebuild. The class does not even care about that method. As a result, it wastes time and can cause bugs.

A Hands-On Example

Here is a bloated interface that breaks ISP:

// Breaks ISP: forces classes to add methods they don't need
public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
    void AttendMeeting();
}

public class Robot : IWorker
{
    public void Work() { /* robot works */ }
    public void Eat() => throw new NotSupportedException();
    public void Sleep() => throw new NotSupportedException();
    public void AttendMeeting() => throw new NotSupportedException();
}

The Robot class must add Eat and Sleep methods that make no sense for a robot. Throwing errors like this is a code smell. It almost always points to an ISP problem. Let’s fix it:

// Follows ISP: small, focused interfaces
public interface IWorkable
{
    void Work();
}

public interface IFeedable
{
    void Eat();
}

public interface IRestable
{
    void Sleep();
}

public class HumanWorker : IWorkable, IFeedable, IRestable
{
    public void Work() { /* human works */ }
    public void Eat() { /* human eats */ }
    public void Sleep() { /* human sleeps */ }
}

public class Robot : IWorkable
{
    public void Work() { /* robot works */ }
}

Now each class only uses the interfaces that fit. Robot only cares about IWorkable. The HumanWorker uses all three. This is also more flexible since you can mix and match as needed. Take a look at static abstract interface members in C# 11 to see how modern C# takes this even further.

Dependency Inversion Principle (DIP)

This is the last piece of the SOLID puzzle. Indeed, it is perhaps the most useful for modern .NET work. It says two things. First, high-level code should not depend on low-level code. Instead, both should depend on contracts. Second, contracts should not depend on details. Likewise, details should depend on contracts.

Why DIP Matters

Without DIP, your core logic gets glued to things like databases, file systems, and web APIs. As a result, your code becomes hard to test. You can’t swap in fakes or mocks. Furthermore, if you change your database, you’d have to rewrite your core logic. That consequently kills the whole point of having layers.

A Hands-On Example

Here is a tightly bound alert service that breaks DIP:

// Breaks DIP: high-level class depends on low-level class
public class EmailSender
{
    public void SendEmail(string to, string message)
    {
        // SMTP logic here
    }
}

public class NotificationService
{
    private readonly EmailSender _emailSender = new();

    public void NotifyUser(string userId, string message)
    {
        var email = GetUserEmail(userId);
        _emailSender.SendEmail(email, message);
    }

    private string GetUserEmail(string userId) => "user@example.com";
}

Now let’s apply DIP by adding a contract between them:

// Follows DIP: both sides depend on the contract
public interface IMessageSender
{
    Task SendAsync(string to, string message);
}

public class EmailSender : IMessageSender
{
    public async Task SendAsync(string to, string message)
    {
        // SMTP logic here
        await Task.CompletedTask;
    }
}

public class SmsSender : IMessageSender
{
    public async Task SendAsync(string to, string message)
    {
        // SMS API logic here
        await Task.CompletedTask;
    }
}

public class NotificationService
{
    private readonly IMessageSender _sender;

    public NotificationService(IMessageSender sender)
    {
        _sender = sender;
    }

    public async Task NotifyUserAsync(string userId, string message)
    {
        var contact = GetUserContact(userId);
        await _sender.SendAsync(contact, message);
    }

    private string GetUserContact(string userId) => "user@example.com";
}

NotificationService no longer cares if it sends emails, texts, or push alerts. It only knows about the IMessageSender contract. You can set up the right class in .NET’s DI system and swap it without touching your core code.

How SOLID Feeds into Clean Architecture

Clean architecture, made famous by Robert C. Martin, splits code into rings. Your core models sit in the middle. Use cases wrap around them. Then come adapters, and last the outer frameworks. The key rule is simple: things point inward. Outer layers know about inner ones, but not the other way around.

Each SOLID rule maps to a clean design goal. SRP keeps your core classes focused. OCP lets you add new use cases without changing old rules. LSP makes sure your contracts work the same across all classes. ISP keeps those contracts lean. And DIP is the backbone of the whole thing. It lets inner layers stay blind to outer layer details. The original clean architecture post by Robert C. Martin goes deeper into these layer lines.

In a typical ASP.NET Core app, this might look like this folder layout:

// Domain layer (core): pure logic, no outside needs
MyApp.Domain/
    Entities/
    Interfaces/       // IOrderRepository, IMessageSender
    Services/         // OrderCalculator (SRP)

// Application layer: use cases and workflow
MyApp.Application/
    UseCases/
    DTOs/

// Infrastructure layer (outer): real-world hookups
MyApp.Infrastructure/
    Repositories/     // OrderRepository implements IOrderRepository (DIP)
    Services/         // EmailSender implements IMessageSender (DIP)
    Data/

Your domain layer sets up contracts (DIP). Meanwhile, your outer layer builds the real classes. The middle layer then ties it all together. As a result, it’s easy to swap your database, change your email tool, or add new alert channels. None of that touches your core logic. For example, if you build APIs with .NET, things like rate limiting can go in the outer layer without changing the core. That’s OCP at work.

SOLID and Dependency Injection in .NET

Dependency injection (DI) is not a SOLID rule by itself. But it is the tool that makes DIP work in .NET. The built-in DI system in ASP.NET Core wires up your classes at runtime. Your code never has to build its own needs.

Here is how you’d set up the alert example from before:

// In Program.cs (minimal API style)
var builder = WebApplication.CreateBuilder(args);

// Register the contract with its real class
builder.Services.AddScoped<IMessageSender, EmailSender>();
builder.Services.AddScoped<NotificationService>();

var app = builder.Build();

app.MapPost("/notify", async (NotificationService service) =>
{
    await service.NotifyUserAsync("user123", "Your order has shipped!");
    return Results.Ok();
});

app.Run();

With one line of code, you can switch from EmailSender to SmsSender across your whole app. That is the power of DIP plus .NET’s DI system. Moreover, you can also register more than one class and use patterns like strategy or factory to pick the right one.

Testing gets much easier too. In your unit tests, you can plug in a mock:

// Unit test with a mock
public class NotificationServiceTests
{
    [Fact]
    public async Task NotifyUserAsync_CallsSender_WithCorrectMessage()
    {
        var mockSender = new Mock<IMessageSender>();
        var service = new NotificationService(mockSender.Object);

        await service.NotifyUserAsync("user123", "Hello!");

        mockSender.Verify(s => s.SendAsync(It.IsAny<string>(), "Hello!"), Times.Once);
    }
}

Without DIP, this kind of testing would not be possible. Instead, you’d need real email servers in your tests. In practice, most .NET devs see DI as a must-have alongside SOLID. The official .NET DI docs cover the DI system in detail. Similarly, if you have been looking at required properties in C# 11, you’ll see how they make sure all needs are met when a class is built.

Common Mistakes When Applying SOLID

SOLID rules are very useful, but you can take them too far. Here are some common traps to watch for.

Too Many Layers

Making an interface for every class is not what SOLID means. For instance, if a class has just one version and likely always will, wrapping it adds weight for no gain. Instead, use contracts where they bring real value. That means places where you need test doubles or more than one class.

Thinking SRP Means “One Method Per Class”

SRP does not mean a class should have just one method. It means a class should have one reason to change. A UserValidator class might have methods like ValidateEmail, ValidatePassword, and ValidateAge. All of these are about one job: checking user data. So they belong in the same class.

Skipping the Real World

SOLID rules are guides, not laws. For example, in a small proof of concept, strict use of SOLID can slow you down for little gain. Instead, use your own judgment to decide when clean design pays off. As a rule of thumb, the bigger and longer-lived the code base, the more SOLID helps.

Using DIP Without DI

Adding contracts without a DI system means you still make real classes by hand somewhere. That “somewhere” becomes a pain point. In .NET, always pair DIP with the built-in DI system. You can also use a third-party tool like Autofac for more control.

If you liked this guide, these posts might help too:

Over to You

SOLID principles in C# are the kind of thing you learn once and use for life. They form the base of clean design, test-friendly code, and systems that age well. The key is to use them with care. Start with the rule that fixes your biggest pain. That might be messy classes (SRP), brittle child types (LSP), or tightly bound code (DIP). Then refactor from there.

Which SOLID rule do you find hardest to use in your own projects? Do you ever skip a SOLID guide on purpose for speed? I’d love to hear your take in the comments below.

The post SOLID Principles in C#: A Beginner-Friendly Guide with Modern .NET Examples appeared first on Programming and Tech Blog.

Read the whole story
alvinashcraft
18 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Aspire 13.2.0

1 Share

Aspire 13.2

Aspire 13.2 brings major CLI enhancements, a new TypeScript AppHost (preview), dashboard data export/import, Microsoft Foundry integration, and multi-language improvements β€” all focused on making local development more streamlined for developers and AI coding agents
alike.

Highlights

  • πŸ› οΈ CLI overhaul β€” New commands including aspire start/stop/ps for detached mode, aspire describe for resource monitoring, aspire doctor for environment diagnostics, aspire secret for managing user secrets, aspire docs for browsing documentation from the terminal,
    and aspire agent (renamed from aspire mcp) for AI agent integration.
  • 🌐 TypeScript AppHost (preview) β€” Write your apphost in TypeScript with createBuilder(), using the same app model concepts as C#. Full VS Code extension support included.
  • 🧩 VS Code extension β€” Dedicated Aspire Activity Bar panel with live resource state, inline CodeLens with health status and actions, gutter decorations, and a new Getting Started walkthrough.
  • πŸ“Š Dashboard improvements β€” Bulk telemetry export/import, export environment variables as .env files, a new telemetry HTTP API, set parameters directly from the dashboard, and improved resource graph layout.
  • πŸ€– Microsoft Foundry β€” Replaces Azure AI Foundry integration with broader Aspire.Hosting.Foundry support including hosted agents and model deployments.
  • πŸ”’ Azure Virtual Network & Private Endpoints β€” New Aspire.Hosting.Azure.Network integration for defining VNets, subnets, NAT gateways, NSGs, and private endpoints directly in your apphost.
  • 🐳 Docker Compose publishing β€” Generate docker-compose.yaml from your app model with AddDockerComposeEnvironment.
  • πŸ“¦ New integrations β€” Azure Data Lake Storage, MongoDB EF Core (Aspire.MongoDB.EntityFrameworkCore), Bun support for JS resources, and Certbot for automated SSL certificates.
  • ⚑ App model β€” WithMcpServer for declaring MCP endpoints, rebuild command for project resources, contextual endpoint resolution, and improved secret/certificate handling.

⚠️ Breaking changes

Notable breaking changes include service discovery env vars now using endpoint scheme instead of name, aspire.config.json replacing split config files, AIFoundry β†’ Foundry rename, WithSecretBuildArg β†’ WithBuildSecret, and updated default Azure credential behavior.
See the full list of breaking changes.

πŸ“– Learn more

For the full details on everything in this release, check out the What's new in Aspire 13.2 documentation.

Thank you to all the community contributors who helped make this release happen! πŸ’œ

Read the whole story
alvinashcraft
37 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Your Inbox As Options, Not Obligations

1 Share

I’ve heard this many times over the years:

… getting everything out of your head into a million projects and tasks feels like you are asked to do a little of everything.

https://bsky.app/profile/deejaygraham.bsky.social/post/3mfu3lh7oe22n

This leads to conclusions that I don’t share, such as β€œGetting Things Done pushes people towards multitasking [and I agree that multitasking is generally a mistake]”. I understand how reaching this conclusion can sour a person on Getting Things Done, but I see that conclusion as both a pity and avoidable.

The Apparent Moral Goodness of Never Saying β€œNo”

You might have been conditioned to do (maybe literally) whatever people ask you to do. Some well-meaning adults might have taught you to do this when you were too young to question it. They taught you the virtues of helping, of being of service, of doing for others what you hope they would do for you when you needed it. Entire populations are taught the undeniable moral goodness of behaving this way. Religion aside, all this generally guides young people to become helpful, reliable members of society.

Sadly, it also risks teaching young people that they can’t say β€œNo”. They grow up with a vague, unexplained, visceral, undeniable aversion to rejecting people’s requests to do things for them. They feel over-responsible for disappointing people, so they keep committing to more and more and more. And this becomes why they need a system such as Getting Things Done in the first place!

Folks struggling with Too Much To Do and Constantly Disappointing Everyone (Especially Themselves!) have a big, messy Inbox in their head: a master list of things they need to figure out when they’re going to do. Some of them also have some structure in their mind resembling Projects and Next Actions. Mostly, however, they have a jumble of too many tasks that they are constantly expediting and constantly forgetting and rediscovering. It feels really painful. It saps their energy. It sends them back to bed. Or worse.

Along comes Getting Things Done, which encourages them to start by bringing order to the chaos: write everything down and put it in The Inbox. Gather everything into The Inbox. Once you’ve got things safely in The Inbox, you can begin to figure out what you’ll do when, in what order: how you’ll manage to follow through on your commitments. It claims that this is a path to no longer having deadlines sneak up on them. It helps them stop letting important tasks fall through the cracks until they become urgent (and expensive!). It allows them to forget safely, so that they can focus on one thing at a time until, almost like magic, things are routinely getting done.

Sadly, it also risks freaking them out when they realize just how much they’ve committed to doing. And since The Universe keeps asking them to do things, they see The Inbox growing and growing and growing. The more they work The System, the farther behind they fall! They conclude that Getting Things Done hasn’t worked.

If you’re in this situation, or worried about this becoming your future, I have a proposal for you!

Options, Not Obligations

When you look at The Inbox and your Projects and Next Actions, what do you see? I suspect you see long lists of commitments, stretching as far as the eye can see. Commitments. Future Broken Promises. Obligations.

Guess what? You can choose.

It probably doesn’t feel like it, but you can choose. Over time, you can learn to see what’s in your Trusted System as options, not obligations. I suggest you start with The Inbox.

Detour: Software Development and Backlogs

In the world of software development, they talk often about Backlogs as a fundamental tool of planning. They have Product Backlogs (what we have to do in order to ship the product) and Iteration Backlogs (what we have to do in order to declare victory over the next 2 weeks or month). They probably have other kinds of Backlogs that they didn’t even read about in a book. These Backlogs are glorified, vaunted, revered, and feared shared β€œto do” lists. The details don’t matter much for this discussion, it might shock you to learn that these Backlogs are a source of significant confusion, debate, and dysfunction in the world of professional software development. I help clients with these problems. Many of those problems are problems of their own creation. (No shade; just facts. The struggle is real.)

Just like you and seeing The Inbox as frightening, painful obligations.

One big problem for software development professionals is that they see the Backlog as an immutable list of commitments. Once they put items in their various Backlogs, negotiation stops. They don’t adjust the plan to match emerging reality, such as workers quitting, getting sick, or going on vacation. Or learning that this key feature that we bet on is not going to sell. Or learning that that key feature is going to take 4 times as long to build as everyone thought, because technology. In professional software development, they cling to the misguided notion that they first plan, then they execute the plan, then they profit. In this environment, they turn Backlogs into immutable plans. Backlogs become constant reminders that they’ve bitten off more than they can chew. They feel like prisons.

Software development professionals use some of the tools of planning, but they forget to plan: to adjust their expectations as they encounter unpleasant surprises, to change their minds as they find out that their plan isn’t going to work, to react to the changing personnel and cash flows and market pressures.

This Is Not About Software Development

Maybe you do this, too. You intend to volunteer at your child’s school, but then you find out that it takes 3 times as much time and energy to do as you’d expected, and now you’re stuck. You tried to clean the garage last weekend, but once you started, you found out that it would cost way too much money to haul away all the stuff you wanted to donate and you didn’t have the heart to throw it all into a landfill, so you gave up. You keep staring at the piano, imagining how much fun it would be to play for an hour (or two!), but then you see the stack of papers on your desk and can’t justify the self-indulgence. And somehow you don’t take care of the papers, either. Who has the strength? I’ll watch a movie instead.

Stop the world. I want to get off!

Ceci N’est Pas Un Backlog

I propose you start here: The Inbox is Not a Backlog.

Instead of treating The Inbox like a list of commitments, treat it as a list of options: ideas, possibilities, things to reconsider later, idle thoughts to spend 10 minutes thinking more deeply about on Friday, potentialities… but not obligations. You need to make it safe in your own mind to put things in The Inbox without assuming that you actually have to do them!

I tell my software development clients to do this, but they often struggle, because there are many people in their project community, each with their own needs. They have competing objectives. They have differing philosophies. They care about different things. Any change to their working environment requires consent and getting that consent takes effort. Some people simply don’t feel ready to try to treat their Backlogs as anything less than obligations to do things under threat of losing their job. And, you know, their managers and executives routinely remind them about this threat every week!

You have an advantage: it’s just you in there! You can choose. You can experiment with making it safe to see The Inbox as options, not obligations. Nobody else can see inside your head. You can try and fail and try and fail and try fail until you suddenly succeed. Until you feel the click. Until you start seeing The Inbox as a collection of options, not obligations.

Feeling Safe in The Inbox

When you see The Inbox as options instead of obligations, it becomes safe to capture everything in The Inbox. You’re not going to do it all, anyway. You can’t. You don’t have enough time, energy, and money to do it all. You’re going to have more ideas and you need the freedom to sit and think about them. To mull them over, even when you’re not in the shower. You’re going to need to negotiate with The Universe regarding what you can reasonably commit to and what you can’t. You’re going to need to make room for doing things for yourself, because if you don’t, then you will run entirely out of energy and everything will stop! (We call this β€œburnout” and even β€œdepression”.)

When you make The Inbox a safe place to capture every little idea, these big things happen:

  • You let yourself have ideas without fear of having to do anything with them. Your creativity kicks into high gearβ€”or at least springs back to life.

  • You build a daily practise of scratching tasks off your list. You literally practise saying β€œNo” to The Universe, but inside your own mind, where it’s safe because they can’t hear you and you don’t have face their disappointment. You can build confidence in your β€œNo”, so that you can say it when you need to say it.

  • You focus more deeply and fully on the tasks you actually do, so you complete them sooner and with more energy. Sometimes you even have fun doing them!

  • You do more of the things you want to do and fewer of the things that you don’t. (No, really!)

  • You feel more confident in the commitments you keep, because you have increasingly clear evidence of what you can do and what you can’t do, of what you have space to do and what you don’t. You become the reliable, helpful person that those well-meaning (or maybe not) adults taught you to become.

As the saying goes, if you can’t say β€œNo”, then your β€œYes” means nothing. I prefer to say this:

Once you make peace with saying β€œNo” to folks, you recapture the purest joy that comes from being able to say β€œYes!” and meaning it.

And that starts with seeing The Inbox as full of options, not obligations. You can choose.

Getting Started

Spend 10 minutes with some paper and a pen and write all your uncompleted tasks (and projects) down. Just write. Write down anything you’re worried about forgetting. Don’t edit anything yet; just write. Write down the things you’ve always wanted to do, but never found time to do. Write it all down and don’t read any of it. Write down all the little things, too. The obvious things. The mundane, everyday things. Just write. This is The Inbox.

How do you feel now?

Once you have The Inbox, you can decide what to do with those items: scratch some off, put some deadlines or appointments on your calendar, set a reminder for Friday to spend 20 minutes thinking more about That Project You Maybe Want To Do, and then pick some things that you’ve already committed to, or that you want to get out of the way, or that you genuinely want to do today. (What? Meeting my own needs? In this economy?!)

Now put The Inbox away. You don’t ever have to look at it again, unless you want to spend more time processing it. Maybe what you’ve done now is enough to clarify what you need to today, for the rest of the week, and to begin next week. Maybe on Monday you can spend 10 minutes with some paper and pen and do this all again.

This is enough to get started. You’re doing Getting Things Done! It’s happening!

At some point, rewriting The inbox every Monday is going to become annoying. Or you might need to do it more often. Or less often. This means you’re ready for the next trick. Maybe then you have the energy to read the book. Or to come back here for the next trick. Or to send me a complaint or ask me a question, so that I can write about it.

Epilogue, or Future Work

Once you feel comfortable seeing The Inbox as options, not obligations, then you can imagine seeing your Projects and Next Actions as merely options, not obligations. (🀯?)

Related Reading

Gabor MatΓ©, The Myth of Normal. Very young brains seem to behave a lot like adult brains in a hypnotic trance. If this is true, then you were literally hypnotized to believe what adults told you when you were a small child. This would explain why those Survival Rules seem automatic and impossible to ignore!

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Coding Azure 23: Reading Settings from an App Configuration Service

1 Share

Today, we’ll get the code needed to retrieve the App Configuration service setting values in the application code.

In my last post, Coding Azure 22, I showed how to add settings to an App Configuration service to hold configuration information that needs to be shared among multiple components in a distributed application (the example I used was the name of a queue that would be written to by some frontend application and read from by some backend processor). In this post, I’ll look at the code you need to retrieve those setting values in your application code.

If you’ve worked with retrieving information from a .NET configuration file (e.g., the appsettings.json file in an ASP.NET Core application) through the IConfiguration interface, then you’re already familiar with most of what you need to do. It’s just a matter of integrating your App Configuration service into your configuration settings.

That means that you can treat your configuration file and your App Configuration service (and, if your application is running in an App Service, your App Service’s Environment Variables) as a single source.

Having said that, an App Configuration service provides a lot more options than a plain old JSON file or an Environments settings file. You may want to consider making an App Configuration service as your only source for configuration information.

Integrating Your App Configuration Service

To enable your application to access your App Configuration service, your application needs two NuGet packages:

  • Add Azure.Identity.Web (probably already part of your application’s dependencies)
  • Microsoft.Extensions.Configuration.AzureAppConfiguration

You’ll then need to update your application’s Program.cs file to integrate your App Configuration service into your application’s configuration information. Provided you have only one setting for any Key in your service, you just need to call the Configuration object’s AddAzureAppConfiguration, passing the URL for your service (as a Uri object) and some credential object. That could be as simple as this code:

builder.Configuration.AddAzureAppConfiguration(o =>
    o.Connect(new Uri("<url for your App Configuration Service>"), 
    new DefaultAzureCredential())   

You can retrieve the URL for your service from your App Configuration’s Overview page. Using DefaultAzureCredential will enable you to run your code from an App Service using the App Service’s Managed Identity and test your code from with Visual Studio or Visual Studio Code (assuming that you’ve added the Azure extension to Visual Studio Code).

Retrieving Setting Values

In an ASP.NET Core application, to retrieve a value from your merged configuration source, you must first retrieve the IConfiguration object from your application’s services collection. This example from an ASP.NET controller retrieves the service in the controller’s constructor and stores it in a field called config:

private readonly IConfiguration config;

 public Worker(ILogger<Worker> logger, IConfiguration config)
 {
     this.config = config;        

With the IConfiguration object in the config field, you could retrieve the value for a setting with its Key set to “QueueName” using this code:

string? qname = config["QueueName"];

In a Razor file you can retrieve the IConfiguration object using the inject directive. This code retrieves the IConfiguration object into a field called config and then uses it to retrieve a setting with a Key set to “AltText”:

@inject IConfiguration config
…
<img alt='@config ["AltText"]' …

Handling Duplicate Key Values

If the Key you specify doesn’t exist or there is more than one version of the Key, then both of those sets of code return null. (Because of that, it’s a good practice to always check for null after retrieving configuration values.)

You can have multiple versions of a Key if you’ve taken advantage of the App Configuration service’s ability to have multiple settings with the same Key, each with a different Label (e.g., “production,” “dev”). To avoid retrieving multiple versions of the same key, you’ll need to specify which version of any Key you want to retrieve when configuring your application.

In your Program.cs file, you can specify the version of each Key that you want by configuring AddAzureAppConfiguration through its options object, using the object’s Select method. The Select method accepts two parameters:

  • KeyFilter.Any
  • Either
    • LabelFiler.Null which matches to Labels that have no value
    • A string value to compare Labels to

You can use multiple Selects when configuring AddAzureAppConfiguration. Multiple Selects will not result in duplicate Keys because values retrieved by later Select methods overwrite values retrieved in earlier Selects.

The following example is typical code in a Program.cs in the production version of your application:

  • The first Select retrieves settings that don’t have a Label (i.e., settings that are to be used in any environment)
  • The second Select retrieves settings that have their Label set to “production”
builder.Configuration.AddAzureAppConfiguration(o =>
{
   o.Connect(new Uri("<url for your App Configuration Service>"), 
             new DefaultAzureCredential())
           .Select(KeyFilter.Any, LabelFilter.Null)
           .Select(KeyFilter.Any, "production");
});

Order matters here: If there is a Key that has both a version with no Label and a version with a Label set to "production,” the “production” version will overwrite the values from the version without a Label because the “production” version is selected second.

You can make this code more portable by replacing the hard-coded “production” string with a call to builder.Environment.EnvironmentName (provided, of course, that your environment names match the Labels you’re using in your App Configuration service). Be aware: Labels are case-sensitive: “Production” is not a match to “production.”

You have a different issue if Keys in your App Configuration file duplicate settings in either your application’s configuration file (e.g., appsettings.json) or in an App Service’s Environment Variables (all of which are merged into IConfiguration). Those matching keys form a hierarchy: App Configuration values override Environment Variables which, in turn, override configuration file settings. The danger here is that you may lose a setting in your configuration file because it’s accidentally overridden by a matching Key in your App Configuration service.

Handling Data Type

By default, all configuration values are treated as strings. You can use the config object’s GetValue method to handle conversions and, as a bonus, provide a default value.

The GetValue method is a generic method, which lets you specify the data type of the value you’re retrieving. You pass it the Key of the setting to retrieve and, optionally, a default value to return if the Key can’t be found or if the Key can’t be converted.

This example converts the setting’s value to an integer or returns -1 if the setting isn’t found or can’t be converted:

int? value = config.GetValue<int>("QueueLength", -1);

Retrieving Multiple Settings

If you used the recommended naming convention to group related Keys in your settings, then you retrieve those settings together. These sample Keys group together the WebServiceUrl and AuthorizationKey*n* settings by prefixing them with “SalesOrder”:

SalesOrder:WebServiceUrl
SalesOrder:AuthorizationKey1
SalesOrder:AuthorizationKey2

You can retrieve their values individually with code like this:

string url = config["SalesOrder:WebServiceUrl"];
string authKey = config["SalesOrder:AuthorizationKey1"];

However, you can also retrieve all the SalesOrder prefixed settings as a single group. You have two options here. One approach treats the SalesOrder settings as a class with properties; the other approach treats the SalesOrder settings as nested arrays.

Retrieving a Section as a Class

The first step in treating grouped settings as a class is to define a class with property names that match the members of the section (the class name doesn’t matter but the property names, while not case sensitive, must match the names of the child settings). A typical class for my SalesOrder settings might look like this:

internal class SalesOrderSection
{
   public string webServiceUrl {get; set;} = string.Empty;
   public string authorizationKey1 {get; set;} = string.Empty;
   public string authorizationKey2 {get; set;} = string.Empty;
}

Then, in your Program.cs file, as part of configuring your App Configuration service, you use the Configuration object’s GetSection method to retrieve the SalesOrder prefixed settings as a section. You then use the Services object’s Configure method to both load that section into your class and add that class to your application’s Services collection.

Typical code would look like this:

IConfiguration soSection = builder.Configuration
                                                                        .GetSection("SalesOrder");
builder.Services.Configure<SalesOrderSection>(soSection);

That code would create an IOptions<SalesOrderSection> object and add it to your application’s Services collection. The Value property of that IOptions object would hold the class with the SalesOrder section’s values.

Typical code in one of your application’s components to pull that object from the Services collection and retrieve the values stored in it would look like this:

private SalesOrderSection soSettings;

public HomeController(IOptions<SalesOrderSection> soSettings)
{
    this.soSettings = soSettings.Value;    
    string url = this.soSettings.webServiceUrl;

Retrieving a Section as an Array

Treating the SalesOrder prefixed settings as an array is more flexible but require more code. With this approach, instead of working in your Program.cs file, you use the IConfiguration object’s GetSection method in your application where you would retrieve individual values.

The GetSection method, when passed the prefix that defines your section returns an IConfigurationSection object. You then use that section object’s GetChildren method to convert all the settings in the section into an array of IConfigurationSection objects, each of which has a Key and a Value property (and a GetChildren method if your settings have their own nested settings).

This approach can be especially useful if your settings consist of repeated values because you can loop through the array without having to know how many repeated values there are.

This sample code loops through my SalesOrder settings looking for my Authorization keys:

IConfigurationSection secSO = config.GetSection("SalesOrder");
IEnumerable<IConfigurationSection> childrenSO = secSO.GetChildren();
string? key = string.Empty;

foreach (IConfigurationSection childSo in childrenSO)
{
    if ( childSO.Key.StartsWith("Auth")  )
    {
         key = childSO.Value;
       …process authorization key…
    }
}

Alternatively, you can use LINQ to retrieve specific values, as this example does by using the child object’s Key property:

string? value = childrenSO.FirstOrDefault(c => c.Key == "WebServiceUrl")?.Value;

Retrieving JSON Settings

If the value of your setting is a JSON document and you’ve set the Content type property of the setting to application/json, then you can treat the JSON document as if it were a section.

I could have, for example, created a setting with a Key set to “SalesOrder” and put this JSON document in it (which would also let me define my authorization keys as an array):

{
   "WebServiceUrl":"https://…",
   "AuthorizationKeys":   [
                                                    "a1…41",
                                                     "4b..ff"
                                                 ]
}

I can then retrieve individual values as if they were configuration settings in my service using code like this:

string? url = config["SalesOrder:url"]
string? authkey1 = config["SalesOrder:AuthorizationKeys:0"]
string? authkey2 = config["SalesOrder:AuthorizationKeys:1"]

You can also use the IConfigurationSection object with the GetSection and GetChildren methods to process your JSON document as I described earlier.

Retrieving Key Vault References

If you are using Key Vault references in your settings, you’ll need to configure your App Configuration’s service in your application’s Program.cs file to grant permission to access the vault at runtime.

The AddAzureAppConfiguration’s options object has a ConfigureKeyVault method that accepts an options object. You need to pass that options object’s SetCredential method the credentials that allow access to the Key Vault used in the Key Vault reference. Using DefaultAzureCredential will pick up your credentials in Visual Studio or Visual Studio Code for testing purposes and, at runtime, the Managed Identity assigned to the App Service hosting your application in the cloud.

This code would pick up the default credentials and use them for any Key Vault references:

builder.Configuration.AddAzureAppConfiguration(o =>
{
    o.ConfigureKeyVault(kvo =>
    {
        kvo.SetCredential(new DefaultAzureCredential());
    });
   o.Connect(});

You can then retrieve the unencrypted value from the Key Vault like any other setting just by using the setting’s Key like any other configuration setting:

string? value = config["AuthorizationKey"];

If you’re using multiple Key Vaults, the credentials you provide to the ConfigureKeyVault options object will be used for all the Key Vaults. If your Key Vaults require different credentials, you can use that options object’s SetSecretResolver method to specify different sets of credentials for different Key Vaults.

Retrieving Snapshots

If you’ve created an App Configuration snapshot to hold a collection of settings, you must configure the AddAzureAppConfiguration’s options object to select the snapshot you want to use. That’s done through the options object’s SelectSnapshot method, passing the Key for your Snapshot Reference setting (i.e., not the name of the snapshot itself). Be aware: If the snapshot can’t be found, no exception is raised.

Your snapshot’s settings are merged with the rest of your service’s settings. This can result in duplicate Keys (e.g., there’s a setting with a Key of “QueueName” in both your configuration settings and in the snapshot). If that happens, the last entry in the configuration settings list overrides any earlier settings, regardless of where the setting comes from.

This means that if you want to have your snapshot’s settings to always override your configuration settings then you should give your Snapshot References names that appear late in the alphabet (e.g., begin all your Snapshot reference Key values with “x-”).

Because snapshots are intended to provide a history of revisions made to your settings, you can not replace or edit a snapshot. However, you can change the snapshot used by a Snapshot Reference in your configuration. So, if you want to change the settings used by an existing Snapshot Reference in your configuration settings, then you must:

  1. Create a new snapshot
  2. Edit your Snapshot Reference to point to the new snapshot

Keeping Up to Date

With the code so far, if the value in a setting in your App Configuration service is changed while your application is running, your application won’t pick up the change.

If you want to keep your application using the current version of the settings in your App Configuration service (or, at least, one that isn’t very far out of date), then you should configure the service to refresh its values at some interval. You can do that by using the AddAzurAppConfiguration’s options object and calling its ConfigureRefresh method, passing a lambda expression that calls the SetCacheExpiration method (that method accepts a TimeSpan object).

The code for doing that is probably easier to understand than the explanation. Here’s some code that configures the configuration service to refresh its values every 10 minutes:

builder.Configuration.AddAzureAppConfiguration(o =>
{
o.ConfigureRefresh(ropt =>
        ropt.SetCacheExpiration(TimeSpan.FromMinutes(10)));

For this to be effective, though, in your code you must always retrieve your configuration values from the IConfiguration object. If, for example, you store a string value from the IConfiguration object in a field in your application, that field won’t be updated when your service is refreshed.

Handling More Complex Scenarios

If you find that you can’t do what you want by merging your App Configuration service into the rest of your configuration sources, you can create a ConfigurationClient object). The ConfigurationClient object provides methods for managing your App Configuration store, including adding and removing settings and snapshots.

More relevant to this post, the ConfigurationClient also provides GetConfigurationSetting and GetConfigurationSettingForSnapshot methods with multiple overloads that allow you to retrieve a value by any combination of Key, Label and date/time.

This code, for example, retrieves a complete setting by specifying both the Key and the Label:

ConfigurationClient cc = new(new Uri("https://whconfig.azconfig.io"),
                              new DefaultAzureCredential()); 
ConfigurationSetting cs = cc.GetConfigurationSettingAsync("QueueName", "production");

And that wraps up the App Configuration service! While App Configuration is essential for distributed applications, its integration with Key Vaults, its support Snapshots and its ability to be automatically refreshed all make it an attractive replacement, I think, for storing configuration information even for standalone applications.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete

Generative AI for Beginners .NET: Version 2 on .NET 10

1 Share

Today we’re releasing Version 2 of Generative AI for Beginners .NET, our free, open-source course for building AI-powered .NET applications.

If you want to understand and build generative AI applications, from core concepts to production-ready patterns, this is your starting point!

This new version restructured the entire curriculum into five focused lessons with full explanations, rebuilt everything on .NET 10, and moved to Microsoft.Extensions.AI as the primary abstraction.

If you went through v1, this is a different course.

Graphic for the announcement of Generative AI for Beginners .NET - Version 2

TL;DR

  • Completely rewritten curriculum with five structured lessons
  • All samples updated to .NET 10
  • New AI abstraction layer using Microsoft.Extensions.AI
  • Updated RAG implementations using native SDKs
  • New Microsoft Agent Framework lesson

Five lessons, fully rewritten

The biggest change in Version 2 is the course itself! We threw out the old lesson structure and rewrote everything from scratch into five lessons, each with complete explanations, working samples, and a clear learning arc.

  1. Introduction to Generative AI – What generative AI is, how large language models work, and how they connect to .NET, helping you understand the foundation for everything that follows.
  2. Generative AI Techniques – The practical core. Learn about chat completions, prompt engineering, function calling, RAG, reasoning, and structured outputs. Basically, write and understand AI code for real applications.
  3. AI Patterns and Applications – Taking the techniques from Lesson 2 and applying them to real application patterns. Learn how to apply generative AI techniques to real application architectures and production patterns.
  4. Agents with MAF – Multi-agent systems using the Microsoft Agent Framework. Tool use, orchestration, and how agents collaborate to solve complex tasks.
  5. Responsible AI – Safety, content filtering, evaluation, and the practices you need to ship AI features responsibly.

Version 2 goes deeper. Each lesson explains the concepts fully, walks through the code, and connects to the next lesson. You come out of it understanding not just how to call an API, but why the patterns work the way they do and how to apply them in real applications.

# Lesson Link Description
01 Introduction to Generative AI
  • What generative AI is and how it differs from traditional programming
  • Why .NET is a first-class citizen for AI development
  • The Microsoft AI stack and where each piece fits
  • How to run samples in GitHub Codespaces or configure local development
02 Generative AI Techniques
  • How to create chat conversations with context and memory
  • How text embeddings work and why they matter
  • How to process different content types including images and documents
  • How to call AI models using Microsoft.Extensions.AI abstractions
03 AI Patterns and Applications
  • How to build semantic search that understands meaning
  • How to implement retrieval augmented generation (RAG)
  • How to create applications that process and understand documents
  • When to use each pattern and how to combine them
04 AI Agents with Microsoft Agent Framework
  • What makes an agent different from a chatbot
  • How to build agents that use tools and take actions
  • How to orchestrate multiple agents working together
  • How to integrate with Model Context Protocol (MCP)
05 Responsible AI
  • How to identify and mitigate bias in AI applications
  • How to implement content safety and guardrails
  • How to build transparency and explainability into your systems
  • Ethical considerations specific to agentic systems

.NET 10 across the board

The samples now follow modern .NET patterns such as dependency injection, middleware pipelines, and file-based apps introduced in .NET 10. Authentication has also been standardized. All file-based samples now use AzureCliCredential, so you authenticate once through the Azure CLI and every sample picks it up. No more juggling connection strings or API keys across dozens of projects.

We also updated all model references to gpt-5-mini across the documentation and samples, reflecting the latest available models.

Microsoft.Extensions.AI as the foundation

Version 1 used Semantic Kernel as the primary way to talk to AI models. Version 2 uses Microsoft.Extensions.AI (MEAI).

The reasoning was straightforward. MEAI ships as part of the .NET 10 ecosystem, follows the same patterns as ILogger and IConfiguration, and works across providers without locking you into a specific orchestration framework.

In practice, this meant rewriting every core sample to use IChatClient and the MEAI middleware pipeline. The code got simpler. A basic chat completion that previously needed SK kernel setup, plugins, and connector configuration now looks like any other .NET service registration.

For example, the classic Space Invaders sample to learn how to integrate classic apps with AI!

RAG samples rewritten with native SDKs

We moved 11 pure Semantic Kernel samples to samples/deprecated/. They still build, they still work, and you can still reference them. But they’re no longer part of the main learning path.

For projects that mixed both SK and MEAI (like BasicChat-05AIFoundryModels and BasicChat-11FoundryClaude), we removed the SK dependency entirely and kept them running on pure MEAI.

We archived these samples because a beginner course should teach the foundational layer first, and in .NET 10, that layer is MEAI, and for agentic use, Microsoft Agent Framework is the premier toolkit to deploy and create agents.

Microsoft Agent Framework RC

Lesson 4 now covers the Microsoft Agent Framework, and we’ve documented it in the course materials! The five Microsoft Agent Framework web application samples continue to cover multi-agent orchestration, PDF ingestion, and chat middleware patterns.

Updated translations

All eight language translations (Chinese, French, Portuguese, Spanish, German, Japanese, Korean, Traditional Chinese) have been updated to reflect the new lesson structure, deprecation changes, and .NET 10 migration.

Getting started

Join the fun at Generative AI for Beginners – .NET!

Pick a provider (Microsoft Foundry or Ollama for local development), open Lesson 1, and work through the five lessons in order. Each one builds on the last.

If you run into issues or have suggestions, open an issue. If you want to contribute, PRs are welcome.

The post Generative AI for Beginners .NET: Version 2 on .NET 10 appeared first on .NET Blog.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories