Controller constructor does not get called - http

Hello i am trying to understand why do my requests not enter my api route.They seem to reach the server but they wont fan out in the MVC.
The server is running on: http://localhost:9300
The route i am requesting is : http://localhost:9300/api/getusers
Program
public class Program {
public static void Main(string[] args) {
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) {
var builder = new WebHostBuilder();
builder.UseStartup<Startup>();
var url = Address.Default.ToUrl();
builder.UseKestrel().UseUrls(url);
return builder;
}
}
Startup
public class Startup {
public Startup(IConfiguration configuration) {
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) {
services.AddOptions();
services.AddMvc();
}
public IConfiguration Configuration;
public void Configure(IApplicationBuilder app) {
Debug.WriteLine("Entered server"); //enters successfully here
app.UseMvc(); //does not enter the controller
}
}
Controller
This is a simple controller with a GET method.The constructor is not invoked at all.Why would this happen?I know it when the server runs the first time ..it does a health check on its routes.
[ApiController]
class UserController : ControllerBase {
private static List<User> users = new List<User> {
new User{Id=0,Age=0,Name="Failed"},
new User{Id=12,Age=33,Name="Daniel"},
new User{Id=13,Age=33,Name="Marian"},
};
public UserController() {
Debug.WriteLine("Controller called"); //does not get called !
}
[HttpGet]
[Route("api/getusers")]
public async Task<HttpResponseMessage> GetUsers() {
await Task.Delay(1000);
return new HttpResponseMessage {
Content = new StringContent(users.ToJson()),
StatusCode = HttpStatusCode.OK
};
}
}
P.S Do i have to add anyything ? What am i missing i followed other implementations closely.

I've created the webapi project using dotnet new webapi.
I've managed to get to the url with the similar configuration by changing the access modifier of a similar controller. Try to add public keyword to the class UserController. So it should be public class UserController
I will provide more information about the configuration of the project if it is necessary and the step above does not help.

Related

.net 6.0 Integration tests

I have a problem that I can`t create HttpClient for integration tests.
I have ready carefully this article:
https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests?view=aspnetcore-6.0
Microsoft.AspNetCore.Mvc.Testing installed
<Project Sdk="Microsoft.NET.Sdk.Web"> in csproj
CustomWebApplicationFactory:
public class CustomWebApplicationFactory<TStartup>
: WebApplicationFactory<TStartup> where TStartup : class
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
Environment.SetEnvironmentVariable("HANGFIRE_DASHBOARD_USERNAME", "test");
Environment.SetEnvironmentVariable("HANGFIRE_DASHBOARD_PASSWORD", "test");
Environment.SetEnvironmentVariable("ASPNETCORE_URLS", "https://+:1229");
base.ConfigureWebHost(builder);
}
}
ControllerIntegrationTests:
public class ControllerIntegrationTests : IClassFixture<CustomWebApplicationFactory<Startup>>
{
private readonly HttpClient client;
private readonly CustomWebApplicationFactory<Startup> factory;
public ControllerIntegrationTests(CustomWebApplicationFactory<Startup> factory)
{
this.factory = factory;
client = this.factory.CreateClient();
}
[Fact]
public async Task TestGetEndpointReturnSuccess()
{
// Arrange
// Act
var response = await client.GetAsync("/Information");
// Assert
response.EnsureSuccessStatusCode();
}
}
Nothing special in my code, just a simple example. As a result in Output I have :
Hosting environment:Development
Now listening on : https://[::]:1229
Application started. Press Ctrl+C to shut down.
It seems like it started the main application. And I cant move to Act in test, because its hanging in CreateClient(). So I can`t finish my test. Whats wrong?
You forget in your CustomWebApplicationFactory to override the CreateHostBuilder.
protected override IHostBuilder CreateHostBuilder()
{
return Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<TStartup>();
});
}
Try to add this method in your Factory class using the TStartup.

.NET Core, SignalR Hub's constructor IHubCallerClients is NULL

I'm trying to implement .NET Core 2.2/SignalR 1.1.0.
In startup:
public void ConfigureServices(IServiceCollection services)
services.AddSignalR();
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
});
It works smoothly when I apply a one-to-one example.
But I need an architectural change.
My example:
public class ChatHub : Hub
{
ResponseHandler ResponseHandler { get; set; }
public ChatHub()
{
IHubCallerClients hubCallerClients = this.Clients;
ResponseHandler = new ResponseHandler(hubCallerClients);
}
public async Task SendMessage(string user, string message)
{
IHubCallerClients hubCallerClients = this.Clients;
await ResponseHandler.R();
}
}
If I tried to get this.Clients in the constructor it is coming with null data. But if I try to take it in the method, it comes full as expected.
I should get IHubCallerClients in the contructor so that I can forward it to another Response context.
Thanks advance!
OK. I solved the problem by
public class RequestHandler : Hub
{
ResponseHandler ResponseHandler { get; set; }
public RequestHandler(IHubContext<RequestHandler> hubContext)
{
ResponseHandler = new ResponseHandler(hubContext);
}
public async Task SendMessage(string user, string message)
{
await ResponseHandler.R();
}
}
Due to the nature of .net core, context comes to constructor as dependency.
"services.AddSignalR();" we're sure to add it to Scope.
"IHubContext hubContext" In this way, we can collect the contructured object.

EasyNetQ (AMQP) Single Application Connection In ASP.NET?

Community:
I'm struggling to figure out how to create a single AMQP connection that lives with my ASP.NET application lifecycle in ASP.NET using .NET Core 2.1. After researching, I've found lots of references to using a single AMQP connection for the whole application as they are expensive and slow to create and I was headed down the road of creating the connection using DI but it appears my approach is flawed, I can't seem to identify which interface I need to add as a singleton...
public void ConfigureServices(IServiceCollection services)
{
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("DefaultConnection"));
var envSQL = Environment.GetEnvironmentVariable("ASPNETCORE_SQL_SERVER");
if (envSQL != null)
sqlConnectionStringBuilder.DataSource = envSQL;
services.AddSingleton<IMessageBusService, MessageBusService>();
services.AddSingleton<EasyNetQ.IAdvancedBus, RabbitAdvancedBus>();
services.AddSingleton<EasyNetQ.IConnectionFactory, ConnectionFactoryWrapper>();
services.AddMvc();
}
Adding the above interfaces works but I get an error about ConnectionConfiguration service not being locatable. Is this the right direction or is there a more proper way to create a single application once EasyNetQ connection in ASP.NET core?
You can use AutoSubcriber in .net core
and use the sample code here.
add connection to appsettings.json
"MessageBroker": {
"ConnectionString": "host=localhost"
}
then add IBus in ConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IBus>(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddSingleton(RabbitHutch.CreateBus(Configuration["MessageBroker:ConnectionString"]));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
add class AppBuilderExtension and use extension method for auto subscriber
public static class AppBuilderExtension
{
public static IApplicationBuilder UseSubscribe(this IApplicationBuilder appBuilder, string subscriptionIdPrefix, Assembly assembly)
{
var services = appBuilder.ApplicationServices.CreateScope().ServiceProvider;
var lifeTime = services.GetService<IApplicationLifetime>();
var Bus = services.GetService<IBus>();
lifeTime.ApplicationStarted.Register(() =>
{
var subscriber = new AutoSubscriber(Bus, subscriptionIdPrefix);
subscriber.Subscribe(assembly);
subscriber.SubscribeAsync(assembly);
});
lifeTime.ApplicationStopped.Register(() => Bus.Dispose());
return appBuilder;
}
}
add UseSubscribe in Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseSubscribe("ClientMessageService", Assembly.GetExecutingAssembly());
app.UseHttpsRedirection();
app.UseMvc();
}
then create Producers controller
[Route("api/[controller]")]
[ApiController]
public class ProducersController : ControllerBase
{
private readonly IBus _bus;
public ProducersController(IBus bus)
{
_bus = bus;
}
[HttpGet]
[Route("Send")]
public JsonResult Send()
{
_bus.Publish(new TextMessage { Text = "Send Message from the Producer" });
return new JsonResult("");
}
}
then create Consumers controller
[Route("api/[controller]")]
[ApiController]
public class ConsumersController : ControllerBase, IConsume<TextMessage>
{
[HttpGet]
[Route("Receive")]
public JsonResult Receive()
{
using (var bus = RabbitHutch.CreateBus("host=localhost"))
{
bus.Subscribe<TextMessage>("test", HandleTextMessage);
}
return new JsonResult("");
}
private static void HandleTextMessage(TextMessage textMessage)
{
var item = textMessage.Text;
}
public void Consume(TextMessage message)
{
// code receive message
}
}

ASP.NET 5 JSON with Azure Web App

I am trying to use custom configuration for a web app that I intent to host on Azure. The configuration should be overridable by Environment variables so that I can change it on Azure portal.
I tried with following code but it does not work - details below
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("config.json", optional:true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<AppSettings>(Configuration);
}
In the controller,
public class HomeController : Controller
{
private IOptions<AppSettings> Configuration;
public HomeController(IOptions<AppSettings> configuration)
{
Configuration = configuration;
}
public async Task<IActionResult> Index()
{
string location = Configuration.Value.Location;
...
}
The default config.json file looks like,
{
"AppSettings": {
"Location" : "Singapore"
}
}
On Azure Portal, under app settings I have assigned value to AppSettings:Location to US and I am expecting US value in the controller.
Locally, in ConfigureServices I can see the value as Singapore but in the controller action Index it is null.
Am I missing something here?
When you are reading the config.json, you need to load the section that you want to read, you need to update your code as follow:
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}

ASP.NET vNext global config access

What is the correct/recommended way of accessing the config.json file (or wherever else config is stored) in ASP.NET vNext?
In the Startup class, I set up the config like so:
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var configurationBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = configurationBuilder.Build();
}
But then if I need to access the connection string elsewhere, how do I do it? For example, in the OnConfiguring of an EF context, how do I get the connection string:
protected override void OnConfiguring(EntityOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer( ??? );
I've read that you could use this:
var config = Startup.Configuration
?? new Configuration()
.AddJsonFile("config.json")
.AddEnvironmentVariables();
But (a) Startup is not static and (b) you surely don't want to go rebuilding the configuration setup every time you need it - that's duplicating code everywhere it's used.
I've also read that you should use Dependency Injection, but that link doesn't fully show you how to do it. If my DbContext constructor has an injected parameter, then how do I inject that into a parameterless BaseApiController?
This really seems like a common/simple requirement: After the configuration in Startup, how to I access that configuration elsewhere? This should in documentation/examples everywhere.
Here's what I've got that's working:
Startup
public class Startup
{
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
// create & store the configuration once
var configurationBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json")
.AddEnvironmentVariables();
Configuration = configurationBuilder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<Context>(options => options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
services.AddIdentity<User, IdentityRole>()
.AddEntityFrameworkStores<Context>()
.AddDefaultTokenProviders();
services.AddMvc();
// adding/registering the dbContext for dependency injection as a singleton
services.AddSingleton(s => new Context(Configuration));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseIdentity();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "Home", action = "Index" });
});
}
}
DbContext
public sealed class Context : IdentityDbContext<IdentityUser>
{
private readonly IConfiguration _config;
public DbSet<Client> Clients { get; set; }
public Context(IConfiguration config)
{
// store the injected config
_config = config;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ForSqlServer().UseIdentity();
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(EntityOptionsBuilder optionsBuilder)
{
// use the injected config
optionsBuilder.UseSqlServer(_config.Get("Data:DefaultConnection:ConnectionString"));
base.OnConfiguring(optionsBuilder);
}
}
Controller
[Route("api/[controller]")]
public class TestController : BaseController
{
private readonly Context _context;
// have the context injected
public TestController(Context context)
{
_context = context;
}
[HttpGet]
public ActionResult Get()
{
return new ObjectResult(_context.Clients.ToList());
}
}
In the first place, you should avoid registering your database context as a singleton. Also passing around the raw IConfiguration interface isn't a good practice.
In stead could create a POCO options class:
public class DbOptions
{
public string ConnectionString { get; set }
}
And populate it in the ConfigureServices method using the section in the config.json:
services.Configure<DbOptions>(Configuration.GetConfigurationSection("Data:DefaultConnection"));
Then you can inject it into your DbContext (and in controllers, etc.):
public sealed class Context : IdentityDbContext<IdentityUser>
{
private readonly DbOptions _options;
public DbSet<Client> Clients { get; set; }
public Context(IOptions<DbOptions> optionsAccessor)
{
// store the injected options
_options = optionsAccessor.Options;
}
// other code..
}

Resources