DEV Community

loading...
Split Software

Using Feature Flags in .NET Core Web API

Split Blog
In real life, I'm an RSS feed.
Originally published at split.io on ・11 min read

Have you ever sat in a planning session debating whether or not to make a new feature and its endpoints publicly available? It doesn’t seem like such a big deal while you’re working on it during the sprint, but eventually, you’ll have to release it to the public.

The issue comes when you need the endpoints to be available to other team members working on separate user interface applications. You want the endpoints accessible but not by the whole world. One method is to have the feature’s endpoints restricted by authorized groups or roles, so only certain people can interact with them.

It’s a great way to keep the feature out of a long running branch but only accessible to the people who need it. However, in this case, the feature is intended to be available to all. So, now you’re using role based access controls in an artificial way.

What if you could have the feature turned on in production, but only for the team that needs to exercise it before it becomes generally available? Then you wouldn’t have to worry about who has access to it because it would remain off for your end users until it was ready to go live.

Well, good news, you can! All you have to do is create a feature flag to control whether the feature is on or off.

In this short series of blog posts, I’ll show you how to turn a feature on and off, manage access to the feature, and how to automate the workflow so it’s on in development but off in production.

Using a feature flag in the frontend is relatively simple; you just need to hide whatever element you don’t want people to see. What if you want to hide something in the backend? That’s simple, you have conditional statements surrounding the logic, and you use the feature flag to toggle it on and off. Right?

Well, suppose you’re developing an API, and someone else on the development team is consuming it. What then? You can’t just hide your method calls, especially since they’re using a completely different application. You also can’t add the feature to your existing endpoints because that would break whatever application your teammates are building.

What if you could control access to your endpoints? You could create a set of endpoints just for that feature, then toggle the feature whenever you wanted, which would be a massive help when you’re ready to make it publicly available.

In this example, I’ll show you how to build a simple API for a pantry application to help keep track of what you have on hand.

To follow along with this post, you’ll need these tools:

  • .NET Core 3.1
  • Postman
  • A free Split developer account

If you don’t have .NET Core installed, you can find the SDK here.

ProTip: Did you know you can run .NET on Linux and Mac too? The above link has resources for both platforms in addition to Windows.

You can get Postman here.

Sign up for a free developer account with Split if you haven’t already.

Set up your .NET Core app

This post uses Visual Studio 2019. If you’re not on a Windows platform, you can grab the source here and use the editor of your choice to follow along.

Search for and select ASP.Net Core Web API using C#

Name your application Pantry

Be sure to uncheck “Configure for HTTPS.”

Suppose you want to use the .NET CLI version of this setup. Be sure you’re in the folder you want your app created in.


`dotnet new api --no-https --output Pantry`
<small id="shcb-language-1"><span>Code language:</span> <span>Bash</span> <span>(</span><span>bash</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Build an in-memory database

You’ll use an in-memory database in this demo to keep things simple. You could easily swap this out for any data storage solution you want.

First, you need to install Microsoft.EntityFrameworkCore. Right-click Dependencies and click Manage NuGet Packages. Select Browse, select Microsoft.EntityFrameworkCore, then click install.

You can also use the Package Manager Console if you want. You can find it in Tools > NuGet Package Manager > Package Manager Console. Just copy and paste the following command:


`Install-Package Microsoft.EntityFrameworkCore -ProjectName Pantry`
<small id="shcb-language-2"><span>Code language:</span> <span>Bash</span> <span>(</span><span>bash</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

If you’re using the command line, paste the following command:


`dotnet add Pantry package Microsoft.EntityFrameworkCore`
<small id="shcb-language-3"><span>Code language:</span> <span>Bash</span> <span>(</span><span>bash</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Do the same thing again for Microsoft.EntityFrameworkCore.InMemory and Splitio.

The pantry has many different items in it so let’s make a generic model to describe them.

Start by creating a folder called Models, then add a new class inside named Product.cs and another called ImageLocation.cs.


`using System;

namespace Pantry.Models
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public DateTime Expiration { get; set; }
        public int Weight { get; set; }
        public int Count { get; set; }
    }
}`
<small id="shcb-language-4"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>


`namespace Pantry.Models
{
    public class ImageLocation
    {
        public int Id { get; set; }
        public string Location { get; set; }
    }
}`
<small id="shcb-language-5"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

The models have to be registered with a DbContext so later, the application will know how to build the database.

Create a class called AppDbContext.cs in the top level of the project. It’ll look something like this:


`using Microsoft.EntityFrameworkCore;
using Pantry.Models;

namespace Pantry
{
    public class AppDbContext : DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<ImageLocation> ImageLocations { get; set; }
        public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
        {
        }
    }
}`
<small id="shcb-language-6"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Now that the DbContext is in place you’ll have to tell the application where to find it. Open Startup.cs, add using Microsoft.EntityFrameworkCore;, find the section called ConfigureServices, and add the following to the beginning:


`services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase("PantryInMemoryDb"));`
<small id="shcb-language-7"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Here you’re telling the application to build an in-memory database called “PantryInMemoryDb” then supplying it with the AppContext so it understands how to make the database.

Build a .NET API

When you create a new Web API project, Visual Studio adds a default example called WeatherForecast; ignore that and create your own controller.

Right-click the Controllers folder, then click Add > Controller, Select MVC Controller Empty, click add, then name it PantryController.cs.

Note: if you’re using VS Code, just add a file called PantryController.cs to the Controllers folder.

Then replace PantryController.cs with the following:


`using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Pantry.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Pantry.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PantryController : Controller
    {
        private readonly AppDbContext Context;

        public PantryController(AppDbContext context)
        {
                Context = context;
        }

        [HttpGet]
        public async Task<ActionResult<IEnumerable<Product>>> GetProducts() => await Context.Products.ToListAsync();

        [HttpGet("{id}")]
        public async Task<ActionResult<Product>> GetProduct(int id) => await Context.Products.FindAsync(id) ?? (ActionResult<Product>)NotFound();

        [HttpPost]
        public async Task<ActionResult<int>> PostProduct(Product product)
        {
            var entityProduct = await Context.Products.AddAsync(product);
            await Context.SaveChangesAsync();

            return entityProduct.Entity.Id;
        }

        [HttpDelete("{id}")]
        public async Task<ActionResult> DeleteProduct(int id)
        {
            var product = await Context.Products.FindAsync(id);
            if (product == null)
            {
                return NotFound();
            }

            Context.Products.Remove(product);
            await Context.SaveChangesAsync();

            return Ok();
        }
    }

}`
<small id="shcb-language-8"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Now tell the application to use the Pantry controller by default. Open launchSettings.json in the Properties folder and replace both instances of "launchUrl": "weatherforecast" with "launchUrl": "api/pantry".

Why and when to use Split feature flags

There’s been some debate about when is the right time to make the ability to store images generally available. Since there’s a chance it might be a while, you’ll keep the ImageLocation information in a separate class and table.

Simply speaking, you’d want to use a feature flag wherever there’s something you want in an on or off state and would like control of that state outside the application.

By wrapping the feature’s endpoints in a feature flag you don’t have to worry about leadership’s final decision. You continue to work on the project itself without losing time because the feature is loosely coupled.

Add Feature Flags to Your .NET Core App

Now you’ll need to create the feature flag in Split. You’ll want to give it a meaningful name, so it’s easier to keep track of, such as Pantry_API_ImageLocation.

Click Add Rules, Save Changes, and Confirm. That’s all there is to it!

A default rule will always serve off unless you make changes to it.

You need a copy of the API key; click the icon in the Top left corner -> Admin Settings -> API Keys. Then click copy next to your Server-side Staging SDK key.

Open appsettings.json and replace it with the following, making sure to use your API key:


`{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Split": {
    "ApiKey": "Your Server-side Staging SDK API KEY"
  }
}`
<small id="shcb-language-9"><span>Code language:</span> <span>JSON / JSON with Comments</span> <span>(</span><span>json</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Add the following to Startup.cs


`using Splitio.Services.Client.Classes;
using Splitio.Services.Client.Interfaces;`
<small id="shcb-language-10"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Then add the following to ConfigureServices in Startup.cs:


`services.AddSingleton<ISplitFactory, SplitFactory>(s => new SplitFactory(Configuration["Split:ApiKey"]));`
<small id="shcb-language-11"><span>Code language:</span> <span>JavaScript</span> <span>(</span><span>javascript</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Now you’ll be able to inject the SplitFactory into any class you want via Dependency Injection, and it’ll already be configured.

Head over to PantryController and add using Splitio.Services.Client.Interfaces; in the top section.

Replace the constructor with the following ( Note: we’re also adding the ShowImageLocation bool and an ISplitClient named Client above the constructor):


`public ISplitClient Client { get; }

private bool ShowImageLocation
{
    get { return GetStateOfImageLocation(); }
}

public PantryController(AppDbContext context, ISplitFactory split)
{
    Context = context;

    var client = split.Client();
    client.BlockUntilReady(10000);

    Client = client;
}`
<small id="shcb-language-12"><span>Code language:</span> <span>Arduino</span> <span>(</span><span>arduino</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Then you’ll need to add these three endpoints and a method to the controller:


`[HttpGet("/api/[controller]/image")]
public async Task<ActionResult<ImageLocation>> GetProductImages(int id)
{
    if (ShowImageLocation is false)
    {
            return NotFound();
    }

    var output = await Context.ImageLocations.FindAsync(id);

    return output;
}

[HttpGet("/api/[controller]/image/{id}")]
public async Task<ActionResult<ImageLocation>> GetProductImage(int id)
{
    if (ShowImageLocation is false)
    {
            return NotFound();
    }

    return await Context.ImageLocations.FindAsync(id);
}

[HttpPost("/api/[controller]/image")]
public async Task<ActionResult<int>> PostProductImage(ImageLocation imageLocation)
{
    if (ShowImageLocation is false)
    {
            return NotFound();
    }

    var entityProduct = await Context.ImageLocations.AddAsync(imageLocation);
    await Context.SaveChangesAsync();

    return entityProduct.Entity.Id;
}

private bool GetStateOfImageLocation()
{
    var treatment = Client.GetTreatment("Default_Value", "Pantry_API_ImageLocation");

    if (treatment == "on")
    {
            return true;
    }

    if(treatment == "off")
    {
            return false;
    }

    throw new System.Exception("Something went wrong!");
}`
<small id="shcb-language-13"><span>Code language:</span> <span>C#</span> <span>(</span><span>cs</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Testing

Time test things out!

Start the project and make a note of which port your application is using. You may need to replace the port number for the following API calls.

Note: If you’re using VS Code and the command line use dotnet run

Open up Postman and create a new POST tab.

Make sure to change the Content-Type in Headers to application/json for the two POST calls you’re about to make. You may need to uncheck Content-Type and create a new one at the bottom of the list.

For the first POST call http://localhost:37095/api/pantry, but in Body select raw and add the following:


`{
  "id": 1,
  "name": "Cereal",
  "expiration": "2022-01-01T00:00:00",
  "weight": 1,
  "count": 1
}`
<small id="shcb-language-14"><span>Code language:</span> <span>JSON / JSON with Comments</span> <span>(</span><span>json</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Click SEND. You should get an HTTP status of 200 and a response of 1.

For the first GET, call http://localhost:37095/api/pantry.

Now create another POST using http://localhost:37095/api/pantry/image and the following just like before:


`{
  "id": 1,
  "location": "/somewhere"
}`
<small id="shcb-language-15"><span>Code language:</span> <span>JSON / JSON with Comments</span> <span>(</span><span>json</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Click SEND. You should get an HTTP status of 404 and an error response. Why? Because the treatment is set to off by default meaning the feature is also currently turned off.


`{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
    "title": "Not Found",
    "status": 404,
    "traceId": "|6705d58f-4b896196d97bab13."
}`
<small id="shcb-language-16"><span>Code language:</span> <span>JSON / JSON with Comments</span> <span>(</span><span>json</span><span>)</span></small>
Enter fullscreen mode Exit fullscreen mode

Lastly, create another GET to fetch specific image locations based on product ID using http://localhost:37095/api/pantry/image/1.

If the feature is turned off you’ll receive the same 404 error as before.

Currently, if you run them in the order shown above: The product is added to the database; all available products are shown; an image location can’t be added or retrieved as the treatment is turned off and the user will only receive a 404 response.

Let’s turn the treatment on!

Go to your Splits and click Pantry_API_ImageLocation or whatever you named your split. Scroll down to Set the default rule, click the dropdown, and select on. Click Save Changes in the top right corner, then when the Change summary page opens click Confirm at the bottom.

Now your treatment is set to serve on as default, which means the feature is available to everyone, and the endpoints are now active.

If you repeat the POST and GET steps for the pantry image above, you’ll be able to add and retrieve an image location.

Now you have your feature up and running, and you can turn it on and off whenever you want! Even better, you can show it to leadership and let them turn it on and off, so you don’t have to every time they want to take a look. One of the best things about feature flags is that you can release features to production and not worry about removing them right away because you can just turn them off. Using feature flags is a great way to enhance your CI/CD (Continuous Integration Continuous Deployment) workflow.

Interested in digging deeper on A/B testing and experimentation? Take a look at some of our other resources:

Learn more about A/B testing and A/B/n testing.

Dig deeper into multivariate testing.

Check out the state of feature delivery in 2020.

As always, if you’re looking for more great content like this, we’d love to have you follow us on Twitter @splitsoftware and subscribe to our YouTube channel.

Discussion (0)