Application
I’m working on an IIoT project. I need to be able to receive simple messages (json payload) on a piece of equipment whose HMI runs on Win7. We use 3rd party MQTT for this in some cases but for this situation I’m looking at HTTP, specifically a tiny http server in C# (.NET Framework 4.0). There doesn’t need to be a front end, just an api with a single endpoint.
Approach
I’ve found System.Net.HttpListener and got it doing what I want in a console app for initial testing. Problem is I’m using the GetContext method which blocks the main thread. Intuitively, this is not what I want, but I don’t know a more reasonable approach. I see there is a built in async method, HttpListener.BeginGetContext, but is that enough to do what I want? Ultimately looking to wrap this up into a service using the TopShelf NuGet package.
Not looking for a code-complete solution, but the proper conceptual approach.
you may use the Microsoft.Owin package to host http controllers in the console application
The idea is that you do not need the IIS to be installed in such case.
And you may host the application as windows service (using the TopShelf) if required.
However you are getting the ASP controllers working and should not bother with HttpListeners any more.
There are a lot of how-to guides available, like here
In few words you have to
add Microsoft.AspNet.WebApi.OwinSelfHost package
Configure the Owin in the code like
public class SelfHostStart
{
private readonly WebSettings config;
public SelfHostStart(WebSettings config)
{
this.config = config;
}
void Start<T>(string hostAddress, string message)
{
if (string.IsNullOrEmpty(hostAddress))
throw new ArgumentException("hostAddress", $"no value to start host {message}");
var oh = new OwinHost();
server = oh.Start<T>(hostAddress, message);
}
public void Start()
{
if (config?.Enabled == true)
{
Start<Startup>(config.HostAddress, "webServer");
}
}
}
public class WebSettings
{
public bool Enabled { get; set; }
public string HostAddress { get; set; }
}
then you may to create Startup.cs to configure http hosting like below:
public class Startup
{
public void Configuration(IAppBuilder appBuilder)
{
HttpConfiguration config = GetHttpConfig(appBuilder);
ConfigureWebApi(appBuilder, config);
DEBUG
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.LocalOnly;
if
appBuilder.UseCors(CorsOptions.AllowAll);
appBuilder.UseWebApi(config);
}
public HttpConfiguration GetHttpConfig(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
// you may override some default behavior here
//config.Services.Replace(typeof(IHttpControllerActivator), new UnityControllerActivator());
//config.Services.Add(typeof(System.Web.Http.ExceptionHandling.IExceptionLogger), new ExceptionLog(appBuilder.CreateLogger<ExceptionLog>()));
//config.Services.Replace(typeof(System.Web.Http.ExceptionHandling.IExceptionHandler), new CustomExceptionHandler()); // to make ExceptionHandler works
//config.MessageHandlers.Add(new LoggingHandler(appBuilder.CreateLogger("rest")));
return config;
}
public void ConfigureWebApi(IAppBuilder app, HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.EnsureInitialized();
}
}
and controller (class with methods that are available for clients of a service):
[AllowAnonymous]
public class StatusController : ApiController
{
public StatusController()
{
}
[Route("status")]
public StatusDto Get()
{
var res = new StatusDto
{
DbVersion = "123",
LocalCurrency = "USD",
SwiftCode = "12345", // just sample
};
return res;
}
}
public class StatusDto
{
public string DbVersion { get; set; }
public string LocalCurrency { get; set; }
public string SwiftCode { get; set; }
}
and start the http server in your console
var config = new WebSettings() { HostAddress = "http://localhost:8080" };
var start = new SelfHostStart(config);
start.Start();
You should provide permissions for your console to listen a http url using the netsh http add urlacl command like
netsh http add urlacl url=http://+:8080/ user=everyone
Then you can browse the http://localhost:8080/status and should get report from the http console server.
You may extend you service by creating new controllers with methods
My program is for Self hosting a Web API in a Windows Application.
As I dont have much experience in web servie, I request someone's help to fix the problem.
I could make a console application successfully.
But when I change it in to Windows application, my IDE goes stuck with Error "The application is in Break Mode".
Console Program;
using System.Web.Http;
using System.Web.Http.SelfHost;
Main Function:
var config = new HttpSelfHostConfiguration("http://localhost:8080");
config.Routes.MapHttpRoute("API Default", "api/{controller}/{id}", new { id = RouteParameter.Optional });
using (HttpSelfHostServer server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine("Press Enter to quit."); // Removed in Win Form
Console.ReadLine(); // Removed in Win Form
}
API Controller Class; I need to receive data here from "FromBody" attribute.
public class FieldsController : ApiController
{
[HttpPost]
public void PostAction([FromBody] Field model)
{
int patientID;
string name;
string age;
patientID = model.patientID;
name = model.patientName;
age = model.patientAge;
}
}
Can you put your error in more detail ?
I have also done in similar way and my one is working.
In the solution of the project I added a new class libraray type project for the webapi.
From the main windowsForm application I am intatiating the webapi.
The webapi project is as below:
using System;
using System.ServiceModel;
using System.Web.Http;
using System.Web.Http.SelfHost;
namespace MYWebAPI
{
public class WebApiProgram : IDisposable
{
private string URL = "http://localhost:1234/";
private HttpSelfHostServer server;
public WebApiProgram()
{
}
public WebApiProgram(string url)
{
this.URL = url;
}
public void Dispose()
{
server.CloseAsync().Wait();
}
public void Start()
{
var config = new HttpSelfHostConfiguration(URL);
config.TransferMode = TransferMode.Streamed;
AddAPIRoute(config);
server = new HttpSelfHostServer(config);
var task = server.OpenAsync();
task.Wait();
//Console.WriteLine("Web API Server has started at:" + URL);
//Console.ReadLine();
}
private void AddAPIRoute(HttpSelfHostConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
From the main() [program.cs file] winform applition I call the webapi start function.
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
//Start the webAPI
WebApiProgram.StartMain();
Thread.CurrentThread.CurrentCulture
= Thread.CurrentThread.CurrentUICulture
= CultureInfo.InvariantCulture;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new MainForm();
Application.Run(mainForm);
}
}
How can I use .NET Core's default dependency injection in Hangfire?
I am new to Hangfire and searching for an example which works with ASP.NET Core.
See full example on GitHub https://github.com/gonzigonz/HangfireCore-Example.
Live site at http://hangfirecore.azurewebsites.net/
Make sure you have the Core version of Hangfire:
dotnet add package Hangfire.AspNetCore
Configure your IoC by defining a JobActivator. Below is the config for use with the default asp.net core container service:
public class HangfireActivator : Hangfire.JobActivator
{
private readonly IServiceProvider _serviceProvider;
public HangfireActivator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public override object ActivateJob(Type type)
{
return _serviceProvider.GetService(type);
}
}
Next register hangfire as a service in the Startup.ConfigureServices method:
services.AddHangfire(opt =>
opt.UseSqlServerStorage("Your Hangfire Connection string"));
Configure hangfire in the Startup.Configure method. In relationship to your question, the key is to configure hangfire to use the new HangfireActivator we just defined above. To do so you will have to provide hangfire with the IServiceProvider and this can be achieved by just adding it to the list of parameters for the Configure method. At runtime, DI will providing this service for you:
public void Configure(
IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory,
IServiceProvider serviceProvider)
{
...
// Configure hangfire to use the new JobActivator we defined.
GlobalConfiguration.Configuration
.UseActivator(new HangfireActivator(serviceProvider));
// The rest of the hangfire config as usual.
app.UseHangfireServer();
app.UseHangfireDashboard();
}
When you enqueue a job, use the registered type which usually is your interface. Don't use a concrete type unless you registered it that way. You must use the type registered with your IoC else Hangfire won't find it.
For Example say you've registered the following services:
services.AddScoped<DbManager>();
services.AddScoped<IMyService, MyService>();
Then you could enqueue DbManager with an instantiated version of the class:
BackgroundJob.Enqueue(() => dbManager.DoSomething());
However you could not do the same with MyService. Enqueuing with an instantiated version would fail because DI would fail as only the interface is registered. In this case you would enqueue like this:
BackgroundJob.Enqueue<IMyService>( ms => ms.DoSomething());
DoritoBandito's answer is incomplete or deprecated.
public class EmailSender {
public EmailSender(IDbContext dbContext, IEmailService emailService)
{
_dbContext = dbContext;
_emailService = emailService;
}
}
Register services:
services.AddTransient<IDbContext, TestDbContext>();
services.AddTransient<IEmailService, EmailService>();
Enqueue:
BackgroundJob.Enqueue<EmailSender>(x => x.Send(13, "Hello!"));
Source:
http://docs.hangfire.io/en/latest/background-methods/passing-dependencies.html
Note: if you want a full sample, see my blog post on this.
All of the answers in this thread are wrong/incomplete/outdated. Here's an example with ASP.NET Core 3.1 and Hangfire.AspnetCore 1.7.
Client:
//...
using Hangfire;
// ...
public class Startup
{
// ...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddHangfire(config =>
{
// configure hangfire per your requirements
});
}
}
public class SomeController : ControllerBase
{
private readonly IBackgroundJobClient _backgroundJobClient;
public SomeController(IBackgroundJobClient backgroundJobClient)
{
_backgroundJobClient = backgroundJobClient;
}
[HttpPost("some-route")]
public IActionResult Schedule([FromBody] SomeModel model)
{
_backgroundJobClient.Schedule<SomeClass>(s => s.Execute(model));
}
}
Server (same or different application):
{
//...
services.AddScoped<ISomeDependency, SomeDependency>();
services.AddHangfire(hangfireConfiguration =>
{
// configure hangfire with the same backing storage as your client
});
services.AddHangfireServer();
}
public interface ISomeDependency { }
public class SomeDependency : ISomeDependency { }
public class SomeClass
{
private readonly ISomeDependency _someDependency;
public SomeClass(ISomeDependency someDependency)
{
_someDependency = someDependency;
}
// the function scheduled in SomeController
public void Execute(SomeModel someModel)
{
}
}
As far as I am aware, you can use .net cores dependency injection the same as you would for any other service.
You can use a service which contains the jobs to be executed, which can be executed like so
var jobId = BackgroundJob.Enqueue(x => x.SomeTask(passParamIfYouWish));
Here is an example of the Job Service class
public class JobService : IJobService
{
private IClientService _clientService;
private INodeServices _nodeServices;
//Constructor
public JobService(IClientService clientService, INodeServices nodeServices)
{
_clientService = clientService;
_nodeServices = nodeServices;
}
//Some task to execute
public async Task SomeTask(Guid subject)
{
// Do some job here
Client client = _clientService.FindUserBySubject(subject);
}
}
And in your projects Startup.cs you can add a dependency as normal
services.AddTransient< IClientService, ClientService>();
Not sure this answers your question or not
Currently, Hangfire is deeply integrated with Asp.Net Core. Install Hangfire.AspNetCore to set up the dashboard and DI integration automatically. Then, you just need to define your dependencies using ASP.NET core as always.
If you are trying to quickly set up Hangfire with ASP.NET Core (tested in ASP.NET Core 2.2) you can also use Hangfire.MemoryStorage. All the configuration can be performed in Startup.cs:
using Hangfire;
using Hangfire.MemoryStorage;
public void ConfigureServices(IServiceCollection services)
{
services.AddHangfire(opt => opt.UseMemoryStorage());
JobStorage.Current = new MemoryStorage();
}
protected void StartHangFireJobs(IApplicationBuilder app, IServiceProvider serviceProvider)
{
app.UseHangfireServer();
app.UseHangfireDashboard();
//TODO: move cron expressions to appsettings.json
RecurringJob.AddOrUpdate<SomeJobService>(
x => x.DoWork(),
"* * * * *");
RecurringJob.AddOrUpdate<OtherJobService>(
x => x.DoWork(),
"0 */2 * * *");
}
public void Configure(IApplicationBuilder app, IServiceProvider serviceProvider)
{
StartHangFireJobs(app, serviceProvider)
}
Of course, everything is store in memory and it is lost once the application pool is recycled, but it is a quick way to see that everything works as expected with minimal configuration.
To switch to SQL Server database persistence, you should install Hangfire.SqlServer package and simply configure it instead of the memory storage:
services.AddHangfire(opt => opt.UseSqlServerStorage(Configuration.GetConnectionString("Default")));
I had to start HangFire in main function. This is how I solved it:
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
try
{
var liveDataHelper = services.GetRequiredService<ILiveDataHelper>();
var justInitHangfire = services.GetRequiredService<IBackgroundJobClient>();
//This was causing an exception (HangFire is not initialized)
RecurringJob.AddOrUpdate(() => liveDataHelper.RePopulateAllConfigDataAsync(), Cron.Daily());
// Use the context here
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "Can't start " + nameof(LiveDataHelper));
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Actually there is an easy way for dependency injection based job registration.
You just need to use the following code in your Startup:
public class Startup {
public void Configure(IApplicationBuilder app)
{
var factory = app.ApplicationServices
.GetService<IServiceScopeFactory>();
GlobalConfiguration.Configuration.UseActivator(
new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
}
}
However i personally wanted a job self registration including on demand jobs (recurring jobs which are never executed, except by manual trigger on hangfire dashboard), which was a little more complex then just that. I was (for example) facing issues with the job service activation, which is why i decided to share most of my implementation code.
//I wanted an interface to declare my jobs, including the job Id.
public interface IBackgroundJob {
string Id { get; set; }
void Invoke();
}
//I wanted to retrieve the jobs by id. Heres my extension method for that:
public static IBackgroundJob GetJob(
this IServiceProvider provider,
string jobId) => provider
.GetServices<IBackgroundJob>()
.SingleOrDefault(j => j.Id == jobId);
//Now i needed an invoker for these jobs.
//The invoker is basically an example of a dependency injected hangfire job.
internal class JobInvoker {
public JobInvoker(IServiceScopeFactory factory) {
Factory = factory;
}
public IServiceScopeFactory Factory { get; }
public void Invoke(string jobId)
{
//hangfire jobs should always be executed within their own scope.
//The default AspNetCoreJobActivator should technically already do that.
//Lets just say i have trust issues.
using (var scope = Factory.CreateScope())
{
scope.ServiceProvider
.GetJob(jobId)?
.Invoke();
}
}
//Now i needed to tell hangfire to use these jobs.
//Reminder: The serviceProvider is in IApplicationBuilder.ApplicationServices
public static void RegisterJobs(IServiceProvider serviceProvider) {
var factory = serviceProvider.GetService();
GlobalConfiguration.Configuration.UseActivator(new Hangfire.AspNetCore.AspNetCoreJobActivator(factory));
var manager = serviceProvider.GetService<IRecurringJobManager>();
var config = serviceProvider.GetService<IConfiguration>();
var jobs = serviceProvider.GetServices<IBackgroundJob>();
foreach (var job in jobs) {
var jobConfig = config.GetJobConfig(job.Id);
var schedule = jobConfig?.Schedule; //this is a cron expression
if (String.IsNullOrWhiteSpace(schedule))
schedule = Cron.Never(); //this is an on demand job only!
manager.AddOrUpdate(
recurringJobId: job.Id,
job: GetJob(job.Id),
cronExpression: schedule);
}
//and last but not least...
//My Method for creating the hangfire job with injected job id
private static Job GetJob(string jobId)
{
var type = typeof(JobInvoker);
var method = type.GetMethod("Invoke");
return new Job(
type: type,
method: method,
args: jobId);
}
Using the above code i was able to create hangfire job services with full dependency injection support. Hope it helps someone.
Use the below code for Hangfire configuration
using eForms.Core;
using Hangfire;
using Hangfire.SqlServer;
using System;
using System.ComponentModel;
using System.Web.Hosting;
namespace eForms.AdminPanel.Jobs
{
public class JobManager : IJobManager, IRegisteredObject
{
public static readonly JobManager Instance = new JobManager();
//private static readonly TimeSpan ZeroTimespan = new TimeSpan(0, 0, 10);
private static readonly object _lockObject = new Object();
private bool _started;
private BackgroundJobServer _backgroundJobServer;
private JobManager()
{
}
public int Schedule(JobInfo whatToDo)
{
int result = 0;
if (!whatToDo.IsRecurring)
{
if (whatToDo.Delay == TimeSpan.Zero)
int.TryParse(BackgroundJob.Enqueue(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName)), out result);
else
int.TryParse(BackgroundJob.Schedule(() => Run(whatToDo.JobId, whatToDo.JobType.AssemblyQualifiedName), whatToDo.Delay), out result);
}
else
{
RecurringJob.AddOrUpdate(whatToDo.JobType.Name, () => RunRecurring(whatToDo.JobType.AssemblyQualifiedName), Cron.MinuteInterval(whatToDo.Delay.TotalMinutes.AsInt()));
}
return result;
}
[DisplayName("Id: {0}, Type: {1}")]
[HangFireYearlyExpirationTime]
public static void Run(int jobId, string jobType)
{
try
{
Type runnerType;
if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
var runner = runnerType.CreateInstance<JobRunner>();
runner.Run(jobId);
}
catch (Exception ex)
{
throw new JobException($"Error while executing Job Id: {jobId}, Type: {jobType}", ex);
}
}
[DisplayName("{0}")]
[HangFireMinutelyExpirationTime]
public static void RunRecurring(string jobType)
{
try
{
Type runnerType;
if (!jobType.ToType(out runnerType)) throw new Exception("Provided job has undefined type");
var runner = runnerType.CreateInstance<JobRunner>();
runner.Run(0);
}
catch (Exception ex)
{
throw new JobException($"Error while executing Recurring Type: {jobType}", ex);
}
}
public void Start()
{
lock (_lockObject)
{
if (_started) return;
if (!AppConfigSettings.EnableHangFire) return;
_started = true;
HostingEnvironment.RegisterObject(this);
GlobalConfiguration.Configuration
.UseSqlServerStorage("SqlDbConnection", new SqlServerStorageOptions { PrepareSchemaIfNecessary = false })
//.UseFilter(new HangFireLogFailureAttribute())
.UseLog4NetLogProvider();
//Add infinity Expiration job filter
//GlobalJobFilters.Filters.Add(new HangFireProlongExpirationTimeAttribute());
//Hangfire comes with a retry policy that is automatically set to 10 retry and backs off over several mins
//We in the following remove this attribute and add our own custom one which adds significant backoff time
//custom logic to determine how much to back off and what to to in the case of fails
// The trick here is we can't just remove the filter as you'd expect using remove
// we first have to find it then save the Instance then remove it
try
{
object automaticRetryAttribute = null;
//Search hangfire automatic retry
foreach (var filter in GlobalJobFilters.Filters)
{
if (filter.Instance is Hangfire.AutomaticRetryAttribute)
{
// found it
automaticRetryAttribute = filter.Instance;
System.Diagnostics.Trace.TraceError("Found hangfire automatic retry");
}
}
//Remove default hangefire automaticRetryAttribute
if (automaticRetryAttribute != null)
GlobalJobFilters.Filters.Remove(automaticRetryAttribute);
//Add custom retry job filter
GlobalJobFilters.Filters.Add(new HangFireCustomAutoRetryJobFilterAttribute());
}
catch (Exception) { }
_backgroundJobServer = new BackgroundJobServer(new BackgroundJobServerOptions
{
HeartbeatInterval = new System.TimeSpan(0, 1, 0),
ServerCheckInterval = new System.TimeSpan(0, 1, 0),
SchedulePollingInterval = new System.TimeSpan(0, 1, 0)
});
}
}
public void Stop()
{
lock (_lockObject)
{
if (_backgroundJobServer != null)
{
_backgroundJobServer.Dispose();
}
HostingEnvironment.UnregisterObject(this);
}
}
void IRegisteredObject.Stop(bool immediate)
{
Stop();
}
}
}
Admin Job Manager
public class Global : System.Web.HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
if (Core.AppConfigSettings.EnableHangFire)
{
JobManager.Instance.Start();
new SchedulePendingSmsNotifications().Schedule(new Core.JobInfo() { JobId = 0, JobType = typeof(SchedulePendingSmsNotifications), Delay = TimeSpan.FromMinutes(1), IsRecurring = true });
}
}
protected void Application_End(object sender, EventArgs e)
{
if (Core.AppConfigSettings.EnableHangFire)
{
JobManager.Instance.Stop();
}
}
}
I've written simple server using Owin Self-hosting and WebApi:
namespace OwinSelfHostingTest
{
using System.Threading;
using System.Web.Http;
using Microsoft.Owin.Hosting;
using Owin;
public class Startup
{
public void Configuration(IAppBuilder builder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"Default",
"{controller}/{id}",
new { id = RouteParameter.Optional }
);
builder.UseWebApi(config);
}
}
public class Server
{
private ManualResetEvent resetEvent = new ManualResetEvent(false);
private Thread thread;
private const string ADDRESS = "http://localhost:9000/";
public void Start()
{
this.thread = new Thread(() =>
{
using (var host = WebApp.Start<Startup>(ADDRESS))
{
resetEvent.WaitOne(Timeout.Infinite, true);
}
});
thread.Start();
}
public void Stop()
{
resetEvent.Set();
}
}
}
When there is exception in controller, then Owin returns XML response like this:
<Error>
<Message>An error has occurred.</Message>
<ExceptionMessage>Attempted to divide by zero.</ExceptionMessage>
<ExceptionType>System.DivideByZeroException</ExceptionType>
<StackTrace>
...
</StackTrace>
</Error>
But i want different output - so how can i override this?
You do so by creating an OWIN MiddleWare and hooking it into the pipeline:
public class CustomExceptionMiddleware : OwinMiddleware
{
public CustomExceptionMiddleware(OwinMiddleware next) : base(next)
{}
public override async Task Invoke(IOwinContext context)
{
try
{
await Next.Invoke(context);
}
catch(Exception ex)
{
// Custom stuff here
}
}
}
And hook it on startup:
public class Startup
{
public void Configuration(IAppBuilder builder)
{
var config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"Default",
"{controller}/{id}",
new { id = RouteParameter.Optional }
);
builder.Use<CustomExceptionMiddleware>().UseWebApi(config);
}
}
That way any unhandled exception will be caught by your middleware and allow you to customize the output result.
An important thing to note: if the thing being hosted has an exception handling logic of it's own, as WebAPI does, exceptions will not propagate. This handler is meant for any exception which goes by unhandeled by the underlying service being hosted.
When you await a async method, an exception won't be thrown into the caller's context, but will be available by inspecting the task returned to the caller.
So, with this modification to the solution provided by #yuval-itzchakov you'll be able to capture underlying exceptions:
var subtask = Next.Invoke(context);
await subtask;
if (subtask.Exception != null)
{
// log subtask.Exception here
}
I am new to SignalR and am experimenting with setting up a self host service that I can use to send events to other applications. I am having a problem getting a method to be called on all clients. In the code below _context.Clients.All is always empty. Any Ideas?
Test Method:
var server = new SignalRServer();
server.Start("http://localhost:8555");
var hubConnection = new HubConnection("http://localhost:8555");
var proxy = hubConnection.CreateHubProxy("testHub");
var executed = false;
proxy.On("testMethod", () =>
{
executed = true;
});
hubConnection.Start().Wait();
var hub = new TestHubContext(GlobalHost.ConnectionManager.GetHubContext<TestHub>());
hub.Test();
hubConnection.Stop();
server.Stop();
Self host server:
public class SignalRServer
{
private IDisposable _signalR;
public void Start(string url)
{
_signalR = WebApp.Start<SignalRStartup>(url);
}
public void Stop()
{
_signalR.Dispose();
}
}
public class SignalRStartup
{
public static IAppBuilder App = null;
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
Hub:
public class TestHub : Hub
{
}
public class TestHubContext
{
private IHubContext _context;
public TestHubContext(IHubContext context)
{
_context = context;
}
public void Test()
{
if (_context != null)
{
// At this poing _context.Clients.All is always empty
_context.Clients.All.testMethod();
}
}
}
I think your context / client connections are fine. Without further information I'm guessing your problem is that you are closing your connection and server too quickly after calling hub.Test()
Comment out the two .Stop() calls (and stick a Console.ReadLine in there to keep the console open) and see what happens