I am writing unit tests for my SignalR hubs and using the SignalR .NET client (version 2.3.0) to carry out the tests, but I am not able to get other connections to receive a broadcast when the current connection is excluded from the broadcast.
The hub method is making a broadcast as such:
Clients.Group(groupName, Context.ConnectionId).sendMessage("A message");
My test is configured to have 2 connections to the hub and each connection will call a hub method that will put the connection into groupName. I then have a HubConnection.On event to handle the broadcast the hub method makes, the result of which is then used to assert the test. This might be clearer explained in code (I have removed some AddBroadcast overloads for brevity):
/// <summary>
/// Class to handle the connection, calling of methods and broadcast for a SignalR hub
/// </summary>
public class HubManager
{
public enum Froms
{
Other1,
Other2
}
private HubConnection hubConnection = null;
private IHubProxy hub = null;
/// <summary>
/// The outcome of the broadcast that is made
/// </summary>
public object Result { get; private set; } = null;
/// <summary>
/// The connection ID of this hub connection
/// </summary>
public string ConnectionID { get { return this.hubConnection.ConnectionId; } }
public HubManager(string h)
{
//Create the SignalR connection
this.hubConnection = new HubConnection("http://mywebsite.com");
//Gain access to the hub
this.hub = hubConnection.CreateHubProxy(h);
//Start the connection up and wait for it to complete
this.hubConnection.Start()
.ContinueWith(task =>
{
if (task.IsFaulted)
{
throw new Exception($"Error opening the hub connection ({h}): {task.Exception.GetBaseException()}");
}
})
.Wait();
}
/// <summary>
/// Broadcast a message to clients
/// </summary>
/// <param name="methodName">The name of broadcast message</param>
public void AddBroadcast(string methodName)
{
this.hub.On(methodName, () => {
this.Result = methodName;
});
}
/// <summary>
/// Broadcast a message to clients
/// </summary>
/// <param name="methodName">The name of broadcast message</param>
public void AddBroadcast<T>(string methodName)
{
this.hub.On<T>(methodName, _1 => {
this.Result = _1;
});
}
/// <summary>
/// Invokes a specific hub method
/// </summary>
/// <param name="methodName">The name of the hub method to invoke</param>
/// <param name="args">The parameters for the method</param>
public void CallMethod(string methodName, params object[] args)
{
this.hub.Invoke(methodName, args)
.ContinueWith(task =>
{
if (task.IsFaulted)
{
throw new Exception($"Error calling hub method {methodName}: {task.Exception.GetBaseException()}");
}
})
.Wait();
}
}
Usage case:
//Create a dictionary to hold the connections
var hubManagers = new Dictionary<HubManager.Froms, HubManager>();
hubManagers.Add(HubManager.Froms.Other1, new HubManager(hubName));
hubManagers.Add(HubManager.Froms.Other2, new HubManager(hubName));
//Call HubMethod1 which will add the connection to the same group
hubManagers[HubManager.Froms.Other1].CallMethod("HubMethod1", user1ID);
hubManagers[HubManager.Froms.Other2].CallMethod("HubMethod1", user2ID);
//Set a broadcast handle for the second connection (Other2)
hubManagers[HubManager.Froms.Other2].AddBroadcast<string, string>("callbackMethod");
//Make a hub method call (from Other1) to cause the callbackMethod to run
//As from above, the Other1 connection should not receive it but Other2 should
hubManagers[HubManager.Froms.Other1].CallMethod("HubMethod2", user1ID);
//Get the broadcast result for the second connection (Other2)
var result = hubManagers[HubManager.Froms.Other2].Result;
//result == null
I have tried using the AdddBroadcast on the following combinations and each case results in result being null: Other1, Other2 and Other1 & Other2.
If I change Clients.Group(groupName, Context.ConnectionId).sendMessage("A message"); to not exclude the current connection (Clients.Group(groupName).sendMessage("A message");) and I use AddBroadcast on Other2, result contains the expected value.
The process works as expected on the live system (using ASP.NET and JavaScript) where the calling connection does not get sent the sendMessage but the other members of the group do.
Any ideas on how I make the second SignalR .NET client connection receive the broadcast when the first connection is excluded from the broadcast are very much welcomed!
After some time, I realised that the this.hub.On event was firing, but after the call to get the result (var result = hubManagers[HubManager.Froms.Other2].Result;). I therefore added a crude wait method (which I am not massively proud of!) and that has done the trick.
New method:
/// <summary>
/// Wait for up to 5 seconds for the broadcast to complete
/// </summary>
public void WaitForBroadcastResult()
{
bool stop = false;
const double WAIT_FOR = 5D;
DateTime started = DateTime.Now;
while (!stop)
{
if (this.Result != null || (DateTime.Now - started).TotalSeconds >= WAIT_FOR)
{
stop = true;
}
else
{
System.Threading.Thread.Sleep(100);
}
}
}
Use:
...
//Set a broadcast handle for the second connection (Other2)
hubManagers[HubManager.Froms.Other2].AddBroadcast<string, string>("callbackMethod");
//Make a hub method call (from Other1) to cause the callbackMethod to run
//As from above, the Other1 connection should not receive it but Other2 should
hubManagers[HubManager.Froms.Other1].CallMethod("HubMethod2", user1ID);
//Wait for the broadcast to complete
this.HubManagers[_HubManager.Froms.Other2].WaitForBroadcastResult();
//Get the broadcast result for the second connection (Other2)
var result = hubManagers[HubManager.Froms.Other2].Result;
If anybody has a better idea, then I'm all ears!
Related
I am trying to start my own C# service as a Windows service. I installed my service using sc.exe create. I can see and modify the Service in the Services Application, but when I try to start, I am always getting the Error 1053.
In my Program I am starting a new Thread which runs an infinite while loop in Execute.
static void Main(string[] args) {
Console.WriteLine("Starting Thread");
mainThread = new Thread(new ThreadStart(Execute));
mainThread.Start();
Console.WriteLine("Thread Started!");
}
public static void Execute() {
//connect to Database
while(true) { //while connection open
...
}
}
When I run my Program manually, in a Console, Powershell and in Visual Studio, the Program runs as expected. When I try to run it via Services I get the error. I'm also getting the error when I'm running a empty Main (Main only with Console.WriteLine).
I googled really much and tried extending the Timeout in Registry(ServicesPipeTimeout), Using Threads, Installing Core Framework, Reinstalling the service and being owner of the service.
EDIT: I changed everything to run as a Hosted Service. In my Main i have
await new HostBuilder()
.ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); })
.Build()
.RunAsync();
But i Still can't run it as a Windows Service, I'm getting the same Error.
I am out of Ideas, any help is highly appreciated.
Regards
What you wrote is a standard Console app. A service shall expose Start and Stop facilities to be correctly interpreted by Windows (or by SystemD equivalently).
Your core loop of the main should be hosted on a IHostedService, or a Worker service.
Give a look at [here][1] to figure out what you have to do.
I give you here below a plausible Program.cs file that I wrote some time ago for a Linux service (there is actually no difference with Windows services, just remove the .UseSystemD() call).
/// <summary>
/// Remote service App for Monday Parser and Database manager.
/// </summary>
public static class Program
{
private static readonly ILogger logger = new LoggerConfiguration()
.ReadFrom.Configuration(MondayConfiguration.Configuration, sectionName: "AppLog")
.CreateLogger().ForContext("Origin", "MondayService");
/// <summary>
/// Helper to create hosting environment.
/// </summary>
/// <param name="args">
/// command line arguments if any- no management is occurring now.
/// </param>
/// <returns>
/// </returns>
public static IHostBuilder CreateWebHostBuilder(string[] args)
{
string curDir = MondayConfiguration.DefineCurrentDir();
IConfigurationRoot config = new ConfigurationBuilder()
// .SetBasePath(Directory.GetCurrentDirectory())
.SetBasePath(curDir)
.AddJsonFile("servicelocationoptions.json", optional: false, reloadOnChange: true)
#if DEBUG
.AddJsonFile("appSettings.Debug.json")
#else
.AddJsonFile("appSettings.json")
#endif
.Build();
return Host.CreateDefaultBuilder(args)
.UseContentRoot(curDir)
.ConfigureAppConfiguration((_, configuration) =>
{
configuration
.AddIniFile("appSettings.ini", optional: true, reloadOnChange: true)
#if DEBUG
.AddJsonFile("appSettings.Debug.json")
#else
.AddJsonFile("appSettings.json")
#endif
.AddJsonFile("servicelocationoptions.json", optional: false, reloadOnChange: true);
})
.UseSerilog((_, services, configuration) => configuration
.ReadFrom.Configuration(config, sectionName: "AppLog")// (context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console())
// .UseSerilog(MondayConfiguration.Logger)
.ConfigureServices((hostContext, services) =>
{
services
.Configure<ServiceLocationOptions>(hostContext.Configuration.GetSection(key: nameof(ServiceLocationOptions)))
.Configure<HostOptions>(opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(30));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
ServiceLocationOptions? locationOptions = config.GetSection(nameof(ServiceLocationOptions)).Get<ServiceLocationOptions>();
string url = locationOptions?.HttpBase + "*:" + locationOptions?.Port;
webBuilder.UseUrls(url);
})
.UseSystemd();
}
Here below you find the main implementation of the service, I added a lot of XML to show you all the exception you may get when moving things around in the service and which methods should be implmented as a minimum to have the service to work.
If you remove all the variables that you don't understand, you will remain with a working skeleton of a windows service running simply a ping service. Try it.
/// <summary>
/// Main Worker class for managing the service inside SystemD.
/// </summary>
public partial class MondayService : BackgroundService
{
/// <summary>
/// Initializes a new instance of the <see cref="MondayService"/> class. Std ctr.
/// </summary>
/// <param name="mondayhub">
/// </param>
/// <param name="containerServer">
/// </param>
public MondayService(IHubContext<MondayHub, IMondayServiceHub> mondayhub,
IFeatureContainer containerServer)
{
_containerServer = containerServer;
_dbManager = _containerServer.DbManager;
_parser = _containerServer.Parser;
_syslogQueue = _containerServer.SyslogQueue;
_segmentManager = _containerServer.SegmentManager;
_orderManager = _containerServer.OrderManager;
while (!MondayConfiguration.SerilogFactoryReady)
{
Thread.Sleep(20);
}
// _logger = MondayConfiguration.LoggerFactory.CreateLogger("");
_logger = new LoggerConfiguration().ReadFrom.Configuration(MondayConfiguration.Configuration, sectionName: "AppLog").CreateLogger().ForContext("Origin", "MondayService");
_mondayHub = mondayhub;
}
/// <summary>
/// Setup activities for the Monday service.
/// </summary>
/// <param name="cancellationToken">
/// </param>
/// <returns>
/// </returns>
/// <exception cref="OverflowException">
/// <paramref><name>value</name></paramref> is less than <see cref="TimeSpan.MinValue"/>
/// or greater than <see cref="TimeSpan.MaxValue"/>.
/// -or- value is <see><cref>System.Double.PositiveInfinity</cref></see> .
/// -or- value is <see cref="double.NegativeInfinity"/>.
/// </exception>
/// <exception cref="AggregateException">
/// The task was canceled. The <see><cref>InnerExceptions</cref></see> collection
/// contains a <see cref="TaskCanceledException"/> object.
/// -or- An exception was thrown during the execution of the task. The
/// <see><cref>InnerExceptions</cref></see> collection contains information about the
/// exception or exceptions.
/// </exception>
/// <exception cref="TaskCanceledException">
/// The task has been canceled.
/// </exception>
/// <exception cref="ArgumentNullException">
/// The <paramref><name>function</name></paramref> parameter was <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The <see cref="CancellationTokenSource"/> associated with <paramref
/// name="cancellationToken"/> was disposed.
/// </exception>
/// <exception cref="IOException">
/// destFileName already exists and overwrite is <see langword="false"/>.
/// -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="FileNotFoundException">
/// <paramref><name>sourceFileName</name></paramref> was not found.
/// </exception>
public override Task StartAsync(CancellationToken cancellationToken)
{
_logger.DebugInfo().Information("MondayService: Starting StartAsync");
var parserRenewed = false;
if (_parser is null)
{
_parser = new();
_parser.OnParserSocketDataArrived +=
(sender, e) => _containerServer.BroadcastParserSocketDataViaSignalR(sender, e);
parserRenewed = true;
Task.Run(ParserSubscriptions, cancellationToken);
_logger.DebugInfo().Information("MondayService: Instantiating again the parser inside StartAsync");
}
if (_dbManager is null || parserRenewed)
{
_dbManager = new(_parser);
_logger.DebugInfo().Information("MondayService: Instantiating again the db manager inside StartAsync");
_dbManager.ConnectToParserSocket();
_dbManager.OnFilteredSocketDataArrived +=
(sender, e) => _containerServer.BroadcastFilteredSocketDataViaSignalR(sender, e);
if (!_tagsDataSavedOnce)
{
// ReSharper disable once ExceptionNotDocumented
Tags.SaveAll();
_tagsDataSavedOnce = true;
}
}
if (_segmentManager is null)
{
_segmentManager = new(_parser, _dbManager);
_segmentManager.ConnectToParserSocket(_parser);
_segmentManager.OnSegmentClosure += (sender, e) => _containerServer.BroadcastSegmentDataViaSignalR(sender, e);
_logger.DebugInfo().Information("MondayService: Instantiating again the segment manager inside StartAsync");
}
if (_orderManager is null)
{
_orderManager = new(_parser);
_orderManager.OnOrderManagerEvent +=
(sender, e) => _containerServer.BroadcastOrderManagerEventsViaSignalR(sender, e);
_logger.DebugInfo().Information("MondayService: Instantiating again the order manager inside StartAsync");
}
_logger.DebugInfo().Information("MondayService: Completing StartAsync");
return base.StartAsync(cancellationToken);
}
/// <summary>
/// Graceful shutdown and disposal of Monday service (parser and database manager comprised).
/// </summary>
/// <param name="cancellationToken">
/// </param>
/// <returns>
/// </returns>
/// <exception cref="IOException">
/// destFileName already exists and overwrite is <see langword="false"/>.
/// -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="AggregateException">
/// The task was canceled. The <see><cref>InnerExceptions</cref></see> collection
/// contains a <see cref="TaskCanceledException"/> object.
/// -or- An exception was thrown during the execution of the task. The
/// <see><cref>InnerExceptions</cref></see> collection contains information about the
/// exception or exceptions.
/// </exception>
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.DebugInfo().Information("MondayService: Starting StopAsync");
if (!_tagsDataSavedOnce)
{
Tags.SaveAll();
_tagsDataSavedOnce = true;
}
_logger.DebugInfo().Information("Stopping Monday Service hosted on Linux.");
if (_parser is not null) await _parser.UnsubscribeAllAsync();
foreach (string ex in Tags.Exchanges.ExchangeNames.ToList())
{
_parser?.DeactivateRest(ex);
}
_parser?.Dispose();
_dbManager?.Dispose();
_orderManager?.Dispose();
_segmentManager?.Dispose();
_logger.DebugInfo().Information("MondayService: Completing StopAsync");
await base.StopAsync(cancellationToken);
}
/// <summary>
/// Core loop of the service. Here all the assets are instantiated and managed up to
/// final disposal. This instantiates the SignalR service and manages it.
/// </summary>
/// <param name="stoppingToken">
/// </param>
/// <returns>
/// </returns>
/// <exception cref="TaskCanceledException">
/// The task has been canceled.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref><name>delay</name></paramref> represents a negative time interval other
/// than <see langword="TimeSpan.FromMilliseconds(-1)"/>.
/// -or- The <paramref><name>delay</name></paramref> argument's <see
/// cref="P:System.TimeSpan.TotalMilliseconds"/> property is greater than <see cref="int.MaxValue"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// The provided <paramref><name>cancellationToken</name></paramref> has already been disposed.
/// </exception>
/// <exception cref="OverflowException">
/// <paramref><name>value</name></paramref> is less than <see cref="TimeSpan.MinValue"/>
/// or greater than <see cref="TimeSpan.MaxValue"/>.
/// -or- <paramref><name>value</name></paramref> is <see cref="double.PositiveInfinity"/>.
/// -or- <paramref><name>value</name></paramref> is <see cref="double.NegativeInfinity"/>.
/// </exception>
/// <exception cref="IOException">
/// destFileName already exists and overwrite is <see langword="false"/>.
/// -or- An I/O error has occurred, e.g. while copying the file across disk volumes.
/// </exception>
/// <exception cref="UnauthorizedAccessException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="SecurityException">
/// The caller does not have the required permission.
/// </exception>
/// <exception cref="FileNotFoundException">
/// <paramref><name>sourceFileName</name></paramref> was not found.
/// </exception>
/// <exception cref="DirectoryNotFoundException">
/// The specified path is invalid (for example, it is on an unmapped drive).
/// </exception>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.DebugInfo().Information("Monday Service starting at {Now}", DateTimeOffset.Now);
while (!stoppingToken.IsCancellationRequested)
{
Task tlog = FetchLogAsync(stoppingToken);
Task? tping = null;
int seconds = DateTimeOffset.Now.Second;
int minutes = DateTimeOffset.Now.Minute;
// logging a ping every 5 minutes
if (seconds < 5 && minutes % 1 == 0)
{
tping = Ping();
if (!_tagsDataSavedOnce)
{
Tags.SaveAll();
_tagsDataSavedOnce = true;
}
}
// looping every 5 seconds
var tLoop = Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
if (tping is null)
{
await Task.WhenAll(tlog, tLoop);
}
else
{
await Task.WhenAll(tlog, tLoop, tping);
}
}
_logger.DebugInfo().Information("Monday Service stopping at {Now}", DateTimeOffset.Now);
}
[MustUseReturnValue]
private Task FetchLogAsync(CancellationToken stoppingToken)
{
var ok = true;
while (ok)
{
try
{
ok = _containerServer.SyslogQueue.Reader.TryRead(
out (LogLevel lvl, string line) item);
if (ok)
{
switch (item.lvl)
{
case LogLevel.Trace:
case LogLevel.Debug:
_logger.DebugInfo().Debug("{FetchedMessage}", item.line);
break;
case LogLevel.Information:
_logger.DebugInfo().Information("{FetchedMessage}", item.line);
break;
case LogLevel.Warning:
_logger.DebugInfo().Warning("{FetchedMessage}", item.line);
break;
case LogLevel.Error:
_logger.DebugInfo().Error("{FetchedMessage}", item.line);
break;
case LogLevel.Critical:
_logger.Fatal("{FetchedMessage}", item.line);
break;
case LogLevel.None:
break;
}
}
if (stoppingToken.IsCancellationRequested)
{
ok = false;
}
}
catch
{
ok = false;
}
}
return Task.CompletedTask;
}
private Task<CallResult<UpdateSubscription>> ParserSubscriptions()
{
Guard.Against.Null(nameof(_parser));
return _parser!.SubscribeFromSettingsAsync();
}
private async Task Ping()
{
await _containerServer.SyslogQueue.Writer.WriteAsync((
LogLevel.Information,
$"Monday Service active at: {DateTime.UtcNow.ToLocalTime()}"));
}
/// <summary>
/// This is a debug utility to check whether the service creates too many ThreaPpool threads.
/// </summary>
public static class ProcessTracker
{
static ProcessTracker()
{
}
/// <summary>
/// See https://stackoverflow.com/questions/31633541/clrmd-throws-exception-when-creating-runtime/31745689#31745689
/// </summary>
/// <returns>
/// </returns>
public static string Scan()
{
StringBuilder sb = new();
StringBuilder answer = new();
answer.Append("Active Threads").Append(Environment.NewLine);
// Create the data target. This tells us the versions of CLR loaded in the target process.
int countThread = 0;
var pid = Environment.ProcessId;
using (var dataTarget = DataTarget.AttachToProcess(pid, 5000, AttachFlag.Passive))
{
// Note I just take the first version of CLR in the process. You can loop over
// every loaded CLR to handle the SxS case where both desktop CLR and .Net Core
// are loaded in the process.
ClrInfo version = dataTarget.ClrVersions[0];
var runtime = version.CreateRuntime();
// Walk each thread in the process.
foreach (ClrThread thread in runtime.Threads)
{
try
{
sb = new();
// The ClrRuntime.Threads will also report threads which have recently
// died, but their underlying data structures have not yet been cleaned
// up. This can potentially be useful in debugging (!threads displays
// this information with XXX displayed for their OS thread id). You
// cannot walk the stack of these threads though, so we skip them here.
if (!thread.IsAlive)
continue;
sb.Append("Thread ").AppendFormat("{0:X}", thread.OSThreadId).Append(':');
countThread++;
// Each thread tracks a "last thrown exception". This is the exception
// object which !threads prints. If that exception object is present, we
// will display some basic exception data here. Note that you can get
// the stack trace of the exception with ClrHeapException.StackTrace (we
// don't do that here).
ClrException? currException = thread.CurrentException;
if (currException is ClrException ex)
{
sb.Append("Exception: ")
.AppendFormat("{0:X}", ex.Address)
.Append(" (").Append(ex.Type.Name)
.Append("), HRESULT=")
.AppendFormat("{0:X}", ex.HResult)
.AppendLine();
}
// Walk the stack of the thread and print output similar to !ClrStack.
sb.AppendLine(" ------> Managed Call stack:");
var collection = thread.EnumerateStackTrace().ToList();
foreach (ClrStackFrame frame in collection)
{
// Note that CLRStackFrame currently only has three pieces of data:
// stack pointer, instruction pointer, and frame name (which comes
// from ToString). Future versions of this API will allow you to get
// the type/function/module of the method (instead of just the
// name). This is not yet implemented.
sb.Append(" ").Append(frame).AppendLine();
}
}
catch
{
//skip to the next
}
finally
{
answer.Append(sb);
}
}
}
answer.Append(Environment.NewLine).Append(" Total thread listed: ").Append(countThread);
return answer.ToString();
}
}
}
The main core loop is pinging a queue for logging. It is just a way to let something run over the service permanently while other classes do their work (in this case they are almost all based on EAP).
Definitely, look at MSDN at first. It seems that you are missing the basics.
[1]: https://learn.microsoft.com/en-us/dotnet/core/extensions/workers
I ended up writing it as a Windows Service. I simply inherited ServiceBase and wrote Start and Stop methods. The start method starts a Timer which calls a Method with the infinite loop every minute.
See: Error 1053: Service did not respond in time
I have the following code for a MQTT Subscriber in a Background Task:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PickByLight.BackgroundTask.Models;
using PickByLight.Database.Wrapper.Interfaces;
using PickByLight.Logic;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
namespace PickByLight.BackgroundTask
{
/// <summary>
/// Hosted MQTT Background Service
/// </summary>
public class HostedMQTTService : IHostedService, IDisposable
{
private readonly Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
/// <summary>
/// MQTT Client
/// </summary>
private MqttClient MqttClient { get; set; }
/// <summary>
/// Name of the Pick by Light
/// </summary>
private string PickByLight_Name { get; set; }
/// <summary>
/// MQTT is activated
/// </summary>
private bool MqttIsActive { get; set; }
/// <summary>
/// IP Adress of the MQTT URL
/// </summary>
private string MqttURL { get; set; }
/// <summary>
/// Storage Process for an material
/// </summary>
private MaterialStorageProcess StorageProcess { get; set; }
/// <summary>
/// Service Scope Factory
/// </summary>
private IServiceScopeFactory ServiceScopeFactory { get; set; }
/// <summary>
/// Configuration
/// </summary>
private IConfiguration Configuration { get; set; }
/// <summary>
/// Logger
/// </summary>
private readonly ILogger<HostedMQTTService> _logger;
/// <summary>
/// Constructor
/// </summary>
/// <param name="configuration"></param>
public HostedMQTTService(IConfiguration configuration, ILogger<HostedMQTTService> logger, IServiceScopeFactory serviceScopeFactory)
{
this.PickByLight_Name = configuration.GetValue<string>("PickByLight_Name");
this.MqttURL = configuration.GetValue<string>("MQTTUrl");
this.MqttIsActive = configuration.GetValue<bool>("MQTTConnection");
this.ServiceScopeFactory = serviceScopeFactory;
this.Configuration = configuration;
this._logger = logger;
}
/// <summary>
/// Start the Task of the Background Service
/// </summary>
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Background-Service started...");
while (true)
{
try
{
//No Object is created
if (this.MqttClient == null)
{
_logger.LogInformation("Try to establishe new MQTT Client");
this.MqttClient = CreateNewMqttConnection();
}
else if (this.MqttClient.IsConnected == false)
{
_logger.LogInformation("MQTT Client is disconnected... Try to reconnect!");
this.MqttClient = CreateNewMqttConnection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ein schwerwiegender Fehler im MQTT Background-Service ist aufgetreten.");
}
}
}
/// <summary>
/// Prints out all received messages
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Mqtt_Message_Received(object sender, MqttMsgPublishEventArgs e)
{
try
{
var agcMessage = Encoding.UTF8.GetString(e.Message);
_logger.LogInformation("Topic: " + e.Topic + " | Nachricht: " + agcMessage + " | QOS: " + e.QosLevel);
var resultString = Encoding.UTF8.GetString(e.Message);
MqttReadTopicClass mqttContent = JsonConvert.DeserializeObject<MqttReadTopicClass>(resultString);
using (var scope = this.ServiceScopeFactory.CreateScope())
{
var storageConfigurationManager = scope.ServiceProvider.GetService<IStorageConfigurationManager>();
var storageElementManager = scope.ServiceProvider.GetService<IStorageElementManager>();
this.StorageProcess = new MaterialStorageProcess(storageConfigurationManager, storageElementManager, this.Configuration);
StorageProcess.Remove(mqttContent.storageLocation);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Schwerwiegender Fehler beim Lesen von MQTT Nachrichten");
}
}
/// <summary>
/// Create new MQTT connection if connection is lost or doesn't exist
/// </summary>
private MqttClient CreateNewMqttConnection()
{
_logger.LogInformation("Create MQTT Client");
MqttClient client = new MqttClient(this.MqttURL, 32005, false, null, null, MqttSslProtocols.None);
string clientId = Guid.NewGuid().ToString();
client.MqttMsgPublishReceived += Mqtt_Message_Received;
client.Connect(clientId);
client.Subscribe(new string[] { "buttonpress_sepioo_pdi/" + this.PickByLight_Name }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
_logger.LogInformation("MQTT Client created");
return client;
}
/// <summary>
/// Stop the Task of the Background Service
/// </summary>
public async Task StopAsync(CancellationToken cancellationToken)
{
//Stop called without start
if (_executingTask == null)
{
return;
}
try
{
//Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
//wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
/// <summary>
/// Dispose the Background Service
/// </summary>
public void Dispose()
{
_stoppingCts.Cancel();
}
}
}
In my startup.cs File i am doing the following:
//Register Background Task
services.AddHostedService<HostedMQTTService>();
The problem is, that it seems to me that the hosted service is blocking the user-interface/webserver threads because i can not access the url of the .net 6 mvc application.
Could you give me a hint or a solution to this problem?
Thanks.
You will need to change your StartAsync-method to something like this:
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
{
_logger.LogInformation("Background-Service started...");
while (!cancellationToken.IsCancellationRequested)
{
try
{
//No Object is created
if (this.MqttClient == null)
{
_logger.LogInformation("Try to establish new MQTT Client");
this.MqttClient = CreateNewMqttConnection();
}
else if (this.MqttClient.IsConnected == false)
{
_logger.LogInformation("MQTT Client is disconnected... Try to reconnect!");
this.MqttClient = CreateNewMqttConnection();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ein schwerwiegender Fehler im MQTT Background-Service ist aufgetreten.");
}
}
});
}
You are blocking the process, because your hosted service never starts.
Remove the while(true) loop from the Start method.
Ensure base.StartAsync(cancellationToken); is always called.
Edit: I saw you implement the IHostedService interface. Try to inherits from the BackgroundService class
I have an ASP.NET MVC project and one page of it must show my device state. I want get device state with MQTT from a broker (broker.mqttdashboard.com). I am using MQTTnet. I can not connect to broker , I have not username or password and I want to use a public channel to publish messages to broker by pressing a button. the button run PublishMqttMsg method and I want to give messages from broker.
I don't know how to connect to broker. Can anyone help me?
My controller code is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
using System;
using System.Text;
using System.Threading.Tasks;
using MQTTnet.Diagnostics;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet;
using Microsoft.AspNet.Identity;
namespace WebApplication10.Controllers
{
public class MqttController : Controller
{
private static IManagedMqttClient client;
// GET: Mqtt
public ActionResult Index()
{
var CurrentUser = User.Identity.GetUserId();
ConnectAsync(CurrentUser, "broker.mqttdashboard.com", "", 8000, true);
client.UseConnectedHandler(e =>
{
Console.WriteLine("Connected successfully with MQTT Brokers.");
});
client.UseDisconnectedHandler(e =>
{
Console.WriteLine("Disconnected from MQTT Brokers.");
});
client.UseApplicationMessageReceivedHandler(e =>
{
try
{
string topic = e.ApplicationMessage.Topic;
if (string.IsNullOrWhiteSpace(topic) == false)
{
string payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
Console.WriteLine($"Topic: {topic}. Message Received: {payload}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message, ex);
}
});
return View();
}
public ActionResult SubscribeMqttMsg()
{
SubscribeAsync("test_topic/1");
return View("Index");
}
public ActionResult PublishMqttMsg()
{
PublishAsync("test_topic/1", "test message");
return View("Index");
}
/// <summary>
/// Connect to broker.
/// </summary>
/// <returns>Task.</returns>
public static async Task ConnectAsync(string user, string uri, string pass, int port, bool userSSL)
{
string clientId = Guid.NewGuid().ToString();
string mqttURI = uri;
string mqttUser = user;
string mqttPassword = pass;
int mqttPort = port;
bool mqttSecure = userSSL;
var messageBuilder = new MqttClientOptionsBuilder()
//.WithClientId(clientId)
//.WithCredentials(mqttUser, mqttPassword)
.WithTcpServer(mqttURI, mqttPort)
.WithCleanSession();
var options = mqttSecure
? messageBuilder
.WithTls()
.Build()
: messageBuilder
.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(options)
.Build();
client = new MqttFactory().CreateManagedMqttClient();
await client.StartAsync(managedOptions);
}
/// <summary>
/// Publish Message.
/// </summary>
/// <param name="topic">Topic.</param>
/// <param name="payload">Payload.</param>
/// <param name="retainFlag">Retain flag.</param>
/// <param name="qos">Quality of Service.</param>
/// <returns>Task.</returns>
public static async Task PublishAsync(string topic, string payload, bool retainFlag = true, int qos = 1) =>
await client.PublishAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.WithRetainFlag(retainFlag)
.Build())
;
/// <summary>
/// Subscribe topic.
/// </summary>
/// <param name="topic">Topic.</param>
/// <param name="qos">Quality of Service.</param>
/// <returns>Task.</returns>
public static async Task SubscribeAsync(string topic, int qos = 1) =>
await client.SubscribeAsync(new TopicFilterBuilder()
.WithTopic(topic)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.Build());
}
}
Port 8000 at broker.hivemq.com is the "Websocket Port" (so listening for MQTT over websockets connections). You are attempting to connect using a TCP client. Either change the port (to 1833) or change:
var messageBuilder = new MqttClientOptionsBuilder()
.WithTcpServer(mqttURI, mqttPort)
...
to:
var messageBuilder = new MqttClientOptionsBuilder()
.WithWebSocketServer("broker.hivemq.com:8000/mqtt")
...
(see the documentation).
I have been struggling on this issue for weeks now.
I have an app where i have configured owin backend with web api and autofac DI with background handfire jobs. I have alsmost looked at every question on Stackoveflow regarding this but nothing seems to work.
My app regarding OWIN/Hangfire/WebAPI all seems to work okay. Until it comes to SignalR push messages.
If i call any notification hub endpoint from js client push messages go okay and i can receive push messages on any other connected client. But when i wan to send message from my api controller or hangfire job it never reaches to any client.
Startup.cs
public void Configuration(IAppBuilder app)
{
//var signalRHelper = new SignalRHelper(GlobalHost.ConnectionManager.GetHubContext<NotificationHub>());
var constants = new Constants();
constants.Set(ConstantTypes.AllyHrNoReplyEmailAddress, Util.Constants.AllyHrNoReplyEmailAddress);
constants.Set(ConstantTypes.SendGridKey, Util.Constants.SendGridKey);
constants.Set(ConstantTypes.EncryptionKey, Util.Constants.EncryptionKey);
constants.Set(ConstantTypes.ApiUrl, Util.Constants.ApiUrl);
constants.Set(ConstantTypes.RootFolder, Util.Constants.RootFolder);
constants.Set(ConstantTypes.FrontEndUrl, Util.Constants.FrontEndUrl);
GlobalConfiguration.Configuration
.UseSqlServerStorage("AllyHrDb");
var config = System.Web.Http.GlobalConfiguration.Configuration;
var builder = new ContainerBuilder();
var jobBuilder = new ContainerBuilder();
var signalRBuilder = new ContainerBuilder();
var hubConfig = new HubConfiguration();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).PropertiesAutowired();
builder.Register(x => constants);
builder.RegisterModule(new ServiceModule());
jobBuilder.Register(x => constants);
jobBuilder.RegisterModule(new HangfireServiceModule());
signalRBuilder.RegisterModule(new SignalRServiceModule());
signalRBuilder.Register(x => constants);
signalRBuilder.RegisterType<AutofacDependencyResolver>().As<IDependencyResolver>().SingleInstance();
signalRBuilder.RegisterType<ConnectionManager>().As<IConnectionManager>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<NotificationHub>().ExternallyOwned().SingleInstance();
signalRBuilder.RegisterType<SignalRHelper>().PropertiesAutowired().ExternallyOwned().SingleInstance();
signalRBuilder.Register(context => context.Resolve<IDependencyResolver>().Resolve<IConnectionManager>().GetHubContext<NotificationHub, INotificationHub>()).ExternallyOwned().SingleInstance();
var hubContainer = signalRBuilder.Build();
builder.RegisterInstance(hubContainer.Resolve<IConnectionManager>());
builder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
builder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
builder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
jobBuilder.RegisterInstance(hubContainer.Resolve<IHubContext<INotificationHub>>());
jobBuilder.RegisterInstance(hubContainer.Resolve<NotificationHub>());
jobBuilder.RegisterInstance(hubContainer.Resolve<SignalRHelper>());
var container = builder.Build();
var jobContainer = jobBuilder.Build();
var idProvider = new SignalRCustomUserIdProvider();
hubConfig.Resolver = new AutofacDependencyResolver(hubContainer);
hubConfig.Resolver.Register(typeof(IUserIdProvider), () => idProvider);
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new QueryStringOAuthBearerProvider()
});
map.RunSignalR(hubConfig);
});
GlobalConfiguration.Configuration.UseAutofacActivator(jobContainer);
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseHangfireServer();
app.UseHangfireDashboard();
ConfigureAuth(app);
app.UseWebApi(config);
}
I had to use different container because i have db set to InstancePerRequest scope.
All my services are being resolved in notification hub class, no problems there. The only issues is when i try and send message from hangfire service or even from api controller using hub context it never reaches to any client.
NotificationHub.cs
public interface INotificationHub
{
/// <summary>
///
/// </summary>
void pushNotification(string message);
/// <summary>
///
/// </summary>
/// <param name="model"></param>
void getNotification(object model);
void getMessage(object model);
}
/// <summary>
/// Notification Hub
/// </summary>
[HubName("NotificationHub")]
[Authorize]
public class NotificationHub : Hub<INotificationHub>
{
/// <summary>
///
/// </summary>
public static IHubContext<INotificationHub> GlobalContext { get; private set; }
private readonly IChatMessagingService _chatMessagingService;
private readonly IUserService _userService;
private Guid LoggedInUserId
{
get
{
var claims = ((ClaimsIdentity)Context.User.Identity).Claims.ToArray();
var userIdClaim = claims.FirstOrDefault(x => x.Type.Equals("UserId"));
if (userIdClaim == null) return Guid.Empty;
return Guid.Parse(userIdClaim.Value);
}
}
/// <summary>
/// Consructor
/// </summary>
/// <param name="lifetimeScope"></param>
/// <param name="context"></param>
public NotificationHub(ILifetimeScope lifetimeScope, IHubContext<INotificationHub> context)
{
GlobalContext = context;
try
{
var childScope = lifetimeScope.BeginLifetimeScope();
_chatMessagingService = childScope.Resolve<IChatMessagingService>();
_userService = childScope.Resolve<IUserService>();
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
/// <summary>
/// Notifications
/// </summary>
public void Notifications()
{
Clients.All.pushNotification("AllyHr" + LoggedInUserId);
}
/// <summary>
/// Send Message
/// </summary>
/// <param name="model"></param>
public void SendMessage(SendChatMessageBindingModel model)
{
var chatMessage = _chatMessagingService.SendMessageToGroup(LoggedInUserId, model.GroupId, model.Message);
var recipientIds = _chatMessagingService.GetChatMembersByGroupId(LoggedInUserId, model.GroupId);
var stringUserIds = new List<string>();
var chatGroup = _chatMessagingService.GetChatGroupById(model.GroupId);
foreach (var recipientId in recipientIds)
{
stringUserIds.Add(recipientId.ToString());
}
Clients.Users(stringUserIds).getNotification(new
{
message = "A new Message is Recieved in Chat Group: " + chatGroup.Name,
groupId = chatGroup.Id
});
var chatMessageVm = chatMessage.Map<ChatMessage, ChatMessageViewModel>();
chatMessageVm.Sender = _userService.Get(chatMessageVm.SenderId).Map<User, UserViewModel>();
stringUserIds.Add(LoggedInUserId.ToString());
Clients.Users(stringUserIds).getMessage(chatMessageVm);
}
}
signalRhelper.cs use to call from api or from Hangfire services
public class SignalRHelper
{
public IConnectionManager ConnectionManager { get; set; }
public IHubContext<INotificationHub> HubContext { get; set; }
/// <summary>
/// Send Notifications to Users
/// </summary>
/// <param name="message"></param>
/// <param name="userIds"></param>
public void GetNotification(object message, IList<string> userIds)
{
HubContext.Clients.Users(userIds).getNotification(message);
}
/// <summary>
/// Get LoggedInUser Id for SignalR
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public static Guid GetLoggedInUserId(IPrincipal user)
{
var claim = GetLoggedinUserClaim(user);
if (claim == null) return Guid.Empty;
return Guid.Parse(claim.Value);
}
private static Claim GetLoggedinUserClaim(IPrincipal user)
{
var claim = ((ClaimsIdentity)user.Identity).Claims.ToArray();
return claim.FirstOrDefault(x => x.Type.Equals("UserId"));
}
}
Could this be related to Autofac creating a new lifetimescope for your call, but you were expecting to continue using the existing scope? Maybe check your autofac registrations for singleinstance / instanceperlifetimescope
Just saying, but have you registered any static classes? They can keep your scope alive for far too long.
I see you're using multiple containerbuilders - that's not something we do over here, we have one 'massive' containerbuilder for each app. I'm curious why you're doing that? To satisfy my curiosity, could you try using a single containerbuilder and registering everything on that single builder? (Although it looks like this is a pattern for SignalR and autofac)
The documentation says: " a common error in OWIN integration is the use of GlobalHost."
It looks like you're doing exactly that.
Good morning every body.
I have developing a C# WinForms simple aplication to manage "Apache" windows service, i can start, stop and other operations.
My Scenario:
I open the apache config file and change any line, i made it to causes error configuration (i know it).
When i try start Apache service, this cant start, because the config file syntax is incorrect (i know it).
The message error from the services is registered in Windows Events Viewer (i know it).
I want get this message in my C# WinForms Application, it is posible?
My code:
public void ManageService(string serviceName, int Operation)
{
ServiceController sc = new ServiceController();
sc.ServiceName = serviceName;
try
{
switch (Operation)
{
case 1: sc.Stop(); ; break;
case 2: sc.Start(); break;
}
}
catch (InvalidOperationException e)
{
}
}
How to modify this code to catch messages from the apache service.
PD. Sorry if my english is bery bad ;).
Okay, not a answer per-say but don't know how to place this into a comment. Note the use of WaitForStatus and also a property for error messages. This is fairly bare-bones.
using System;
using System.Linq;
using System.ServiceProcess;
namespace Revenue.Common.Utility
{
public class WindowsServices
{
private string _ErrorMessage;
public string ErrorMessage { get { return _ErrorMessage; } }
/// <summary>
/// Stop a Windows service service name
/// </summary>
/// <param name="ServiceName"></param>
/// <remarks>
/// A service does not stop instantly, so WaitForStatus method
/// is used to 'wait' until the service has stopped. If the
/// caller becomes unresponsive then there may be issues with
/// the service stopping outside of code.
/// </remarks>
public void StopService(string ServiceName)
{
ServiceController sc = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == ServiceName);
if (sc == null)
return;
if (sc.Status == ServiceControllerStatus.Running)
{
try
{
sc.Stop();
sc.WaitForStatus(ServiceControllerStatus.Stopped);
}
catch (InvalidOperationException e)
{
_ErrorMessage = e.Message;
}
}
}
/// <summary>
/// Start a Windows service by service name
/// </summary>
/// <param name="ServiceName"></param>
public void StartService(string ServiceName)
{
ServiceController sc = ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == ServiceName);
if (sc == null)
return;
sc.ServiceName = ServiceName;
if (sc.Status == ServiceControllerStatus.Stopped)
{
try
{
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running);
}
catch (InvalidOperationException)
{
_ErrorMessage = e.Message;
}
}
}
}
}