Feature Flags Patterns and Applications

The purpose of a feature flag, or known as a feature toggle, is to add code to an existing system without modifying its current behavior. If a feature flag is added to a system and the system breaks while the feature flag is turned off, it was not done properly. The end goal of a feature flag is to provide a low risk way to introduce new code to a production environment to let select users test the new feature, and for the feature to be easily deployed. The term mainstream feature will apply to a feature flag that is now turned on for all users. A feature that has the possibility of being turned off again is not considered a mainstream feature at that point. A more in depth look can be found on Martin Fowler’s site Martin Fowler’s Feature Toggle

Style sheet change Pattern (MVC)

In the MVC world, the controller is responsible for gathering and presenting a model to the view. This should include the results of any feature flags. In ASP.Net MVC, you can modify the model, or you can use a ViewBag, it is up to personal preference as both are acceptable. For CSS that is embedded in static files, you may need to create a new static file and use the feature flag to determine if you want to add the stylessheet as a page resource. Do not attempt to bundle the new style sheet, as if you have more than one feature in the system, you will have too many combinations of bundled and minified code to make this happen. Future web technologies, like WebRTC will make additional files no longer be a extra network request.

Controller Code

[code language=”csharp”]
public class HomeController : Controller
{
public ActionResult StyleSheet()
{
var feature = new US1234StyleSheetChange(HttpContext.User.Identity);

ViewBag.US1234StyleSheetChange = feature.Enabled();

return View();
}
}
[/code]

View File
[code language=”csharp”]
@section stylesheets{
<style>
@if (ViewBag.US1234StyleSheetChange)
{
<text>
.ok-button{
background-color: green;
weight:bold;
}
</text>
}
else
{
<text>
background-color: yellow;
color: red;
</text>
}
</style>
}

@if (ViewBag.US1234StyleSheetChange)
{
@*we couldn’t make all the changes with style sheets alone*@
<button id="go" class="ok-button confirm">Ok</button>
}
else
{
<button id="go" class="ok-button">Ok</button>
}
[/code]

Javascript Pattern

In the previous Stylesheet Pattern, we saw that it was easy to add to the view any changes to the stylesheet. In the below example, you can see we wrap the entire jQuery event around the same feature flag variable. In the case of minified or bundled javascript, it would be best to leave it out of the bundled files until it makes it as a mainstream feature.

[code language=”javascript”]
<script>
$(function () {
@if (ViewBag.US1234StyleSheetChange)
{
<text>
$(".confirm").click(function () {
var userAction = confirm("Are you sure you want to continue");
});
</text>
}

});
</script>
[/code]

Code only feature change

In the case of a code only change wrapped in a feature flag, a simple if statement suffices in most cases.
[code language=”csharp”]
public class HomeController : Controller
{
public ActionResult Index()
{
var feature = new US1234TitleChange(HttpContext.User.Identity);

if (feature.Enabled())
{
ViewBag.Title = "Home Page Changed for US1234";
return View();
}

ViewBag.Title = "Home Page";
return View();
}
}
[/code]
In extreme cases where code clarity may be results in a more unmanageable feature, you can break the feature into a private function and split the code path at the beginning of the function as shown below.
[code language=”csharp”]
public class HomeController : Controller
{
public ActionResult Index()
{
var feature = new US1234TitleChange(HttpContext.User.Identity);

if (feature.Enabled())
{
return US1234TitleChange();
}

//Additional changes made here should be made in the US1234TitleChange
//until it is a mainstream feature
ViewBag.Title = "Home Page";
return View();
}
/// <summary>
/// Please replace Index() with this function once the feature is mainstream
/// </summary>
/// <returns></returns>
private ActionResult US1234TitleChange()
{
ViewBag.Title = "Home Page Changed for US1234";
return View();
}
}
[/code]

SQL – Stored procedure pattern (Selects)

Some frameworks do not do well with additional columns returning. If you have one of those, you can apply the below pattern to make sure the new column does not come back from the select statement.

[code language=”sql”]
CREATE PROCEDURE [dbo].[usp_GetSalesVolume]
@param1 int = 0,
@param2 int
AS

if (Exists(Select 1 from FeatureFlags where id=4 and active=1))
begin
select ‘new data elements here’, @param1, @param2
return
end

SELECT @param1, @param2

RETURN 0
[/code]

Sql stored procedure (Updates)

In the case where logic changes for an update and the change has to occur in the stored procedure. You can add a new parameter that MUST have a default value set. Then break the code path into two and return or use an else statement. In this case a new table has been added, one the feature is a mainstream feature, the old table should be deleted along with the previous code.

[code language=”sql”]
CREATE PROCEDURE [dbo].[usp_UpdateSalesVolume]
@param1 int = 0,
@param2 int,
@param3 int =0 /*added per feature Goal Column */
AS
if (Exists(Select 1 from FeatureFlags where id=4 and active=1))
begin
update Goalsv2 Set [email protected], [email protected] where [email protected]
return
end
update Goals Set [email protected] where [email protected]
RETURN 0
[/code]

WCF Custom tool warning “Cannot import wsdl:portType”

Yet another odd problem to deal with today. “Custom tool warning: cannot import wsdl:portType Could not load file or assembly”Capture

Thanks to Nuget, it appears that it tries to load all assemblies when “Reuse types in all referenced assemblies” is marked.

Capture

The WSDL tool doesn’t use the below setting so it fails.

[code langauge=”xml”]
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="RabbitMQ.Client" publicKeyToken="89e7d7c5feba84ce" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.4.3.0" newVersion="3.4.3.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
[/code]

Un-check, deselect, “Reused types in referenced assemblies” and your problems will go away, just don’t check the offending assembly again 🙂 If you have to use that assembly, for your project file to the correct version.

Unit Testing Web API Controllers with Headers

I couldn’t find a good example of how to unit test a controller with

[code langauge=”csharp”] var headerValue = Request.Headers.GetValues("HEADER");[/code]

in the controller. Not too common, but happens enough it can be expected. Also the controller inheirts “ApiController” which doesn’t inheirt from “ControllerBase” so the usual controller context call won’t work.

Here is how I coded the unit test

[code language=”csharp”]
var config = new HttpConfiguration();
//add in the actual route or method if your are looking at it in your code
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/random");
//DefaultApi maps to your web api route setup
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}");
//add in the extra data, similar to when you call @Url.Route
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", "products" } });
var controller = new MyProject.Controllers.Api.ExampleController();
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Headers.Add("X-Your-Header","HeaderValue");
[/code]

In the controller, do a

[code langauge=”csharp”] var headerValue = Request.Headers.GetValues("HEADER");[/code]

do not do

[code langauge=”csharp”]
//DON’T DO THIS
var headerValue = HttpContext.Current.Request.Headers.GetValues("HEADER");[/code]

This will hit the old IIS style objects and your unit test will fail.

If you don’t have Resharper, you might need these using statements.

[code langauge=”csharp”]

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Routing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[/code]