I'm currently using SignalR to communicate between a server and multiple separate processes spawned by the server itself.
Both Server & Client are coded in C#. I'm using SignalR 2.2.0.0
On the server side, I use OWIN to run the server.
I am also using LightInject as an IoC container.
Here is my code:
public class AgentManagementStartup
{
public void ConfigurationOwin(IAppBuilder app, IAgentManagerDataStore dataStore)
{
var serializer = new JsonSerializer
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.Auto,
TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple
};
var container = new ServiceContainer();
container.RegisterInstance(dataStore);
container.RegisterInstance(serializer);
container.Register<EventHub>();
container.Register<ManagementHub>();
var config = container.EnableSignalR();
app.MapSignalR("", config);
}
}
On the client side, I register this way:
public async Task Connect()
{
try
{
m_hubConnection = new HubConnection(m_serverUrl, false);
m_hubConnection.Closed += OnConnectionClosed;
m_hubConnection.TraceLevel = TraceLevels.All;
m_hubConnection.TraceWriter = Console.Out;
var serializer = m_hubConnection.JsonSerializer;
serializer.TypeNameHandling = TypeNameHandling.Auto;
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
m_managementHubProxy = m_hubConnection.CreateHubProxy(AgentConstants.ManagementHub.Name);
m_managementHubProxy.On("closeRequested", CloseRequestedCallback);
await m_hubConnection.Start();
}
catch (Exception e)
{
m_logger.Error("Exception encountered in Connect method", e);
}
}
On the server side I send a close request the following way:
var managementHub = GlobalHost.ConnectionManager.GetHubContext<ManagementHub>();
managementHub.Clients.All.closeRequested();
I never receive any callback in CloseRequestedCallback. Neither on the Client side nor on the server side I get any errors in the logs.
What did I do wrong here ?
EDIT 09/10/15
After some research and modifications, I found out it was linked with the replacement of the IoC container. When I removed everything linked to LightInject and used SignalR as is, everything worked. I was surprised about this since LightInject documented their integration with SignalR.
After I found this, I realised that the GlobalHost.DependencyResolver was not the same as the one I was supplying to the HubConfiguration. Once I added
GlobalHost.DependencyResolver = config.Resolver;
before
app.MapSignalR("", config);
I am now receiving callbacks within CloseRequestedCallback. Unfortunately, I get the following error as soon as I call a method from the Client to the Server:
Microsoft.AspNet.SignalR.Client.Infrastructure.SlowCallbackException
Possible deadlock detected. A callback registered with "HubProxy.On"
or "Connection.Received" has been executing for at least 10 seconds.
I am not sure about the fix I found and what impact it could have on the system. Is it OK to replace the GlobalHost.DependencyResolver with my own without registering all of its default content ?
EDIT 2 09/10/15
According to this, changing the GlobalHost.DependencyResolver is the right thing to do. Still left with no explanation for the SlowCallbackException since I do nothing in all my callbacks (yet).
Issue 1: IoC Container + Dependency Injection
If you want to change the IoC for you HubConfiguration, you also need to change the one from the GlobalHost so that returns the same hub when requesting it ouside of context.
Issue 2: Unexpected SlowCallbackException
This exception was caused by the fact that I was using SignalR within a Console Application. The entry point of the app cannot be an async method so to be able to call my initial configuration asynchronously I did as follow:
private static int Main()
{
var t = InitAsync();
t.Wait();
return t.Result;
}
Unfortunately for me, this causes a lot of issues as described here & more in details here.
By starting my InitAsync as follow:
private static int Main()
{
Task.Factory.StartNew(async ()=> await InitAsync());
m_waitInitCompletedRequest.WaitOne(TimeSpan.FromSeconds(30));
return (int)EndpointErrorCode.Ended;
}
Everything now runs fine and I don't get any deadlocks.
For more details on the issues & answers, you may also refer to the edits in my question.
Related
Does anyone know why I sometimes get exception when I use Selenium together with Testcontainers. See below:
Exception has occurred: CLR/OpenQA.Selenium.WebDriverException
An exception of type 'OpenQA.Selenium.WebDriverException' occurred in WebDriver.dll but was not handled in user code: 'An unknown exception was encountered sending an HTTP request to the remote WebDriver server for URL http://localhost:4444/session. The exception message was: An error occurred while sending the request.'
Inner exceptions found, see $exception in variables window for more details.
Innermost exception System.IO.IOException : The response ended prematurely.
at System.Net.Http.HttpConnection.d__61.MoveNext()
This happens half the time when i run the following test constructor (C# / xUnit.net):
public DockerShould()
{
var gridNetwork = new NetworkBuilder()
.WithName("gridNetwork")
.Build();
const int SessionPort = 4444;
var containerHub = new ContainerBuilder()
.WithImage("selenium/hub:4.8")
.WithName("selenium-hub")
.WithPortBinding(4442, 4442)
.WithPortBinding(4443, 4443)
.WithPortBinding(SessionPort, SessionPort)
.WithNetwork(gridNetwork)
.Build();
var firefoxEnvironment = new Dictionary<string, string>()
{
{"SE_EVENT_BUS_HOST", "selenium-hub"},
{"SE_EVENT_BUS_PUBLISH_PORT", "4442"},
{"SE_EVENT_BUS_SUBSCRIBE_PORT", "4443"}
};
var containerFirefox = new ContainerBuilder()
.WithImage("selenium/node-firefox:4.8")
.WithEnvironment(firefoxEnvironment)
.WithNetwork(gridNetwork)
.Build();
var firefoxVideoEnvironment = new Dictionary<string, string>()
{
{"DISPLAY_CONTAINER_NAME", "firefox"},
{"FILE_NAME", "firefox.mp4"}
};
var containerFirefoxVideo = new ContainerBuilder()
.WithImage("selenium/video:ffmpeg-4.3.1-20230210")
.WithNetwork(gridNetwork)
.WithEnvironment(firefoxVideoEnvironment)
// .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(SessionPort))
.Build();
gridNetwork.CreateAsync().Wait();
containerHub.StartAsync().Wait();
containerFirefox.StartAsync().Wait();
containerFirefoxVideo.StartAsync().Wait();
Thread.Sleep(5000);
_remoteWebDriver = new RemoteWebDriver(new Uri("http://localhost:4444"), new FirefoxOptions());
}
The exception occurs when creating the new RemoteWebDriver. I've added a thread.sleep to give a bit of a delay before the variable is created. I'm not sure it's really helping much. Is there a more elegant way to ensure all containers are started up before attempting to create the web driver (which i'm assuming is the problem)?
Your configuration has a few shortcomings. I am uncertain as to which one is ultimately causing the issue, but I have provided a working example below. The crucial parts have been commented. Please note that the example does not incorporate a wait strategy to determine the readiness of the container or the service inside it. That is an aspect that you will still need to address. But first lets take a look at some basics.
Please consider reading the article Consuming the Task-based Asynchronous Pattern. Testcontainers for .NET utilizes the Task-based Asynchronous Pattern (TAP). I noticed that you tend to block the asynchronous context frequently.
You do not need to bind ports for container-to-container communication.
Avoid using fixed port bindings such as WithPortBinding(4444, 4444). To prevent port conflicts, assign a random host port by using WithPortBinding(4444, true) and retrieve it from the container instance using GetMappedPublicPort(4444).
Do not use fixed container names for the same reason mentioned in 3. Use WithNetworkAliases(string) instead.
Do not use localhost to access services running inside containers. The endpoint varies according to the Docker environment. Use the Hostname property instead.
public sealed class StackOverflow : IAsyncLifetime
{
private const ushort WebDriverPort = 4444;
private readonly INetwork _network;
private readonly IContainer _selenium;
private readonly IContainer _firefox;
private readonly IContainer _ffmpg;
public StackOverflow()
{
_network = new NetworkBuilder()
.Build();
_selenium = new ContainerBuilder()
.WithImage("selenium/hub:4.8")
// Use random assigned host ports to access the service running inside the containers.
.WithPortBinding(WebDriverPort, true)
.WithNetwork(_network)
// Use a network-alias to communication between containers.
.WithNetworkAliases(nameof(_selenium))
.Build();
_firefox = new ContainerBuilder()
.WithImage("selenium/node-firefox:4.8")
.WithEnvironment("SE_EVENT_BUS_HOST", nameof(_selenium))
.WithEnvironment("SE_EVENT_BUS_PUBLISH_PORT", "4442")
.WithEnvironment("SE_EVENT_BUS_SUBSCRIBE_PORT", "4443")
.WithNetwork(_network)
.WithNetworkAliases(nameof(_firefox))
.Build();
_ffmpg = new ContainerBuilder()
.WithImage("selenium/video:ffmpeg-4.3.1-20230210")
.WithEnvironment("DISPLAY_CONTAINER_NAME", nameof(_firefox))
.WithEnvironment("FILE_NAME", nameof(_firefox) + ".mp4")
.WithNetwork(_network)
.WithNetworkAliases(nameof(_ffmpg))
.Build();
}
public async Task InitializeAsync()
{
await _network.CreateAsync()
.ConfigureAwait(false);
// You can await Task.WhenAll(params Task[]) too.
await _selenium.StartAsync()
.ConfigureAwait(false);
await _firefox.StartAsync()
.ConfigureAwait(false);
await _ffmpg.StartAsync()
.ConfigureAwait(false);
}
public async Task DisposeAsync()
{
await _selenium.DisposeAsync()
.ConfigureAwait(false);
await _firefox.DisposeAsync()
.ConfigureAwait(false);
await _ffmpg.DisposeAsync()
.ConfigureAwait(false);
await _network.DeleteAsync()
.ConfigureAwait(false);
}
[Fact]
public async Task Question()
{
// TODO: The container configurations mentioned above lack a wait strategy. It is crucial that you add a wait strategy to each of them to determine readiness (afterwards remove this line).
await Task.Delay(TimeSpan.FromSeconds(15))
.ConfigureAwait(false);
// Use the Hostname property instead of localhost. Get the random assigned host port with GetMappedPublicPort(ushort).
var webDriver = new RemoteWebDriver(new UriBuilder(Uri.UriSchemeHttp, _selenium.Hostname, _selenium.GetMappedPublicPort(WebDriverPort)).Uri, new FirefoxOptions());
Assert.NotNull(webDriver.SessionId);
}
}
I will like to ask you please: What do you use hub and node selenium mode?
I recommended using in this case standalone mode - and why?
Because the webDriver testcoaniner in my opinion works like a dynamic grid in s: docker-selenium github
I am also asking because I just working on that: Feature- WebDriver container
So I would like your opinion and how I can map between the testcotanienr and the RemoteWebDriver capabilities
I just worked my way through this MS Learn Tutorial regarding SignalR in Blazor.
At the end of the tutorial, you get a program that can have multiple clients hooked up to a "ChatHub" to send and receive messages, like a "Townsquare-Chatroom"
While testing I realized, that if you send some messages and afterward create a new client, the new client does not display the previously send messages. This is because every client stores its received messages locally as shown here:
#code{
// ...
private List<string> messages = new();
// ...
}
I decided to implement such a feature.
To do so, I created ChatLog.cs which is supposed to log the messages for all clients instead of saving them inside of each individual client:
public class ChatLog
{
private List<string> _messages= new List<string>();
public List<string> Messages
{
get { return _messages; }
set
{
_messages = value;
}
}
}
Of course, I also had to make some changes inside of index.razor to make things work:
I added a new service in program.cs as singleton
==> Program.cs
// ...
builder.Services.AddSingleton<ChatLog>();
// ...
and injected ChatLog into my index.razor
==> Index.razor
// ...
#inject ChatLog ChatLogger
// ...
I changed the code in index.razor #code to add the messages to ChatLog.Messages instead of the "local" messages-List
protected override async Task OnInitializedAsync()
{
// Change
if(ChatLogger.Messages is null)
{
ChatLogger.Messages = new();
}
hubConnection = new HubConnectionBuilder()
.WithUrl(NavManager.ToAbsoluteUri("/chathub"))
.WithAutomaticReconnect()
.Build();
hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
{
var formattedMessage = $"{user}: {message}";
// Change
ChatLogger.Messages.Add(formattedMessage);
InvokeAsync(StateHasChanged);
});
await hubConnection.StartAsync();
}
Now I run into a new problem.
Since the event
hubConnection.On<string, string>...
is called by every client, and all new messages get added into ChatLog.Messages X-times (x == amount of active clients).
I just can't think of a way to avoid this problem and only log every message exactly once.
Can someone help me?
Thanks in advance and sorry for the long explanation. Maybe someone can also help shorten it?
EDIT
To clarify the problem: Since the messages get added to the messages List inside of the event (as shown above), every instance (or every tab of the website) adds the message, resulting in multiple (and unwanted) adds.
E.g.
Two clients
Message "Hello" was sent once but added twice
Message "Ciao" was sent twice but added four times
From what I can gather this is more a learning exercise than something you're actually planning on using in a production environment, so we can ignore the fact that this isn't really a very robust implementation.
In any case, a simply solution would be to have the sender of the message store it in the messagelog, instead of storing it upon reception.
Taking from the tutorial you followed:
using Microsoft.AspNetCore.SignalR;
namespace BlazorServerSignalRApp.Server.Hubs
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// STORE YOUR MESSAGE IN YOUR MESSAGE LOG HERE
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
You should be able to inject your MessageLog service into the ChatHub in order to access it from there. (If I'm understanding your project structure correctly)
I'm currently reworking a microservices-based solution into a modular monolith with four APIs (pro, cyclist, management, thirdparty). One of the changes that need to be done is adapting the topology of our broker (RabbitMQ) so it fits our requirements. These requirements are shown on the diagram below.
The idea is that we currently always use the Request/Response mechanism for all our commands and queries and Publish mechanism for events, meaning that we always expect a response, whenever issuing a query (obviously) or a command.
We want the topology to support scaling in a way that if API1 (any instance of this executable) has multiple instances
commands/queries issued by any instance of the API1 will be executed by the consumers running in any instance of the API1 - this means that if both API1 and API2 executables have the same consumer, API2 consumers cannot execute commands/queries issued by the API2
when scaling, queues for commands and queries should not be scaled, just new consumers will be added and round robin should fire up
events are always received by all registered consumers so when scaling new queues are created
Right now I'm trying to figure out how to create this topology in MassTransit but I can't seem to get rid of the default publish exchange of type fanout. Here's the code that I use for automatic registration of command/queries endpoints and queues
private static IRabbitMqBusFactoryConfigurator AddNonEventConsumer<TConsumer>(
IRabbitMqBusFactoryConfigurator config,
IRegistration context)
where TConsumer : class, IConsumer
{
var routingKey = Assembly.GetEntryAssembly().GetName().Name;
var messageType = typeof(TConsumer)
.GetInterfaces()
?.First(i => i.IsGenericType)
?.GetGenericArguments()
?.First();
if (messageType == null)
{
throw new InvalidOperationException(
$"Message type could not be extracted from the consumer type. ConsumerTypeName=[{typeof(TConsumer).Name}]");
}
config.ReceiveEndpoint(e =>
{
// var exchangeName = new StringBuilder(messageType.FullName)
// .Replace($".{messageType.Name}", string.Empty)
// .Append($":{messageType.Name}")
// .ToString();
var exchangeName = messageType.FullName;
e.ConfigureConsumeTopology = false;
e.ExchangeType = ExchangeType.Direct;
e.Consumer<TConsumer>(context);
e.Bind(exchangeName, b =>
{
e.ExchangeType = ExchangeType.Direct;
b.RoutingKey = routingKey;
});
});
config.Send<TestCommand>(c =>
{
c.UseRoutingKeyFormatter(x => routingKey);
});
config.Publish<TestCommand>(c =>
{
c.ExchangeType = ExchangeType.Direct;
});
return config;
}
Again, we do want to use Request/Response mechanism for queries/commands and Publish mechanism for events (events are not a part of this question, it's a topic on its own, just queries/commands).
The question is - how do I configure endpoints and queues in this method in order to achieve the desired topology?
Alternative question - how else can I achieve my goal?
Cyclist? Pro? What kind of modular monolith is this anyway??
You're almost there, but need to configure a couple of additional items. First, when publishing, you'll need to set the routing key, which can be done using a routing key formatter. Also, configure the message type to use a direct exchange.
configurator.Send<TestCommand>(x =>
{
x.UseRoutingKeyFormatter(context => /* something that gets your string, pro/cyclist */);
});
config.Publish<TestCommand>(c =>
{
c.ExchangeType = ExchangeType.Direct;
});
Also, if you're using custom exchange names, I'd add a custom entity name formatter. This will change the exchange names used for message types, so you can stick with message types in your application – keeping all the magic string stuff in one place.
class CustomEntityNameFormatter :
IEntityNameFormatter
{
public string FormatEntityName<T>()
where T : class
{
return new StringBuilder(typeof(T).FullName)
.Replace($".{typeof(T).Name}", string.Empty)
.Append($":{typeof(T).Name}")
.ToString();
}
}
config.MessageTopology
.SetEntityNameFormatter(new CustomEntityNameFormatter());
Then, when configuring your receive endpoint, do not change the endpoint's exchange type, only the bound exchange to match the publish topology. Using an endpoint name formatter, custom for you application, you can configure it manually as shown.
var routingKey = Assembly.GetEntryAssembly().GetName().Name;
var endpointNameFormatter = new CustomEndpointNameFormatter();
config.ReceiveEndpoint(endpointNameFormatter.Message<TMessage>(), e =>
{
e.ConfigureConsumeTopology = false;
e.Bind<TMessage>(b =>
{
e.ExchangeType = ExchangeType.Direct;
b.RoutingKey = routingKey;
});
e.Consumer<TConsumer>(context);
});
This is just a rough sample to get your started. There is a direct exchange sample on GitHub that you can look at as well to see how various things are done in there. You could likely clean up the message type detection as well to avoid having to do all the type based reflection stuff, but that's more complex.
I'm using MassTransit + RabbitMQ combo in Asp.Net Core app. The relevant configuration part below:
public IBusControl CreateBus(IServiceProvider serviceProvider)
{
var options = serviceProvider.GetService<IConfiguration>().GetOptions<RabbitMqOptions>("rabbitmq");
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host($"rabbitmq://{options.Host}:{options.Port}");
cfg.ReceiveEndpoint("ingest-products", ep =>
{
ep.PrefetchCount = 16;
ep.UseMessageRetry(r => r.Interval(2, 1000));
ep.Bind<CreateProducts>(x =>
{
x.RoutingKey = "marketplace";
x.ExchangeType = ExchangeType.Direct;
x.AutoDelete = false;
x.Durable = true;
});
ep.ConfigureConsumer<CreateProductsConsumer>(serviceProvider);
});
});
}
When I run the application, I'm getting this exception:
ArgumentException: The
MassTransit.RabbitMqTransport.Topology.Entities.ExchangeEntity entity
settings did not match the existing entity
What am I doing wrong here? Am I not supposed to configure a consumer with the IServiceProvider after I bind exchange to a receive endpoint? If not, then how do I configure it properly (well, I still want stuff to get injected into my consumers)?
If you are binding the messages types to the receive endpoint that are the same as message types in the consumer, you need to disable the automatic exchange binding.
// for MassTransit versions v6 and earlier
endpoint.BindMessageExchanges = false;
// for MassTransit versions 7 and onward
endpoint.ConfigureConsumeTopology = false;
This will prevent MassTransit from trying to bind the messages types of the consumer on the endpoint.
I spent most of my day trying to find this. In v7+ it was renamed to:
endpoint.ConfigureConsumeTopology = false;
You need to disable automatic message exchange binding in order for your custom message binding to work.
endpoint.ConfigureConsumeTopology = false;
By following the source code on GitHub we can see that ConfigureConsumeTopology method obsoletes the previous methods, such as BindMessageTopics, BindMessageExchanges, SubscribeMessageTopics.
I'm currently working on a "server-app" / "client-app" project where the goal is to get some data from the server-app to the client app. I tried this with a WCF approach but since I've never worked with WCF it ain't an easy task for me.
So what I've already set up are the two apps in one solution à two different projects. Project one contains the server-app (TRunnerServer) and project two contains the client-app (TRunnerClient).
I've setup the interface for the service like that (in ServerApp MainWindowViewModel.cs):
[ServiceContract]
public interface ITRunnerService
{
[OperationContract]
ObservableCollection<Program> GetProgramList();
}
Than I've added the method to the class etc. like following:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
internal class MainWindowViewModel : ViewModelBase, ITRunnerService
{
public ObservableCollection<Program> GetProgramList()
{
return this.ProgramList;
}
public MainWindowViewModel()
{
var uris = new Uri[1];
string addr = "net.tcp://localhost:7000/MainWindowViewModel";
uris[0] = new Uri(addr);
ITRunnerService tRunnerService = this;
ServiceHost host = new ServiceHost(tRunnerService, uris);
var binding = new NetTcpBinding(SecurityMode.None);
host.AddServiceEndpoint(typeof(ITRunnerService), binding, string.Empty);
host.Open();
}
}
Now in the GUI of the application TRunnerClient I've got an button that I press to get the data from the TRunnerServer App.
I've just got an method binded with following:
private void Refresh(object parameter)
{
var uri = "net.tcp://localhost:7000/MainWindowViewModel";
NetTcpBinding binding = new NetTcpBinding(SecurityMode.None);
var channel = new ChannelFactory<ITRunnerService>(binding);
var endPoint = new EndpointAddress(uri);
var proxy = channel.CreateChannel(endPoint);
this.ProgramList = proxy.GetProgramList();
}
Yet when I start the app only the client starts normal and the server app gives an exception based on the error warning from the xaml "A registration already exists for URI 'net.tcp://localhost:7000/MainWindowViewModel'".
How could I solve this problem?
Note: Other questions with a similar title didn't really helped me out before someone strikes it as duplicate.
The error message indicates that there is an old server process still hanging around. Try to kill it in the task manager and try again.
In order to avoid hanging processes, make sure that you exit the application gracefully and that you don't create any windows that you don't show and close.