I am new to Rebus.
There are one questions i want to ask:
It is a good idea to make web app publish message and subscribe to message. And does Rebus support this features.
I test Server mode , however it does not work. It handles the message only one message(from pubsubsample.websubscriber1.input queue) when web app starts.
BTW,It works well on One-way client mode.(Send message only)
Here is my code segment for server modeļ¼
public class CheckStatus : IHandleMessages<NewTradeRecorded>
{
readonly IBus bus;
public CheckStatus(IBus bus)
{
this.bus = bus;
}
public void Handle(NewTradeRecorded message)
{
}
}
Asp.net MVC
protected void Application_Start()
{
using (var adapter = new BuiltinContainerAdapter())
{
adapter.Register(() => new CheckStatus(adapter.Bus));
Configure.With(adapter)
.Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig())
.MessageOwnership(o => o.FromRebusConfigurationSection())
.CreateBus()
.Start();
adapter.Bus.Subscribe<NewTradeRecorded>();
}
}
web.config
<rebus inputQueue="pubsubsample.websubscriber1.input" errorQueue="pubsubsample.websubscriber1.error" workers="1" maxRetries="5">
<endpoints>
<add messages="Trading.Messages" endpoint="trading.input"/>
</endpoints>
To answer your first question, whether Rebus supports publishing and subscribing from the same process, the answer is yes - there's no technical reason why you cannot subscribe to messages and publish the same messages from the same process, and that includes your web application.
Whether you should is another thing :)
Web applications in .NET are kind of transient in nature, i.e. they're recycled when IIS decides that it's time to recycle, and then it's usually not the best idea to subscribe to messages, because your application might not be running when an event is published, so it's not around to handle it.
And then, when it wakes up because IIS dispatches a web request to it, you might have 1,000,000 events waiting to be handled by your application, which will take quite a while to chew through.
In some cases, I've heard of people wanting to use Rebus pub/sub in web applications to keep a cache updated in the web app - but then they had severe issues, coming from the fact that IIS supports overlapping two instances of the same web application - iow, suddenly, for a short while, two instances of the same web applications were running, thus allowing a web application about to shut down to snatch a few events that should have been handled by the new instance.
For these reasons, in general I would not recomment doing pub/sub in web applications.
So, why doesn't your pub/sub thing work? Well - first thing: Don't dispose the container adapter immediately after creating it! :)
Do this instead:
static readonly _stuffToDispose = new List<IDisposable>();
protected void Application_Start()
{
var adapter = new BuiltinContainerAdapter();
_stuffToDispose.Add(adapter);
adapter.Register(() => new CheckStatus(adapter.Bus));
Configure.With(adapter)
.Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig())
.MessageOwnership(o => o.FromRebusConfigurationSection())
.CreateBus()
.Start();
adapter.Bus.Subscribe<NewTradeRecorded>();
}
protected void Application_End()
{
_stuffToDispose.ForEach(d => d.Dispose());
}
This way, you bus will not stop handling messages immediately after your web app has started.
Related
I have followed the guide from Microsoft for getting started with SignalR. This worked perfectly, and I was able to publish and deploy the application to IIS.
Now I need to communicate with the .NET application from another Windows process (specifically a Delphi program). What I want to do is to tell the .NET application to send SignalR message (i.e. invoke a method on all connected clients).
How can I accomplish this?
I'm not sure how the .NET application is being executed - does it have its own Windows process that I could send Windows messages to? Or would it be easier to send a local HTTP GET/POST request from the Delphi program to localhost? If so, how can I make the SignalR application handle it?
You can create a controller and inject the IHubContext<ChatHub>. Use the hub context to send message to clients.
public class MessageController : Controller
{
private readonly IHubContext<ChatHub> _hubContext;
public MessageController(IHubContext<MessageHub> hubContext)
{
_hubContext = hubContext;
}
[HttpPost]
public async Task<IActionResult> SendMessage([FromForm] string message)
{
await _hubContext.Clients.All.SendAsync("ReceiveMessage", message);
return Ok();
}
}
Then call this endpoint from your delphi app.
I'm in the process of porting some server application from .NET Framework+WCF to .NET Core, but I'm having trouble managing the server exit. On our current WCF server, we allow quitting both from a WCF request, and from a console input:
static void Main()
{
hExit = new ManualResetEvent(false);
StartServer();
Console.WriteLine("Server started. Press [Enter] to quit.");
char key;
while((key=WaitForKeyOrEvent(hExit)) != '\0' && key != '\r') { }
Console.WriteLine();
StopServer();
}
private static char WaitForKeyOrEvent(System.Threading.WaitHandle hEvent)
{
const int Timeout = 500;
bool dueToEvent = false;
while(!Console.KeyAvailable)
{
if(hEvent.WaitOne(Timeout, false))
{
dueToEvent = true;
break;
}
}
char ret = '\0';
if(!dueToEvent)
{
ret = Console.ReadKey().KeyChar;
if(ret == '\0')
ret = char.MaxValue;
}
return ret;
}
...
class ServerObj : IMyWcfInterface
{
void IMyWcfInterface.ExitServer() { hExit.Set(); }
}
Would this approach also work in .NET Core? (using some other tech than WCF of course, since it was abandoned) I vaguely remember hearing that KeyAvailable/ReadKey might not work for an application in a Docker Container, and use of Containers is one of the "end goals" of migrating to .NET Core...
Generally, when running in a container, you generally don't have access to an input device (think keyboard). So that option is not reliable in a containerized application.
Listening to some sort of network request (eg, HTTP, grpc, protobuf) could work, but you would have to be sure that the source of the request is valid and wasn't a malicious entity attacking your application and forcing it to shutdown.
The idiomatic approach in a container environment (eg, Kubernetes, Docker ) is that the container engine sends your application Linux signals such as SIGTERM. docker stop will do this, as will Kubernetes when stopping your pods. Your application should then handle that and shut down correctly.
The implementation is different depending on whether you are using ASP.NET Core or not.
In ASP.NET Core you can use IApplicationLifetime.ApplicationStopping to register some code to be called when the application is being stopped.
Here's a StackOverflow answer that covers the implementation side: graceful shutdown asp.net core
If you are not using ASP.NET Core, you can handle AppDomain.ProcessExit to register a handler to be called when the application is stopping.
In my case, I had an ASP.NET application that starts up several sub ASP.NET processes. When I shutdown IIS, I wanted the plugin processes to gracefully shutdown, so I added a /shutdown GET route that calls IHostApplicationLifetime.StopApplication(). Whenever I called it, however, it would block and not actually shut down the application.
The fix was that, in my host application, where I start up the processes, I needed to set UseShellExecute = true. This completely solved my problem as my host application could do a GET request to all the plugin processes and they would shutdown almost immediately.
Strange but it is what it is.
I have a WCF Service Using MSMQ hosted on IIS. I want to create a windows application which can stop WCF Service from picking MSMQ message. Once I have seen the MSMQ message in the queue I need to click a button and Start the WCF service to pick the message in MSMQ. Code sample would be apperciated.
IIS is not an appropriate container to host a MSMQ client in. This is because when the app pool unloads during times of low traffic the queue client also unloads. This behaviour is automatic and you don't have any control over it.
It would be far better to host your client in a windows service. However, the kind of "consume-on-demand" functionality you require is not easy to achieve and certainly is not supported by the standard bindings.
The best I can suggest is consume the message as soon as it's received and persist it somewhere until the user clicks the button, upon which you do whatever you want as the data in the message is already available.
I was able to solve this problem by applying a workaround. I created another queue in a different machine. Changed the address of the WCF client endpoint address to this queue in config. I created another external application which moved the message from the alternate queue to the actual queue. Thus the behavior of stopping IIS hosted WCF service with MSMQ binding was achieved
Stopping the "Net.Msmq Listener Adapter" Windows service and the "Windows Process Activation Service" will stop the messages from being pulled out of the queue. Starting the services back up will causes the messages to be pulled from the queue again. I'm doing this manually, rather than through another application, but I'd assume you could do it through another application as well. I haven't tested this completely, but something like this would probably work:
Dictionary<string,List<string>> runningDependentServices = new Dictionary<string,List<string>>();
private void StartMsmqBinding()
{
StartService("WAS");
StartService("NetMsmqActivator");
}
private void StopMsmqBinding()
{
StopService("NetMsmqActivator");
StopService("WAS");
}
private void StartService(string serviceName)
{
List<string> previouslyRunningServices = null;
var sc = new ServiceController();
sc.ServiceName = serviceName;
if (runningDependentServices.ContainsKey(serviceName))
{
previouslyRunningServices = runningDependentServices[serviceName];
}
try
{
sc.Start();
sc.WaitForStatus(ServiceControllerStatus.Running);
if(previouslyRunningServices != null)
{
previouslyRunningServices.ForEach(a =>
{
var serviceController = new System.ServiceProcess.ServiceController() { ServiceName = a };
serviceController.Start();
serviceController.WaitForStatus(ServiceControllerStatus.Running);
});
}
}
catch (InvalidOperationException)
{
}
}
private void StopService(string serviceName)
{
var sc = new System.ServiceProcess.ServiceController() { ServiceName = serviceName };
runningDependentServices[serviceName] = sc.DependentServices.Where(a => a.Status == System.ServiceProcess.ServiceControllerStatus.Running).Select(a => a.ServiceName).ToList();
if (sc.CanStop)
{
try
{
sc.Stop();
sc.WaitForStatus(ServiceControllerStatus.Stopped);
}
catch (InvalidOperationException)
{
}
}
}
I'd think a similar approach would work for Net.Tcp binding. You'd probably have to stop the "Net.Tcp Listener Adapter" Windows service (ServiceName: "NetTcpActivator") and the "Windows Process Activation Service" in that case.
Is there any way of finding out when a WCF client has disconnected. Currently the only approach seems to be to wait until a call on the client from the service eventually times out.
I have tried subscribing to the OperationContext.Current.Channel.Faulted event but unfortunately it is never called; my understanding was that this event should be fired when the client disappears. On the other hand, when things close down gracefully OperationContext.Current.Channel.Closed is called.
In our application we only support a single client connection at a time, hence when somebody closes and re-starts the client app it would be nice if the server could be made aware of the the disconnection, tidy up gracefully and then accept another connection.
Yes, clients will disconnect gracefully most of the time, but this can't be guaranteed. Currently the only option seems to be to poll the client and wait for a CommunicationTimeout, which is hardly ideal.
Any suggestions greatly appreciated.
Theoretically, a service need not have knowledge of client's state. But it can insist on whom to serve for by dictating the authentication needs, concurrency limitation etc.
If you intention is to make sure only one client is served at a time, you can simply opt for Single Concurrency mode.
For example.
[ServiceBehavior(ConcurrencyMode=ConcurrencyMode.Single)]
public class CalculatorService : ICalculatorConcurrency
This will ensure only one client request is served at a time. Following link may help you as well.
http://msdn.microsoft.com/en-us/library/ms731193.aspx
EDIT
If you think an user's action of keeping the channel open does disturb the other user's work, it may not be the usual case.
Because each user's call is considered to be a different session. By default WCF calls are considered to be instantiated per call.
If you would like to persist data between user's calls, you may opt for perSession instancing mode.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class CalculatorService : ICalculatorInstance
This would make sure that each user would have an instance of the service which would not inturrupt servicing the other user.
You can set the concurrency mode accordingly i.e Multiple or Reentrant if you wish. Even if the concurrency mode is single, when a response is sent back to the user the service would be ready to serve the next user. It won't wait for the client to close the connection. User's connection would be useful only to keep the session live.
You can use IChannelInitializer and hook up Channel Close and Channel faulted events to detect graceful or abrupt closing of the client. Refer to a very nice post on this by Carlos - http://blogs.msdn.com/b/carlosfigueira/archive/2012/02/14/wcf-extensibility-initializers-instance-context-channel-call-context.aspx
You could use Callback Operations to make a call to the client to see if its still connected.
Take a look at this article on MSDN magazine
if (HttpContext.Current.Response.IsClientConnected == false
{
...
}
it can help you
I've had success using a "disconnection detector" like this:
// Code based on https://blogs.msdn.microsoft.com/carlosfigueira/2012/02/13/wcf-extensibility-initializers-instance-context-channel-call-context/
public class WcfDisconnectionDetector : IEndpointBehavior, IChannelInitializer
{
public event Action Disconnected;
public int ConnectionCount { get; set; } = 0;
public WcfDisconnectionDetector() { }
public WcfDisconnectionDetector(Action onDisconnected) => Disconnected += onDisconnected;
void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime cr)
=> cr.ChannelInitializers.Add(this);
void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher ed)
=> ed.ChannelDispatcher.ChannelInitializers.Add(this);
void IEndpointBehavior.Validate(ServiceEndpoint endpoint) { }
void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
void IChannelInitializer.Initialize(IClientChannel channel)
{
ConnectionCount++;
Trace.WriteLine($"Client {channel.SessionId} initialized");
channel.Closed += OnDisconnect;
channel.Faulted += OnDisconnect;
}
void OnDisconnect(object sender, EventArgs e)
{
ConnectionCount--;
Disconnected?.Invoke();
}
}
Install it before calling ServiceHost.Open:
var detector = new WcfDisconnectionDetector();
serviceHost.Description.Endpoints.Single().EndpointBehaviors.Add(
new WcfDisconnectionDetector(() => {/*disconnected*/}));
Given:
- the application - desktop GUI (WPF) .NET app
- windows service watching for application (.NET also)
The windows service periodically "pings" application to get sure it's healthy (and if it's not winservice will restart it).
I was going to implement "pinging" via named pipes. To make things simpler I decided to do it with WCF. The application hosts a WCF-service (one operation Ping returning something). The windows service is a client for this WCF-service, invokes it periodically based on a timer.
That's all in Windows 7.
Windows service is running under LocalService (in session#0).
Desktop application is running under currently logged in user (in session#1).
The problem:
Windows service can't see WCF endpoint (with NetNamedPipeBinding) created in and being listened in desktop application. That means that on call via wcf proxy I get this exception: "The pipe endpoint 'net.pipe://localhost/HeartBeat' could not be found on your local machine"
I'm sure code is ok, because another desktop application (in session#1) can see the endpoint.
Obviously here I'm dealing with some security stuff for Win32 system object isolation.
But I believe there should be a way to workaround restrictions I've encountered with.
I can sacrifice WCF approach and go the raw NamedPipe way.
An easier solution might be to use a WCF duplex contract with the Windows service hosting the WCF service. The client App would call an operation on the service to register itself, when it starts up. The Ping would then be an operation invoked periodically by the service on the client's callback contract, to which the App would respond.
Service visibility works this way round, because the Windows service can run with SeCreateGlobalPrivilege, and so the shared memory object via which the pipe name is published by the service can be created in the Global kernel namespace, visible to other sessions. Interactive applications can't easily get that privilege in Windows7, so WCF services in such applications fall back to publishing the pipe in the Local kernel namespace, visible only within their own session.
Finally I've found a solution - using Named Pipes from System.IO.Pipes directly. It's seems that WCF's pipes support implementation doesn't use System.IO.Pipes.
Server:
using (var pipeServer = new NamedPipeServerStream("mypipe", PipeDirection.Out, 1))
{
try
{
while (true)
{
// #1 Connect:
try
{
pipeServer.WaitForConnection();
}
catch (ObjectDisposedException)
{
yield break;
}
if (ae.IsCanceled())
return;
// #2: Sending response:
var response = Encoding.ASCII.GetBytes(DateTime.Now.ToString());
try
{
pipeServer.Write(response, 0, response.Length);
}
catch (ObjectDisposedException)
{
return;
}
// #3: Disconnect:
pipeServer.Disconnect();
}
}
finally
{
if (pipeServer.IsConnected)
pipeServer.Disconnect();
}
}
Client:
using (var pipeClient = new NamedPipeClientStream(".", "mypipe", PipeDirection.In))
{
try
{
try
{
pipeClient.Connect(TIMEOUT);
}
catch(TimeoutException ex)
{
// nobody answers to us
continue;
}
using (var sr = new StreamReader(pipeClient))
{
string temp;
while ((temp = sr.ReadLine()) != null)
{
// got response
}
}
}
catch(Exception ex)
{
// pipe error
throw;
}
}