Introduction
Chameleon.AspNet
is a library containg an ASP.NET Core
middleware and a ChameleonApp
helper class
that eases creating a Chameleon
Host or connecting an existing ASP.NET Core
web application to Chameleon
.
Chameleon.AspNet
is a library containg an ASP.NET Core
middleware and a ChameleonApp
helper class
that eases creating a Chameleon
Host or connecting an existing ASP.NET Core
web application to Chameleon
.
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);
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.
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-id s after their consumption is finished. |
In order to manually set-up Chameleon
middleware in an existing ASP.NET Core
application, we should follow the following two steps.
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" });
IApplicationBuilder
in Startup
class in Configure()
method.
public class Startup
{
public void Configure(IApplicationBuilder app)
{
...
app.useChameleon();
}
...
}
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>();
}
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-id
s is the responsibility of context-id provider
s. 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
uses an incremental approach of an integer field, plus using an internal list of generated context-ids. It adds generated context-id
s 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.
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.
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
.