Injecting SignalR into a Core WorkerService - c#

I am trying to inject SignalR into a WorkerService project, and then simulate server-side notifications.
Further down in my Worker.cs file, I'm getting some null references - which tells me I'm not properly initially my Hub object.
I first created a NotificationHub solution with a project of the same name.
Here's Notifications.cs - which works fine.
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NotificationHub.Hubs
{
public interface IHubClient
{
Task MessageReceived(string user, string message);
Task MessageReceived(string message);
}
public class Notifications: Hub<IHubClient>
{
[HubMethodName("SendMessageToClient")]
public async Task SendMessage(string username, string message)
{
await Clients.All.MessageReceived(username, "from Notification Hub:" + message);
}
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
_ = SendMessage("From Server", "Hub reconnected on server.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
Console.Write("Hub just disconnected on server.");
return base.OnDisconnectedAsync(exception);
}
}
}
The NotificationHub runs in IIS on my local machine, which let's me call the Notification Hub from my TypeScript client.
i.e. I just added a simple HTML button to trigger a message from the client. My function fires:
this.hubConnection.send('SendMessageToClient', 'client', `Message from client side. ${today}`)
.then(() => console.log('Message sent from client.'));
Core WorkerService: My problem is here
I then added a NotificationWorkService, in which I am trying to inject SignalR. The goal is to eventually have this service running on a Windows server which regularly checks for notifications, and delivers them to the browser.
In my WorkerService project, here's Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace HarmonyNotificationWorkerService
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
}
and here is Worker.cs -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Client;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NotificationHub.Hubs;
namespace HarmonyNotificationWorkerService
{
public interface IHubClient
{
Task MessageReceived(string user, string message);
Task MessageReceived(string message);
}
public class Worker : BackgroundService
{
private IHubContext<Notifications> _hubContext;
private readonly Hub<IHubClient> _hubNotif;
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using (var hubConnection = new HubConnection("/hub"))
{
IHubProxy hubproxy = hubConnection.CreateHubProxy("https://localhost:44311/hub");
await hubproxy.Invoke("SendMessage", "test");
}
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
private async Task CheckForNotifications()
{
await _hubNotif.Clients.All.MessageReceived("from work server", "Dude, you've got mail!");
}
}
}
PROBLEM:
Problem is that _hubContext and _hubNotif objects are always null, so I can't test either one. I'm def not initially them correctly, but I'm unsure of how to do that. C# is also showing me a warning about this null reference.
System.InvalidOperationException: 'Data cannot be sent because the connection is in the disconnected state. Call start before sending any data.`
UPDATE - INSTALL SIGNALR CLIENT PACKAGE
I added using Microsoft.AspNetCore.SignalR.Client; to Worker.cs, and set a private readonly HubConnection _hubConnection; at the top of the Worker class.
Then I modified the following method to check the conn state, then send message:
private async Task CheckForNotifications()
{
if (_hubConnection.State == HubConnectionState.Disconnected)
{
await _hubConnection.StartAsync();
}
_ = _hubConnection.SendAsync("SendMessageToClient", "from service worker", "You've got mail!");
}
It all seems to be working successfully.
The thread does exited with code 0 at times, but I believe it's normal in this case since I'm running on a Task.Delay(5000);

Do not mix ASP.NET SignalR and ASP.NET Core SignalR. They are incompatible with each other. Your server is using ASP.NET Core SignalR so your worker should create a HubConnection by using the HubConnectionBuilder.
https://learn.microsoft.com/aspnet/core/signalr/dotnet-client?view=aspnetcore-3.1&tabs=visual-studio#connect-to-a-hub.

Related

IHostedService for database changes hung forever

I am trying to implement database change notificaitons using SignalR in a web api.
below is the code for NotificationService.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Npgsql;
using webapi.DBCalls;
using webapi.Models;
namespace webapi.Notification
{
public class NotificationService : IHostedService
{
private IHubContext<HubConfig> _hub;
private readonly ModelAppSettings _appSettings;
private DBInterceptor _db_interceptor;
public NotificationService(IHubContext<HubConfig> hub, IOptions<ModelAppSettings> appSettings)
{
_appSettings = appSettings.Value;
_db_interceptor = new DBInterceptor(_appSettings);
_hub = hub;
}
public Task StartAsync(CancellationToken stoppingToken)
{
onDataTableChangeListener();
return Task.CompletedTask;
}
public void onDataTableChangeListener()
{
using (var connection = new NpgsqlConnection(_appSettings.ConnectionString))
{
connection.Open();
connection.Notification += (o, e) => notifyDataChange(e.Payload);
using (var cmd = new NpgsqlCommand("LISTEN datachange", connection))
cmd.ExecuteNonQuery();
while (true)
connection.Wait();
}
}
public void notifyDataChange(string payload)
{
//DO some work here
}
public Task StopAsync(CancellationToken stoppingToken)
{
return Task.CompletedTask;
}
}
}
And I am registering this service in Startup.cs under ConfigurationService AS below
services.AddHostedService<NotificationService>();
When I run the program, it never starts and is hung at below line.
while (true)
connection.Wait();
I know I need to rewrite this method, but not sure how to write it.
Any help would be appreciated.
You are not implementing the IHostedService interface correctly. You are blocking the current thread with connection.Wait(). In StartAsync you should start a new thread or a Task on the ThreadPool that will process the notifications in the background. In StopAsync you would terminate processing and perform cleanup if needed.
This is partially implemented by the BackgroundService class where you have to implement only the ExecuteAsync method. Using BackgroundService class your service should like this:
public class NotificationSerice : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var connection = new NpgsqlConnection("ConnectionString");
await connection.OpenAsync();
connection.Notification += (o, e) => notifyDataChange(e.Payload, stoppingToken);
using (var cmd = new NpgsqlCommand("LISTEN datachange", connection))
await cmd.ExecuteNonQueryAsync(stoppingToken);
while (true)
await connection.WaitAsync(stoppingToken);
}
private void notifyDataChange(string payload, CancellationToken stoppingToken)
{
///
}
}
In the example it is implemented as an asynchronous method. It is also possible to use Task.Run() which runs tasks on the ThreadPool. stoppingToken will be triggered when the application shuts down.

How do I get my commands to work in Discord.Net

So I THINK I know what may be the problem but I'm unsure how to go about fixing it. I'm very new to C# coding. I've been coding Discord bots in node for over a year now so switching over is kinda difficult. I'm following the instructions from the Discord.NET documentations and guide. Here is the code in the Program file
using System;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
namespace GalacticBot
{
class MainClass
{
public static void Main(string[] args) => new MainClass().MainAsync().GetAwaiter().GetResult();
private DiscordSocketClient client;
// Calls the class holding information
private Config config = new Config();
public async Task MainAsync()
{
client = new DiscordSocketClient();
// Logs to console
client.Log += Log;
// Uses the token to start the bot
await client.LoginAsync(TokenType.Bot, config.TestToken);
await client.StartAsync();
await Task.Delay(-1);
}
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
}
}
Here is the code in the CommandHandler file
using System;
using System.Reflection;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
namespace GalacticBot
{
public class CommandHandler
{
private readonly DiscordSocketClient client;
private readonly CommandService commands;
private readonly Config config = new Config();
public CommandHandler(DiscordSocketClient _client, CommandService _commands)
{
client = _client;
commands = _commands;
}
public async Task InstallCommandsAsync()
{
client.MessageReceived += HandleCommandAsync;
await commands.AddModulesAsync(assembly: Assembly.GetEntryAssembly(), services: null);
}
private async Task HandleCommandAsync(SocketMessage MessageParam)
{
var message = MessageParam as SocketUserMessage;
if (message == null) return;
int ArgPos = 0;
// If there's no prefix or the message is from a bot then nothing happens
if (!(message.HasCharPrefix('!', ref ArgPos) || message.HasMentionPrefix(client.CurrentUser, ref ArgPos)) || message.Author.IsBot) return;
var context = new SocketCommandContext(client, message);
await commands.ExecuteAsync(
context: context,
argPos: ArgPos,
services: null
);
}
}
}
and here is the code for the commands themselves
using System;
using System.Threading.Tasks;
using Discord.Commands;
public class Hi : ModuleBase<SocketCommandContext>
{
[Command("hey")]
[Summary("Just says hi.")]
public async Task SayAsync()
{
Console.WriteLine("Command used");
await Context.Channel.SendMessageAsync("Just saying hi!");
}
}
The Console.WriteLine in the command is for testing purposes to see if it's even attempting to work. My thought is that I'm not calling and using the CommandHandler class anywhere. I don't know if this is the problem and if it is, I don't know what I need to do.
After a lot more Googling and trying a bunch of different ideas, I finally found a solution. Anu6is was right in that you have to create a new instance and call the install method. This led to returning null when starting the bot. First I made a variable for commands and made it static with the _client variable as well as making a variable for the CommandHandler
private static DiscordSocketClient _client;
private static CommandService _commands;
private CommandHandler commandHandler;
Inside MainAsync I had to create a new instance of the CommandService, a new instance of the command handler passing _client and _commands and then call the InstallCommandsAsync method
_commands = new CommandService();
commandHandler = new CommandHandler(_client, _commands);
await commandHandler.InstallCommandsAsync();
After the changes, build came back successful with no warnings and the test command worked. Hope this helps someone else at some point.

Running a SQL script (Transact-SQL) with a HostedService [ASP.NET CORE 3.1.0 C#]

I'm trying to run a script that saves my database with a service that I created:
Here is the code of my service:
using Microsoft.Extensions.Logging;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using System;
using System.Data.SqlClient;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace TEST_BACKUP_DB
{
internal interface IScopedDBService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedDBService : IScopedDBService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedDBService(ILogger<ScopedDBService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
string sqlConnectionString =
#"Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=C:\Users\user1\Documents\DB_TEST.mdf;Integrated Security=True;Connect Timeout=30";
string script = File.ReadAllText(#"myscript.sql");
try {
SqlConnection conn = new SqlConnection(sqlConnectionString);
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
executionCount++;
_logger.LogInformation(
"Saving DB count: {Count}", executionCount);
}
catch(Exception e)
{
_logger.LogInformation("Error not saved: {e}",e);
}
await Task.Delay(5000, stoppingToken);
}
}
}
}
To consume that service I'm using the following code:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using static TEST_BACKUP_DB.ScopedDBService;
namespace TEST_BACKUP_DB
{
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedDBService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await Task.CompletedTask;
}
}
}
I have registred them on Startup.cs Class as following
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedDBService, ScopedDBService>();
}
My Transact-SQL(myscript.sql) file is:
BACKUP DATABASE [DB_TEST]
TO DISK = 'D:\BACKUPS\_1.BAK'
MIRROR TO DISK = 'D:\BACKUPS\_2.BAK'
WITH FORMAT
GO
I have tested the scopped service without this part of code, and it's working fine:
Server server = new Server(new ServerConnection(conn));
server.ConnectionContext.ExecuteNonQuery(script);
When I start my app it gives me this exception:
When I debug I get this on the server attribute:
ActiveDirectory = 'server.ActiveDirectory' threw an exception of type
'Microsoft.SqlServer.Management.Smo.UnsupportedFeatureException'
I have installed the nugetPackage: Microsoft.SqlServer.SqlManagementObject to use the Server Class, is the problem coming from there? Does anyone have any alternatives or solutions?
You don't need SMO to run a TSQL batch, and the BACKUP/RESTORE errors will all be in the SqlException.Errors collection. You are only seeing the last one.
EG
using (SqlConnection conn = new SqlConnection(sqlConnectionString))
{
conn.Open();
var cmd = conn.CreateCommand();
cmd.CommandText = script;
try
{
cmd.ExecuteNonQuery();
}
catch (SqlException ex)
{
foreach (var e in ex.Errors)
{
Console.WriteLine(e.ToString());
}
throw;
}
}
will output the root cause, something like:
Microsoft.Data.SqlClient.SqlError: Database 'xxxxx' does not exist. Make sure that the name is entered correctly.
Microsoft.Data.SqlClient.SqlError: BACKUP DATABASE is terminating abnormally.

How to make a Windows Service from .NET Core 2.1/2.2

Recently I had a need to convert a .NET Core 2.1 or 2.2 console application into a Windows Service.
As I didn't have a requirement to port this process to Linux, I could dispense with the multiple platform solutions that I had seen on Stack Overflow that dealt with any combination of .NET Framework, .NET Standard and .NET Core.
In this post I will describe the steps required to set up a .NET Core 2.1 or 2.2 process as a Windows Service.
As I have no requirement for Linux, I could look for a solution that was Windows-specific.
A bit of digging turned up some posts from Steve Gordon (thanks!), in particular where he presents the Microsoft.Extensions.Hosting package and Windows hosting (click here for post and here for his GitHub sample).
Here are the steps required:
First create a .NET Core console application.
Set the language version to at least 7.1 to support async Task for the Main method.  (Access the language version from the project settings->Build->Advanced->Language Settings.
Add the Microsoft.Extensions.Hosting and the System.ServiceProcess.ServiceController packages.
Edit the project .csproj file and include in the PropertyGroup: <RuntimeIdentifier>win7-x64</RuntimeIdentifier>
Ensure you have in the PropertyGroup  <OutputType>Exe</OutputType>
Now go to Program.cs and copy the following:
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AdvancedHost
{
internal class Program
{
private static async Task Main(string[] args)
{
var isService = !(Debugger.IsAttached || args.Contains("--console"));
var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<LoggingService>();
});
if (isService)
{
await builder.RunAsServiceAsync();
}
else
{
await builder.RunConsoleAsync();
}
}
}
}
This code will support interactive debugging and production execution, and runs the example class LoggingService.
Here is a skeleton example of the service itself:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
namespace AdvancedHost
{
public class LoggingService : IHostedService, IDisposable
{
public Task StartAsync(CancellationToken cancellationToken)
{
// Startup code
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
// Stop timers, services
return Task.CompletedTask;
}
public void Dispose()
{
// Dispose of non-managed resources
}
}
}
The final two files necessary to complete the project:
File ServiceBaseLifetime.cs:
using Microsoft.Extensions.Hosting;
using System;
using System.ServiceProcess;
using System.Threading;
using System.Threading.Tasks;
namespace AdvancedHost
{
public class ServiceBaseLifetime : ServiceBase, IHostLifetime
{
private readonly TaskCompletionSource<object> _delayStart = new TaskCompletionSource<object>();
public ServiceBaseLifetime(IApplicationLifetime applicationLifetime)
{
ApplicationLifetime = applicationLifetime ?? throw new ArgumentNullException(nameof(applicationLifetime));
}
private IApplicationLifetime ApplicationLifetime { get; }
public Task WaitForStartAsync(CancellationToken cancellationToken)
{
cancellationToken.Register(() => _delayStart.TrySetCanceled());
ApplicationLifetime.ApplicationStopping.Register(Stop);
new Thread(Run).Start(); // Otherwise this would block and prevent IHost.StartAsync from finishing.
return _delayStart.Task;
}
private void Run()
{
try
{
Run(this); // This blocks until the service is stopped.
_delayStart.TrySetException(new InvalidOperationException("Stopped without starting"));
}
catch (Exception ex)
{
_delayStart.TrySetException(ex);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Stop();
return Task.CompletedTask;
}
// Called by base.Run when the service is ready to start.
protected override void OnStart(string[] args)
{
_delayStart.TrySetResult(null);
base.OnStart(args);
}
// Called by base.Stop. This may be called multiple times by service Stop, ApplicationStopping, and StopAsync.
// That's OK because StopApplication uses a CancellationTokenSource and prevents any recursion.
protected override void OnStop()
{
ApplicationLifetime.StopApplication();
base.OnStop();
}
}
}
File ServiceBaseLifetimeHostExtensions.cs:
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AdvancedHost
{
public static class ServiceBaseLifetimeHostExtensions
{
public static IHostBuilder UseServiceBaseLifetime(this IHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices((hostContext, services) => services.AddSingleton<IHostLifetime, ServiceBaseLifetime>());
}
public static Task RunAsServiceAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
return hostBuilder.UseServiceBaseLifetime().Build().RunAsync(cancellationToken);
}
}
}
In order to install, run or delete the service I use the 'sc' utility:
sc create AdvancedHost binPath="C:\temp\AdvancedHost\AdvancedHost.exe"
where AdvancedHost is the service name and the value for binPath is the compiled executable.
Once the service is created, to start:
sc start AdvancedHost
To stop:
sc stop AdvancedHost
And finally to delete (once stopped):
sc delete AdvancedHost
There are many more features contained in sc; just type 'sc' alone on the command line.
The results of sc can be seen in the services Windows control panel.
You no longer need to copy-paste a lot of code to do it.
All you need is to install the package Microsoft.Extensions.Hosting.WindowsServices
Then:
Append UseWindowsService() to the HostBuilder. This will also configure your application to use the EventLog logger.
Change the SDK in your project to Microsoft.NET.Sdk.Worker (<Project Sdk="Microsoft.NET.Sdk.Worker">).
Make sure that the output project type is EXE file (<OutputType>Exe</OutputType>)
Append <RuntimeIdentifier>win7-x64</RuntimeIdentifier> to the project file.
Debug your service like a regular console application, and then run dotnet publish, sc create ..., etc.
That's it.
This also works for .NET Core 3.0/3.1.
Read more here.
The minimal code example is shown below.
.csproj file:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<OutputType>Exe</OutputType>
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="3.1.3" />
</ItemGroup>
</Project>
File Program.cs:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace NetcoreWindowsService
{
class Program
{
static void Main()
{
new HostBuilder()
.ConfigureServices(services => services.AddHostedService<MyService>())
.UseWindowsService()
.Build()
.Run();
}
}
}
File MyService.cs:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
namespace NetcoreWindowsService
{
internal class MyService : IHostedService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger) => _logger = logger;
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("The service has been started");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("The service has been stopped");
return Task.CompletedTask;
}
}
}
Top shelf has been a good "framework" for Windows Services.
It provides good functionality for the routine stuff that all windows services share.
Especially "install".
The basics are:
https://www.nuget.org/packages/Topshelf/
Note the nuget above can be run under NetStandard2.0.
Now below. You can create MyWindowsServiceExe.csproj .. and code it as an Exe/2.1 or Exe/3.1 (shown when you open up the csproj). Note that 2.2 is not Long Term Support anymore and I would avoid writing new code to 2.2. (off topic, but that link is https://dotnet.microsoft.com/platform/support/policy/dotnet-core )
public class LoggingService : TopShelf.ServiceControl
{
private const string logFileFullName = #"C:\temp\servicelog.txt";
private void MyLogMe(string logMessage)
{
Directory.CreateDirectory(Path.GetDirectoryName(logFileFullName));
File.AppendAllText(logFileFullName, DateTime.UtcNow.ToLongTimeString() + " : " + logMessage + Environment.NewLine);
}
public bool Start(HostControl hostControl)
{
MyLogMe("Starting");
return true;
}
public bool Stop(HostControl hostControl)
{
MyLogMe("Stopping");
return true;
}
}
static void Main(string[] args)
{
HostFactory.Run(x =>
{
x.Service<LoggingService>();
x.EnableServiceRecovery(r => r.RestartService(TimeSpan.FromSeconds(333)));
x.SetServiceName("MyTestService");
x.StartAutomatically();
}
);
}
and build/publish it to windows:
dotnet publish -r win-x64 -c Release
and then the helper methods I mentioned
MyWindowsServiceExe.exe install
(now check windows-services under control panel to see it installed) (check "if crash, what should I do" tab as well).
and finally (another helper), you can STOP the the windows-service , you can run it from the command line (outside of windows-services) This is my favorite for debugging.
MyWindowsServiceExe.exe start

(Discord bot 1.0 c#) How to make a new class and add cmds?

Hello there Stackoverflow! (first time posting here so plz be nice :P)
So, I've decided to make a discord bot 1.0 in c# (i'm learning c# atm) and I have gotten in to a problem and i'm not sure how to fix it..
So, to describe what i'm trying to do is following.
I'm trying to make it so i can have different classes for x commands such as .say etc instead of having em all in the "commands" one below so its a bit easier to work with.
I got these working three scripts but cant get the fourth to work
//Startup
using System;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Discord.Commands;
namespace MyBot
{
public class Program
{
// Convert our sync main to an async main.
public static void Main(string[] args) =>
new Program().Start().GetAwaiter().GetResult();
private DiscordSocketClient client;
private CommandHandler handler;
public async Task Start()
{
// Define the DiscordSocketClient
client = new DiscordSocketClient();
var token = "Censored";
// Login and connect to Discord.
await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();
var map = new DependencyMap();
map.Add(client);
handler = new CommandHandler();
await handler.Install(map);
// Block this program until it is closed.
await Task.Delay(-1);
}
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
}
}
//My command handler
using System.Threading.Tasks;
using System.Reflection;
using Discord.Commands;
using Discord.WebSocket;
namespace MyBot
{
public class CommandHandler
{
private CommandService commands;
private DiscordSocketClient client;
private IDependencyMap map;
public async Task Install(IDependencyMap _map)
{
// Create Command Service, inject it into Dependency Map
client = _map.Get<DiscordSocketClient>();
commands = new CommandService();
_map.Add(commands);
map = _map;
await commands.AddModulesAsync(Assembly.GetEntryAssembly());
client.MessageReceived += HandleCommand;
}
public async Task HandleCommand(SocketMessage parameterMessage)
{
// Don't handle the command if it is a system message
var message = parameterMessage as SocketUserMessage;
if (message == null) return;
// Mark where the prefix ends and the command begins
int argPos = 0;
// Determine if the message has a valid prefix, adjust argPos
if (!(message.HasMentionPrefix(client.CurrentUser, ref argPos) || message.HasCharPrefix('!', ref argPos))) return;
// Create a Command Context
var context = new CommandContext(client, message);
// Execute the Command, store the result
var result = await commands.ExecuteAsync(context, argPos, map);
// If the command failed, notify the user
if (!result.IsSuccess)
await message.Channel.SendMessageAsync($"**Error:** {result.ErrorReason}");
}
}
}
//Commands
using System;
using System.Linq;
using System.Threading.Tasks;
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace MyBot.Modules.Public
{
public class PublicModule : ModuleBase
{
[Command("invite")]
[Summary("Returns the OAuth2 Invite URL of the bot")]
public async Task Invite()
{
var application = await Context.Client.GetApplicationInfoAsync();
await ReplyAsync(
$"A user with `MANAGE_SERVER` can invite me to your server here: <https://discordapp.com/oauth2/authorize?client_id={application.Id}&scope=bot>");
}
[Command("leave")]
[Summary("Instructs the bot to leave this Guild.")]
[RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Leave()
{
if (Context.Guild == null) { await ReplyAsync("This command can only be ran in a server."); return; }
await ReplyAsync("Leaving~");
await Context.Guild.LeaveAsync();
}
}
}
//This is the one i want to work but i only get "Unknown command" as error?
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace MyBot.Modules.Public
{
class test : ModuleBase
{
[Command("say")]
[Alias("echo")]
[Summary("Echos the provided input")]
public async Task Say([Remainder] string input)
{
await ReplyAsync(input);
}
}
}
If you know what i do wrong please tell me or reefer me to some info about the problem and i can try fix it :)
Thanks in advance!
PS, im sorry if there is a dupe of this question but i don't know what to search for to find it
EDIT
I've been told to "Pit the metohds (cmds) in the class" but how would i go around todo that?
The answer is following
Add Public before the class {name}so it would be
namespace MyBot.Modules.Public
{
**Public** class test : ModuleBase
{
[Command("say")]
[Alias("echo")]
[Summary("Echos the provided input")]
public async Task Say([Remainder] string input)
{
await ReplyAsync(input);
}
}
}

Categories

Resources