ICYMI C# 8 New Features: Upgrade Interfaces Without Breaking Existing Code

This is part 7 in a series of articles.

Prior to C# 8, if you add members to an interface, exiting code breaks if you do not implement the new members in any class that implements the interface.

As an example, consider the following interface definition:

public interface ICustomer
{
    int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    // etc.
}

We could implement this interface:

class Customer : ICustomer
{
    public int Id { get; }
    public string Name { get; set; }
    public int MonthsAsACustomer { get; set; }
    public decimal TotalValueOfAllOrders { get; set; }
    public Customer(int id) => Id = id;
}

This will compile without error. We could have multiple classes implementing this interface in the same project or across multiple projects.

What happens now if we wanted to add a new interface method that represents the ability to calculate a discount based on the customer’s previous order value and how long they have been a customer?

We could make the following change:

public interface ICustomer
{
    int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    decimal CalculateLoyaltyDiscount();
    // etc.
}

Now if we try and build, we’ll get the error: 'Customer' does not implement interface member 'ICustomer.CalculateLoyaltyDiscount()'

If we have multiple implementations of ICustomer, they will all break.

Default Interface Methods in C# 8

From C# 8 we can fix this problem by providing a default implementation of an interface method.

If we only had one implementation of ICustomer then we could go and add the implementation of CalculateLoyaltyDiscount. But if we had multiple implementations or we didn’t want to force a breaking change on existing implementers then we can actually add the implementation of the the method in the interface itself.

public interface ICustomer
{
    int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    public decimal CalculateLoyaltyDiscount()
    {
        if (MonthsAsACustomer > 24 || TotalValueOfAllOrders > 10_000)
        {
            return 0.05M;
        }

        return 0;
    }
    // etc.
}

If we build now there will be no error, even though we haven’t implemented the method in Customer. Customer ‘inherits’ the default implementation.

Notice in the preceding code that from C# 8, access modifiers are now allowed on interface members.

We could make use of this new method:

var c = new Customer(42)
{
    MonthsAsACustomer = 100
};

decimal discount = ((ICustomer)c).CalculateLoyaltyDiscount();

Notice in the preceding code that we have to cast Customer to ICustomer to be able to call CalculateLoyaltyDiscount – that’s because the method implementation is in the interface, not the Customer class.

The Customer class can still implement it’s own version of the CalculateLoyaltyDiscount method if the default implementation is not acceptable:

class Customer : ICustomer
{
    public int Id { get; }
    public string Name { get; set; }
    public int MonthsAsACustomer { get; set; }
    public decimal TotalValueOfAllOrders { get; set; }
    public Customer(int id) => Id = id;
    public decimal CalculateLoyaltyDiscount()
    {
        if (TotalValueOfAllOrders > 1_000_000)
        {
            return 0.1M;
        }

        return 0;
    }
}

We could refactor the upgraded interface to allow implementers to still be able to access the default implementation:

public interface ICustomer
{
    public int Id { get; }
    string Name { get; set; }
    int MonthsAsACustomer { get; set; } 
    decimal TotalValueOfAllOrders { get; set; }
    public decimal CalculateLoyaltyDiscount() => CalculateDefaultLoyaltyDiscount(this);
    
    protected static decimal CalculateDefaultLoyaltyDiscount(ICustomer customer)
    {
        if (customer.MonthsAsACustomer > 24 || customer.TotalValueOfAllOrders > 10_000)
        {
            return 0.1M;
        }

        return 0;
    }
    // etc.
}

And then in Customer:

class Customer : ICustomer
{
    public int Id { get; }
    public string Name { get; set; }
    public int MonthsAsACustomer { get; set; }
    public decimal TotalValueOfAllOrders { get; set; }
    public Customer(int id) => Id = id;
    public decimal CalculateLoyaltyDiscount()
    {
        if (TotalValueOfAllOrders > 1_000_000)
        {
            return 0.2M;
        }

        return ICustomer.CalculateDefaultLoyaltyDiscount(this);
    }
}

From C# 8, interfaces can also now have static fields, methods, and properties, e.g.:

private static int MonthsAsACustomerThreshold = 24;
public static int MonthsThreshold
{
    get => MonthsAsACustomerThreshold;
    set => MonthsAsACustomerThreshold = value;
}

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

ICYMI C# 8 New Features: Prevent Bugs with Static Local Functions

This is part 6 in a series of articles.

C# 7 introduced local functions that allow you to create functions “local” to another function or method. These local functions are implicitly private to the enclosing function and cannot be accessed by other methods/properties/etc.  in the class. You can read more about local functions in this previous article.

C# 8 introduced the ability to make local functions static. This can help prevent bugs when using local functions.

Take the following code:

[Fact]
public void NestedCs7()
{
    int a = 10;

    int result1 = Add(10, 5);

    Assert.Equal(15, result1); // True, pass

    int result2 = Add(15, 2);

    Assert.Equal(17, result2); // False, fail

    int Add(int num1, int num2)
    {
        return a + num2;
    }
}

In the preceding code, the Add function is a local function. However there is a bug in this Add method, we are accidently referencing the variable a from the enclosing method NestedCs7. This means that instead of adding num1 and num2 and returning the result, we are adding a and num2. This will cause errors in the application.

Sometimes it may be convenient to reference (“capture”) the enclosing methods variables but to be more explicit and prevent unintended side-effects and bugs, from C# 8 you can make the method static.

In the following code we make the Add method static:

[Fact]
public void NestedCs8()
{
    int a = 10;

    int result1 = Add(10, 5);

    Assert.Equal(15, result1); 

    int result2 = Add(15, 2);

    Assert.Equal(17, result2); 

    static int Add(int num1, int num2)
    {
        return a + num2;
    }
}

If we try and build the preceding code, we’ll get a compilation error: Error CS8421    A static local function cannot contain a reference to 'a'.

This error tells us we’ve accidentally captured a and we can go and fix the bug:

[Fact]
public void NestedCs8()
{
    int a = 10;

    int result1 = Add(10, 5);

    Assert.Equal(15, result1); // True, pass

    int result2 = Add(15, 2);

    Assert.Equal(17, result2); // True, pass

    static int Add(int num1, int num2)
    {
        return num1 + num2;
    }
}

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

ICYMI C# 8 New Features: Simplify Array Access and Range Code

This is part 5 in a series of articles.

One of the new features that C# 8 introduced was the ability to work more simply with arrays and items within arrays.

Take the following code that uses various ways to manipulate an array of strings:

string[] letters = { "A", "B", "C", "D", "E", "F" };

string firstLetter = letters[0];
string secondLetter = letters[1];
string lastLetter = letters[letters.Length - 1];
string penultimateLetter = letters[letters.Length - 2];

string[] middleTwoLetters = letters.Skip(2)
                                   .Take(2)
                                   .ToArray();

string[] everythingExceptFirstAndLast = letters.Skip(1)
                                               .Take(letters.Length - 2)
                                               .ToArray();

It’s pretty easy to get the first element in an array [0] and the last item [letters.Length - 1] but when we get to getting ranges (like the middle 2 letters) or 2 from the end things get a little more complicated.

C# 8 introduced a new shorter syntax for dealing with indices and ranges. The preceding code could be re-rewritten in C# 8 as follows:

string[] letters = { "A", "B", "C", "D", "E", "F" };

string firstLetter = letters[0];
string secondLetter = letters[1];
string lastLetter = letters[^1];
string penultimateLetter = letters[^2];

string[] middleTwoLetters = letters[2..4];

string[] everythingExceptFirstAndLast = letters[1..^1];

There’s a few different things going on in the C#8 version.

First of all, C# 8 gives us the new index from end operator ^. This essentially gives us an element by starting at the end and counting back. One thing to note here is that the index ^0 is not the last element, rather the length of the array (remember in C# arrays are zero-based). The last element is actually at ^1. If you try and access an element using [^0] you’ll get an IndexOutOfRangeException just as you would if you wrote [letters.length].

In additional to the index from end operator, C# 8 also introduced the range operator .. – this allows you to specify a range of elements. For example in the code, the range [2..4] gives us the middle 2 letters C & D – or more specifically, it gives us the range of elements starting at 2 and ending at 4. Why 4 though? When using the range operator, the first index is inclusive but the last index is exclusive. So [2..4] really means “2 to 3 inclusive”.

You can also use open ended ranges where you omit the start or end range, for example string[] lastThreeLetters = letters[^3..];

To make the new indexing feature work, there is a new struct called Index. And to make ranges work there is a new struct called Range.

For example, you can create and pass Range instances around:

Range middleTwo = new Range(2, 4);
string[] middleTwoLetters = letters[middleTwo];

You can also use the index from end operator in a range, for example to get the last 3 letters:

string[] lastThreeLetters = letters[^3..^0]; // D E F

Notice in this code we are using ^0 (and get no exception) to specify the end because the end of a range is exclusive.

As well as arrays you can also use these techniques with other types which you can read more about  in the Microsoft documentation e.g. the MS docs state: “For example, the following .NET types support both indices and ranges: String, Span<T>, and ReadOnlySpan<T>. The List<T> supports indices but doesn't support ranges.” And also from the docs: “Any type that provides an indexer with an Index or Range parameter explicitly supports indices or ranges respectively. An indexer that takes a single Range parameter may return a different sequence type, such as System.Span<T>.”…and… “A type is countable if it has a property named Length or Count with an accessible getter and a return type of int. A countable type that doesn't explicitly support indices or ranges may provide an implicit support for them. For more information, see the Implicit Index support and Implicit Range support sections of the feature proposal note. Ranges using implicit range support return the same sequence type as the source sequence.”

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

ICYMI C# 8 New Features: Nested Switch Expressions

This is part 4 in a series of articles.

In this series we’ve already covered switch expressions and one little-known feature is the ability to nest switch expressions.

Suppose we have the following 3 classes:

class SavingsAccount
{
    public decimal Balance { get; set; }
}

class HomeLoanAccount
{
    public int MonthsRemaining { get; set; }
}

class ChequingAccount
{
    public decimal NumberOfTimesOverdrawn { get; set; }
    public int NumberOfAccountHolders { get; set; }
}

(Notice that none of the preceding classes are linked by inheritance.)

Suppose we wanted to run a gift card promotional mailing depending on what accounts customers had. We can use pattern matching on the type of object in a switch expression:

decimal CalculatePromotionalGiftCardValue(object account) => account switch
{
    SavingsAccount sa when (sa.Balance > 10_000) => 100,
    SavingsAccount _ => 0, // all other savings accounts

    HomeLoanAccount hla when (hla.MonthsRemaining < 12) => 20,
    HomeLoanAccount _ => 0, // all other home loan accounts

    ChequingAccount ca when (ca.NumberOfTimesOverdrawn == 0 && ca.NumberOfAccountHolders == 1) => 20,
    ChequingAccount ca when (ca.NumberOfTimesOverdrawn == 0 && ca.NumberOfAccountHolders == 2) => 40,
    ChequingAccount ca when (ca.NumberOfTimesOverdrawn == 0 && ca.NumberOfAccountHolders == 3) => 50,
    ChequingAccount _ => 0, // all other chequing accounts

    { } => throw new ArgumentException("Unknown account type", paramName: nameof(account)), // all other non-null object types
    null => throw new ArgumentNullException(nameof(account))
};

Notice in the preceding expression-bodied method containing a switch expression (that’s a mouthful!), that there is a bit of repetition in the ChequingAccount section with the ca.NumberOfTimesOverdrawn == 0 code being repeated. We can replace this section with a nested switch expression:

decimal CalculatePromotionalGiftCardValueNested(object account) => account switch
{
    SavingsAccount sa when (sa.Balance > 10_000) => 100,
    SavingsAccount _ => 0,

    HomeLoanAccount hla when (hla.MonthsRemaining < 12) => 20,
    HomeLoanAccount _ => 0,

    ChequingAccount ca when (ca.NumberOfTimesOverdrawn == 0) => ca.NumberOfAccountHolders switch
    {
        1 => 20,
        2 => 40,
        3 => 50,
        _ => 0
    },
    ChequingAccount _ => 0,

    { } => throw new ArgumentException("Unknown account type", paramName: nameof(account)),
    null => throw new ArgumentNullException(nameof(account))
};

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

ICYMI C# 8 New Features: Simplify If Statements with Property Pattern Matching

This is part 3 in a series of articles.

In the first part of this series we looked at switch expressions.

When making use of switch expressions, C# 8 also introduced the concept of property pattern matching. This enables you to match on one or more items of an object and helps to simplify multiple if..else if statements into a more concise form.

For example, suppose we had a CustomerOrder:

class CustomerOrder
{
    public string State { get; set; }
    public bool IsVipMember { get; set; }
    // etc.
}

And we created an instance of this:

var order1 = new CustomerOrder
{
    State = "WA",
    IsVipMember = false
};

Now say we wanted to calculate a delivery cost based on what State the order is being delivered to. If the customer is a VIP member then the delivery fee may be waived depending on what the State is. We could write this using if…else if:

if (order1.State == "WA" && order1.IsVipMember)
{
    deliveryCost = 0M;
}
else if (order1.State == "WA" && !order1.IsVipMember)
{
    deliveryCost = 2.3M;
}
else if (order1.State == "NT" && !order1.IsVipMember)
{
    deliveryCost = 4.1M;
}
else
{
    deliveryCost = 5M;
}

The preceding code will get bigger and harder to read the more states we add.

An alternative could be to use a switch statement to try and simplify this:

decimal deliveryCost;

switch (order1.State, order1.IsVipMember)
{
    case ("WA", true):
        deliveryCost = 0M;
            break;
    case ("WA", false):
        deliveryCost = 2.3M;
        break;
    case ("NT", false):
        deliveryCost = 4.1M;
        break;
    default:
        deliveryCost = 5M;
        break;
}

In the preceding code there is still a bit of “ceremony” with all the case blocks.

We could instead use a switch expression that makes use of property pattern matching:

deliveryCost = order1 switch
{
    { State: "WA", IsVipMember: true } => 0M,
    { State: "WA", IsVipMember: false } => 2.3M,
    { State: "NT", IsVipMember: false } => 4.1M,
    _ => 5M
};

Notice how the preceding code is a lot more succinct, and it’s easy to see all the cases and combinations.

What if for some States, the VIP status was not relevant for calculating delivery cost?

Suppose that the state “QE” always had a high delivery cost that never got reduced even for VIPs:

deliveryCost = order1 switch
{
    { State: "WA", IsVipMember: true } => 0M,
    { State: "WA", IsVipMember: false } => 2.3M,
    { State: "NT", IsVipMember: false } => 4.1M,
    { State: "QE"} => 99.99M,
    _ => 5M
};

In the preceding code, if the State is “QE” then the delivery cost will be 99.99. Also notice the use of the discard _ that says “for all other combinations not listed above set the delivery cost to 5”.

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

ICYMI C# 8 New Features: Write Less Code with Using Declarations

This is part 2 in a series of articles.

One nice little enhancement introduced in C# 8 helps to simplify code that uses disposable objects.

For example consider the following:

class MyDisposableClass : IDisposable
{
    public void Dispose()
    {            
        Console.WriteLine("Disposing");
    }

    public void Run() 
    {
        Console.WriteLine("Running");
    }
}

Prior to C# 8, if you wanted to use a disposable object (something that implements IDisposable) then you would usually use a using block as follows:

private static void Process()
{
    using (var x = new MyDisposableClass())
    {
        x.Run();
    }
}

At the end of the using block, the Dispose() method is called automatically.

With C# 8, instead of the using block, you can instead use a using declaration:

private static void Process()
{
    using var x = new MyDisposableClass();

    x.Run();
}

Notice in the preceding code, with a using declaration there is no need for the additional {}. When using a using declaration, the Dispose() method is called automatically at the end of the Process() method. Just as with the using block approach, if an exception occurs within the Process() method then Dispose() will still be called.

Using declarations help to keep code less cluttered because you have fewer braces {} and one level less of indenting.

If you have multiple usings, for example:

private static void Process()
{
    using (var x = new MyDisposableClass())
    using (var y = new MyDisposableClass())
    using (var z = new MyDisposableClass())
    {
        x.Run();
        y.Run();
        z.Run();
    }
}

You can rewrite this in C# 8 as follows:

private static void Process()
{
    using var x = new MyDisposableClass();
    using var y = new MyDisposableClass();
    using var z = new MyDisposableClass();

    x.Run();
    y.Run();
    z.Run();
}

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

ICYMI C# 8 New Features: Switch Expressions

In the first part of this series on what was introduced in C# 8, we’re going to take a look at switch expressions.

Switch expressions allow you to write fewer lines of code when making use of switch statements. This is useful if you have a switch statement that sets/returns a value based on the input.

Prior to C# 8, the following code could be used to convert an int to its string equivalent:

string word;
switch (number)
{
    case 1:
        word = "one";
        break;
    case 2:
        word = "two";
        break;
    case 3:
        word = "three";
        break;
    default:
        throw new ArgumentOutOfRangeException(nameof(number));                    
}

In the preceding code if the input int number is not 1,2, or 3 an exception is thrown, otherwise the variable word is set to the string representation “one”, “two”, or “three”.

From C# 8 we could instead use a switch expression. A switch expression returns a value, this means we can return the string into the word variable as follows:

string word = number switch
{
    1 => "one",
    2 => "two",
    3 => "three",
    _ => throw new ArgumentOutOfRangeException(nameof(number))
};

Compare this version with first version and you can see we have a lot less code, we don’t have all the repetitive case and breaks.

Also notice that the default block has been replaced with an expression that throws the exception. Also notice that the code makes use of a discard _ as we don’t care about the value. (Discards are “placeholder variables that are intentionally unused in application code” (MS)).

If you want to fill in the gaps in your C# knowledge be sure to check out my C# Tips and Traps training course from Pluralsight – get started with a free trial.

SHARE:

New Pluralsight Course: Feature Flag Fundamentals with Microsoft Feature Management

My latest Pluralsight video training course was just published just in time for some holiday season learning! :)

From the description: “Releasing software to production can be hard, risky, and time-consuming, especially if there is a problem and you need to roll back the deployment. In this course, Feature Flags Fundamentals and Microsoft Feature Management, you’ll gain the ability to effectively and efficiently manage the development and deployment of features. First, you’ll explore how to configure and use feature flags in code. Next, you’ll discover how to control features and HTML rendering using Microsoft feature flags in an ASP.NET Core app. Finally, you’ll learn how to customize Microsoft Feature Management and even manage features from Azure. When you’re finished with this course, you’ll have the skills and knowledge of Microsoft Feature Management needed to effectively deploy and manage features in production.”

You can read more about the course over on the official course homepage. Or start watching with a free trial today.

SHARE:

Simplify and Reduce Test Code with AutoFixture

AutoFixture is a library that you can use alongside your testing framework to reduce the amount of boilerplate test code you need to write and thus improve your productivity.

At its core, AutoFixture helps you setup your tests by generating anonymous test data for you. This anonymous test data can be used to fulfil non-important boilerplate test data; this is test data that is required for the test to execute but whose value is unimportant.

Take the follow abbreviated test:

[Fact]
public void ManualCreation()
{
    // arrange

    Customer customer = new Customer()
    {
        CustomerName = "Amrit"
    };

    Order order = new Order(customer)
    {
        Id = 42,
        OrderDate = DateTime.Now,
        Items =
                      {
                          new OrderItem
                          {
                              ProductName = "Rubber ducks",
                              Quantity = 2
                          }
                      }
    };


    // act and assert phases...
}

Suppose the previous test code was only creating an Order (with associated Customer) just to fulfil some dependency and the actual Customer and OrderItems did not matter. In this case we could use AutoFixture to generate them for us.

AutoFixture can be installed via NuGet and once installed allows a Fixture instance to be instantiated. This Fixture object can then be used to generate anonymous test data and greatly simplify the arrange phase, as the following test shows:

[Fact]
public void AutoCreation()
{
    // arrange

    var fixture = new Fixture();

    Order order = fixture.Create<Order>();

    // act and assert phases...
}

If we were to debug this test we’d see the following values:

AutoFixture anonymous test data generation for complex object graphs

Notice in the preceding screenshot that AutoFixture has created the object graph for us, including the Customer and 3 OrderItem instances.

There’s a lot more to AutoFixture than just this simple example, for example you can combine with the AutoFixture.Xunit2 package to further reduce code:

[Theory, AutoData]
public void SubtractWhenZeroTest(int aPositiveNumber, Calculator sut)
{
    // Act
    sut.Subtract(aPositiveNumber);

    // Assert
    Assert.True(sut.Value < 0);
}

If you want to learn more about how AutoFixture can improve your productivity check out the docs or start watching for free  my AutoFixture Pluralsight course with a free trial:

SHARE:

Configuring Custom Feature Filters with Azure App Configuration (Microsoft.FeatureManagement)

This is part ten in a series of articles.

EDIT: my Feature Management Pluralsight training course is now available.

In part 4 we looked at creating custom feature filters and in part 5 we looked at configuring features with Azure App Configuration. We can combine these techniques to create a custom feature filter that we can configure remotely in Azure.

For example suppose we have the following class representing a custom feature filter that  enables a feature if a query string field is present:

[FilterAlias("BetaQueryString")]
public class BetaQueryStringFeatureFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public BetaQueryStringFeatureFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
    }

    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        BetaQueryStringFeatureFilterSettings settings = context.Parameters.Get<BetaQueryStringFeatureFilterSettings>();           

        bool isEnabled = _httpContextAccessor.HttpContext.Request.Query.ContainsKey(settings.QueryStringFieldName);

        return Task.FromResult(isEnabled);
    }
}

The configurable parameters for this feature filter are represented by the following class:

public class BetaQueryStringFeatureFilterSettings
{
    public string QueryStringFieldName { get; set; }
}

In the appsettings.json we could configure a feature called “printing” to use this custom feature filter:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "BetaQueryString",
        "Parameters": {
          "QueryStringFieldName": "beta"
        }
      }
    ]
  }
}

Notice in the preceding config that the query string field that needs to be present in the request URL is the string “beta”. This means if the URL was something like “”http://localhost:5607/?beta” the printing feature would be enabled.

If we wanted to enable the filter when the URL query string contained a field called “earlyaccess” we could change the appsettings.config to:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "BetaQueryString",
        "Parameters": {
          "QueryStringFieldName": "earlyaccess"
        }
      }
    ]
  }
}

Configuring Custom Feature Filters with Azure App Config

Instead of having the query string field defined in the appsettings.config we could instead hold this value in Azure. Check out part 5 for more info on setting this up.

After configuring the web app to use Azure App Configuration for feature flag settings, we can modify the appsettings.json to remove the QueryStringFieldName parameter because this will now be coming from Azure:

"FeatureManagement": {
  "Printing": {
    "EnabledFor": [
      {
        "Name": "BetaQueryString"          
      }
    ]
  }
}

Setting up a custom/conditional feature filter in Azure App Configuration is a little unintuitive at the moment. After clicking Add and specifying the feature name (in this case “Printing”) you then need to click the On toggle and then click the  Add filter button.

Adding a new feature flag in Azure App Configuration

Next, enter a key that matches the name of the custom feature filter, in this case “BetaQueryString” and then click the ellipses and choose Edit parameters:

Configuring a custom feature filter in Azure App Configuration

 

The name of the parameter should match what settings value the custom feature filter is looking for, in this case “QueryStringFieldName” and in the Value box enter the configured value you want, for example “beta” :

image

Click Apply and then Apply again and you should now see the Printing feature marked as conditional:

image

Now you can run the web app and remotely configure what query string parameter will enable the printing feature.

SHARE: