Usage

In the simplest form, the only thing we need to connect an ASP.NET Core application to a Chameleon-based database is using ChameleonApp class. The target project requires no other thing and can be completely empty. The only thing that is required is Asp.Net Core runtime that creates HttpContext.

In order to connect the application to a Chameleon database, the only thing Chameleon.AspNet requires is a connection string. By default, it looks for a DefaultConnection connectionString in appsettings.json. This can be customized though through ChameleonOptions. This is described in the next section.

ASP.NET Core 3.x-5.0

				using System;
using Chameleon.AspNetCore;

class Program
{
	static void Main(string[] args)
	{
		ChameleonApp.Run(args);
	}
}
				
			

ASP.NET Core 6.0+

      			using Chameleon.AspNetCore;

ChameleonApp.Run(args);
			

Options

We can pass a ChameleonOptions object as the second parameter of ChameleonApp.Run() in order to customize database connection string and a few more options on how Chameleon.AspNet works.

      using Chameleon.AspNetCore;

ChameleonApp.Run(args, new ChameleonOptions { ConnectionStringName = "MyConnection" });

    

ChameleonOptions class is declared as below:

      public class ChameleonOptions
{
    public string ConnectionStringName { get; set; }
    public string ConnectionString { get; set; }
    public string[] Paths { get; set; }
    public Func<int, Exception, string> UnhandledException { get; set; }
}

    

Description

Property Type Description
ConnectionStringName string Name of ConnectionString in appsettings.json. default is DefaultConnection
ConnectionString string manual ConnectionString. Overrides using appsettings.json connectionStrings.
DialogMode string Specifies how the host speaks to Chameleon Framework in database. Possible values: pure (default), chatty.
Paths string[] array of relative paths on which Chameleon.AspNet middleware should listen and pass requests to Chameleon if url matches any of them. Default is null which means Chameleon will capture all requests.
UnhandledException Func<int, Exception, string> A function that is expected to return response of requests that resulted in an unhandled exception.

We can also pass a context-id provider through the third parameter of ChameleonApp.Run() method to customize context-id provider dependency of Chameleon.AspNet mmidleware. Context-id provider is explained in a later sections.

Package content

List of classes/interfaces in Chameleon.AspNet package is as follows:

Class Description
ChameleonApp A utility or helper class that creates a host builder and sets it up as an stand-alone chameleon-enabled web application.
ChameleonMiddleware An ASP.NET Core middleware that intercepts requests and passes HttpContext to an IChameleonService that is responsible for processing the request.
IChameleonService An interface that defines structure of chameleon service.
ChameleonService A service that performs the request processing operation by calling [chameleon].[Run] SPROC, passing web request details, receiving the response and writing it onto the HttpContext.
ChameleonOptions A class by which a few options can be customized in using Chameleon in an ASP.NET Core web application, like setting a manual connectionString or default response of faulted requests.
IContextIdProvider An interface that defines structure of context_id providers
AlwaysIncrementalContextProvider A context-id provider that uses an incremental approach to provide context-id for chameleon requests
ReusingContextIdProvider A context-id provider that reuses its generated context-ids after their consumption is finished.

Manual Setup

In order to manually set-up Chameleon middleware in an existing ASP.NET Core application, we should follow the following two steps.

  • Adding chameleon dependencies to IServiceCollection in Startup class in ConfigureServices() method. ChameleonService has four dependencies. They are described in the next section. There is a helper extension method AddChameleon() that simplifies setting up dependencies.
      public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		...
		services.AddChameleon();
	}
	...
}

    

We can pass a ChameleonOptions instance to AddChameleon() to customize Chameleon.AspNet. This is described later.

      services.AddChameleon(new ChameleonOptions { ConnectionStringName = "MyConnection" });

    
  • Adding chameleon middleware to IApplicationBuilder in Startup class in Configure() method.
      public class Startup
{
	public void Configure(IApplicationBuilder app)
	{
		...
		app.useChameleon();
	}
	...
}

    

Chameleon service dependencies

Chameleon service depends on four dependencies:

Dependency Namespace Description
IConfiguration Microsoft.Extensions.Configuration It is used to read connection strings in appsettings.json
IHostEnvironment Microsoft.Extensions.Hosting It is used to read host environment variables.
ILoggerFactory Microsoft.Extensions.Logging It is used to for logging.
IContextIdProvider Chameleon.AspNet It is used to get context-id for serving a web request.

AddChameleon() extension method sets up dependencies this way:

      public static void AddChameleon(this IServiceCollection services, ChameleonOptions options = null, IContextIdProvider contextIdProvider = null)
{
    services.AddSingleton(sp => options ?? new ChameleonOptions());
    services.AddSingleton(sp => contextIdProvider ?? new ReusingContextIdProvider(0));
    services.AddTransient<IChameleonService, ChameleonService>();
}

    

Context-Id Provider

ChameleonService passes details of incoming web requests to [chameleon].[Run] SPROC. The SPROC then inserts given arguments into a series of tables so that, details of the current request could be avaialble by later SPROCs or UDFs that will take part in request processing pipeline and proving the response.

In order to avoid requests collision, they are distincted by a unique number named context-id. This context-id is a unique key on which details of requests are separated of each other.

Generating context-ids is the responsibility of context-id providers. They are abstracted away through an IContextIdProvider interface. This interface is defined this way:

				public interface IContextIdProvider
{
	int GetContextId();
	void ReleaseContextId(int contextid);
}
		  
			  

GetContextId() should return a new unique context-id. This method is called when starting serving a request. It must be implemented by a context id provider. ReleaseContextId() is used to release a consumed context-id. Implementing this method is optional and it depends on the context-id provider whether to support reusing already generated context-ids or not.

Chameleon.AspNet packages comes with two implementations for IContextIdProvider.

  • AlwaysIncrementalContextIdProvider: This context-id provider uses an integer field and increments it on each request.
  • ReusingContextIdProvider: This context-id provider like AlwaysIncrementalContextIdProvider uses an incremental approach of an integer field, plus using an internal list of generated context-ids. It adds generated context-ids to its list when its ReleaseContextId() method is called. This way, it is able to reuse already generated context-ids.

It is important to know that, context-id providers should be Thread-Safe and avoid context-id collision. The simplest approach is to set-up context-id provider as a singleton dependency during ASP.NET Core application's startup in configure services. This is already done in AddChameleon() extension method.

IMPORTANT NOTE: When using AlwaysIncrementalContextIdProvider, context tables and hence your database will grow unlimitedly. You MUST set a SQL job that clears context tables for previous requests.

There is a stored procedure named [chameleon].[ClearUnusedContexts] that does the job. It has two parameters @fromContextId and @toContextId. It removes any context record that falls between the given parameters. If @fromContextId is 0 or null it starts from the begining (first record). If @toContextId is 0 or null, it removes until the last record in tables. If both of them are 0 or null, it removes all records.

Web farms

Chameleon.AspNet by default uses a ReusingContextIdProvider for its IContextIdProvider, but this can be customized through appsettings.json in Chameleon.Host or second parameter of AddChameleon() method.

Customizing context-id provider is helpful when using the application in a web-farm. In a web-farm, not only does context-id need to be unique in the ASP.NET application, but also requires to be unique across the web farm.

In order to guarantee uniqueness for context-ids over the web farm, we can define a separate seed for AlwaysIncrementalContextIdProvider or ReusingContextIdProvider.

For example suppose have 3 servers in our web farm. If we are using Chameleon.Host, we can set up context-id seeds through our appsettings.json file for each server instance.

Server-I

				{
	"ConnectionStrings": {
		"DefaultConnection": "Data Source=.;Initial Catalog=ChameleonDb;User Id=***;Password=***"
	},
	"Chameleon": {
		"ContextIdSeed": "0"
	}
	...
}
		  
			  

Server-II

				{
	"ConnectionStrings": {
		"DefaultConnection": "Data Source=.;Initial Catalog=ChameleonDb;User Id=***;Password=***"
	},
	"Chameleon": {
		"ContextIdSeed": "10000"
	}
	...
}
		  
			  

Server-III

				{
	"ConnectionStrings": {
		"DefaultConnection": "Data Source=.;Initial Catalog=ChameleonDb;User Id=***;Password=***"
	},
	"Chameleon": {
		"ContextIdSeed": "20000"
	}
	...
}
		  
			  

This way, each server can serve up to ten thousands concurrent requests. Context-ids are unique across the farm, but are limited to ten thousands on each server and not more than that. If a server's load exceeds ten thousands, requests could collide.

If we know that requests' load could go beyond ten thousands on each server, we must set larger seeds.

Context-Id vs Session

In Asp.Net, Session is a storage that can hold data for web application visitors. The data that is put in a user's session will be available across his/her other requests.

In chameleon, context is a key across a single request that is required to be unique only among con-currents requests.

Its main use is to be able to track request or response details (url, routing data, headers, cookies, etc.) inside database.

When processing a request is finished, the context-id could be freed to be used again.

So, context-id satisfies a different need than Session.