IP address when self hosting and using xUnit Test - c#

I have the following function, GetIpAddress in my production code. I have a xUnit test which calls the website where the function is called. The function works correctly when running normally but RemoteIpAddress is always null if run from the xUnit test. Below is my test start up class that is called by the host builder and the Test function is used to send the request.
internal static string GetIpAddress(HttpRequest request)
{
try
{
if (request.HttpContext.Connection.RemoteIpAddress != null)
return request.HttpContext.Connection.RemoteIpAddress.ToString();
}
catch (System.Exception ex)
{
DataLink.ProcessError(ex, "Error getting IP address");
return "error";
}
return "unknown";
}
class TestStartup
{
public TestStartup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
ApmCore.Startup.Connection = Configuration.GetConnectionString("DefaultConnection");
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddMemoryCache();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
public static HttpClient GetClient()
{
var server = new TestServer(new WebHostBuilder()
.UseStartup<TestStartup>());
var client = server.CreateClient();
client.DefaultRequestHeaders
.Accept
.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
public static HttpRequestMessage GetRequest(string args, string url)
{
return new HttpRequestMessage(HttpMethod.Get, new System.Uri(url))
{
Content = new StringContent(args, Encoding.UTF8, "application/json")
};
}
}
[Theory]
[MemberData(nameof(TestDataHandler.LogData), MemberType = typeof(TestDataHandler))]
public async Task TestGet(string args, bool expected)
{
var response = await this._Client.SendAsync(TestStartup.GetRequest(args, "http://localhost/api/Log"));
var data = await response.Content.ReadAsStringAsync();
var result = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType(data, new { success = false });
Assert.Equal(result.success, expected);
}

This question seems to be a duplicate of Set dummy IP address in integration test with Asp.Net Core TestServer
That question has an answer that could be used to resolve this issue:
https://stackoverflow.com/a/49244494/90287
You can write middleware to set custom IP Address since this property
is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());

Related

Unexpected response when using HttpClient

I've been coding a simple API with the view of getting placeholder image of a specialized site which offers this service. However, when I make a request to site with correspondent path, I'm not able of getting the image displayed on screen instead I got this HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: http://fakeimg.pl/300x300/. If not click the link.
I read the official documentation all day long but up to now I haven't realized a way to get this work properly.
This is my code. Obs: I have used the url "https://fakeimg.pl/300X300" and the dotnet core version 3.1.302 for this sample request.
FakePhotoService.cs
namespace FakePhotoApi
{
public class FakePhotoService
{
private readonly HttpClient _httpClient;
private readonly ILogger<FakePhotoService> _logger;
public FakePhotoService(HttpClient httpClient, ILogger<FakePhotoService> logger)
{
_logger = logger;
_httpClient = httpClient;
}
public HttpRequestMessage GenerateRequest(Uri uri)
{
return new HttpRequestMessage(HttpMethod.Get, uri);
}
public async Task<string> GetFakePhoto(Tuple<int, int> dimensions)
{
var baseUri = new Uri($"https://fakeimg.pl/{dimensions.Item1}x{dimensions.Item2}");
var request = GenerateRequest(baseUri);
var response = await _httpClient.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
}
}
FakePhotoController.cs
namespace FakePhotoApi.Controllers
{
[Route("[controller]")]
[ApiController]
public class FakePhotoController : ControllerBase
{
private readonly FakePhotoService _fakePhotoService;
public FakePhotoController(FakePhotoService fakePhotoService)
{
_fakePhotoService = fakePhotoService;
}
[HttpGet("/")]
public async Task<IActionResult> GetFakePhoto()
{
var result = await _fakePhotoService.GetFakePhoto(new Tuple<int, int>(300, 300));
return Ok(result);
}
}
}
Startup.cs
namespace FakePhotoApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpClient<FakePhotoService>()
.ConfigurePrimaryHttpMessageHandler(() =>
{
return new HttpClientHandler
{
AllowAutoRedirect = true,
MaxAutomaticRedirections = 5
};
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Program.cs
namespace FakePhotoApi
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Please let me know what I'm doing wrong.
Update your code like so:
public HttpRequestMessage GenerateRequest(Uri uri)
{
var msg = new HttpRequestMessage(HttpMethod.Get, uri);
msg.Headers.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36"); // or some other real browser string
return msg;
}

Passing a Hub Context to a non Controller Class

I am trying to send a message to a client in the server using SignalR
I am trying to do that in a class that is not a Controller. I have made the Startup like so:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.Configure<ConfigurationModel>(Configuration.GetSection("configurationModel"));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
app.UseSignalR(routes => { routes.MapHub<MoveViewHub>("/movehub"); });
}
}
In my Program, this one:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
This is in my Hub:
public class MoveViewHub : Hub
{
private async void ReceiveTagNumber(object sender, EventArgs e)
{
await Clients.All.SendAsync("ReceivedFromServer", sender.ToString());
}
public async Task MoveViewFromServer(float newX, float newY)
{
Console.WriteLine(#"Receive position from Server app: " + newX + "/" + newY);
await Clients.Others.SendAsync("ReceivedNewPosition", newX, newY);
//await Clients.All.SendAsync("ReceivedNewPosition", newX, newY);
}
public async Task WriteThisMessage(string message)
{
Console.WriteLine(message);
await Clients.Others.SendAsync("ReceivedStatus", "Message was received. Thank you.");
}
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
RfidClass rfidClass = new RfidClass("THE HUB CONTEXT SHOULD BE HERE"); ====>> I NEED TO PASS MY HUBCONTEXT
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.Write("Client has disconnected");
return base.OnDisconnectedAsync(exception);
}
}
This is the RfidClass:
private IHubContext<MoveViewHub> hubContext;
public RfidClass(IHubContext<MoveViewHub> hubContext)
{
this.hubContext = hubContext;
}
public void sas()
{
Start();
}
private void Start()
{
try
{
hubContext.Clients.Others.SendAsync("ReceivedFromServer", "You are connected");
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
How can I make it right?
You need to inject IServiceProvider into your Hub by .NET Core DI (like into standard Controller, injecting by constructor):
public class MoveViewHub : Hub
{
private readonly IServiceProvider provider
public MovieViewHub(IServiceProvider provider)
{
this.provider = provider
}
}
Then you can do something like this:
public override Task OnConnectedAsync()
{
Console.WriteLine("Client has connected");
// you need to inject service provider to your hub, then get hub context from
// registered services
using (var scope = this.provider.CreateScope())
{
// get instance of hub from service provider
var scopedServices = scope.ServiceProvider;
var hub = scopedServices.GetRequiredService<IHubContext<MoveViewHub>>
// pass hub to class constructor
RfidClass rfidClass = new RfidClass(hub)
rfidClass.sas();
RfidClass.SendTagNumber += ReceiveTagNumber;
}
System.Diagnostics.Process.Start(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Notepad++", #"notepad++.exe"));
return base.OnConnectedAsync();
}
EDIT:
If you just want to SignalR work, you dont need to work on Hub. Instead make service. In this service inject HubContext<> of your Hub:
// you need to make your own class and interface and inject hub context
public interface ISignalRService
{
Task SendMessageToAll(string message);
}
public class SignalRService : ISignalRService
{
private readonly IHubContext<YourHub> hubContext;
public SignalRService (IHubContext<NotificationHub> hubContext)
{
this.hubContext = hubContext;
}
public async Task SendMessageToAll(string message)
{
await this.hubContext.Clients.All.SendAsync("ReciveMessage", message);
}
}
Then register that service in your Startup class:
services.AddScoped<ISignalRService, SignalRService>();
After that you can call SignalRService wherever you want to like normal service from .NetCore DI container:
private readonly ISignalRService notificationService;
public SomeController(ISignalRService notificationService)
{
this.notificationService = notificationService;
}
[HttpGet]
public async Task<IActionResult> Send()
{
await this.notificationService.SendMessageToAll("message");
return Ok();
}
You dont need to make some work around like RfidClass.

UnitTest cant find endpoint

Using xUnit 2.4.1 to test the Api always fails to find Controller
When I create a WebApplicationFactory and pass Startup file as parameter the HTTP Client from WebApplicationFactory.CreatVlient() always returns 404 for Get requests.
Testing a .Net Core Api that uses MVC.
The CommonContext is an internal class that sets the connection.
The Configfile reads correctly
The Connectionstring to DB is correct
The Endpoint is not called correctly and therefore never hits the controller.
Class that inherits WebApplocationFactory
public class WebApiTestFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup: class
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
var configValues = new Dictionary<string, string>
{
{"RunStatus", "Test"},
};
builder.AddInMemoryCollection(configValues);
return WebHost.CreateDefaultBuilder()
.UseConfiguration(builder.Build())
.UseUrls("http://localhost:53976/")
.UseSetting("applicationUrl", "http://localhost:53976/")
.UseStartup<Else.WebApi.Core.Startup>();
}
}
Unit Test
public class TestControllerTest : IClassFixture<WebApiTestFactory<Startup>>
{
private readonly WebApiTestFactory<Startup> _factory;
public TestControllerTest(WebApiTestFactory<Startup> factory)
{
_factory = factory;
}
[Theory]
[InlineData("api/Test/GetExample")]
public async Task Create(string url)
{
// Arrange
var clientOptions = new WebApplicationFactoryClientOptions();
clientOptions.BaseAddress = new Uri("http://localhost:53976");
var client = _factory.CreateClient(clientOptions);
// Act
var response = await client.GetAsync(url);
// Assert
response.EnsureSuccessStatusCode(); // Status Code 200-299
Assert.Equal("text/html; charset=utf-8",
response.Content.Headers.ContentType.ToString());
}
}
Controller is in the project im testing
[ApiController]
[Route("api/Test")]
public class TestController : Controller
{
[HttpGet("GetExample")]
public ActionResult GetExample()
{
return Ok();
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
HostingEnvironment = env;
Configuration = configuration;
EwBootstrapper.BootstrapElsewareServices();
}
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
if (Configuration["RunStatus"] != "Test")
{
services.AddTransient<AuthenticationTokens>();
services.AddTransient<IPasswordValidator, PasswordValidator>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddMvc();
services.AddScoped(_ =>
new CommonContext(Configuration.GetConnectionString("DbConnection")));
services.AddSwaggerDocumentation();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // Configure authentication (JWT bearer)
.AddJwtBearer(jwtOpt => // Configure JWT bearer
{
jwtOpt.TokenValidationParameters = AuthenticationTokens.GetValidationParameters();
});
}
else
{
//services.AddMvcCore().AddJsonFormatters();
services.AddScoped(_ =>
new CommonContext(Configuration.GetConnectionString("DbTestConnection")));
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
if (Configuration["RunStatus"] != "Test")
{
app.UseSwaggerDocumentation();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API");
});
app.UseMiddleware<ApiLoggerMiddleware>();
app.UseMvc(builder => builder.MapRoute("Default", "api/{controller}/{action=Get}/{id?}")); // No default values for controller or action
app.UseDefaultFiles(); // Enable default documents ( "/" => "/index.html")
app.UseStaticFiles(); // Static files under wwwroot
app.UseAuthentication();
}
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
}
}
According to the attribute routing in your controller, the action method has the url api/Test/GetExample: [HttpGet("GetExample")], yet in your in test you are testing for CreateExample:
[InlineData("api/Test/CreateExample")]
So I guess, your test is correct in returning a 404. That route simply will not resolve to any existing action method.
I suggest you change your theory to [InlineData("api/Test/GetExample")]

Registering Modules Autofac

I am using .NetCore 2.1 with autofaq in an asp.net core web application, my problem is the load method of my service module is not firing, I am instantiating a new instance of it as a parameter to registermodule, and the constructor of my service module is firing, this is a pretty typical setup, is there something i am doing wrong that anyone here can see?
ServiceModule.cs
namespace MyApp.Managers.DependencyManagement
{
public class ServiceModule : Module
{
public ServiceModule()
{
Console.WriteLine("YES THIS LINE OF CODE IS FIRING?");
}
protected override void Load(ContainerBuilder builder)
{
Console.WriteLine("Why am i not firing? :-( ");
builder.RegisterType<ItemManager>().As<IItemManager>().InstancePerLifetimeScope();
}
}
}
Program.cs (pretty basic void main here)
namespace MyApi.Api
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(services => services.AddAutofac())
.ConfigureAppConfiguration((context, options) =>
{
options.SetBasePath(Directory.GetCurrentDirectory())
.AddCommandLine(args);
})
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
Startup.cs (lots of stuff going on here)
namespace MyApi.Api
{
public class Startup
{
private readonly IHostingEnvironment env;
private readonly IConfiguration config;
private readonly ILoggerFactory loggerFactory;
public Startup(
IHostingEnvironment env,
IConfiguration config,
ILoggerFactory loggerFactory)
{
this.env = env;
this.config = config;
this.loggerFactory = loggerFactory;
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", EnvironmentVariableTarget.Machine);
var appParentDirectory = new DirectoryInfo(this.env.ContentRootPath).Parent.FullName;
var environmentName = environment ?? "Dev";
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{environmentName}.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; private set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Item Service", Version = "v1" });
c.DescribeAllEnumsAsStrings();
});
services
.AddMvc()
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1)
.AddFluentValidation(x => x.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void ConfigureContainer(ContainerBuilder builder)
{
var connectionString = this.Configuration.GetConnectionString("GildedRose");
ServiceConfiguration.Register(this.AddWebServices, connectionString);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.UseMvc();
}
private void AddWebServices(ContainerBuilder builder)
{
}
}
}
ServiceConfiguration.cs (the constructor is firing, but the load method never fires)
namespace MyApi.Api
{
public class ServiceConfiguration
{
public static ContainerBuilder Register(Action<ContainerBuilder> additionalRegistration, string connectionString)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<ConfigurationStore>().As<IConfigurationStore>().InstancePerLifetimeScope();
containerBuilder.RegisterType<Context>().AsSelf().InstancePerLifetimeScope();
containerBuilder.RegisterModule(new StoreModule()
{
ConnectionString = connectionString,
});
containerBuilder.RegisterModule(new Managers.DependencyManagement.ServiceModule());
additionalRegistration(containerBuilder);
return containerBuilder;
}
}
}
You are not using the ContainerBuilder passed to the ConfigureContainer() method, instead you are instantiating and using a new one in the ServiceConfiguration.Register(), but that is not the one wired in the ASP.NET Core framework and also won't be built by it. That is why the Load() is not firing, you should use the one which is used by the framework.
Try to pass it to your static method like this:
ServiceConfiguration.Register(this.AddWebServices, connectionString, builder);
And use it in your method like:
public static ContainerBuilder Register(Action<ContainerBuilder> additionalRegistration,
string connectionString,
ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<ConfigurationStore>()
.As<IConfigurationStore>()
.InstancePerLifetimeScope();
// the rest
}
With autofac you've got a couple ways of starting a service on creation:
Implementing IStartable on your service and adding a Start() method
or something like this:
var builder = new ContainerBuilder();
builder.RegisterBuildCallback(c => c.Resolve<DbContext>());
// The callback will run after the container is built
// but before it's returned.
var container = builder.Build();

How to invoke SignalR Clients.All.InvokeAsync() in places other than the Controller?

I'm able to access my IHubContext<MyHub> fine and dandy in my .NET Core WebAPI's Controller through DI in the constructor, but I want to access it elsewhere too.
Specifically, when I consume a message from RabbitMQ, sometimes I want to update clients through _myHubContext.Clients.All.InvokeAsync(), but I just can't figure out how to get it.
I'm also having issues finding documentation for doing this kind of thing outside of the controller.
Any help would be appreciated.
EDIT:
To add some detail, and where the cause of my problem may originate, I'm trying to access the IHubContext (and some of my own services registered in ConfigureServices) within my Startup class, specifically during IApplicationLifetime ApplicationStarted and ApplicationStopped which call a RabbitMQ consumer's methods to connect and disconnect.
I'm I correct in guessing that maybe I'm unable to access registered services in the Startup class? If so, how would I go about starting these services?
Update:
Moving services.AddSignalR() and some of the services that are called at startup one level up to the WebHost.ConfigureServices in Program.cs solved some of my problems, but of course there are more.
I wasn't getting any messages on my JS client when I received a message from RabbitMQ, but my client was connecting successfully. "Weird..." I thought. To get more info, I wired up a an GET action in my controller to sent some content through the SignalR Hub. Whenever I called that GET, it works... the IHubContext<MyHub>. I get the hubContext through the constructor in my RabbitMQ listener, just like I do with the controller.
The new question: are things injected differently in the controller than they are into services that I register myself at startup? How so, and how do I overcome that?
Some code to go with it...
Excerpt from Program.cs
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.ConfigureServices(services => {
services.AddSignalR();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IHealthCheckProcessor, HealthCheckProcessor>();
services.AddTransient<INodeProcessor, NodeProcessor>();
})
.UseStartup<Startup>()
.Build();
From Startup.cs
public class Startup
{
public Startup(IConfiguration _configuration, ISubscriber _subscriber)
{
configuration = _configuration;
subscriber = _subscriber;
}
public IConfiguration configuration { get; }
public ISubscriber subscriber { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime applicationLifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
// CORS stuff);
app.UseSignalR(routes =>
{
routes.MapHub<StatusHub>("Status");
});
app.UseMvc();
applicationLifetime.ApplicationStarted.Register(OnStartup);
applicationLifetime.ApplicationStopping.Register(OnShutdown);
}
private void OnStartup() {
// MessageBroker stuff
subscriber.Start(messageBroker);
}
private void OnShutdown() {
subscriber.Stop();
}
}
From Subscriber.cs
public class Subscriber : ISubscriber {
public static IConnection connection;
public static IModel channel;
public IHubContext<StatusHub> hubContext;
public static IHealthCheckProcessor healthCheckProcessor;
public static INodeProcessor nodeProcessor;
public Subscriber(IHubContext<StatusHub> _hubContext, INodeProcessor _nodeProcessor, IHealthCheckProcessor _healthCheckProcessor)
{
connection = new ConnectionFactory().CreateConnection();
channel = connection.CreateModel();
hubContext = _hubContext;
nodeProcessor = _nodeProcessor;
healthCheckProcessor = _healthCheckProcessor;
}
public void Start(MessageBroker messageBroker)
{
var factory = new ConnectionFactory() { HostName = messageBroker.URL }.CreateConnection();
foreach (Queue queue in messageBroker.Queues)
{
channel.QueueDeclare(
queue: queue.Name,
durable: queue.Durable,
exclusive: queue.Exclusive,
autoDelete: queue.AutoDelete,
arguments: null
);
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
byte[] body = ea.Body;
string message = Encoding.UTF8.GetString(body);
RouteMessage(queue, message);
};
channel.BasicConsume(
queue: queue.Name,
autoAck: queue.AutoAck,
consumer: consumer
);
hubContext.Clients.All.InvokeAsync("Send", "It worked - from the subscriber");
}
}
public void RouteMessage(Queue queue, string message) {
if(queue.Name == "discovery") {
nodeProcessor.Process(message);
}
if(queue.Name == "health") {
healthCheckProcessor.Process(message);
}
}
public void Stop()
{
Console.WriteLine("Terminating connection to RabbitMQ instance.");
channel.Close(200, "Goodbye");
connection.Close();
}
}
From HealthCheckProcessor.cs
public class HealthCheckProcessor : IHealthCheckProcessor {
private IDataService dataService;
private IHubContext<StatusHub> hubContext;
public HealthCheckProcessor(IDataService _dataService, IHubContext<StatusHub> _hubContext)
{
dataService = _dataService;
hubContext = _hubContext;
}
public void Process(string message) {
HealthCheck health = JsonConvert.DeserializeObject<HealthCheck>(message);
Node node = dataService.GetSingle(health.NodeId);
node.Health = health;
dataService.Update(node);
Console.WriteLine("It's sending.");
hubContext.Clients.All.InvokeAsync("Send", "It worked - from the processor");
}
}
From the Controller:
[Route("api/[controller]")]
public class MyController: Controller
{
private IDataService _dataService;
private readonly IConfiguration configuration;
private static IHubContext<StatusHub> hubContext;
public NodesController(IConfiguration config, IDataService dataService, IHubContext<StatusHub> _hubContext)
{
_dataService = dataService;
configuration = config;
hubContext = _hubContext;
}
[HttpGet]
public string Get()
{
hubContext.Clients.All.InvokeAsync("Send", "Blarg!");
return "Well, I tried.";
}
}
You are trying to access services that are not available at the time you request them.
Configure is called after ConfigureServices specifically so that any services registered can be accessible.
public class Startup {
public Startup(IConfiguration _configuration) {
configuration = _configuration;
}
public IConfiguration configuration { get; }
public void ConfigureServices(IServiceCollection services) {
services.AddCors();
services.AddMvc();
services.AddSignalR();
services.AddTransient<ISubscriber, Subscriber>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IHealthCheckProcessor, HealthCheckProcessor>();
services.AddTransient<INodeProcessor, NodeProcessor>();
}
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
IApplicationLifetime applicationLifetime,
IServiceProvider sp
) {
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
app.UseCors(builder => builder
// CORS stuff);
app.UseMvc();
app.UseSignalR(routes => {
routes.MapHub<StatusHub>("Status");
});
//At this point all the necessary dependencies have been registered and configured
var subscriber = sp.GetService<ISubscriber>();
applicationLifetime.ApplicationStarted.Register(() => OnStartup(subscriber));
applicationLifetime.ApplicationStopping.Register(() => OnShutdown(subscriber));
}
private void OnStartup(ISubscriber subscriber) {
// MessageBroker stuff
subscriber.Start(messageBroker);
}
private void OnShutdown(ISubscriber subscriber) {
subscriber.Stop();
}
}
You should be able to now remove the convenience ConfigureServices when building the host.
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.Build();

Categories