I have a web application that uses Redis to coordinate server nodes. The application has integration tests that spin up a server instance with WebApp.Start
and make calls against it with an HttpClient and a SignalR .NET Client.
I'd like to enhance the integration tests to call WebApp.Start
multiple times to spin up multiple server instances, using different port numbers as a substitute for different host addresses. That should be easy enough to do. The part I can't figure out is how to access per-instance objects from the controllers. These are objects such as the connection to Redis and caching dictionaries. Traditionally, they've been simple static fields. Now, I'd like them to be instance fields instantiated with the Startup
object.
My question is how should I create these objects that are per web application, and how do I access them from a Web API and SignalR controller?
Simplified example:
[TestMethod]
public void IntegrationTest()
{
// Starts 3 server instances.
for (int port = 9000; port < 9003; ++port)
WebApp.Start("http://localhost:" + port);
// Validates SignalR events on 9000.
var hubConnection9000 = new HubConnection("http://localhost:9000");
hubConnection9000.CreateHubProxy("MyHub").On<MyMessage>(name, m => Assert.IsTrue(...));
hubConnection9000.Start().Wait();
// Sends a SignalR command to 9001. Causes an event on 9000.
var hubConnection9001 = new HubConnection("http://localhost:9001");
hubConnection9001.CreateHubProxy("MyHub").Invoke("DoIt").Wait();
// Sends a Web API request to 9002. Causes an event on 9000.
var httpClient9002 = new HttpClient { BaseAddress = new Uri("http://localhost:9002") };
httpClient9002.PostAsJsonAsync("api/My/NewRecord", "[]").Result.EnsureSuccessStatusCode();
}
// From Startup.Configuration(IAppBuilder app):
// Creates a per-web-application backplane object which communicates to other servers.
object addresses;
int customPort = app.Properties.TryGetValue("host.Addresses", out addresses) ? int.Parse((string)((List<IDictionary<string, object>>)addresses)[0]["port"], CultureInfo.InvariantCulture) : 0; // The custom port number if one was provided at startup; otherwise, 0.
var backplane = new Backplane(customPort);
new AppProperties(app.Properties).OnAppDisposing.Register(() => { backplane.Dispose(); });
public class MyController : ApiController
{
[HttpPost]
public void NewRecord(string[] items)
{
// I want to use Backplane. How do I get at it?
}
}
public class MyHub : Hub<IMy>
{
public void DoIt()
{
// I want to use Backplane. How do I get at it?
}
}
internal class Backplane
{
// A Redis object that is expensive to create and should be shared with the entire web application.
readonly StackExchange.Redis.ConnectionMultiplexer redis = StackExchange.Redis.ConnectionMultiplexer.Connect();
}