I've updated SignalR to 2.0 in an ASPNET MVC 4.0 application and now I'm receiving the following error:
Counter 'Message Bus Messages Published Total' does not exist in the specified Category
The error is thrown on
app.MapSignalR();
This is the code I'm using now in the Startup class (previously I used RegisterHubs.
public static class Startup
{
public static void Configuration(IAppBuilder app)
{
var signalrDependencyContainer = new WindsorContainer().Install(new HubsInstaller());
var signalrDependency = new SignalrDependencyResolver(signalrDependencyContainer.Kernel);
GlobalHost.DependencyResolver = signalrDependency;
app.MapSignalR();
}
}
UPDATE
Here is an partial extract of the code as requested.
This is what I get when installing the counters
PM> signalr ipc
SignalR Utility Version: 1.1.0.0
Installing performance counters...
Connections Connected
Connections Reconnected
Connections Disconnected
Connections Current
Connection Messages Received Total
Connection Messages Sent Total
Connection Messages Received/Sec
Connection Messages Sent/Sec
Message Bus Messages Received Total
Message Bus Messages Received/Sec
Scaleout Message Bus Messages Received/Sec
Messages Bus Messages Published Total
Messages Bus Messages Published/Sec
Message Bus Subscribers Current
Message Bus Subscribers Total
Message Bus Subscribers/Sec
Message Bus Allocated Workers
Message Bus Busy Workers
Message Bus Topics Current
Errors: All Total
Errors: All/Sec
Errors: Hub Resolution Total
Errors: Hub Resolution/Sec
Errors: Hub Invocation Total
Errors: Hub Invocation/Sec
Errors: Tranport Total
Errors: Transport/Sec
Scaleout Streams Total
Scaleout Streams Open
Scaleout Streams Buffering
Scaleout Errors Total
Scaleout Errors/Sec
Scaleout Send Queue Length
Performance counters installed!
StartupOwin.cs
using org.app.Ui.Web.App_Start;
using Microsoft.Owin;
[assembly: OwinStartup(typeof(StartupOwin), "Configuration")]
namespace org.app.Ui.Web.App_Start
{
using Owin;
public class StartupOwin
{
public void Configuration(IAppBuilder app)
{
org.app.Ui.Web.App_Start.StartupSignalR.ConfigureSignalR(app);
}
}
}
StartupSignalR.cs
namespace org.app.Ui.Web.App_Start
{
using Castle.Windsor;
using org.app.Ui.Web.Infrastructure;
using Microsoft.AspNet.SignalR;
using Owin;
public static class StartupSignalR
{
public static void ConfigureSignalR(IAppBuilder app)
{
app.MapSignalR(new HubConfiguration
{
Resolver = new SignalrDependencyResolver(
new WindsorContainer()
.Install(new HubsInstaller()).Kernel)
});
}
}
}
HubNewHandler.cs
namespace org.app.Ui.Web.Hubs
{
using System;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Linq;
using System.Timers;
using Castle.MicroKernel;
using Castle.MicroKernel.Lifestyle;
using Castle.Windsor;
using org.app.Data.Contracts;
using org.app.Data.Model;
using Microsoft.AspNet.SignalR;
public class HubNewHandler : IHubNewHandler
{
private System.Timers.Timer aTimer;
private DateTime lastDate = DateTime.UtcNow;
readonly private IKernel kernel;
public HubNewHandler(IKernel kernel)
{
this.kernel = kernel;
}
public void StartTimer()
{
aTimer = new System.Timers.Timer(10000);
aTimer.Elapsed += new ElapsedEventHandler(SendNewMessage);
aTimer.Enabled = true;
GC.KeepAlive(aTimer);
}
public void SendNewMessage(object state, ElapsedEventArgs elapsedEventArgs)
{
using (kernel.BeginScope())
{
var gdpUow = kernel.Resolve<IGdpUow>();
var gdpTesisConfigurationsRecord = gdpUow.GdpConfigurations.GetByPredicate(a => a.Description.Equals("LastDateTimeMessagesCheck")).SingleOrDefault();
if (gdpTesisConfigurationsRecord == null)
{
gdpTesisConfigurationsRecord = new GdpConfiguration
{
Description = "LastDateTimeMessagesCheck",
DateTimeValue = DateTime.Now.ToUniversalTime()
};
gdpUow.GdpConfigurations.Add(gdpTesisConfigurationsRecord);
}
var lastMessagesDateTimeCheck = gdpTesisConfigurationsRecord.DateTimeValue;
var messagesList = GetShowableMessagesList(gdpUow, lastMessagesDateTimeCheck);
// Get a hub context for ServerHub
var serverHub = GlobalHost.ConnectionManager.GetHubContext<ServerHub>();
// Send a message to all the clients
serverHub.Clients.All.addNewMessagesToMap(messagesList, false);
gdpTesisConfigurationsRecord.DateTimeValue = elapsedEventArgs.SignalTime.ToUniversalTime();
gdpUow.GdpConfigurations.Update(gdpTesisConfigurationsRecord);
gdpUow.Commit();
}
}
}
}
ServerHub.cs
namespace org.app.Ui.Web.Hubs
{
using System;
using System.Linq;
using System.Threading.Tasks;
using Data.Contracts;
using Data.Model;
using Microsoft.AspNet.SignalR;
public class ServerHub : Hub
{
public IGdpUow Uow { get; set; }
public override Task OnConnected()
{
var connectionId = Guid.Parse(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected()
{
var connectionId = Guid.Parse(Context.ConnectionId);
return base.OnDisconnected();
}
public override Task OnReconnected()
{
var connectionId = Guid.Parse(Context.ConnectionId);
return base.OnReconnected();
}
public void GetAllMessages()
{
var messagesList = Uow.Messages.GetAll();
Clients.All.addNewMessagesToMap(messagesList, true);
}
}
}
Any ideas what could be the problem?
Thanks in advance. Guillermo.
Try installing a different version of Microsoft.AspNet.SignalR.Utils. I got this error after installing version 1.1.2 of the utils. I uninstalled that, and installed version 2.1.1:
Install-Package microsoft.aspnet.SignalR.Utils -version 2.1.1
Then I ran the command again:
signalr.exe ipc
Finally, I restarted my application and it worked. The problem seems to be that one version uses "Messages Bus Messages Published Total" and the other uses "Message Bus Messages Published Total" (Note the "s" is missing in "Message" in the second one). See more information here (how's your Korean?)
For SignalR 2.0 , add the Owin references.
(Note: I don't know your application name, change yourapplicationname (mentioned twice in the code) to the name of your application.
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(yourapplicationname.Startup))]
namespace yourapplicationname
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
var signalrDependencyContainer = new WindsorContainer().Install(new HubsInstaller());
var signalrDependency = new SignalrDependencyResolver(signalrDependencyContainer.Kernel);
GlobalHost.DependencyResolver = signalrDependency;
app.MapSignalR();
}
}
}
Related
I am trying to create a chat application in a Unity game. So basically in one instance of the game if someone sends a message , all the other open instances of the game should get the message.
I successfully created a self-hosted SignalR 2 server using this tutorial
The code for the console app is as follows:
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Hosting;
using Owin;
using Microsoft.Owin.Cors;
namespace SignalRSelfHost
{
class Program
{
static void Main(string[] args)
{
// This will *ONLY* bind to localhost, if you want to bind to all addresses
// use http://*:8080 to bind to all addresses.
// See http://msdn.microsoft.com/library/system.net.httplistener.aspx
// for more information.
string url = "http://localhost:8080";//a web application of type Startup is started at the specified URL (http://localhost:8080).
using (WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
class Startup
{// the class containing the configuration for the SignalR server ,which creates routes for any Hub objects in the project.
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
public class MyHub : Hub
{//the SignalR Hub class that the application will provide to clients.
public void Send(string name, string message)
{
Clients.All.addMessage(name, message);//clients will call to broadcast a message to all other connected clients.
}
}
}
I created a dummy Unity game as of now. There are the username input field and chat input field. Next to the chat input field there is a send button. So the person enters his/her name and enters something into the chat and all running instances of the game should receive the message , but I am not able to achieve that thing
The script for this is attached to a empty GameObject SignalRManager. The code is as follows:
using System.Collections.Generic;
using UnityEngine;
using Microsoft.AspNet.SignalR.Client;
using UnityEngine.UI;
using System;
using TMPro;
public class SignalRManager : MonoBehaviour
{
string url = "http://localhost:8080";
HubConnection connection;
[SerializeField] private GameObject ChatMessage;
[SerializeField] private GameObject UserName;
// Start is called before the first frame update
void Start()
{
connection = new HubConnection(url);
connection.Start();
connection.StateChanged += connection_StateChanged;
}
private void connection_StateChanged(StateChange state)
{
if(state.NewState== ConnectionState.Connected)
{
Debug.Log("Connected to Server");
}
if(state.NewState == ConnectionState.Disconnected)
{
Debug.Log("Disconnected from Server");
}
}
public void OnClickSendChatButton()
{
string message= ChatMessage.GetComponent<TMP_InputField>().text;
string userName= UserName.GetComponent<TMP_InputField>().text;
}
private void OnDisable()
{
connection.StateChanged -= connection_StateChanged;
}
}
I am able to connect to the server as the log message appears , but dont know what to write to send message to the server. Also how do I receive the message from the server as well
I tried to use a function called connection.Send(), but it is not accepting two arguments. I tried to look into many tutorials ,but many are for asp.netcore Signal r , but I need it for asp.net signalR because thats my requirement.
<<<<--------edit------>>>
I was able to send data to the server but I am not able to receive data from the server on my Unity game. The updated code is as follows:
using System.Collections.Generic;
using UnityEngine;
using Microsoft.AspNet.SignalR.Client;
using UnityEngine.UI;
using System;
using TMPro;
public class SignalRManager : MonoBehaviour
{
string url = "http://localhost:8080";
HubConnection connection;
IHubProxy hubProxy;
[SerializeField] private GameObject ChatMessage;
[SerializeField] private GameObject UserName;
[SerializeField] private TextMeshProUGUI OutputText;
// Start is called before the first frame update
void Start()
{
connection = new HubConnection(url);//Create a connection for the SignalR server
hubProxy = connection.CreateHubProxy("MyHub");//Get a proxy object that will be used to interact with the specific hub on the server.There may be many hubs hosted on the server, so provide the type name for the hub
connection.Start();
connection.StateChanged += connection_StateChanged;
OutputText.text = "";
}
private void OnReceivedMessageFromServer(string name, string message)
{
OutputText.text = OutputText.text+name + ":" + message + "\n";
Debug.Log(OutputText.text + name + ":" + message + "\n");
}
private void connection_StateChanged(StateChange state)
{
if(state.NewState== ConnectionState.Connected)
{
Debug.Log("Connected to Server");
}
if(state.NewState == ConnectionState.Disconnected)
{
Debug.Log("Disconnected from Server");
}
}
public void OnClickSendChatButton()
{
string message= ChatMessage.GetComponent<TMP_InputField>().text;
string userName= UserName.GetComponent<TMP_InputField>().text;
hubProxy.On<string, string>("addMessage", OnReceivedMessageFromServer);//register hub events (methods invoked by the hub). The following code registers a handler method for ChatMessage event.
hubProxy.Invoke("Send", userName, message);
}
private void OnDisable()
{
connection.StateChanged -= connection_StateChanged;
}
}
The callBack OnReceivedMessageFromServer is not invoked. Can anyone help me here?
I'm trying to build my first NserviceBus application, but I cannot get it to work. The message arrives on the "backend" but the handler uses DI and NServicebus claims it cannot find the service for the interface. But the code was lifted directly from the examples. I'm missing something and I cannot figure it out.
The Error:
dbug: NServiceBus.LoadHandlersConnector[0]
Processing message type: Messages.JobStartCommand
Message headers:
NServiceBus.MessageId : 0044f261-e3b3-4287-b6f0-ad7400ef43cb
NServiceBus.MessageIntent : Send
NServiceBus.ConversationId : bb4f276f-63fe-450b-b234-ad7400ef43cd
NServiceBus.CorrelationId : 0044f261-e3b3-4287-b6f0-ad7400ef43cb
NServiceBus.ReplyToAddress : ClientUI
NServiceBus.OriginatingMachine : L19002992
NServiceBus.OriginatingEndpoint : ClientUI
$.diagnostics.originating.hostid : e1fecb2b72b8185e47341bb4dfb37dd7
NServiceBus.ContentType : text/xml
NServiceBus.EnclosedMessageTypes : Messages.JobStartCommand, Messages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
NServiceBus.Version : 7.5.0
NServiceBus.TimeSent : 2021-07-29 14:31:08:228497 Z
NServiceBus.Retries : 3
NServiceBus.Retries.Timestamp : 2021-07-29 14:31:40:397277 Z
Handlers to invoke:
Extractor.JobStartHandler
Extractor.JobStartHandler
info: NServiceBus.RecoverabilityExecutor[0]
Immediate Retry is going to retry message '0044f261-e3b3-4287-b6f0-ad7400ef43cb' because of an exception:
System.InvalidOperationException: Unable to resolve service for type 'IExtract' while attempting to activate 'Extractor.JobStartHandler'.
at Microsoft.Extensi
My backend program.cs
using Microsoft.Extensions.Hosting;
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NServiceBus;
using System.Threading.Tasks;
using Messages;
using Extractor;
namespace ExtractorOsiris
{
class Program
{
static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
var builder = Host.CreateDefaultBuilder(args);
builder.UseWindowsService();
//builder.UseMicrosoftLogFactoryLogging();
builder.ConfigureLogging((ctx, logging) =>
{
logging.AddConfiguration(ctx.Configuration.GetSection("Logging"));
//logging.AddEventLog();
logging.AddConsole();
logging.SetMinimumLevel(LogLevel.Debug);
});
#region back-end-use-nservicebus
builder.UseNServiceBus(ctx =>
{
var endpointConfiguration = new EndpointConfiguration("Sample.Extractor");
endpointConfiguration.UseTransport<LearningTransport>();
var transport = endpointConfiguration.UseTransport<LearningTransport>();
transport.Routing().RouteToEndpoint(typeof(ProcessObjectCommand), "Sample.Processor");
endpointConfiguration.DefineCriticalErrorAction(OnCriticalError);
return endpointConfiguration;
});
#endregion
#region back-end-register-service
builder.ConfigureServices(services =>
{
services.AddSingleton<IExtract, ExtractOsiris>();
});
#endregion
return builder;
}
private static Task OnCriticalError(ICriticalErrorContext arg)
{
throw new NotImplementedException();
}
}
}
The Interface
using Newtonsoft.Json.Linq;
using NServiceBus;
using System;
using System.Threading.Tasks;
namespace Extractor
{
public interface IExtract
{
Task<JArray> Extract(string #object, DateTime deltaTime);
}
}
The handler
using System.Threading.Tasks;
using Messages;
using Newtonsoft.Json.Linq;
using NServiceBus;
namespace Extractor
{
#region back-end-handler
public class JobStartHandler : IHandleMessages<JobStartCommand>
{
private readonly IExtract extractor;
public JobStartHandler(IExtract extractor)
{
this.extractor = extractor;
}
public async Task Handle(JobStartCommand message, IMessageHandlerContext context)
{
Task<JArray> result = extractor.Extract("Medewerkers", message.DeltaTime);
await result;
JArray test = result.Result;
foreach (JObject x in test)
{
// send the object to be processed.
//await context.Send(new ProcessObjectCommand(x.ToString()));
}
}
}
#endregion
}
The implementation of the interface
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Extractor;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NServiceBus;
namespace ExtractorOsiris
{
public class ExtractOsiris : IExtract
{
private readonly ILogger logger;
public ExtractOsiris(ILogger<ExtractOsiris> logger)
{
this.logger = logger;
}
public Task<JArray> Extract(string #object, DateTime deltaTime)
{
logger.LogInformation($"getting {#object} for delta time {deltaTime}");
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://xx.yy");
WebResponse response = request.GetResponse();
JObject temp = JObject.Load(new JsonTextReader(new StreamReader(response.GetResponseStream())));
return Task<JArray>.FromResult(temp["items"] as JArray);
}
}
}
I know the code ain't pretty, but it's pure quick and dirty testing code.
I cleaned out the offending interface from the code and still got the error on an interface that was not even there anymore. I removed the bin folder and build everything back up step by step. Stupid thing is, source control says I am back where I started, without changes. And it just works....
So must have bin something stupid with VS2019. Sorry to anyone who's time I wasted
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());
}
}
}
I have a problem with configuring a fault consumer in my app. The problem is that consumed message is passed to an *_error_skipped queue and it doesn't disappear entirely.
Below is a very simple example. The client app receives failed message and it disappear from a test_error queue but it still exists on the test_error_skipped queue.
Service project
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using GreenPipes;
using MassTransit;
using MassTransit.Util;
namespace MassTransitTest.Service
{
public class RequestModel
{
public DateTime RequestTime { get; set; }
}
class MassTransitService : IDisposable
{
private readonly IBusControl _busControl;
public MassTransitService()
{
_busControl = Bus.Factory.CreateUsingRabbitMq(configure =>
{
var host = configure.Host(new Uri("rabbitmq://localhost/mt_test"), h =>
{
h.Username("guest");
h.Password("guest");
});
configure.ReceiveEndpoint(host, "test", c =>
{
c.UseRetry(r => r.None());
c.Consumer<RequestConsumer>();
});
});
TaskUtil.Await(_busControl.StartAsync());
Console.WriteLine("bus started");
}
public void Dispose()
{
_busControl?.StopAsync().Wait();
}
}
class RequestConsumer : IConsumer<RequestModel>
{
public Task Consume(ConsumeContext<RequestModel> context)
{
Console.WriteLine($"request with message id {context.MessageId} received: {context.Message.RequestTime}");
throw new NotImplementedException();
}
}
}
Client project
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using GreenPipes;
using MassTransit;
using MassTransit.Util;
using MassTransitTest.Service;
namespace MassTransitTest.Client
{
class MassTransitClient
{
private readonly IBusControl _busControl;
public MassTransitClient()
{
_busControl = Bus.Factory.CreateUsingRabbitMq(configure =>
{
var host = configure.Host(new Uri("rabbitmq://localhost/mt_test"), h =>
{
h.Username("guest");
h.Password("guest");
}); enter code here
configure.ReceiveEndpoint(host, "test_error", c =>
{
c.Consumer<ErrorConsumer>();
});
});
TaskUtil.Await(_busControl.StartAsync());
Console.WriteLine("bus started");
}
public async Task Send()
{
Console.WriteLine("sending request");
await (await _busControl.GetSendEndpoint(new Uri("rabbitmq://localhost/mt_test/test"))).Send(new RequestModel()
{
RequestTime = DateTime.Now
});
Console.WriteLine("request sent");
}
}
class ErrorConsumer : IConsumer<Fault<RequestModel>>
{
public Task Consume(ConsumeContext<Fault<RequestModel>> context)
{
Console.WriteLine($"request with message id {context.Message.FaultedMessageId} failed. requested time: {context.Message.Message.RequestTime}");
return Task.CompletedTask;
}
}
}
I'm using .net core 2.1.1 and MassTransit 5.1.3
To answer your question, which has several parts, first:
MassTransit receive endpoints move the message to the _error queue when an exception is thrown by the consumer. Creating a receive endpoint on the _error queue is not suggested, and should not be done.
If you simply want to observe if a fault occurred on the consumer, you can create a separate receive endpoint (such as fault-queue) and register your Fault<T> consumer. MassTransit will publish a message that implements Fault<T>, which the broker will route to your consumer via the receive endpoint.
However, based on your example above, you're sending a request and expecting the client to know if a fault occurred. For this, I'd recommend using the request client - which sets up the message headers to return faults back to the request originator. It also allows responses to be sent. If you don't want to wait for the response, or wait to see if the fault occurred, the above fault observer is your best option.
You can see how to use the request client in the documentation.
Is it possible to host SignalR as a part of WCF websocket service and not as a part of ASP.net web site. I am aware about pushing mesage from a web service to signalR clients but is it also possible tht when the socket connection is opened from browser it maps to a web serivce contract?
You can self-host the SignalR server:
Taken from (https://github.com/SignalR/SignalR/wiki/QuickStart-Hubs):
Getting Started
To get started, Install the following packages:
Install-Package Microsoft.Owin.Hosting -pre
Install-Package Microsoft.Owin.Host.HttpListener -pre
Install-Package Microsoft.AspNet.SignalR.Owin -pre
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Hosting;
using Owin;
namespace SignalR.Hosting.Self.Samples
{
class Program
{
static void Main(string[] args)
{
string url = "http://172.0.0.01:8080";
using (WebApplication.Start<Startup>(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
// This will map out to http://localhost:8080/signalr by default
// This means a difference in the client connection.
app.MapHubs();
}
}
public class MyHub : Hub
{
public void Send(string message)
{
Clients.All.addMessage(message);
}
}
}
You can host the SignarR hub in any .Net application, like:
public class Program
{
public static void Main(string[] args)
{
// Connect to the service
var hubConnection = new HubConnection("http://localhost/mysite");
// Create a proxy to the chat service
var chat = hubConnection.CreateProxy("chat");
// Print the message when it comes in
chat.On("addMessage", message => Console.WriteLine(message));
// Start the connection
hubConnection.Start().Wait();
string line = null;
while((line = Console.ReadLine()) != null)
{
// Send a message to the server
chat.Invoke("Send", line).Wait();
}
}
}
Ref: https://github.com/SignalR/SignalR/wiki/QuickStart-Hubs
If there any specific reason you want to use WCF? you can write your service as SignarR hub only.