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

Using Strategy Pattern with Dependency Injection in ASP.NET Core

1 Share

This blog post is originally published on https://blog.elmah.io/using-strategy-pattern-with-dependency-injection-in-asp-net-core/

Selection logic is a prominent part of many applications. Whether you add a simple environment toggle, a UI mode decision, or apply a discount, you have to rely on user input. Sometimes, simply using an intuitive if-else or a switch case can work. However, when conditions are growing or a complex algorithm selection is required, simple conditional statements can't work. Your code becomes exhaustive and hard to maintain. The Strategy pattern rescues the situation, adheres to the open/closed principle, and keeps the logic maintainable. This article walks you through a practical, straightforward example of the strategy pattern: choosing between Regular, VIP, and Student discount strategies at runtime.

Using Strategy Pattern with Dependency Injection in ASP.NET Core

What is the Strategy pattern?

The Strategy design pattern is a behavioral pattern used when you need to switch between different algorithms at runtime. The strategy pattern encapsulates algorithms and selects the right one when needed, usually based on an input. This pattern provides a flexible, maintainable solution to an algorithm-selection problem, keeping the code cleaner and easier to extend. If you need to add a new algorithm, just add another class instead of touching the existing logic, adhering to the open/closed principle.

What is the problem without the Strategy pattern?

To understand the usability of the strategy pattern, we need to identify the problems we may face without it. Suppose we offer different discounts to different users based on their membership. A naive solution is to use an if-else statement or a switch case. Let's do it and evaluate the implementation.

Step 1: Create a Console application

dotnet new console -n StrategyPatternDemo
cd StrategyPatternDemo

Step 2: Create DiscountService class

In the service, we will define discount calculation with a conditional statement.

public class DiscountService
{
    public decimal GetDiscount(string customerType, decimal amount)
    {
        if (customerType.ToLower() == "regular")
        {
            return amount * 0.05m;
        }
        else if (customerType.ToLower() == "vip")
        {
            return amount * 0.20m;
        }
        else
        {
            return 0;
        }
    }
}

Step 3: Use the service in the Strategy Pattern Sword.cs

using StrategyPatternDemo;

Console.Write("Enter customer type (regular/vip): ");
var type = Console.ReadLine();

Console.Write("Enter amount: ");
var amount = decimal.Parse(Console.ReadLine());

var service = new DiscountService();
var discount = service.GetDiscount(type, amount);
var final = amount - discount;

Console.WriteLine($"Discount: {discount}");
Console.WriteLine($"Final Price: {final}");

Step 4: Run and test

Let's test it

dotnet run

Output

Output

It works as expected. But the code contains design and maintainability flaws.

  • The solution violates the Open/Closed principle. Adding a new membership will require changes to the core method, such as adding an else-if block.
  • All the discount logic is tightly coupled in a single class and lacks separation of concerns or single responsibility.
  • Conjoined code makes testing harder. To ensure the functionality, you have to test the monster every time.
  • As the conditions grow, you can fall into a spiral of conditions. Imagine if you have 20 memberships, that will be a nightmare for maintainability.

Implementing the strategy pattern in a console application

In our example, let's address the above issues using the Strategy Pattern.

Step 1: Define Strategy Interface

Adding the discount strategy interface

public interface IDiscountStrategy
{
    decimal ApplyDiscount(decimal amount);
}

Step 2: Add concrete strategies

Adding separate implementations of each algorithm

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

For Vip

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

Notice that none of the strategies implement validation or error handling. In real-world code, you would probably want to look into that. This part has been left out of this post since the focus is around splitting the business logic out in strategies.

Step 3: Define context class

public class DiscountService
{
    private readonly IDiscountStrategy _strategy;

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

    public decimal GetDiscount(decimal amount) => _strategy.ApplyDiscount(amount);
}

The Context class in the strategy pattern holds a reference to a strategy interface (IDiscountStrategy in our case). It receives a strategy from outside. It does not implement logic itself, instead, it delegates work to the strategy, while the concrete classes define their logic.

Step 4: Use the strategy in the Program.cs


Console.WriteLine("Enter customer type (regular/vip): ");
string type = Console.ReadLine()?.ToLower();

IDiscountStrategy strategy;

// Manually picking strategy — no switch needed, but you *can* if you want.
if (type == "vip")
    strategy = new VipDiscount();
else
    strategy = new RegularDiscount();

var service = new DiscountService(strategy);

Console.Write("Enter amount: ");
decimal amount = decimal.Parse(Console.ReadLine());

var discount = service.GetDiscount(amount);
var finalPrice = amount - discount;

Console.WriteLine($"Discount applied: {discount}");
Console.WriteLine($"Final price: {finalPrice}");

Output

Output

We understand basic principles of the strategy pattern. We can proceed with our primary target: implementing the strategy pattern in ASP.NET Core.

Implementing the strategy pattern in an ASP.NET Core API

Step 1: Create a .NET Core api

Run the following command in the terminal

dotnet new webapi -n StrategyPatternApi
cd StrategyPatternApi

Step 2: Add concrete strategies

Adding separate implementations of each algorithm

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

For Vip

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

Step 3: Define context class

public class DiscountService
{
    private readonly Func<string, IDiscountStrategy> _strategyFactory;

    public DiscountService(Func<string, IDiscountStrategy> strategyFactory)
    {
        _strategyFactory = strategyFactory;
    }

    // public API: ask for a discount by customer type
    public decimal GetDiscount(string customerType, decimal amount)
    {
        var strategy = _strategyFactory(customerType);
        return strategy.ApplyDiscount(amount);
    }
}

DiscountService plays the context role in the strategy pattern. DiscountService has a property Func<string, IDiscountStrategy> _strategyFactory that holds a factory delegate. The Func delegate returns an appropriate implementation of IDiscountStrategy based on the given type. Func allows the service to request a strategy at runtime by name/key without knowing the DI container internals or concrete types.

Step 4: Add a controller with the endpoint

[ApiController]
[Route("api/[controller]")]
public class PricingController : ControllerBase
{
    private readonly DiscountService _pricingService;

    public PricingController(DiscountService pricingService)
    {
        _pricingService = pricingService;
    }

    [HttpGet]
    public IActionResult Get([FromQuery] string type, [FromQuery] decimal amount)
    {
        var discount = _pricingService.GetDiscount(type, amount);
        var final = amount - discount;
        return Ok(new { type = type ?? "regular", amount, discount, final });
    }
}

Step 5: Configure Program.cs

Add the concrete services in dependency injection (DI) in the Program.cs file

services.AddTransient<RegularDiscount>();
services.AddTransient<VipDiscount>();

They are transient because discount strategies are stateless, so creating a new instance each time is fine. Note that I haven't injected them with IDiscountStrategy any implementing service because ASP.NET Core decides this automatically. Hence, the final code will look like this:

using StrategyPatternApi;

var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register concrete strategy types so they can be resolved by the factory
services.AddTransient<RegularDiscount>();
services.AddTransient<VipDiscount>();

services.AddSingleton<Func<string, IDiscountStrategy>>(sp => key =>
{
    var k = (key ?? "").Trim().ToLowerInvariant();
    return k switch
    {
        "vip" => sp.GetRequiredService<VipDiscount>(),
        // add more cases if you add more strategies
        _ => sp.GetRequiredService<RegularDiscount>()
    };
});

// Register the service that uses the factory
services.AddScoped<DiscountService>();

// Add controllers (or leave for minimal endpoints)
services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}
app.MapControllers();
app.Run();

In DI, the decisive part is:

services.AddSingleton<Func<string, IDiscountStrategy>>(sp => key =>
{
    var k = (key ?? "").Trim().ToLowerInvariant();
    return k switch
    {
        "vip" => sp.GetRequiredService<VipDiscount>(),
        // add more cases if you add more strategies
        _ => sp.GetRequiredService<RegularDiscount>()
    };
});

As explicitly stated, the switch condition resolves the appropriate concrete strategy via DI based on the type value. If any condition does not match, I made a default choice to get RegularService.

Step 6: Run and test

dotnet run

Now running the project

/api/Pricing endpoint
Response

Extension of algorithms in the ASP.NET Core strategy pattern

The Open/Close principle is one of the benefits of the Strategy Pattern. Let's continue with our example of how we can add a new discount within the bounds of the Open/Close principle.

Step 1: Add the Student discount's concrete strategy

public class StudentDiscount : IDiscountStrategy
{
    public decimal ApplyDiscount(decimal amount) => amount * 0.10m;
}

Step 2: Register a new service

services.AddTransient<StudentDiscount>();

Step 3: Update factory switch

services.AddSingleton<Func<string, IDiscountStrategy>>(sp => key =>
{
    var k = (key ?? "").Trim().ToLowerInvariant();
    return k switch
    {
        "vip" => sp.GetRequiredService<VipDiscount>(),
        "student" => sp.GetRequiredService<StudentDiscount>(),   
        _ => sp.GetRequiredService<RegularDiscount>()
    };
});

To add a new strategy implementation, we simply need to add the strategy code and inject it via dynamic DI.

Step 4: Run and test

dotnet run
/api/Pricing endpoint
Response

By default value

Default value
Response

Conclusion

Writing long if-else or cases is tiring. Every time you need to add a condition, you have to dive into the well and add one condition. The same happens while debugging. The strategy pattern provides a modular solution that keeps the code intact while dynamically allowing you to extend conditions. In this blog post, I highlighted the need for a strategy pattern and showed how to implement it in an ASP.NET Core API.

Example 1: https://github.com/elmahio-blog/StrategyPatternDemo

Example 2: https://github.com/elmahio-blog/StrategyPatternApi



Read the whole story
alvinashcraft
19 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Podcast: Platform Engineering for AI: Scaling Agents and MCP at LinkedIn

1 Share

QCon AI New York Chair Wes Reisz talks with LinkedIn’s Karthik Ramgopal and Prince Valluri about enabling AI agents at enterprise scale. They discuss how platform teams orchestrate secure, multi-agentic systems, the role of MCP, the use of foreground and background agents, improving developer experience, and reducing toil.

By Karthik Ramgopal, Prince Valluri
Read the whole story
alvinashcraft
19 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

'Food and Fossil Fuel Production Causing $5 Billion of Environmental Damage an Hour'

1 Share
An anonymous reader quotes a report from the Guardian: The unsustainable production of food and fossil fuels causes $5 billion of environmental damage per hour, according to a major UN report. Ending this harm was a key part of the global transformation of governance, economics and finance required "before collapse becomes inevitable," the experts said. The Global Environment Outlook (GEO) report, which is produced by 200 researchers for the UN Environment Program, said the climate crisis, destruction of nature and pollution could no longer be seen as simply environmental crises. "They are all undermining our economy, food security, water security, human health and they are also [national] security issues, leading to conflict in many parts of the world," said Prof Robert Watson, the co-chair of the assessment. [...] The GEO report is comprehensive -- 1,100 pages this year -- and is usually accompanied by a summary for policymakers, which is agreed by all the world's countries. However, strong objections by countries including Saudi Arabia, Iran, Russia, Turkey and Argentina to references to fossil fuels, plastics, reduced meat in diets and other issues meant no agreement was reached this time. [...] The GEO report emphasized that the costs of action were much less than the costs of inaction in the long term, and estimated the benefits from climate action alone would be worth $20 trillion a year by 2070 and $100 trillion by 2100. "We need visionary countries and private sector [companies] to recognize they will make more profit by addressing these issues rather than ignoring them," Watson said. [...] One of the biggest issues was the $45 trillion a year in environmental damage caused by the burning of coal, oil and gas, and the pollution and destruction of nature caused by industrial agriculture, the report said. The food system carried the largest costs, at $20 trillion, with transport at $13 trillion and fossil-fuel powered electricity at $12 trillion. These costs -- called externalities by economists -- must be priced into energy and food to reflect their real price and shift consumers towards greener choices, Watson said: "So we need social safety nets. We need to make sure that the poorest in society are not harmed by an increase in costs." The report suggests measures such as a universal basic income, taxes on meat and subsidies for healthy, plant-based foods. There were also about $1.5 trillion in environmentally harmful subsidies to fossil fuels, food and mining, the report said. These needed to be removed or repurposed, it added. Watson noted that wind and solar energy was cheaper in many places but held back by vested interests in fossil fuel. The climate crisis may be even worse than thought, he said: "We are likely to be underestimating the magnitude of climate change," with global heating probably at the high end of the projections made by the Intergovernmental Panel on Climate Change. Removing fossil fuel subsidies could cut emissions by a third, the report said.

Read more of this story at Slashdot.

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Announcing the release of SQL Server Management Studio 22.1

1 Share

Welcome to December and another release of SQL Server Management Studio (SSMS)! Today we released SSMS 22.1, which has a few improvements related to GitHub Copilot, as well as several fixes for both GitHub Copilot and SSMS.

Please visit the release notes for a full list of what's changed. For those of you looking to try out GitHub Copilot, we now have a walk through available that you can access from the Copilot badge (from the dropdown select GitHub Copilot Walkthrough).  And for those of you are already using GitHub Copilot, we fixed that pesky issue of Run ValidateGeneratedTSQL showing up repeatedly, among a few other things.

 

 

While you're here, I'd like to take a moment to clear up some misconceptions about GitHub Copilot that I’ve seen floating around in blog posts and on social media. I'm covering these in a FAQ type format because I think it's easier (and faster) for folks to read.  If you have specific questions, feel free to add a comment. If you want to see specific features in either SSMS or GitHub Copilot in SSMS, please create feedback items on the site (if you put a request in the comment, I’ll ask you to create it as a feedback item 😊). As always, please search first!

 

Does GitHub Copilot use my prompts and its responses to train or retrain models?

No. Understand that not all models are hosted by Microsoft, but those that are hosted elsewhere also make the same commitment.  This is clearly stated in the GitHub doc covering model hosting and includes links to the hosting provider’s commitment statement.

 

Are prompts and responses encrypted?

Yes. We encourage all customers to visit the GitHub Copilot Trust Center to get answers to questions about privacy and security. 

 

Can I see what queries GitHub Copilot executes?

Yes.  Use Extended Events and filter on an Application Name of Microsoft SQL Server Management Studio – GitHub Copilot.

 

Can I use other models with GitHub Copilot?
Yes. Bring your own model (BYOM) is now available in SSMS 22.1 for personal subscriptions. You should use models that support tool calling.

 

Do I get the same answer from every model in GitHub Copilot?

No. The models available in GitHub Copilot in SSMS are available because they support tool calling, but they are different based on training, reasoning, temperature, and more. As such, responses to prompts can and will vary across models.  

 

Can I attach an execution plan for GitHub Copilot to analyze?

Yes. Either open a saved plan, or after capturing a plan in SSMS (using Query > Include Actual Execution Plan), right-click on the plan and select Show Execution Plan in New Tab. With the query editor tab as the active tab, in the chat window use # to bring up the open document list.  Select the execution plan from the list, then enter your prompt ("analyze the execution plan and make recommendations for improvements").

 

Can GitHub Copilot run destructive commands against my database?

Currently, no.  As of this release (22.1) GitHub Copilot in SSMS only supports Ask mode, and all queries executed against your database are SELECT statements (read-only), as noted here

We are planning to bring support for Agent mode to SSMS.  With Agent mode, users will be able to allow GitHub Copilot to run queries on their behalf, but it will require them to approve query execution (read/write with approval).

All queries executed by GitHub Copilot execute in the context of the user that is connected, based on their permissions. If a user has permission to run destructive queries (e.g. DELETE, UPDATE, DROP, etc.) and approves the request from GitHub Copilot to execute a destructive query, then the query will run.

We recommend the following: Ensure users have appropriate permissions (principle of least privilege).  This is a standard recommendation for any user that connects to a database.  There are database roles available that allow reading of data and prevent modification of data.

 

What if I don’t want to allow Agent mode?

You can leverage administrative controls for GitHub Copilot to disable Agent mode, once it’s available in SSMS.  Visual Studio, and thus SSMS, use Administrative Templates  for Group Policies.

 

What if I don’t want to allow my users to use GitHub Copilot in SSMS?

Again, Administrative Templates are the answer.  You can disable Copilot SKUs entirely, or for individual accounts.

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

The 5 Biggest AI Stories To Watch In December

1 Share
From: AIDailyBrief
Duration: 22:17
Views: 215

Gemini 3 and NanoBananaPro accelerated Google's momentum and intensified competition with OpenAI. Vibecoding and agentic versus assisted AI debates intersected with politicization and major tech layoffs, reframing jobs and regulatory concerns. Enterprise focus shifted to ROI, context engineering, and vertical agent labs while DeepSeek, Runway, and AWS signaled renewed competition on models and infrastructure.

Brought to you by:
KPMG – Go to ⁠www.kpmg.us/ai⁠ to learn more about how KPMG can help you drive value with our AI solutions.
Vanta - Simplify compliance - ⁠⁠⁠⁠⁠⁠⁠https://vanta.com/nlw

The AI Daily Brief helps you understand the most important news and discussions in AI.
Subscribe to the podcast version of The AI Daily Brief wherever you listen: https://pod.link/1680633614
Get it ad free at
Join our Discord: https://bit.ly/aibreakdown

Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete

#461 This episdoe has a typo

1 Share
Topics covered in this episode:
Watch on YouTube

About the show

Sponsored by us! Support our work through:

Connect with the hosts

Join us on YouTube at pythonbytes.fm/live to be part of the audience. Usually Monday at 10am PT. Older video versions available there too.

Finally, if you want an artisanal, hand-crafted digest of every week of the show notes in email form? Add your name and email to our friends of the show list, we'll never share it.

Michael #1: PEP 798: Unpacking in Comprehensions

  • After careful deliberation, the Python Steering Council is pleased to accept PEP 798 – Unpacking in Comprehensions.
  • Examples

    [*it for it in its]  # list with the concatenation of iterables in 'its'
    {*it for it in its}  # set with the union of iterables in 'its'
    {**d for d in dicts} # dict with the combination of dicts in 'dicts'
    (*it for it in its)  # generator of the concatenation of iterables in 'its'
    
  • Also: The Steering Council is happy to unanimously accept “PEP 810, Explicit lazy imports”

Brian #2: Pandas 3.0.0rc0

  • Pandas 3.0.0 will be released soon, and we’re on Release candidate 0
  • Here’s What’s new in Pands 3.0.0
    • Dedicated string data type by default
      • Inferred by default for string data (instead of object dtype)
      • The str dtype can only hold strings (or missing values), in contrast to object dtype. (setitem with non string fails)
      • The missing value sentinel is always NaN (np.nan) and follows the same missing value semantics as the other default dtypes.
    • Copy-on-Write
      • The result of any indexing operation (subsetting a DataFrame or Series in any way, i.e. including accessing a DataFrame column as a Series) or any method returning a new DataFrame or Series, always behaves as if it were a copy in terms of user API.
      • As a consequence, if you want to modify an object (DataFrame or Series), the only way to do this is to directly modify that object itself.
    • pd.col syntax can now be used in DataFrame.assign() and DataFrame.loc()
      • You can now do this: df.assign(c = pd.col('a') + pd.col('b'))
    • New Deprecation Policy
    • Plus more
  • -

Michael #3: typos

Like codespell, typos checks for known misspellings instead of only allowing words from a dictionary. But typos has some extra features I really appreciate, like finding spelling mistakes inside snake_case or camelCase words. For example, if you have the line:

*connecton_string = "sqlite:///my.db"*

codespell won't find the misspelling, but typos will. It gave me the output:

*error: `connecton` should be `connection`, `connector`  
╭▸ ./main.py:1:1  │1  connecton_string = "sqlite:///my.db"  
╰╴━━━━━━━━━*

But the main advantage for me is that typos has an LSP that supports editor integrations like a VS Code extension. As far as I can tell, codespell doesn't support editor integration. (Note that the popular Code Spell Checker VS Code extension is an unrelated project that uses a traditional dictionary approach.)

For more on the differences between codespell and typos, here's a comparison table I found in the typos repo: https://github.com/crate-ci/typos/blob/master/docs/comparison.md

By the way, though it's not mentioned in the installation instructions, typos is published on PyPI and can be installed with uv tool install typos, for example. That said, I don't bother installing it, I just use the VS Code extension and run it as a pre-commit hook. (By the way, I'm using prek instead of pre-commit now; thanks for the tip on episode #448!) It looks like typos also publishes a GitHub action, though I haven't used it.

Brian #4: A couple testing topics

  • slowlify
    • suggested by Brian Skinn
    • Simulate slow, overloaded, or resource-constrained machines to reproduce CI failures and hunt flaky tests.
    • Requires Linux with cgroups v2
  • Why your mock breaks later
    • Ned Badthelder
    • Ned’s taught us before to “Mock where the object is used, not where it’s defined.”
    • To be more explicit, but probably more confusing to mock-newbies, “don’t mock things that get imported, mock the object in the file it got imported to.”
      • See? That’s probably worse. Anyway, read Ned’s post.
    • If my project myproduct has user.py that uses the system builtin open() and we want to patch it:
      • DONT DO THIS: @patch("builtins.open")
        • This patches open() for the whole system
      • DO THIS: @patch("myproduct.user.open")
        • This patches open() for just the user.py file, which is what we want
    • Apparently this issue is common and is mucking up using coverage.py

Extras

Brian:

Michael:

Joke: tabloid - A minimal programming language inspired by clickbait headlines





Download audio: https://pythonbytes.fm/episodes/download/461/this-episdoe-has-a-typo.mp3
Read the whole story
alvinashcraft
6 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories