ASP.NET vNext global config access - asp.net

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..
}

Related

Controller cannot reach Controller in other project because of constructor ASP:NET Core

I'm new to ASP.NET Core and I'm trying to solve this problem for a week now.
I have a solution with two projects.
And when I start the porject the browser just says:
InvalidOperationException: Unable to resolve service for type 'TSM_Programm.Data.TSMContext' while attempting to activate 'TSM_Programm.Controllers.ResourcesController'.
The first part of the solution is my API-Layer that passes data to a user (currently via postman).
The second project is my Data Access Layer.
This Layer contains several Controllers, all of them using the same constructor, which is the following:
public TSMContext _context;
public ResourcesController(TSMContext context)
{
_context = context;
}
The TSMContext Class is the following:
namespace TSM_Programm.Data
{
public class TSMContext : DbContext
{
public TSMContext(DbContextOptions<TSMContext> options)
: base(options)
{
}
public DbSet<Resource> Resources { get; set; }
public DbSet<Parameter> Parameters { get; set; }
public DbSet<ResourceToParameter> ResourceToParameters { get; set; }
public DbSet<Reservation> Reservations { get; set; }
}
So far so god, but when I am trying to start the program the controllerof the API-Layer does not seem to be able to handle the constructor.
This is my API-Conrtoller:
namespace TSM_API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class APIController : ControllerBase //Base Class without View Support
{
//Troublemaker
public ResourcesController _resources;
public ParametersController _parameters;
public ReservationsController _reservations;
public APIController(ResourcesController resources, ParametersController parameters, ReservationsController reservations)
{
_resources = resources;
_parameters = parameters;
_reservations = reservations;
}
//Function to check if controller works
//GET: api/API
[HttpGet]
public IEnumerable<string> Get()
{
// ResourcesController controller = new ResourcesController();
return new string[] { "value1", "value2" };
}
The API-Controller was not able to use its own constructors, that's why I changed the Startup.cs.
namespace TSM_API
{
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc().AddApplicationPart(typeof(ResourcesController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(ParametersController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(ReservationsController).Assembly).AddControllersAsServices();
services.AddMvc().AddApplicationPart(typeof(TSMContext).Assembly).AddControllersAsServices();
}
I'm simply out of ideas on how to solve the problem, since I can't add the TSMContext class a service.
Any idea how to solve it?
Thank you.
I see you have not registered your dbcontext as a dependency injection. Your issue might be due to ResourceController trying to access _context as a DI but it is not registered. To use the context as a dependency injection, register it in the startup.cs as following.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TSMContext>(options => options.UseSqlServer(Configuration.GetConnectionString("YOUR_CONNECTION_STRING")));
//If you have any services that should be used as DI, then they also must be registered as like this
services.AddScoped<Interface, Class>(); //Interface refer to the service interface while class is the actual service you will use.
}

Controller constructor does not get called

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.

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
}
}

How to access logger in ASP.NET 5

One of the suggested approaches is to use Dependency Injection to inject logger to the constructor of the class. https://docs.asp.net/en/latest/fundamentals/logging.html
However, what if my class is not registered with DI container or I just want to create a static logger per a class. How can I still access LoggerFactory which was configured in Startup.cs?
This is how I would do it using nlog:
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
You could define a static property in your Startup which exposes the LoggerFactory:
public class Startup
{
public Startup(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory;
}
public static ILoggerFactory LoggerFactory { get; set; }
public void ConfigureServices(IServiceCollection services)
{
//..
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//..
}
}
And then access it where you need a logger:
public class MyClass
{
private static readonly ILogger _log = Startup.LoggerFactory.CreateLogger<MyClass>();
//..
}

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"));
}

Resources