"Microsoft.Extensions.DependencyInjection" and different concretes per environment - c#

"Microsoft.Extensions.DependencyInjection"
List of "environments"
Production
Uat
Qa
DevShared
LocalDev
With DotNet (Framework/Classic) 4.6 or above (aka, "in the past", I used "Unity" with xml configuration. https://blogs.msdn.microsoft.com/miah/2009/04/03/testing-your-unity-xml-configuration/
(In the past before Dot Net core when using "Unity" IoC/DI)...When I had a need to have a concrete specific to an environment, I would tweak the concrete on the .xml.
For instance, let's say my webApi needed authentication in production, uat, qa and dev-shared. but in dev-local, I do not want to deal with authentication all the time as I developed the webApi, I would have 2 concretes.
IAuthorizer
MyRealAuthorizer : IAuthorizer
MyDevLetEverythingThroughAuthorizer : IAuthorizer
and I would "register" one of them .. using xml.
My build process would alter the unity.xml (unity.config to be precise) and change out (via xml-update-tasks in msbuild)
MyDevLetEverythingThroughAuthorizer
to
MyRealAuthorizer
.
.....
Java Spring has "annotation" based:
import org.springframework.context.annotation.Profile;
#Profile("localdev")
public class MyDevLetEverythingThroughAuthorizer implements IAuthorizer {
#Profile("!localdev")
public class MyRealAuthorizer implements IAuthorizer {
But that does not honor the "Composite Root" pattern : (Mark Seeman http://blog.ploeh.dk/2011/07/28/CompositionRoot/ )
.......
So now I'm entering the world of DotNetCore. Everything has been going smooth. But I finally hit a situation where I need a dev-friendly concrete vs a non-dev "real" concretes.
Xml isn't available (to my best knowledge) with "Microsoft.Extensions.DependencyInjection".
I'm not sure of the best practice with DotNetCore in this situation.
I would prefer to honor the Composite Root pattern.
Basically, the below......but respecting the environments.
asp.net'ish
public void ConfigureServices(Microsoft.Extensions.DependencyInjection.IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
/* need this for "local-dev" */
services.AddScoped<IAuthorizer, MyDevLetEverythingThroughAuthorizer>();
/* need this for everything EXCEPT "local-dev" */
services.AddScoped<IAuthorizer, MyRealAuthorizer>();
}
(not asp.net) dot.net core'ish too
private static System.IServiceProvider BuildDi()
{
//setup our DI
IServiceProvider serviceProvider = new ServiceCollection()
.AddLogging()
/* need this for "local-dev" */
.AddSingleton<IAuthorizer, MyDevLetEverythingThroughAuthorizer>()
/* need this for everything EXCEPT "local-dev" */
.AddSingleton<IAuthorizer, MyRealAuthorizer>()
APPEND
This article and snipplet help me understand the "what is built in" portion a little better:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.2
Environments ASP.NET Core reads the environment variable
ASPNETCORE_ENVIRONMENT at app startup and stores the value in
IHostingEnvironment.EnvironmentName. You can set
ASPNETCORE_ENVIRONMENT to any value, but three values are supported by
the framework: Development, Staging, and Production. If
ASPNETCORE_ENVIRONMENT isn't set, it defaults to Production.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
if (env.IsProduction() || env.IsStaging() || env.IsEnvironment("Staging_2"))
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseMvc();
}
The
env.IsEnvironment("Staging_2") (akin to env.IsEnvironment("MyCustomValue") ) is the trick I guess.
APPEND:
This SOF question made it more clear for Asp.Net Core.
How to set aspnetcore_environment in publish file?
And ways you can set the environment variable without actually setting a (machine) environment variable!

public void ConfigureServices(IServiceCollection services, IHostingEnvironment environment) {
if (environment.IsDevelopment()) {
// bla bla bla
} else {
// bla bla bla
}
// register no-matter-which-environment services
}

Your question seems to be talking about two things: setting configuration from XML files and managing services using IServiceCollection. For .net core web applications, these happen in two stages:
A key value pair is consolidated from various pre-defined and custom sources (including json, XML, environment). All preset .net core web templates do this in program.cs.
The key value pair collection is sent to the Startup class that can be accessed via DI from the IConfiguration variable. Check this link for more information.
With this being the process, all config files are added before the ConfigureServices method is called in the Startup class. If you would like to add XML files to this configuration, you can set the following code in your program.cs:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
// additional code
.UseConfiguration(
new ConfigurationBuilder()
.AddXmlFile("file.xml", true) // second param is if file is optional
.Build()
)
// end of additional code
.UseStartup<Startup>();
If you want to access your environment, if they are set as environment variables, you can use one of the Environment.Get... functions.
Regarding your service, I am not sure in what way you are trying to access your XML, but you can always inject the IConfiguration as the simplest solution if you need to. However, I would advise against exposing your entire configuration to your services and have a look at setting up options with the help of this documentation

Related

ASP.Net Core Web Api - Use production and test database connection strings

I am building a web api with asp.net core and I wanted to ask how you would handle production and test environments inside the api. The goal would be to publish the web api on the IIS and then for it to use the production db connection string. When I start my api locally I would like my api to connect to the test database without me changing a lot of stuff in the code.
In my Startup.cs I use Entity Framework like this:
services.AddDbContextFactory<ProdContext>(options =>
{
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString("ProdDBConnection"));
options.UseSqlServer(decrypted,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
services.AddDbContextFactory<TestContext>(options =>
{
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString("TestDBConnection"));
options.UseSqlServer(decrypted,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
In the Configure method I see that you can differentiate between development and production but I can't quite imagine how the DbContexts can be integrated in the if statement:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
//Use Test Db globally
app.UseDeveloperExceptionPage();
}
else if(env.IsProduction())
{
//Use Prod Db globally
}
}
Also in every Controller I inject the Context that I need so to make this happen I would have to check on every endpoint if I am currently in development or production. Is there a more efficient way to do this? The 'lazy' approach would be to publish two instances of my api, one for prod and one for test where the code is changed accordingly.
You can inject IWebHostEnvironment to the Startup class by its constructor, which is the same one as you have among the parameters of Startup.Configure method. Then, you can use env.IsDevelopment() in the Startup.ConfigureServices method, where you set up DB context.
I wonder if you really want to different DB contexts in the development and production environment.
So, having a single context MyDbContext for both environments, Startup class becomes like below. Then, your controllers will get MyDbContext instance injected which connects to a different DB by the environment.
public class Startup
{
public IConfiguration Configuration { get; }
private readonly IWebHostEnvironment _env;
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options =>
{
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString(
_env.IsDevelopment() ? "TestDBConnection" : "ProdDBConnection"));
options.UseSqlServer(decrypted,
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
The Microsoft docs article Use multiple environments in ASP.NET Core contains an example:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
EDIT
I did not notice you have a DbContexts for each environment so I didn't take that into consideration in my answer. You should really just stick with one single DbContext instead of one for each environment unless you have a very good reason not to.
So, if you get rid of the "one DbContext per environment" idea you can just:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContextFactory<MyDbContext>(options =>
{
var cs = builder.Environment.IsDevelopment() ? "TestDBConnection" : "ProdDBConnection";
string decrypted = ConnStringSecurity.Decrypt(Configuration.GetConnectionString(cs));
options.UseSqlServer(decrypted,
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(5),
errorNumbersToAdd: null
);
});
});
And finallly in your controller you can just take a dependency on a single DbContext
class MyController
{
public MyController(MyDbContext dbContext) { ... }
}
EDIT 2
My solution is dotnet core 6.0 (net6.0) and above. For net5 go with yas-ikeda's answer instead.
firstly, its is not a good practice to store you database connection strings in configuration files where you push them into source code management (ex: github, devops, etc).
when you are developing and running your app locally, then you can make use of the "Secret.json" file to store sensitive data like db connection strings which will not be pushed to repository and will not even appear in git changes.
you can create this file by right clicking on the API project -> select "Manage user secrets". this will create a file in your pc. this is similar to appsetting.json but will be only available on your pc while developing locally.
you can set the db connection string like same as in appsetting.json as show below.
you can add all your sensitive key value pairs on the secrets.json when developing. the same keys in the appsetting.json will be overridden by secrets.json if they have the same key.
for production you can create application level variables by the same key value pair or use key-vault, or use app configuration if you are hosting in azure web app service.

React.NET .NET Core 3.1 Not passing props into component

I am using MVC ASP.NET Core 3.1 and React.NET and I am getting this issue.
When I render my component, the component renders, but the props are always null. It is almost as if the Html.React render method isn't properly passing the values over, please help!
I'm only going to add relevent code to the react (my startup.cs has more settings)
Startup.cs
public void ConfigureServices(IServiceCollection services) {
services.AddJsEngineSwitcher(options => options.DefaultEngineName = ChakraCoreJsEngine.EngineName).AddChakraCore();
services.AddReact();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseReact(config =>
{
// If you want to use server-side rendering of React components,
// add all the necessary JavaScript files here. This includes
// your components as well as all of their dependencies.
// See http://reactjs.net/ for more information. Example:
config
.AddScript("~/scripts/react_common/login.jsx");
config.SetLoadBabel(true);
});
}
index.cshtml (or any view, just trying to use this HTML extension helper)
#Html.React("Login", new
{
Test = "Test"
}, serverOnly: true)
login.jsx
class Login extends React.Component {
render() {
return <div>{this.props.Test}</div>
}
No matter what I do, it will never display "Test" for example. I need to know why it isn't passing the values into the props. I am starting to lose my mind over this problem, it worked just fine before I started migrating to .NET Core.
More details (Nuget Packages)
React.Asp.Net(5.1.2)
React.AspNet.Middleware(5.1.2)
Please help.
The default JSON serializer contract resolver is set to automatically convert it into camelCase (React). You have to over-ride this behavior if you want it to maintain the supplied case - in the Configure method in startup.cs:
app.UseReact(...
app.UseStaticFiles();
//Ensure to place this after the UseRact statement above
ReactSiteConfiguration.Configuration.JsonSerializerSettings.ContractResolver = new DefaultContractResolver();
Hopefully this helps someone else from going crazy

What is the proper place for db seeding from json files in ASP.NET Core 2.0?

In my Core 1.0 app, Startup ctor loaded several extra json files into the configuration...
public Startup(IHostingEnvironment env) {
Environment = env;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json")
.AddJsonFile($"activityGroups.{env.EnvironmentName}.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
Then in ConfigureServices, I grabbed the data from these out of the config into strongly typed lists...
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<Models.DbContext>(
options => options.UseSqlServer(
Configuration["Data:DefaultConnection:ConnectionString"]
)
);
services.AddScoped<Repository>();
services.AddTransient<DbSeed>();
services.AddOptions();
services.Configure<List<Models.Task>>(Configuration.GetSection("ActivityGroups"));
}
Then, in Configure, the strongly typed list was injected and use for db init..
public void Configure(
IApplicationBuilder app,
DbSeed seed,
ILoggerFactory logging,
IOptions<List<ActivityGroup>> activityGroupConfig
) {
app.UseAuthentication();
app.UseMvc();
bool seedDb;
bool.TryParse(Configuration.GetSection("SeedDb").Value, out seedDb);
if (seedDb) {
seed.EnsureSeeded(activityGroupConfig.Value);
}
}
However...
In 2.0 projects, move the SeedData.Initialize call to the Main method
of Program.cs...
As of 2.0, it's bad practice to do anything in BuildWebHost except
build and configure the web host. [configure web host includes data seeding?] Anything that's about running the
application should be handled outside of BuildWebHost — typically in
the Main method of Program.cs.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code
Yet, in "new" Main(string[] args) we don't have access to the dbcontext (until Startup.ConfigureServices). Even if in the SeedData.Initialize(services); call as above in the MS documentation, how would SeedData have access to the connection string that is not defined until later in configure services?
The adoption of this new 2.0 pattern is highly recommended and is
required for product features like Entity Framework (EF) Core
Migrations to work.
https://learn.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#update-main-method-in-programcs
I've been googling and digging through docs and samples (that example seeds with hardcoded values) without getting a clear picture (yet) of the expectations or recommended design (beyond, "dont do what you did previously".)
Anyone here more familiar or experienced with how to do database seeding from json on disk in ASP.NET Core 2.0?
So, with help from this SO question, I got the gist of getting the services from within Program.cs
public class Program {
public static void Main(string[] args) {
var host = BuildWebHost(args);
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var logger = services.GetRequiredService<ILoggerFactory>();
var ctx = services.GetRequiredService<DataContext>();
// Now I can access the DbContext, create Repository, SeedData, etc!
Now, the problem is in trying to figure out how to access values from appsettings.json, new SO question here.

Using Tempdata is crashing my application

I'm very new to ASP.NET and am attempting to pass an object between two controllers in a web application I'm making in Visual Studio 2015. The web application is using an ASP.Net 5 Preview Template Web application (if it helps, I think I'm using beta code 7 and I'm not building for DNX Core 5).
The problem I'm having is whenever I try to put anything into the TempData variable, the program seems to crash. For example, in a "Create" method I have:
[HttpPost]
public ActionResult Create(Query query)
{
switch (query.QueryTypeID)
{
case 1:
TempData["Test"] = "Test";
return RedirectToAction("Index", "EventResults");
case 2:
break;
default:
break;
}
return View();
}
In that method, I attempt to add a simple test string under the key "test". When I run the application with that TempData statement in there, I receive an error message stating
An unhandled exception occurred while processing the request.
InvalidOperationException: Session has not been configured for this application >or request.
Microsoft.AspNet.Http.Internal.DefaultHttpContext.get_Session()
I have tried going to the Web.config located in the wwwroot element of the project and adding a "sessionState" object into a "system.web" element, but this had no effect on the error.
Any help would be very much so appreciated as I've been looking for solutions for this everywhere. I'm hoping it's something stupid/blindingly obvious that I somehow missed.
In order to use middleware, such as Session, Cache, etc in ASP.NET 5, you have to enable them explicitly.
Enabling session is done by adding the appropriate nuget package in your project.json file's dependencies section (make sure that the package version matches the versions of the other dependencies you have added):
"Microsoft.AspNet.Session": "1.0.0-*"
and the appropriate session (cache) storage package as well (like the example below; in memory):
"Microsoft.Extensions.Caching.Memory": "1.0.0-*"
and adding the middleware to dependency resolution in the Startup.cs Service configuration:
public void ConfigureServices(IServiceCollection services)
{
services.AddCaching();
services.AddSession(/* options go here */);
}
and adding the middleware to OWIN in the Startup.cs OWIN configuration:
public void Configure(IApplicationBuilder app)
{
app.UseSession();
//...
Make sure that the UseSession comes before the MVC configuration.
For Asp.Net Core, make sure Asp.NetCore.Session is added.
You can configure session in StartUp.cs like below.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
// Adds a default in-memory implementation of IDistributedCache.
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.CookieHttpOnly = true;
});
}
public void Configure(IApplicationBuilder app)
{
app.UseSession();
app.UseMvcWithDefaultRoute();
}

Using Startup class in ASP.NET5 Console Application

Is it possible for an ASP.NET 5-beta4 console application (built from the ASP.NET Console project template in VS2015) to use the Startup class to handle registering services and setting up configuration details?
I've tried to create a typical Startup class, but it never seems to be called when running the console application via dnx . run or inside Visual Studio 2015.
Startup.cs is pretty much:
public class Startup
{
public Startup(IHostingEnvironment env)
{
Configuration configuration = new Configuration();
configuration.AddJsonFile("config.json");
configuration.AddJsonFile("config.{env.EnvironmentName.ToLower()}.json", optional: true);
configuration.AddEnvironmentVariables();
this.Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<Settings>(Configuration.GetSubKey("Settings"));
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationContext>(options => options.UseSqlServer(this.Configuration["Data:DefaultConnection:ConnectionString"]));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(minLevel: LogLevel.Warning);
}
}
I've tried to manually create the Startup class in my Main method, but this doesn't seem like the right solution and hasn't so far allowed me to configure the services.
I'm assuming there's some way for me to create a HostingContext that doesn't start up a web server but will keep the console application alive. Something along the lines of:
HostingContext context = new HostingContext()
{
ApplicationName = "AppName"
};
using (new HostingEngine().Start(context))
{
// console code
}
However so far the only way I can get this to work is if I set the HostingContext.ServerFactoryLocation to Microsoft.AspNet.Server.WebListener, which starts up the web server.
What you're looking for is the right idea, but I think you'll need to back up a moment.
Firstly, you may have noticed that your default Program class isn't using static methods anymore; this is because the constructor actually gets some dependency injection love all on its own!
public class Program
{
public Program(IApplicationEnvironment env)
{
}
public void Main(string[] args)
{
}
}
Unfortunately, there aren't as many of the services you're used to from an ASP.NET 5 hosting environment registered; thanks to this article and the IServiceManifest you can see that there's only a few services available:
Microsoft.Framework.Runtime.IAssemblyLoaderContainer
Microsoft.Framework.Runtime.IAssemblyLoadContextAccessor
Microsoft.Framework.Runtime.IApplicationEnvironment
Microsoft.Framework.Runtime.IFileMonitor
Microsoft.Framework.Runtime.IFileWatcher
Microsoft.Framework.Runtime.ILibraryManager
Microsoft.Framework.Runtime.ICompilerOptionsProvider
Microsoft.Framework.Runtime.IApplicationShutdown
This means you'll get the joy of creating your own service provider, too, since we can't get the one provided by the framework.
private readonly IServiceProvider serviceProvider;
public Program(IApplicationEnvironment env, IServiceManifest serviceManifest)
{
var services = new ServiceCollection();
ConfigureServices(services);
serviceProvider = services.BuildServiceProvider();
}
private void ConfigureServices(IServiceCollection services)
{
}
This takes away a lot of the magic that you see in the standard ASP.NET 5 projects, and now you have the service provider you wanted available to you in your Main.
There's a few more "gotchas" in here, so I might as well list them out:
If you ask for an IHostingEnvironment, it'll be null. That's because a hosting environment comes from, well, ASP.Net 5 hosting.
Since you don't have one of those, you'll be left without your IHostingEnvironment.EnvironmentName - you'll need to collect it from the environment variables yourself. Which, since you're already loading it into your Configuration object, shouldn't be a problem. (It's name is "ASPNET_ENV", which you can add in the Debug tab of your project settings; this is not set for you by default for console applications. You'll probably want to rename that, anyway, since you're not really talking about an ASPNET environment anymore.)

Categories