I'm switching some code which uses MassTransit (v7.2.2 on .NET 5) to use a more declarative format (and away from multiple calls to ReceiveEndpoint()) and ideally to using ConsumerDefinitions for the configuration (though not part of this example for simplicity), along with some Dependency Injection with Quartz.NET (yanked from this example, though it running 3.3.3), in doing so I find now that my Consumers are not consuming, despite messages being sent and examples referenced. Take the following standing up of the MassTransit service:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(mt =>
{
mt.AddBus(provider => Bus.Factory.CreateUsingInMemory(cfg =>
{
//cfg.AutoStart = true; //No change when on
cfg.UseInMemoryOutbox();
cfg.ConfigureEndpoints(provider);
}));
mt.AddConsumer<TheMessageConsumer>();
services.AddMediator(cfg =>
{
cfg.AddConsumer<TheMessageConsumer>();
});
});
services.AddMassTransitHostedService();
});
var host = hostBuilder.Build();
var busControl = host.Services.GetService<IBusControl>();
busControl.Start(); //Just in case
var message = new TheMessage() { Message = $"<Message-{DateTime.Now.ToLongTimeString()}>" };
Console.WriteLine($"Sending: {message.Message}");
await busControl.Publish(message);
host.Run();
For note, the breaking out of the message sending here is to simplify my repro, as in my full code base, it's being sent by a Quartz fired job.
For this example, the message & receiver are also quite simple:
public class TheMessage
{
public string Message { get; set; }
}
public class TheMessageConsumer : IConsumer<TheMessage>
{
public Task Consume(ConsumeContext<TheMessage> context)
{
Console.WriteLine($"Message received: {context.Message.Message}");
return Task.CompletedTask;
}
}
The bus is started, either in the case I explicitly start it, the AutoStart flag is set, or the MassTransitHostedService does it, yet the message doesn't get received. Ditto if I have the full example with Quartz firing off a job with messages much later.
Can someone suggest what I am missing?
The code you posted is seriously a hodgepodge of snippets, none of which make any sense when used together. For example, AddBus is deprecated, and mediator has no business being in that project at all.
I'd suggest using one of the MassTransit Templates to create a new project from scratch (you may need to up the NuGet versions to 7.2.2).
Watch this video which explains the templates and how to use them.
With your comment, and updated question, you really only need the following:
var hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(mt =>
{
mt.AddConsumer<TheMessageConsumer>();
mt.UsingInMemory((context,cfg) =>
{
cfg.UseInMemoryOutbox();
cfg.ConfigureEndpoints(context);
}));
});
services.AddMassTransitHostedService();
});
var host = hostBuilder.Build();
await host.RunAsync();
If you want to test it, and send messages in the same process, simply add a BackgroundService (after AddMassTransitHostedService) to publish your messages.
You shouldn't publish until after the bus has been started.
Related
Im trying to write a unit test that should validate if a consumer ran or not.
But for some reason the consumer never executes.
I have created a more simplified version of the problem using the Getting-Started-sample of MassTransit.
This is "my" code:
[TestClass]
public class UnitTest1
{
private ITestHarness _testHarness;
[TestInitialize]
public void Initialize()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransitTestHarness(busRegistrationConfigurator =>
{
busRegistrationConfigurator.AddConsumer<MessageConsumer>();
});
var serviceProvider = serviceCollection.BuildServiceProvider();
_testHarness = serviceProvider.GetRequiredService<ITestHarness>();
}
[TestMethod]
public async Task TestMethod1()
{
await _testHarness.Bus.Publish(new Message { Text = "Hello, world!" });
(await _testHarness.Published.Any<Message>()).Should().BeTrue();
(await _testHarness.Consumed.Any<Message>()).Should().BeTrue();
}
}
The following assertion:
(await _testHarness.Consumed.Any<Message>()).Should().BeTrue();
Fails, since it always returns False.
I guess that I somehow need to await the consumer to execute? And I might be missing even more..
Been having a look at the documentations of MassTransit regarding testing, but Im not sure if the sample provided in the docs apply to this scenario (?), since the sample provided involves a request and response.
Would very much appreciate any help!
You're missing one line:
await _testHarness.Start();
Of course, you'll need to be sure and Dispose the container to ensure the bus is stopped and cleaned up.
I've discovered no Masstransit configuration that allows a service bus Topic to be created with Duplicate Detection enabled.
You can do it with Queues simply enough. But for Topics it seems a bit of a mystery.
Does anybody have a working sample?
Perhaps it is not possible.
I've been trying to use the IServiceBusBusFactoryConfigurator provided by the Bus.Factory.CreateUsingAzureServiceBus method.
I'd thought that some use of IServiceBusBusFactoryConfigurator.Publish method and IServiceBusBusFactoryConfigurator.SubscriptionEndpoint method would accomplish the task, but after a myriad of trials I've come up with no solution.
To configure your message type topic with duplicate detection, you must configure the publish topology in both the producer and the consumer (it only needs to be configured once per bus instance, but if your producer is a separate bus instance, it would also need the configuration). The topic must also not already exist as it would not be updated once created in Azure.
To configure the publish topology:
namespace DupeDetection
{
public interface DupeCommand
{
string Value { get; }
}
}
var busControl = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
cfg.Publish<DupeCommand>(x => x.EnableDuplicateDetection(TimeSpan.FromMinutes(10)));
cfg.ReceiveEndpoint("dupe", e =>
{
e.Consumer<DupeConsumer>();
});
}
The consumer is normal (no special settings required).
class DupeConsumer :
IConsumer<DupeCommand>
{
public Task Consume(ConsumeContext<DupeCommand> context)
{
return Task.CompletedTask;
}
}
I've added a unit test to verify this behavior, and can confirm that when two messages with the same MessageId are published back-to-back, only a single message is delivered to the consumer.
Test log output:
10:53:15.641-D Create send transport: sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand
10:53:15.784-D Topic: MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand (dupe detect)
10:53:16.375-D SEND sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand dc3a0000-ebb8-e450-949c-08d8e8939c7f MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand
10:53:16.435-D SEND sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection/DupeCommand dc3a0000-ebb8-e450-949c-08d8e8939c7f MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand
10:53:16.469-D RECEIVE sb://masstransit-build.servicebus.windows.net/MassTransit.Azure.ServiceBus.Core.Tests/input_queue dc3a0000-ebb8-e450-949c-08d8e8939c7f MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand MassTransit.IConsumer<MassTransit.Azure.ServiceBus.Core.Tests.DupeDetection.DupeCommand>(00:00:00.0017972)
You can see the (dupe detect) attribute shown on the topic declaration.
Here is the solution I finally found. It does not rely on trying any of the ReceiveEndpoint or SubscriptionEndpoint configuration methods which never seemed to give me what I wanted.
IBusControl bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
cfg.Publish<MembershipNotifications.MembershipSignupMessage>(configure =>
{
configure.EnableDuplicateDetection(_DuplicateDetectionWindow);
configure.AutoDeleteOnIdle = _AutoDeleteOnIdle;
configure.DefaultMessageTimeToLive = _MessageTimeToLive;
});
}
await bus.Publish(new MessageTest());
in our use case, we have the team channel id with me. This is captured and stored into the DB in the event below
this.onTeamsChannelCreatedEvent(async (channelInfo: any, teamInfo: any, turnContext: any, next: () => Promise<void>): Promise<void> => {
}
We are running a periodic job and need to find out the members of a channel to perform some operations. So, how can i find members info from the channelid?
I have seen examples which using the context or context.activity. But in this case context wont be available. we are using nodejs v4.
My code here is
var client = new ConnectorClient(credentials, { baseUri: channel.serviceUrl });
client.fetchMembers(channel.serviceUrl, channel.channelId,
(err, result) => {
if (err) {
functions.logger.log("failed to fetch team members")
}
else {
functions.logger.log("Team members are", result)
}
}
);
This now throws error
TypeError: client.fetchMembers is not a function
i have also tried client.fetchMembers but similar error.
There are two main ways you can do this:
Using the Microsoft Graph, in particular see the List Members operation.
By having a bot registered in the channel, which it sounds like you have already. You don't need an 'activity' at all, you can do this any time by accessing the Conversations object. In fact, this code doesn't even need to run in your "bot" project at all - as long as the bot is added to the team/channel, it will work to use the bot's credentials. You can see sample code here in the Microsoft docs, and I have a sample from a recent conference session, doing this in dotnet from a console app over here.
One thing that's worth noting, the members of a channel and the members of a Team are largely the same (only excluding Private channels), so that's why some of these options refer to the Team or Group.
You'll want to use teamsInfo.getTeamMembers() or teamsInfo.getPagedMembers() (if you want a paginated list), as in this sample:
async getPagedMembers(context) {
var continuationToken;
var members = [];
do {
var pagedMembers = await TeamsInfo.getPagedMembers(context, 100, continuationToken);
continuationToken = pagedMembers.continuationToken;
members.push(...pagedMembers.members);
} while (continuationToken !== undefined);
return members;
}
I'm in the process of rewriting one little WPF-App I wrote to make use of ReactiveUI, to get a feeling about the library.
I really like it so far!
Now I've stumbled upon the Throttle method and want to use it when applying a filter to a collection.
This is my ViewModel:
namespace ReactiveUIThrottle
{
public class MainViewModel : ReactiveObject
{
private string _filter;
public string Filter { get => _filter; set => this.RaiseAndSetIfChanged(ref _filter, value); }
private readonly ReactiveList<Person> _persons = new ReactiveList<Person>();
private readonly ObservableAsPropertyHelper<IReactiveDerivedList<Person>> _filteredPersons;
public IReactiveDerivedList<Person> Persons => _filteredPersons.Value;
public MainViewModel()
{
Filter = string.Empty;
_persons.AddRange(new[]
{
new Person("Peter"),
new Person("Jane"),
new Person("Jon"),
new Person("Marc"),
new Person("Heinz")
});
var filterPersonsCommand = ReactiveCommand.CreateFromTask<string, IReactiveDerivedList<Person>>(FilterPersons);
this.WhenAnyValue(x => x.Filter)
// to see the problem
.Throttle(TimeSpan.FromMilliseconds(2000), RxApp.MainThreadScheduler)
.InvokeCommand(filterPersonsCommand);
_filteredPersons = filterPersonsCommand.ToProperty(this, vm => vm.Persons, _persons.CreateDerivedCollection(p => p));
}
private async Task<IReactiveDerivedList<Person>> FilterPersons(string filter)
{
await Task.Delay(500); // Lets say this takes some time
return _persons.CreateDerivedCollection(p => p, p => p.Name.Contains(filter));
}
}
}
The filtering itself works like a charm, also the throttling, when using the GUI.
However, I'd like to unittest the behavior of the filtering and this is my first attempt:
[Test]
public void FilterPersonsByName()
{
var sut = new MainViewModel();
sut.Persons.Should().HaveCount(5);
sut.Filter = "J";
sut.Persons.Should().HaveCount(2);
}
This test fails because the collection still has 5 people.
When I get rid of the await Task.Delay(500) in FilterPersons then the test will pass, but takes 2 seconds (from the throttle).
1) Is there a way to have the throttle be instant within the test to speed up the unittest?
2) How would I test the async behavior in my filter?
I'm using ReactiveUI 7.x
Short answers:
Yes, by making sure you're using CurrentThreadScheduler.Instance when running under test
Instead of using CurrentThreadScheduler, use a TestScheduler and manually advance it
The longer answer is that you need to ensure your unit tests can control the scheduler being used by your System Under Test (SUT). By default, you'll generally want to use CurrentThreadScheduler.Instance to make things happen "instantly" without any need to advance the scheduler manually. But when you want to write tests that do validate timing, you use a TestScheduler instead.
If, as you seem to be, you're using RxApp.*Scheduler, take a look at the With extension method, which can be used like this:
(new TestScheduler()).With(sched => {
// write test logic here, and RxApp.*Scheduler will resolve to the chosen TestScheduler
});
I tend to avoid using the RxApp ambient context altogether for the same reason I avoid all ambient contexts: they're shared state and can cause trouble as a consequence. Instead, I inject an IScheduler (or two) into my SUT as a dependency.
In my application i am using chain of of web request call for fetching data from the net. Ie from the result of one request i will send other request and so on. But when i am disposing the web request, only the parent request is disposing. The two other request are still running. How i can cancel all these request in Rx
For your subscription to terminate everything, you either cannot break the monad or you need to make sure that you work into the IDisposable model.
To keep the monad (ie. stick with IObservables):
var subscription = initialRequest.GetObservableResponse()
.SelectMany(initialResponse =>
{
// Feel free to use ForkJoin or Zip (intead of Merge) to
// end up with a single value
return secondRequest.GetObservableResponse()
.Merge(thirdRequest.GetObservableResponse());
})
.Subscribe(subsequentResponses => { });
To make use of the IDisposable model:
var subscription = initialRequest.GetObservableResponse()
.SelectMany(initialResponse =>
{
return Observable.CreateWithDisposable(observer =>
{
var secondSubscription = new SerialDisposable();
var thirdSubscription = new SerialDisposable();
secondSubscription.Disposable = secondRequest.GetObservableResponse()
.Subscribe(secondResponse =>
{
// Be careful of race conditions here!
observer.OnNext(value);
observer.OnComplete();
});
thirdSubscription.Disposable = thirdRequest.GetObservableResponse()
.Subscribe(thirdResponse =>
{
// Be careful of race conditions here!
});
return new CompositeDisposable(secondSubscription, thirdSubscription);
});
})
.Subscribe(subsequentResponses => { });
One approah is by using TakeUntil extnsion method as described here. In your case, the event that takes this method as parameter could be some event thrown by the parent request.
If you could show us some code we can face the problem more specifically.
regards,