TelemetryClient.Flush() takes about 2 minutes to run during application exit - azure-application-insights

I have a .NET Core 3.1 console application running as a hosted service. When the application shuts down, it hangs for about 2 minutes. When breaking in I can see that it is hanging on TelemetryClient.Flush(), specifically inside InMemoryChannel.Flush().
So I watch the result in Fiddler and I can see the application is making a request out to https://dc.services.visualstudio.com/v2/track to report the telemetry, but that service doesn't respond. Eventually after 2 minutes the response fails with 500 and in the web response: "IIS 10.0 Detailed Error - 500.1013 - Internal Server Error".
I cannot tell if this is something I am doing wrong or not. So I shortened the application to the bare minimum.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
namespace DotNetCoreConsoleAppTest
{
public class Program
{
public static async Task Main()
{
await Host.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services
.Configure<TelemetryConfiguration>(options =>
{
options.InstrumentationKey = "<put your key here>";
})
.AddSingleton<TelemetryWriter>()
.AddHostedService<ProgramHost>();
})
.RunConsoleAsync()
.ConfigureAwait(false);
}
}
public class TelemetryWriter : IDisposable
{
private readonly TelemetryClient _telemetryClient;
public TelemetryWriter(IOptions<TelemetryConfiguration> telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration.Value);
}
public void WriteEvent() => _telemetryClient.TrackEvent("test");
public void Dispose() => _telemetryClient.Flush();
}
public class ProgramHost : IHostedService
{
private readonly TelemetryWriter _telemetryWriter;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
public ProgramHost(
TelemetryWriter telemetryWriter,
IHostApplicationLifetime hostApplicationLifetime)
{
_telemetryWriter = telemetryWriter;
_hostApplicationLifetime = hostApplicationLifetime;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_telemetryWriter.WriteEvent();
_hostApplicationLifetime.StopApplication();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
Some content from the web response:
<div class="content-container">
<fieldset><h4>Detailed Error Information:</h4>
<div id="details-left">
<table border="0" cellpadding="0" cellspacing="0">
<tr class="alt"><th>Module</th><td> iisnode</td></tr>
<tr><th>Notification</th><td> ExecuteRequestHandler</td></tr>
<tr class="alt"><th>Handler</th><td> iisnode</td></tr>
<tr><th>Error Code</th><td> 0x0000006d</td></tr>
</table>
</div>
<div id="details-right">
<table border="0" cellpadding="0" cellspacing="0">
<tr class="alt"><th>Requested URL</th><td> https://dc.services.visualstudio.com:443/InputServer.js</td></tr>
<tr><th>Physical Path</th><td> E:\sitesroot\0\InputServer.js</td></tr>
<tr class="alt"><th>Logon Method</th><td> Anonymous</td></tr>
<tr><th>Logon User</th><td> Anonymous</td></tr>
<tr class="alt"><th>Request Tracing Directory</th><td> C:\Resources\directory\f3eec886680f474eb56deb0e59f20036.Breeze.DiagnosticStore\FailedReqLogFiles\Web</td></tr>
</table>
<div class="clear"></div>
</div>
</fieldset>
</div>
The hanging stack trace is:
System.Private.CoreLib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
System.Private.CoreLib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
System.Private.CoreLib.dll!System.Threading.Tasks.Task.InternalWaitCore(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) Unknown
System.Private.CoreLib.dll!System.Runtime.CompilerServices.TaskAwaiter.GetResult() Unknown
Microsoft.ApplicationInsights.dll!Microsoft.ApplicationInsights.Channel.InMemoryTransmitter.DequeueAndSend(System.TimeSpan timeout) Unknown
Microsoft.ApplicationInsights.dll!Microsoft.ApplicationInsights.Channel.InMemoryTransmitter.Flush(System.TimeSpan timeout) Unknown
Microsoft.ApplicationInsights.dll!Microsoft.ApplicationInsights.Channel.InMemoryChannel.Flush(System.TimeSpan timeout) Unknown
Microsoft.ApplicationInsights.dll!Microsoft.ApplicationInsights.Channel.InMemoryChannel.Flush() Unknown
Microsoft.ApplicationInsights.dll!Microsoft.ApplicationInsights.TelemetryClient.Flush() Unknown
DotNetCoreConsoleAppTest.dll!DotNetCoreConsoleAppTest.TelemetryWriter.Dispose() Line 43 C#
Microsoft.Extensions.DependencyInjection.dll!Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.DisposeAsync() Unknown
Microsoft.Extensions.Hosting.dll!Microsoft.Extensions.Hosting.Internal.Host.DisposeAsync() Unknown
Microsoft.Extensions.Hosting.Abstractions.dll!Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(Microsoft.Extensions.Hosting.IHost host, System.Threading.CancellationToken token) Unknown
Microsoft.Extensions.Hosting.dll!Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.RunConsoleAsync(Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Threading.CancellationToken cancellationToken) Unknown
DotNetCoreConsoleAppTest.dll!DotNetCoreConsoleAppTest.Program.Main() Line 16 C#
DotNetCoreConsoleAppTest.dll!DotNetCoreConsoleAppTest.Program.<Main>() Unknown
Despite all of this, the events DO appear in the logs inside of Application Insights. The only problem is my host application hangs. Is this a problem with the way I am trying to Flush()? Or is this an Application Insights service problem?

ZakiMa answered my question -- App Insights was having an outage.

Related

GraphApi with Hot Chocolate: No valid mutation field

I'm trying to set up an GraphApi with Hot Chocolate in ASP.NET Core.
Now I want to split my application in multiple projects/components.
There's an Users component which has a UsersMutation containing 1 field:
public sealed class UsersMutation
{
public Task CreateUser([Service] ICreateUserMutationHandler handler, CreateUserParameters parameters)
=> handler.Handle(parameters);
}
I try to add it to the GraphQl schema like this:
public sealed class Mutation
{
public UsersMutation Users => new UsersMutation();
}
Configuration:
public static class GraphApiConfiguration
{
public static IServiceCollection AddGraphApi<TQuery, TMutation>(this IServiceCollection services)
where TQuery : class
where TMutation : class
{
services.AddGraphQLServer()
.AddQueryType<TQuery>()
.AddMutationType<TMutation>();
services.AddScoped<TQuery>();
services.AddScoped<TMutation>();
return services;
}
}
Finally in startup.cs:
services.AddGraphApi<Query, Mutation>();
But I get the following error trying to see the schema in the playground:
HotChocolate.SchemaException: For more details look at the `Errors` property.
1. The object type `UsersMutation` has to at least define one field in order to be valid. (HotChocolate.Types.ObjectType<ChocoGraph.Components.Users.GraphApi.UsersMutation>)
at HotChocolate.Configuration.TypeInitializer.Initialize(Func`1 schemaResolver, IReadOnlySchemaOptions options)
at HotChocolate.SchemaBuilder.Setup.InitializeTypes(SchemaBuilder builder, IDescriptorContext context, IReadOnlyList`1 types, LazySchema lazySchema)
at HotChocolate.SchemaBuilder.Setup.Create(SchemaBuilder builder, LazySchema lazySchema, IDescriptorContext context)
at HotChocolate.SchemaBuilder.Create(IDescriptorContext context)
at HotChocolate.SchemaBuilder.HotChocolate.ISchemaBuilder.Create(IDescriptorContext context)
at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaAsync(NameString schemaName, RequestExecutorSetup options, RequestExecutorOptions executorOptions, IServiceProvider serviceProvider, TypeModuleChangeMonitor typeModuleChangeMonitor, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaServicesAsync(NameString schemaName, RequestExecutorSetup options, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.GetRequestExecutorNoLockAsync(NameString schemaName, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorResolver.GetRequestExecutorAsync(NameString schemaName, CancellationToken cancellationToken)
at HotChocolate.Execution.RequestExecutorProxy.GetRequestExecutorAsync(CancellationToken cancellationToken)
at HotChocolate.AspNetCore.HttpPostMiddleware.HandleRequestAsync(HttpContext context, AllowedContentType contentType)
at HotChocolate.AspNetCore.HttpPostMiddleware.InvokeAsync(HttpContext context)
at HotChocolate.AspNetCore.WebSocketSubscriptionMiddleware.InvokeAsync(HttpContext context)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
'iisexpress.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.10\System.Buffers.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
The program '[44280] iisexpress.exe: Program Trace' has exited with code 0 (0x0).
The program '[44280] iisexpress.exe' has exited with code -1 (0xffffffff).
What am I missing to achieve this? This seemed to work fine if I had the CreateUser field in the Mutation.cs file, but adding this extra step seems to break it.
I found the issue:
Mutation has to return something in order to be a valid field.

EF Core error: The connection does not support MultipleActiveResultSets

I search thru all the links which had solution for the error. But, none of them were applicable for me as I had asynchronous code already and doing everything they have suggested.
We have Azure Functions based on .NET Core 3.1. We use latest version of Entity Framework Core. We are intermittently getting this error:
System.InvalidOperationException: The connection does not support MultipleActiveResultSets.
at Microsoft.Data.SqlClient.SqlCommand.<>c.b__164_0(Task1 result) at System.Threading.Tasks.ContinuationResultTaskFromResultTask2.InnerInvoke()
at System.Threading.Tasks.Task.<>c.<.cctor>b__274_0(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
End of stack trace from previous location where exception was thrown
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)
End of stack trace from previous location where exception was thrown ---
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func4 operation, Func4 verifySucceeded, TState state, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync[TState,TResult](Func4 operation, Func4 verifySucceeded, TState state, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)
When we looked at the logs in AppInsights, we found that the exceptions occurred at the same time with exact same error for same function at the same place. But, it was for three different invocations (InvocationId) but same host instance (HostInstanceId) and different operation Id (Operation ID). Expectation is that for every new invocation, new dbContext will be instantiated as the AddDbContextPool adds scoped dbContext as dependency by default. Not sure if we can deduct anything out of it.
Below is our implementation approach. Appreciate any help on this. Thanking in advance.
We add DbContext to the services using following statement in the startup file:
builder.Services.AddDbContextPool<OurDbContext>(options =>
{
options.UseSqlServer("connectionstring"), builder =>
{
builder.EnableRetryOnFailure(3, TimeSpan.FromSeconds(2), null);
});
});
OurDbContext class has the following constructor:
public OurDbContext(DbContextOptions<OurDbContext> options)
: base(options)
{
}
And then we inject OurDbContext class in different repositories which uses this context to talk to SQL. Similar to below:
public class TypesRepo : RepoBase<Types>, ITypesRepo
{
public TypesRepo(OurDbContext ourDbContext) : base(ourDbContext)
{
}
public async Task RetrieveTypesAsync(List<string> types)
{
var records = await RetrieveConditionAsync(x => types.Contains(x.Type));
return records?.Select(x => new { x.Type, x.TypeId })
.ToDictionary(x => x.Type, x => x.TypeId);
}
}
public abstract class RepoBase<T> where T : class
{
protected OurDbContext OurDbContext { get; set; }
public RepoBase(OurDbContext OurDbContext)
{
this.OurDbContext = OurDbContext;
}
public async Task<List<T>> RetrieveConditionAsync(Expression<Func<T, bool>> expression)
{
return await OurDbContext.Set<T>().Where(expression).AsNoTracking().ToListAsync();
}
}
We inject above Repo class in Function classes and call above methods such as
await _typesRepo.RetrieveAsync()
P.S.: Based on below comment
I think dbcontextpool will reuse the dbcontext instance if it the connection is not active/unused but not the one which is active.
AddDbContext or AddDbContextPool
Your connection string needs to specify MultipleActiveRecordSets=true; to tell SqlServer to make sure this feature is enabled on the connections you make to it.
For example:
Data Source=localhost;Initial Catalog=master;Integrated Security=True;MultipleActiveResultSets=True
More info here: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/enabling-multiple-active-result-sets
We recently ran into a similar issue, and it was due to having a request spin up multiple threads simultaneously, and each thread was using the same DbContext to parallelize a heavy database query.
Further, we use DbReader for working with the results, and that locks up the connection until the reader is disposed of.
That prevents the other threads, which are re-using the same DbContext / connection instance, which also are using DbReader to process results.
Enabling the multiple-active result sets feature solves this for us, and in our case, we actually do not notice a performance hit at all with this.

How to register strongly typed signalR Hub in AutoFac for injecting IHubContext in IHostedService or BackgroundService

I am a new user of signalR and Autofac. I am using signalR with ASP.NET Core Blazor Server and receiving the error listed below from a page that tries to connect to the hub. My Hub is strongly typed (IHubContext<Hub,Interface>) and is used within an IHostedService class implementation. It has a constructor that accepts an ILogger instance.
If I remove the constructor from the Hub implementation then the error does not occur. However, the IHubContext<Hub, IHub> appears not to be despatching to the clients in either case. The log message within the SendMotionDetection method on the hub is not displayed.
The official autofac documentation recommends installing the Autofac.SignalR NuGet package for integration with signalR. However, upon installing the package it is targeted for frameworks :.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8. I am targeting netcoreapp3.1 developing on MacOS.
Question:
How to register a strongly typed signalR Hub in AutoFac ASP.NET Core 3.1 for the purpose of injecting IHubContext<Hub, IHub> in IHostedService or BackgroundService?
Currently, the IHubContext<Hub, IHub> injected parameter is not sending the SendMotionDetection message to all clients, i.e. the console log message from the hubs message is not displayed. Yet, no exception is being thrown???
The error
fail: Microsoft.AspNetCore.SignalR.HubConnectionHandler[1]
Error when dispatching 'OnConnectedAsync' on hub.
Autofac.Core.DependencyResolutionException: An exception was thrown while activating WebApp.Realtime.SignalR.MotionHub.
---> Autofac.Core.Activators.Reflection.NoConstructorsFoundException: No accessible constructors were found for the type 'WebApp.Realtime.SignalR.MotionHub'.
at Autofac.Core.Activators.Reflection.DefaultConstructorFinder.GetDefaultPublicConstructors(Type type)
at Autofac.Core.Activators.Reflection.DefaultConstructorFinder.FindConstructors(Type targetType)
at Autofac.Core.Activators.Reflection.ReflectionActivator.ActivateInstance(IComponentContext context, IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.CreateInstance(IEnumerable`1 parameters)
--- End of inner exception stack trace ---
at Autofac.Core.Resolving.InstanceLookup.CreateInstance(IEnumerable`1 parameters)
at Autofac.Core.Resolving.InstanceLookup.Execute()
at Autofac.Core.Resolving.ResolveOperation.GetOrCreateInstance(ISharingLifetimeScope currentOperationScope, ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.ResolveComponent(ResolveRequest request)
at Autofac.Core.Resolving.ResolveOperation.Execute(ResolveRequest request)
at Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(ResolveRequest request)
at Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance)
at Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters)
at Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType)
at Autofac.Extensions.DependencyInjection.AutofacServiceProvider.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubActivator`1.Create()
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher`1.OnConnectedAsync(HubConnectionContext connection)
at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.RunHubAsync(HubConnectionContext connection)
Source code for the SignalR hub and Startup are listed below.
Within the ConfigureServices of Startup.cs, I have tried registering the SignalR Hub with autofac container registry but still getting the error. Interestingly, if I do not include a constructor for the SignalR hub the error does not occur. However, I am injecting an IHubContext into a background service and when sending a messages from the background service via the IHubContext it does not appear to be dispatching.
Interface
public interface IMotion
{
Task SendMotionDetection(MotionDetection message);
}
Hub
public class MotionHub : Hub<IMotion>
{
private ILogger<MotionHub> _logger;
MotionHub(ILogger<MotionHub> logger)
{
_logger = logger;
_logger.LogInformation("Motion SignalR Hub Created");
}
// send the motion detection event to all clients
public async Task SendMotionDetection(MotionDetection message)
{
_logger.LogInformation("MotionHub => SignalR Hub => SendMotionDetection");
await Clients.All.SendMotionDetection(message);
}
}
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public ILifetimeScope AutofacContainer { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSignalR(o => o.EnableDetailedErrors = true);
services.AddHostedService<MqttListenerWorker>();
services.AddHostedService<ConsumerService>();
services.AddLogging();
}
// ConfigureContainer is where you can register things directly
// with Autofac. This runs after ConfigureServices so the things
// here will override registrations made in ConfigureServices.
// Don't build the container; that gets done for you by the factory.
public void ConfigureContainer(ContainerBuilder builder)
{
// Register your own things directly with Autofac here. Don't
// call builder.Populate(), that happens in AutofacServiceProviderFactory
// for you.
builder.RegisterModule(new MotionDetectionRepositoryModule());
builder.RegisterModule(new KafkaModule());
//builder.RegisterHubs(typeof());
builder.RegisterAssemblyTypes(typeof(MotionDetection).GetTypeInfo().Assembly);
builder.RegisterType<MotionHub>()
.AsSelf();
// builder.RegisterTypes(typeof(MotionHub).GetTypeInfo().Assembly)
// .Where(t => t.Name.EndsWith("Hub"))
// .As(typeof(Hub<MotionHub>))
// .ExternallyOwned();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
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.UseEndpoints(endpoints =>
{
endpoints.MapHub<MotionHub>("/motionhub");
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
IHostedService
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Confluent.Kafka;
using Confluent.Kafka.SyncOverAsync;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using WebApp.Data;
using WebApp.Data.Serializers.Contracts;
using WebApp.Kafka.Contracts;
using WebApp.Kafka.SchemaRegistry.Serdes;
using WebApp.Realtime.SignalR;
namespace WebApp.Kafka
{
public class ConsumerService : IHostedService, IDisposable
{
// At the time of writing Kafka Consumer isn't async so....
// making a long running background thread with a consume loop.
private Thread _pollLoopThread;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private ConsumerConfig _consumerConfig = new ConsumerConfig();
private HashSet<string> _cameras { get; }
private string _topic;
private IHubContext<MotionHub, IMotion> _messagerHubContext;
private JsonDeserializer<MotionDetection> _serializer { get; }
private ILogger<ConsumerService> _logger;
// Using SignalR with background services:
// https://learn.microsoft.com/en-us/aspnet/core/signalr/background-services?view=aspnetcore-2.2
public ConsumerService(
IConfiguration config,
IHubContext<MotionHub, IMotion> messagerHubContext,
JsonDeserializer<MotionDetection> serializer,
ILogger<ConsumerService> logger
)
{
_logger = logger;
config.GetSection("Consumer").Bind(_consumerConfig);
// consider extension method for those settings that cannot be set in cnofig
if (_consumerConfig.EnablePartitionEof != null)
{
throw new Exception("shouldn't allow this to be set in config.");
}
_consumerConfig.EnableAutoCommit = false;
_topic = config.GetValue<string>("Topic");
_messagerHubContext = messagerHubContext;
_serializer = serializer;
_cameras = new HashSet<string>();
_cameras.Add("shinobi/group/monitor/trigger");
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("ConsumerService starting a thread to poll topic => {}...", _topic);
_pollLoopThread = new Thread(async () =>
{
try
{
var consumerBuilder = new ConsumerBuilder<string, MotionDetection>(_consumerConfig);
consumerBuilder.SetValueDeserializer(_serializer.AsSyncOverAsync());
using (var consumer = consumerBuilder.Build())
{
consumer.Subscribe(_topic);
try
{
while (!_cancellationTokenSource.IsCancellationRequested)
{
var consumerResult = consumer.Consume(_cancellationTokenSource.Token);
_logger.LogInformation("Consumer consumed message => {}", consumerResult.Message.Value);
if (_cameras.Contains(consumerResult.Message.Key))
{
// we need to consider here security for auth, only want for user
await _messagerHubContext.Clients.All.SendMotionDetection(consumerResult.Message.Value);
_logger.LogInformation("Consumer dispatched message to SignalR");
}
}
}
catch (OperationCanceledException) { }
consumer.Close();
_logger.LogInformation("Consumer closed, preparing to stop");
}
}
catch (Exception e)
{
_logger.LogCritical("Unexpected exception occurred in consumer thread");
_logger.LogError(e, "Consumer Error");
// update to take remdial action or retry to ensure consumer is available
// during lifetime
}
});
_pollLoopThread.Start();
_logger.LogInformation("Consumer thread started");
return Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
await Task.Run(() =>
{
_cancellationTokenSource.Cancel();
_pollLoopThread.Join();
});
_logger.LogInformation("Consumer stopped...");
}
public void Dispose()
{
_logger.LogInformation("Consumer disposed");
}
}
}
Think I have solved it.
The implementation of methods in the Hub class are invoked from client->server, so I would never see output from that because in this instance the server is pushing to the client.
For the time being I have changed the parameter to the method in IMotion interface to be a string and updated the code on the client blazor page to reflect a string parameter.
I also removed code that injects the Hub into autofac. I suspect that this is being handled by Microsoft DI automatically???
I think the issue might have been serialization/deserialization of object.
I have included the code below for the blazor page.
Next step is to work out how to serialize/deserialize object over signalR connection and also connect to the signalRHub after the page has been rendered instead of when it has been Initialized (executes twice!).
Blazor Page
#page "/"
#using System.Threading
#using System.Collections.Generic;
#using Microsoft.AspNetCore.SignalR.Client
#inject NavigationManager NavigationManager
#using WebApp.Data
<h1>Blazor Server App</h1>
<div>Latest message is => #_latestMessage</div>
<div id="scrollbox">
#foreach (var item in _messages)
{
<div>
<div>#item</div>
</div>
}
<hr />
</div>
#code {
private HubConnection hubConnection;
private string _latestMessage = "";
private List<string> _messages = new List<string>();
public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
protected override async Task OnInitializedAsync()
{
var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
// Uri uri = NavigationManager.ToAbsoluteUri("/motionhub");
try
{
hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.Build();
hubConnection.On<string>("SendMotionDetection", ReceiveMessage);
await hubConnection.StartAsync();
Console.WriteLine("Index Razor Page initialised, listening on signalR hub url => " + hubUrl.ToString());
Console.WriteLine("Hub Connected => " + IsConnected);
}
catch (Exception e)
{
Console.WriteLine("Encountered exception => " + e);
}
}
private void ReceiveMessage(string message)
{
try
{
Console.WriteLine("Hey! I received a message");
_latestMessage = message;
_messages.Add(_latestMessage);
StateHasChanged();
}
catch (Exception ex)
{
Console.Error.WriteLine("An exception was encountered => " + ex.ToString());
}
}
}

Correctly using of dependency injected DbContext to query Database

I have set up my own context using (as I believe is correct):
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<JobTrackingContext>(options => options.UseNpgsql(connection));
services.AddScoped<IJobRepository, JobRepository>();
}
Then I define my JobTrackingContext as follows:
public JobTrackingContext(DbContextOptions<JobTrackingContext> options)
: base(options)
{
public DbSet<Job> Jobs { get; set; }
}
Now I can define a repository to actually create/edit/delete Jobs:
public class JobRepository : GenericRepository<Job, long>, IJobRepository
{
private Job currentJob;
public JobRepository(JobTrackingContext jobTrackingContext, JobTrackingSettings settings)
: base(jobTrackingContext)
{
_settings = settings;
}
public async Task StartSync(JobType jobType, JobTriggerType jobTriggerType)
{
var tempJob = new Job(jobType, jobTriggerType);
await _dbContext.Jobs.AddAsync(tempJob);
await _dbContext.SaveChangesAsync();
}
}
And all this code gets instantiated by a Post-request to this API:
public async void Post()
{
_logger.LogDebug("Going to start account sync");
await _jobRepository.StartSync(JobType.ZRequestSync, JobTriggerType.Scheduled);
try
{
await _sync.StartAsync();
await _jobRepository.ChangeSyncStatus(JobStatusType.Finished);
}
catch (Exception e)
{
_logger.LogError(e, "Error occured during sync :(");
await _jobRepository.ChangeSyncStatus(JobStatusType.Failed);
}
}
Yet when I do this, I get an Exception with the message Reset() called on connector with state Connecting. I do not understand where this comes from.
When I do not use the injected version, but instead do this:
using (var c = new JobTrackingContext())
{
var job = new Job(jobType, jobTriggerType)
await c.Jobs.AddAsync(job);
await c.SaveChangesAsync();
}
All seems to be working fine. It seems that the context gets disposed too early. But how can I prevent this and/or what am I missing?
The Full Stacktrace:
System.ObjectDisposedException
HResult=0x80131622
Message=Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Source=Microsoft.EntityFrameworkCore
StackTrace:
at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed()
at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.<SaveChangesAsync>d__52.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at ZShared.JobRepository.<StartSync>d__4.MoveNext() in C:\Users\richa\Documents\Codes\Company\Product\Shared\Folder\JobRepository.cs:line 38
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at ZAccountSyncService.AccountSyncController.<Post>d__4.MoveNext() in C:\Users\richa\Documents\Code\Company\Product\SubProduct\AccountSyncController.cs:line 32
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
The gist of the problem is the declaration of your AccountSyncController.Post method. You have async void Post() instead of async Task Post().
When there is no task, a request has nothing to await and ends before the call of method _sync.StartAsync() is completed. With an end of a request comes also the end of a lifetime scope. On lifetime scoped end, all instances with scoped lifetime get disposed. Thus, your context is disposed before you get to the call of SaveChanges method. And this is the cause of your exception.

Accessing current controller executing in DelegatingHandler

I was wondering if it's possible to access the controller being executed (or about to be executed) in the SendAsync method of the DelegatingHandler? I can't seem to figure out how to get access to it, and I figure it's because it executes outside of the controller execution...
Is it possible to refer to it?
No, because message handlers operate on raw HttpRequestMessage or raw HttpResponseMessage (in case of continuations). So really, there is no concept of "current controller executing" with DelegatingHandlers since message handlers will be called before dispatching the request to the controller or (again, in the case of continuations) after the controller returns the reponse.
However, it really depends what you are trying to do.
If you want to know to which controller the request will end up getting routed, you can manually call the mechanism that would internally select the controllers.
public class MyHandler : DelegatingHandler
{
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
var config = GlobalConfiguration.Configuration;
var controllerSelector = new DefaultHttpControllerSelector(config);
// descriptor here will contain information about the controller to which the request will be routed. If it's null (i.e. controller not found), it will throw an exception
var descriptor = controllerSelector.SelectController(request);
// continue
return base.SendAsync(request, cancellationToken);
}
}
Extending the #GalacticBoy solution, it would be better to use
public class MyHandler : DelegatingHandler
{
private static IHttpControllerSelector _controllerSelector = null;
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
if (_controllerSelector == null)
{
var config = request.GetConfiguration();
_controllerSelector = config.Services.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
}
try
{
// descriptor here will contain information about the controller to which the request will be routed. If it's null (i.e. controller not found), it will throw an exception
var descriptor = _controllerSelector.SelectController(request);
}
catch
{
// controller not found
}
// continue
return base.SendAsync(request, cancellationToken);
}
}
Depending on what your doing with the information maybe your fine with getting the information after the request is executed. For example logging the executed controller/action.
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace Example
{
public class SampleHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
HttpResponseMessage response = task.Result;
string actionName = request.GetActionDescriptor().ActionName;
string controllerName = request.GetActionDescriptor().ControllerDescriptor.ControllerName;
// log action/controller or do something else
return response;
}, cancellationToken);
}
}
}

Resources