When I try to run a background task, I always create a new scope inside that task. With the update to 3+, it seems that within the new create scope, there is a reference to the original request. The following code would break on the Debugger.Break() statement:
public class TestController : Controller
{
public readonly IServiceScopeFactory ServiceScopeFactory;
public TestController(
IServiceScopeFactory serviceScopeFactory)
{
this.ServiceScopeFactory = serviceScopeFactory;
}
// GET
public IActionResult Index()
{
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (actionContext.ActionDescriptor != null)
Debugger.Break();
}
});
return Content("Test");
}
}
The startup looks like this:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
The problem is that httpContext is shared with the new create scope. When one of the scopes is being disposed of, it affects the other scope. For example with IUrlHelper, which results in an "IFeatureCollection has been disposed of".
For test sake, I added a test if the httpContext would be the same. And it seems it is!
public IActionResult Index()
{
// Just for testing
var originalContext = this.HttpContext;
Task.Run(() =>
{
using (var scope = ServiceScopeFactory.CreateScope())
{
// Make sure the original request was disposed
Thread.Sleep(1000);
var actionContextAccessor = scope.ServiceProvider.GetService<IActionContextAccessor>();
var actionContext = actionContextAccessor.ActionContext;
if (originalContext == actionContext.HttpContext)
Debugger.Break();
}
});
return Content("Test");
}
For me, this seems like odd behaviour, cause I would except the new scope not to have the same httpContext. It should be a NEW scope. Should the scope be created in another way?
Found solution
In my production code I use a transient ActionContext scope, which attempt to detect if it's dealing with a request, or a background scope as followed:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
var actionContext = actionContextAccessor?.ActionContext;
// Create custom actioncontext
if (actionContext == null) {
// create a manual actionContext
}
return actionContext;
});
This doesn't seem to work anymore. The solution seems to be too validate if the httpContext exist through the IHttpContextAccessor:
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
.AddTransient<ActionContext>((s) => {
var currentContextAccess = serviceProvider.GetService<IHttpContextAccessor>();
if (currentContextAccess.HttpContext == null) {
// create a manual actionContext
...
return actionContext;
}
var actionContextAccessor = serviceProvider.GetRequiredService<IActionContextAccessor>();
return actionContextAccessor.ActionContext;
});
For me, this seems like odd behaviour, cause I would except the scope not to be. Should the scope be created in another way?
Why odd? IActionContextAccessor is a singleton (same as IHttpContextAccessor is), so its normal to return the same instance even inside a newly created scope.
Since you are not awaiting, the Task.Run, your request will finish before the task is finished. How do you want to access the HttpContext after the request is done? It's only valid during the request. You have to get all the required data prior to spinning up the new Task and pass the values you need to the background task.
HttpContext is only valid for the duration of the request and since you dont await it, request ends early.
And what your code does is undefined behavior, see David's Guidelines
Do not access the HttpContext from multiple threads in parallel. It is not thread safe.
Do not use the HttpContext after the request is complete
Related
I'm trying to register a singleton class, providing the constructor parameters in Startup.ConfigureServices method.
After several tries, I'm still not able to make the dbContext injection working
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddDbContext<EFContext>();
services.AddSingleton<OPCClient>(x =>
{
string endpointURL = "opc.tcp://xxx.yyy.zzz.nnn:12345";
bool autoAccept = false;
int stopTimeout = Timeout.Infinite;
var efContext = x.GetService<EFContext>();
OPCClient client = new OPCClient(endpointURL, autoAccept, stopTimeout, efContext);
client.Run();
return client;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// warmup
app.ApplicationServices.GetService<OPCClient>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<OPCService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
When var efContext = x.GetService<EFContext>(); is executed, I'm getting the exception
System.InvalidOperationException: 'Cannot resolve scoped service 'EFContext' from root provider.'
Thanks for any help in injecting the DbContext in OPCClient class
It is not a good choice to use a scoped service (the EFContext) inside a singleton.
The DI container creates a new instance of a scoped service for every request, while it creates a singleton only once and this can lead to inconsistent states for your objects. Documentation here
I suggest to change the lifetime of OPCClient to scoped - using services.AddScoped instead of services.AddSingleton. If you cannot do this, pass a reference of IServiceProvider rather than EFContext and resolve that service from the container each time you need to use it:
public class OPCClient
{
private IServicePrivder _serviceProvider;
public OPCClient (IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void DoSomething() {
EfContext efContext = _serviceProvider.GetRequiredService<EfContext>();
}
}
I am having trouble seeding data into the identity role table. I always get the error
System.NullReferenceException: 'Object reference not set to an
instance of an object.'
<>4__this._roleManager was null
I am not sure why this is happening and why it's not seeding data into the table.How do I fix this?
Below is my code
public class UserRoleSeed
{
private readonly RoleManager<IdentityRole> _roleManager;
private string[] _roleArray = { "Admin, TerminalManager, Dispatcher, Driver, Mechanic, Recruiter, MechanicManger" };
public UserRoleSeed(RoleManager<IdentityRole> roleManager)
{
_roleManager = roleManager;
}
public async void Seed()
{
foreach (string index in _roleArray)
{
if ((await _roleManager.FindByNameAsync(index)) == null)
{
await _roleManager.CreateAsync(new IdentityRole { Name = index });
}
}
}
}
for my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<TransportDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
services.AddIdentity<ApplicationUser, IdentityRole<int>>()
.AddEntityFrameworkStores<TransportDbContext>()
.AddDefaultTokenProviders();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
//app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(
routes =>
{
routes.MapRoute("Default", "{controller=Home}/{action=Index}/{id?}");
});
}
// seeds data into the identity role table
new UserRoleSeed(app.ApplicationServices.GetService<RoleManager<IdentityRole>>()).Seed();
}
}
}
You're using an async method to seed your roles, but you're not awaiting it. That means that your code keeps moving on, eventually taking variables you're depending on in your async method along with it when branches go out of scope. Hence, NullReferenceExceptions.
Additionally, services like RoleManager<TRole> are "scoped" services, meaning they can only be retrieved from a particular active scope. In an actual request, a scope would be created for the request, allowing these services to be injected into anything within the request pipeline. However, here, you have no active scope, and therefore must create one.
Instead of attempting to seed as part of your Configure method, you should move this code out into your Program class. The code below addresses both of the above concerns:
public class Program
{
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
await new UserRoleSeed(scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>()).Seed();
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Essentially you'll use an async Main to run your app, which then gives you the ability to await additional things like your seed. For what it's worth, this can be shortened somewhat in C# 7.2 with an actual async Main, i.e.:
public static async Task Main(string[] args)
Without having to proxy from Main to a MainAsync, but under the hood the compiler just sets up this same construction for you.
That's the shortest path to get this code working, but you still have a couple of minor issues. First, you should avoid using async void, which is an antipattern. You're essentially swallowing the async output with that, including any exceptions that may be thrown. You should virtually always use async Task as the return when you don't care about the actual return. The few situations where async void is appropriate are known to individuals who need to use it. In other words, if you don't know when you should use async void, then you shouldn't be using async void.
Also, while there's nothing technically wrong with newing up a class and passing the dependency into the constructor, it's more appropriate in this case to make the class static and pass the required dependencies into the seed method:
await UserRoleSeed.Seed(roleManager);
Finally, again, while not critical, it's convention to name async methods with an Async suffix. This makes it clear that the method is async and prevents accidentally not awaiting the method simply because it's not obvious that it needs to be awaited (which may have been the case here). In short, change the name from Seed to SeedAsync, since it does async work.
Ok guys I figured it out, Below is my solution.
I basically modified the class for seeding the data and renamed it DbInitializer.cs
public class DbInitializer
{
private static readonly string[] _roleArray = { "Admin", "Terminal Manager", "Dispatcher", "Driver", "Mechanic", "Recruiter", "Mechanic Manger" };
public static async Task InitializeAync (TransportDbContext context, IServiceProvider serviceProvider)
{
var roleManager = serviceProvider.GetRequiredService<RoleManager<Role>>();
foreach (string index in _roleArray)
{
if ((await roleManager.FindByNameAsync(index)) == null)
{
await roleManager.CreateAsync(new Role { Name = index });
}
}
}
}}
then I called the function in my Program.cs file as suggested by #Chris Pratt.
public class Program
{
public static void Main(string[] args) =>
MainAsync(args).GetAwaiter().GetResult();
public static async Task MainAsync(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<TransportDbContext>();
await DbInitializer.InitializeAync(context, services);
}
await host.RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
} }
thanks to everyone who tried to help me
I have a simple service that contains a List<Foo>. In Startup.cs, I am using the services.addScoped<Foo, Foo>() method.
I am inject the service instance in two different places (controller and middleware), and for a single request, I would expect to get the same instance. However, this does not appear to be happening.
Even though I am adding a Foo to the List in the Controller Action, the Foo list in the Middleware is always empty. Why is this?
I have tried changing the service registration to a singleton, using AddSingleton() and it works as expected. However, this has to be scoped to the current request. Any help or ideas are greatly appreciated!
FooService.cs
public class FooService
{
public List<Foo> Foos = new List<Foo>();
}
Startup.cs
...
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<FooService, FooService>();
}
[Below are the two places where I am injecting the service, resulting in two different instances]
MyController.cs
public class MyController : Controller
{
public MyController(FooService fooService)
{
this.fooService = fooService;
}
[HttpPost]
public void TestAddFoo()
{
//add foo to List
this.fooService.Foos.Add(new Foo());
}
}
FooMiddleware.cs
public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
this.next = next;
this.serviceProvider = serviceProvider;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
var fooService = this.serviceProvider.GetService(typeof(FooService)) as FooService;
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
That's because when you inject IServiceProvider into your middleware - that's "global" provider, not request-scoped. There is no request when your middleware constructor is invoked (middleware is created once at startup), so it cannot be request-scoped container.
When request starts, new DI scope is created, and IServiceProvider related to this scope is used to resolve services, including injection of services into your controllers. So your controller resolves FooService from request scope (because injected to constructor), but your middleware resolves it from "parent" service provider (root scope), so it's different. One way to fix this is to use HttpContext.RequestServices:
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(() =>
{
var fooService = context.RequestServices.GetService(typeof(FooService)) as FooService;
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
But even better way is to inject it into Invoke method itself, then it will be request scoped:
public async Task Invoke(HttpContext context, FooService fooService)
{
context.Response.OnStarting(() =>
{
var fooCount = fooService.Foos.Count; // always equals zero
return Task.CompletedTask;
});
await this.next(context);
}
First of all you shouldn't be using GetService, use the proper DI system that is in place by passing it into the Invoke method as a parameter.
Secondly, the reason you are getting a different object is because the constructor of the middleware is called outside of the scope of any request, during the app initialisation phase. So the container used there is the global provider. See here for a good discussion.
public class AppMessageMiddleware
{
private readonly RequestDelegate _next;
public AppMessageMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
_next = next;
}
//Note the new parameter here: vvvvvvvvvvvvvvvvvvvvv
public async Task Invoke(HttpContext context, FooService fooService)
{
context.Response.OnStarting(() =>
{
var fooCount = fooService.Foos.Count;
return Task.CompletedTask;
});
await _next(context);
}
}
I have a AspNetCore web app that writes to EventHub and a webjob that reads from it. I'd like the telemetry from both parts of this transaction to have the same operation id in Application Insights.
So, when I'm about to send the data to EventHub I try to pull the operation id out of the TelemetryClient, e.g.
var myOperationId = MyTelemetryClient.Context.Operation.Id;
But this always gives me null. I found this article and tried using
var request.HttpContext.Items["Microsoft.ApplicationInsights.RequestTelemetry"] as RequestTelemetry;
But again null. Any pointers on a way I can extract this value when I need it?
My code looks like this:
public class Startup
{
public void ConfigureServices( IServiceCollection IServices )
{
var builder = TelemetryConfiguration.Active.TelemetryProcessorChainBuilder;
builder.Use((next) => new MyTelemetryProcessor(next));
builder.Build();
var aiOptions = new Microsoft.ApplicationInsights.AspNetCore.Extensions.ApplicationInsightsServiceOptions();
aiOptions.EnableQuickPulseMetricStream = true;
IServices.AddApplicationInsightsTelemetry( Configuration, aiOptions);
IServices.AddMvc();
IServices.AddOptions();
TelemetryClient AppTelemetry = new TelemetryClient();
AppTelemetry.InstrumentationKey = InsightsInstrumentationKey;
IServices.AddSingleton(typeof(TelemetryClient), AppTelemetry);
}
public void Configure( IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory )
{
app.UseApplicationInsightsRequestTelemetry();
app.UseApplicationInsightsExceptionTelemetry();
app.UseMvc();
var configuration = app.ApplicationServices.GetService<TelemetryConfiguration>();
configuration.TelemetryInitializers.Add(new MyTelemetryInitializer());
}
}
[Route("[controller]")]
public class MyController
{
private readonly TelemetryClient mTelemetryClient;
public MyController(
TelemetryClient TelemetryClientArg)
{
mTelemetryClient = TelemetryClientArg;
}
[HttpPost]
public async Task<IActionResult> Post([FromBody]MyPostDataClass MyPostData)
{
string telemetryId = mTelemetryClient.Context.Operation.Id; // this is null
return Ok();
}
}
I did not have OperationIdTelemetryInitializer in my TelemetryConfiguration .Active.TelemetryInitializers.
But this provides me with the current operation id:
System.Diagnostics.Activity.Current.RootId
https://github.com/Microsoft/ApplicationInsights-aspnetcore/issues/504
Think I finally cracked this without creating unwanted telemetry. The following is for AspNetCore, but should translate as long as the operation id initializer is available:
var operationId = default(string);
try
{
var telemetry = new RequestTelemetry();
TelemetryConfiguration
.Active
.TelemetryInitializers
.OfType<OperationIdTelemetryInitializer>()
.Single()
.Initialize(telemetry);
operationId = telemetry.Context.Operation.Id;
}
catch { }
Asp.Net core package has an OperationIdTelemetryInitializer (see github) which tries to set this from a request. Hypothetically if you have request tracking and this telemetry initializer turned on, everything that happens in that request should have an Operation.Id set.
if that isn't working for you, you could create your own custom TelemetryInitializer to set it however you need, or, in the place where you do find it to be null, you could just set it there, and all things in that context should then have that id.
You can get this value by sending a piece of telemetry and then reading back the operation id from the telemetry object. Not elegant, but it works.
[HttpPost]
public async Task<IActionResult> Post([FromBody]MyPostDataClass MyPostData)
{
var dummyTrace = new TraceTelemetry("hello", SeverityLevel.Verbose);
mTelemetryClient.TrackTrace(dummyTrace);
string opId = dummyTrace.Context.Operation.Id;
return Ok();
}
I wrote my custom middleware which I add in
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseAutologin();
app.UseMvc(routes =>
{
//...
So it is the last middleware before the Mvc comes into play.
In my middleware's Invoke method I want to (indirectly) access the DbContext.
public async Task Invoke(HttpContext context)
{
if (string.IsNullOrEmpty(context.User.Identity.Name))
{
var applicationContext = _serviceProvider.GetService<ApplicationDbContext>();
var signInManager = _serviceProvider.GetService<SignInManager<ApplicationUser>>();
var result = await signInManager.PasswordSignInAsync(_options.UserName, _options.Password, true, false);
}
await _next(context);
}
Nearly every time I get the following exception:
InvalidOperationException: An attempt was made to use the context
while it is being configured. A DbContext instance cannot be used
inside OnConfiguring since it is still being configured at this point.
Now this is clearly raised by the PasswordSignInAsync method. But how can I ensure that the model was created before doing such things?
Maybe I was not entirely clear: I don't want to use the DbContext myself - the PasswordSignInAsync uses it when verifying the user and password.
What if you inject the ApplicationDbContext and SignInManager<ApplicationUser> through the Invoke method:
public async Task Invoke(HttpContext context, ApplicationDbContext applicationContext, SignInManager<ApplicationUser> signInManager)
{
if (string.IsNullOrEmpty(context.User.Identity.Name))
{
var result = await signInManager.PasswordSignInAsync(_options.UserName, _options.Password, true, false);
}
await _next(context);
}
This way you the services are resolved from the correct scope. I notice you don't actually use the ApplicationDbContext anywhere, just the SignInManager. Do you really need it?
This error is likely occurring because any middleware acts as a singleton. You have to avoid using member variables in your middleware. Feel free to inject into the Task Invoke, but don't store the inject value into a member object.
See: Saving HttpContext Instance in Middleware,
Calling services in Middleware
I was able to get around this myself, by creating a class that I could then pass into other methods in my middleware:
public async Task Invoke(HttpContext context, IMetaService metaService)
{
var middler = new Middler
{
Context = context,
MetaService = metaService
};
DoSomething(middler);
}
Just by this:-
public async Task Invoke(HttpContext context)
{
var dbContext = context.RequestServices.GetRequiredService<ClinicDbContext>();
await _next(context);
}
This is the simple solution that works great for my use case.
I created a simple method I can call from anywhere in the application to easily get the database context:
public class UtilsApp
{
public static MyDbContext GetDbContext()
{
DbContextOptionsBuilder<MyDbContext> opts =
new DbContextOptionsBuilder<MyDbContext();
optionsBuilder.UseSqlServer(Program.MyDbConnectionString); // see connection string below
return new MyDbContext(opts.Options);
}
}
Then, to use it anywhere in the application:
MyDbContext dbContext = UtilsApp.GetDbContext();
I set Program.MyDbConnectionString (a public static string field) from within Startup.ConfigureServices() (which is a callback that is called from within Program.Main() via CreateHostBuilder(args).Build()). That way I can use that connection string anywhere in the application without having to repeatedly retrieve it from appsettings.json or an environment variable.