My ASP.NET core has this class which gets called first
public class Startup
{
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.AddDbContext<IssuerContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
And my context has this:
public class IssuerContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var connString = "Server=(localdb)\\mssqllocaldb;Database=HavenServer;ConnectRetryCount=0;Trusted_Connection=True;MultipleActiveResultSets=true\"";
optionsBuilder
.UseLoggerFactory(MyConsoleLoggerFactory)
.EnableSensitiveDataLogging(false)
.UseSqlServer(connString, options => options.MaxBatchSize(150));
base.OnConfiguring(optionsBuilder);
}
What is the expected SQLServer options configuration when seemingly overlapping options are defined in two locations?
It is explained in the Configuring a DbContext section of the documentation:
The DbContextOptions can be supplied to the DbContext by overriding the OnConfiguring method or externally via a constructor argument.
If both are used, OnConfiguring is applied last and can overwrite options supplied to the constructor argument.
In general, inside your OnConfiguring override you are supposed to check DbContextOptionsBuilder.IsConfigured property:
Gets a value indicating whether any options have been configured.
This can be useful when you have overridden OnConfiguring to configure the context, but in some cases you also externally provide options via the context constructor. This property can be used to determine if the options have already been set, and skip some or all of the logic in OnConfiguring.
E.g.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var connString = "Server=(localdb)\\mssqllocaldb;Database=HavenServer;ConnectRetryCount=0;Trusted_Connection=True;MultipleActiveResultSets=true\"";
optionsBuilder
.UseLoggerFactory(MyConsoleLoggerFactory)
.EnableSensitiveDataLogging(false)
.UseSqlServer(connString, options => options.MaxBatchSize(150));
}
base.OnConfiguring(optionsBuilder);
}
Generally, both options will be applied with the configuration from the "OnConfiguring" method being "applied in addition to configuration" from the "ConfigureServices" method. ConfigureServices is used to setup dependency injection for your DbContext so it will use those options as a constructor parameter. Any additional configuration done in the OnConfiguring method will be appended or override configuration from the StartUp class. However, in the example you have provided, you do not have a constructor in your DbContext, so configuration from the Startup class will not be used.
Docs
Related
i'm following Autofac guide to migrate to .net 3.1
According to their guide, I need to add (among other things) this function:
public void ConfigureContainer(ContainerBuilder builder)
And this will be called automatically
The problem is that the code inside it that is registering the services is conditional for our app so I need to pass a boolean to the function
for example:
public void ConfigureContainer(ContainerBuilder builder)
{
if (enableTokenAutoRefresh)
{
builder.RegisterType<AuthenticationWrapper>()
.As<IApiProxy>()
}
else
{
builder.RegisterType<ApiProxy>()
.As<IApiProxy>()
}
}
Can I just add a boolean to the ConfigureContainer method?
seems this will break the calling for it?
and if not - how to pass data to it ?
please help
ConfigureContainer, provided by the ASP.NET Core framework, only takes the container builder type of your DI framework (in this case, an Autofac ContainerBuilder). To get additional data in there, you'd need to set a variable on the Startup class somewhere earlier in the startup pipeline and use it.
// NOT a complete Startup, but gives you the idea.
public class Startup
{
public Startup(IConfiguration config)
{
// appSettings.json has the value you want
this.EnableTokenAutoRefresh = config.GetValue<bool>("path:to:key");
}
public bool EnableTokenAutoRefresh { get; set; }
public void ConfigureContainer(ContainerBuilder builder)
{
if(this.EnableTokenAutoRefresh)
{
// Do what you need to based on config
}
}
}
I know that one way to use a context is via the using statement.
I use it like so within my controllers
[ApiController]
public class MyController : ControllerBase
{
[HttpPost]
public ActionResult PostActionHere(ActionRequestClass request)
{
using (var context = new MyEntityFrameworkContext())
{
....
// use context here
context.SaveChanges()
....
}
}
}
I would like to start injecting it into my controller. Mainly because I think it is easier to read and is more uniform with .NET Core dependency injection.
[ApiController]
public class MyController : ControllerBase
{
private MyEntityFrameworkContext _myDb;
public MyController(MyEntityFrameworkContext myDb)
{
_myDb = myDb;
}
[HttpPost]
public ActionResult PostActionHere(ActionRequestClass request)
{
....
// use context here
_myDb.SaveChanges()
....
}
}
Within my startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyEntityFrameworkContext >(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyEntityFrameworkDatabase")));
}
What I am worried about is that injecting it I lose the disposal properties that come with the using statement. Is that true? Feel free to suggest alternate approaches.
injecting it I lose the disposal properties that come with the using statement. Is that true?
No:
The AddDbContext extension method registers DbContext types with a
scoped lifetime by default.
Configuring a DbContext
And when the scope (here the HttpRequest) ends, the Scoped Lifetime object will be Disposed.
With EF Core, DbContext is registered as Scoped by EF service extension. This is desirable because DbContext is not thread-safe and therefore it should be created per request.
ServiceStack IOC treats any Scoped registration in Startup as singleton, which contradicts with the point above.
One possible solution is to not use EF Core's service extension, but that seems to bring a lot of boilerplate code and reduce maintainability. Is there any better way?
--
UPDATE
I'd like to provide sample code for clarity
I added a private Guid to the DbContext class so that I can tell whether we have the new instance.
public class BloggingContext : DbContext
{
private readonly Guid _instance;
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
_instance = Guid.NewGuid();
}
public DbSet<Blog> Blogs { get; set; }
}
With .NET Core MVC, the controller code looks like
public class BlogsController : Controller
{
private readonly BloggingContext _context;
public BlogsController(BloggingContext context)
{
_context = context;
}
// skip for readability
}
For each request hitting the controller, the _instance inside BloggingContext returns an unique value. However, when using within a ServiceStack service, _instance always returns the same value.
public class BlogService : ServiceStack.Service
{
private readonly BloggingContext _context;
public BlogService(BloggingContext context)
{
_context = context;
}
// skip for readability
}
This behaviour is consistent with ServiceStack documentation about .NET Core Container Adapter that scoped dependencies registered in .NET Core Startup is singleton within ServiceStack. However, it is not desirable because we want DbContext to be created per request.
My solution is that I move the DbContext registration into AppHost code as below
public override void Configure(Container container)
{
container.AddScoped(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
return new BloggingContext(optionsBuilder.Options);
});
}
This code works as I expect. Every instance of BloggingContext injected into my BlogService is now unique. However, I find myself unable to use any service collection extension which is very handy in .Net Core Startup anymore. For example, I want to use Entity Framework Unit Of Work and I couldn't call
services
.AddUnitOfWork<BloggingContext>();
Instead, I have to wire up all dependencies of that library myself like
public override void Configure(Container container)
{
container.AddScoped(c =>
{
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
return new BloggingContext(optionsBuilder.Options);
});
container.AddScoped<IRepositoryFactory, UnitOfWork<BloggingContext>>();
container.AddScoped<IUnitOfWork, UnitOfWork<BloggingContext>>();
container.AddScoped<IUnitOfWork<BloggingContext>, UnitOfWork<BloggingContext>>();
}
You should be able to register it in .NET Core's IOC like any .NET Core App:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<BloggingContext>(options =>
options.UseSqlite("Data Source=blog.db"));
}
Then reference like a normal dependency in your ServiceStack Services:
public class MyServices : Service
{
public BloggingContext BloggingContext { get; set; }
}
Which uses ServiceStack's .NET Core Container Adapter to resolve any dependencies not in ServiceStack's IOC, in .NET Core's IOC.
As known, IApplicationBuilder of the method Configure (class Startup) in ASP.NET Core requires specific semantics (to have method 'Invoke' with input parameter of HttpContext type and Task as return value). But why it's not implemented as interface? I can write something like it:
public class FakeMiddleware
{
}
and register it:
app.UseMiddleware<FakeMiddleware>();
and we'll get an runtime error. Of course, it's trivial thing and easy to be found and fix, but it's implemented so rough, without interface?
The Invoke method is flexible and you can ask for additional parameters. ASP.NET will inject the additional parameters using the application's service configuration.
public async Task Invoke(HttpContext ctx,
IHostingEnvironment host,
ISomethingElse service)
{
// ...
}
C# interface definitions can't provide this flexibility in a nice way.
Since AspNetCore2.0 you can set middleware which implements interface IMiddleware.
public class InterfaceMiddleware : IMiddleware
{
private InterfaceMiddlewareOptions _opts;
public InterfaceMiddleware(IOptions<InterfaceMiddlewareOptions> opts)
{
_opts = opts.Value;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await context.Response.WriteAsync(_opts.Message);
}
}
In addition to app.UseMiddleware<InterfaceMiddleware>(). You need to register your middleware in DI(singleton lifetime is not required).
public void ConfigureServices(IServiceCollection services)
{
services.Configure<InterfaceMiddlewareOptions>(opts =>
{
opts.Message = "IMiddleware interface is implemented";
});
services.AddSingleton<InterfaceMiddleware>();
}
Structuremap defines a 'BuildUp' method that takes an already-constructed object and performs setter injection to push in configured dependencies into that object.
Does Autofac have an equivalent method ?
The question was referring to already-constructed objects (ones not registered in the container) so the correct answer is either InjectProperties or InjectUnsetProperties.
The following test demonstrates the behaviour
public class TestPropertyInjection
{
public object ShouldBeInjectedByAutofac { get; set; }
}
[Fact]
public void Autofac_can_inject_properties()
{
var builder = new ContainerBuilder();
builder.RegisterType(typeof(object));
var container = builder.Build();
var existingObjectNotRegisteredInContainer = new TestPropertyInjection();
container.InjectProperties(existingObjectNotRegisteredInContainer);
// can also use InjectUnsetProperties to only set unset properties
//container.InjectUnsetProperties(existingObjectNotRegisteredInContainer);
Assert.NotNull(existingObjectNotRegisteredInContainer.ShouldBeInjectedByAutofac);
}
Try InjectUnsetProperties.
Sample:
public class YourModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<YourClass>().OnActivated(e => e.Context.InjectUnsetProperties(e.Instance));
}
}
In more recent versions of Autofac you can simply use:
builder.RegisterType<YourClass>().PropertiesAutowired();