How to use separate connection strings for Application and Migrations? - c#

I am trying to get an ASP.NET Core 2 with EntityFramework Core 2.0 application up and running. As part of it, also looking to start using Migrations to manage data model changes.
Here is my appsettings.json file where I am storing connection strings. For keeping things simple here, I left the user/pwd open. In real scenario, encryption will be used. Main goal is to use two separate connection strings. One for Application usage, where the user account TestAppServiceAccount will be used only to perform reads/writes (DML operations only). Another one called DbChangeServiceAccount for applying migrations (DML + DDL operations).
{
"SqlServerMigrationsConnection": "Server=SqlServerInstance;Database=TestDb;User Id=DbChangeServiceAccount; Password=xyz$123;",
"SqlServerAppConnection": "Server=SqlServerInstance;Database=TestDb;User Id=TestAppServiceAccount; Password=xyz$123;"
}
Here is how my Startup.cs is setup. Based on my understanding, looks like both application and Migrations are going to depend on the same connection string that is passed in startup.cs to AddDbContext method.
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();
var userServiceSqlConnection = Configuration["SqlServerAppConnection"];
services.AddDbContext<UserContext>(optiopns => optiopns.UseSqlServer(userServiceSqlConnection));
}
// 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();
}
app.UseMvc();
}
}
Just wondering how do I pass a different connection strings, one that will be used purely for application and another one for just applying migrations?

After posting this question, I realized that, I could make use of multi environment setup.
So for migrations I will have a separate environment that will be used to manage CI/CD activities. I feel like this is a deployment concern,
So I could simply create appsettings.migrations.json or something similar and fall back to use just one connection string for both Application and Migrations. And my Startup.cs AddDbContext parameters will stay same.
My appsettings.development.json will look like
{
"SqlServerAppConnection": "Server=SqlServerInstance;Database=TestDb;User Id=TestAppServiceAccount; Password=xyz$123;"
}
My appsettings.migrations.json will look like
{
"SqlServerAppConnection": "Server=SqlServerInstance;Database=TestDb;User Id=DbChangeServiceAccount; Password=xyz$123;"
}
How to manage multiple environments in asp.net core from Microsoft has more details.

Related

AspNet Core AttributeRouting does not discover routes if Startup is in different assembly

I have a scenario where we have a "standardised startup" for many small AspNet Core websites.
A seemingly obvious solution to achieve this is to refactor the Startup.cs class into a separate common assembly (as Infrastructure.Web.Core.Startup). We then have each small AspNet Core website reference it the common assembly and use that startup class instead:
public static Microsoft.AspNetCore.Hosting.IWebHostBuilder CreateWebHostBuilder( string[] args )
{
return new WebHostBuilder()
.UseKestrel()
.ConfigureServices( collection => { } )
.UseContentRoot( System.IO.Directory.GetCurrentDirectory() )
.UseStartup<Infrastructure.Web.Core.Startup>(); //.UseStartup<Startup>();
}
Somehow, this breaks attribute routing in the sense that the routes are not hit. No errors, but not routing. The moment I copy the class back into the website project (with the exact same code) it works again.
As a test, if I wrap the Startup.cs class in the common library in a local startup class (like below), it also works:
public class Startup
{
private readonly Infrastructure.Web.Core.Startup _startup;
public Startup( IConfiguration configuration )
{
_startup = new Infrastructure.Web.Core.Startup( configuration );
}
public IConfiguration Configuration => _startup.Configuration;
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices( IServiceCollection services )
{
_startup.ConfigureServices( services );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IHostingEnvironment env )
{
_startup.Configure( app, env );
}
}
If I had to take a guess, it's probably something to do with the Dependency Injection.
Does anyone have any suggestions?
FYI: It's using typical AspNet Core 2.1 projects
UPDATE
Incidentally, if I use inheritance it also works but the derived class must be in the same project as the website. I guess it seems obvious, but thought I include that information for completeness sake:
public class Startup : Infrastructure.Web.Core.Startup
{
public Startup( IConfiguration configuration ) : base(configuration)
{
}
}
You can fix this by adding the following statement to your services in your Startup.cs method.
services.AddApplicationPart(typeof(AnTypeInTheOtherAssembly).Assembly);
This will tell the View/Controller Discovery to also check for the new location. Your Project which contains the Startup.cs file would be the Startup Project, and all the others would be just references and libraries or similar.
As of .Net Core 3 you can use something called Razor Class Libraries, see the MSDN. This will automatically add this your Controllers and Views to the discovery, it also has debugging support and will work just as a normal Class Library would.

How to configure IdentityOptions outside ConfigureServices?

I want to configure ASP.NET Core Identity based on settings which resides in database rather than AppSetting.json or hard coded values. Consequently, I'am eager to call following line of code outside of method ConfigureServices(IServiceCollection services):
services.Configure<IdentityOptions>(x => x.Password.RequireDigit = true);
This way of calling will allow me to initialize DbContext before trying to configure Identity.
Currently I'm using services.BuildServiceProvider() in the ConfigureServices() to access database values. This style has a huge disadvantage for me: It puts an extra initialization on the application's DbContext which is dirty and slow. In the other hand, DbContext is instantiated two times instead of one.
If I was able to call services.Configure<IdentityOptions>() outside the ConfigureServices(), for example in the configure() method, I would be able to configure Identity options based on database values without initializing DbContext twice.
Again, my question is how to configure IdentityOptions outside ConfigureServices?
Any help is appreciated.
I ended up with injecting IOptions<IdentityOptions> options to the Configure() method as what follows:
public virtual void Configure(IApplicationBuilder app, IOptions<IdentityOptions> options)
{
options.Value.Password.RequireDigit = true;
//rest of configurations...
}
And it worked!
Thanks to #Kirk for the link.

why IOptions is getting resolved even if not registered

In my .NET Core project, I have below settings in Configure method:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//services.AddOptions<UploadConfig>(Configuration.GetSection("UploadConfig"));
}
I have not registerd any IOptions and I am injecting it in a controller
[Route("api/[controller]")]
[ApiController]
public class HelloWorldController : ControllerBase
{
public HelloWorldController(IOptions<UploadConfig> config)
{
var config1 = config.Value.Config1;
}
}
The IOptions is getting resolved with the default instance, and I get to know error only when I am trying to use it (and when I expect the value to be not null).
Can I somehow get it to fail, stating the instance type is not registered or something similar? I just want to catch errors as early as possible.
The options framework is set up by the default host builder as part of its setup, so you do not need to AddOptions() yourself. This however also ensures that you can use IOptions<T> wherever you want since the framework will provide that exact options object for you.
The way options work is that the framework will always give you a T (as long as it can construct one). When you do set up configuration using e.g. AddOptions<T> or Configure<T>, what actually happens is that a configuration action gets registered for that type T. And when an IOptions<T> is later resolved, all those registered actions will run in the sequence they are registered.
This means that it’s valid to not have configured an options type. In that case, the default values from the object will be used. Of course, this also means that you are not able to detect whether you have actually configured the options type and whether the configuration is actually valid. This usually has to be done when you use the values.
For example, if you require Config1 to be configured, you should explicitly look for it:
public HelloWorldController(IOptions<UploadConfig> config)
{
if (string.IsNullOrEmpty(config.Value.Config1))
throw ArgumentException("Config1 is not configured properly");
}
An alternative would be to register a validation action for a type using OptionsBuilder.Validate. This will then be called automatically when you resovle the options object to validate the containing value. That way, you can have the validation set up in a central location:
services.AddOptions<UploadConfig>()
.Bind(Configuration.GetSection("UploadConfig"))
.Validate(c => !string.IsNullOrEmpty(c.Config1));
Unfortunately, this also means that you can only detect these problems when you actually use the values, which can be missed if you are not testing your application thoroughly. A way around this would be to resolve the options once when the application starts and validate them there.
For example, you could just inject your IOptions<T> within your startup’s Configure method:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IOptions<UploadConfig> uploadOptions)
{
// since the options are injected here, they will be constructed and automatically
// validated if you have configured a validate action
// …
app.UseMvc();
}
Alternatively, if you have multiple options you want to validate and if you want to run logic that does not fit into the validation action, you could also create a service that validates them:
public class OptionsValidator
{
private readonly IOptions<UploadConfig> uploadOptions;
public OptionsValidator(IOptions<UploadConfig> uploadOptions)
{
_uploadOptions = uploadOptions;
}
public void Validate()
{
if (string.IsNullOrEmpty(_uploadOptions.Value.Config1))
throw Exception("Upload options are not configured properly");
}
}
And then inject that in your Configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, OptionsValidator optionsValidator)
{
// validate options explicitly
optionsValidator.Validate();
// …
app.UseMvc();
}
Whatever you do, keep also in mind that by default the configuration sources are configured to support updating the configuration at run-time. So you will always have a situation in which a configuration can be invalid temporarily at run-time.
Great answer from poke, I just wanna complete it with this that you can fail-fast in your startup file when no configuration is provided:
public class MyOptions
{
public string MyValue { get; set; }
}
public void ConfigureServices(IServiceCollection services)
{
var options = Configuration.GetSection("MyOptions").Get<MyOptions>();
if (string.IsNullOrWhiteSpace(options?.MyValue))
{
throw new ApplicationException("MyValue is not configured!");
}
}
IOptions configuration values are read lazily. Although the
configuration file might be read upon application startup, the
required configuration object is only created when IOptions.Value is
called for the first time.
When deserialization fails, because of application misconfiguration,
such error will only appear after the call to IOptions.Value. This can
cause misconfigurations to keep undetected for much longer than
required. By reading -and verifying- configuration values at
application startup, this problem can be prevented.
This articles also helps you to get the idea:
Is IOptions Bad?
ASP.NET Core 2.2 – Options Validation

How do I set up 'connectionString' for a mySQL database for Hangfire?

I'm trying to integrate hangfire into my .NET core web app. I have followed the instructions on the Hangfire quickstart by installing all necessary packages. I also installed an extension called Hangfire MySql and installed the necessary packages for it.
Step 1 says to 'Create new instance of MySqlStorage with connection string constructor parameter and pass it to Configuration with UseStorage method:'
GlobalConfiguration.Configuration.UseStorage(
new MySqlStorage(connectionString));
Also it is noted that 'There must be Allow User Variables set to true in the connection string. For example: server=127.0.0.1;uid=root;pwd=root;database={0};Allow User Variables=True'
so my current code for Hangfire inside the 'Configure' service within my Startup.CS file is this:
Hangfire.GlobalConfiguration.Configuration.UseStorage(
new MySqlStorage(connectionString));
app.UseHangfireDashboard();
app.UseHangfireServer();
however MySqlStorage returns the error ''MySqlStorage' does not contain a constructor that takes 1 arguments'
Looking at the readMe for Hangfire mySQL if I use and define my connectionString to
e.g.
connectionString = "server=127.0.0.1;uid=root;pwd=root;database={0};Allow User Variables=True"
GlobalConfiguration.Configuration.UseStorage(
new MySqlStorage(
connectionString,
new MySqlStorageOptions
{
TablesPrefix = "Hangfire"
}));
the application will say there are no errors but I still get an error on startup.
I've tried entering a connection string but nothing that I enter seems to work. Every time I launch the application i get the error:
"crit: Microsoft.AspNetCore.Hosting.Internal.WebHost[6]
Application startup exception
System.InvalidOperationException: Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddHangfire' inside the call to 'ConfigureServices(...)' in the application startup code.
at Hangfire.HangfireApplicationBuilderExtensions.ThrowIfNotConfigured(IApplicationBuilder app)
at Hangfire.HangfireApplicationBuilderExtensions.UseHangfireDashboard(IApplicationBuilder app, String pathMatch, DashboardOptions options, JobStorage storage)
at Alerts.API.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) in /Users/Admin/Desktop/Code Projects/Alerts/Alerts.API/Startup.cs:line 178"
Wondering if someone could give me an example of how to set up Hangfire with a mySqlStorage connection that launches and let's me view the Hangfire dashboard.
References: https://github.com/arnoldasgudas/Hangfire.MySqlStorage
Hangfire: http://docs.hangfire.io/en/latest/quick-start.html
Based on the exception details, it seems that first you need to configure the Hangfire service before be able to call app.UseHangfireDashboard().
In your Startup.cs file you should have a ConfigureServices(IServiceCollection services) method, it seems that you must do the setup here instead of using the GlobalConfiguration class, so you can try this:
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(configuration => {
configuration.UseStorage(
new MySqlStorage(
"server=127.0.0.1;uid=root;pwd=root;database={0};Allow User Variables=True",
new MySqlStorageOptions
{
TablesPrefix = "Hangfire"
}
)
);
};
}

ASP.NET 5 Template crashes on first run

I'm starting a new project in VS 2015.
File -> New -> Project -> ASP.NET Web Application -> ASP.NET 5 Templates -> Web API
A project is initialized. I would assume that if I run the project with IIS Express a service would be available.
It runs through the startup methods.
public class Startup
{
public Startup(IHostingEnvironment env)
{
// Set up configuration sources.
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.UseMvc();
}
// Entry point for the application.
public static void Main(string[] args) =>
WebApplication.Run<Startup>(args);
}
}
But then it crashes. I don't know how to implement global error handling.
I looked at this example.
But when I try to use System.Net.Http or System.Web.Http.ExceptionHandling they can't be found.
I also noticed that through intellisense it says Core 5.0 is no available.
Here is my project.json as requested.
{
"version":"1.0.0-*",
"compilationOptions":{
"emitEntryPoint":true
},
"dependencies":{
"Microsoft.AspNet.IISPlatformHandler":"1.0.0-rc1-final",
"Microsoft.AspNet.Mvc":"6.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel":"1.0.0-rc1-final",
"Microsoft.AspNet.StaticFiles":"1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.FileProviderExtensions":"1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.Json":"1.0.0-rc1-final",
"Microsoft.Extensions.Logging":"1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Console":"1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Debug":"1.0.0-rc1-final"
},
"commands":{
"web":"Microsoft.AspNet.Server.Kestrel"
},
"frameworks":{
"dnx451":{
"frameworkAssemblies":{
"System.Web":"4.0.0.0"
}
},
"dnxcore50":{
}
},
"exclude":[
"wwwroot",
"node_modules"
],
"publishExclude":[
"**.user",
"**.vspscc"
]
}
Try to open Visual Studio Administrator Mode
I guess it depends on what is crashing - it's not clear from your description what crashes, when it crashes and how it crashes.
You can use UseExceptionHandler and UseDeveloperExceptionPage extension methods to configure an error handling page. This article describes it in more details.
If the exception happens during startup you may need to use UseCaptureStartupErrors extension method (it was recently renamed to CaptureStartupErrors).
Also, you already have logging enabled - the logs may also have some useful information. If you can't see logs because you log to the console consider logging to a file.
If this is IIS/IISExpress crashing check event log.
What is your runtime version ?
Maybe you can try scaffolding your application with the AspNet Yeoman generator and compare the files.
Personally I prefer to use the scaffolder as it is often up to date.
Hope this helps !

Categories

Resources