I'm using masstransit/rabbitmq in net core 3.1. I have a dispatcher service which will send messages to worker services when they are available. Each worker service has a rabbitmq queue in front which is created when the service starts. I want to make sure that when the worker service stops, then the queue(and exchange) needs to be deleted. I have been able to get it to work when I set flag AutoDelete in the configuration (Program.cs):
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(config =>
{
config.Host(settings.RabbitMq.Host, settings.RabbitMq.Port,
settings.RabbitMq.VirtualHost, h =>
{
h.Username(settings.RabbitMq.Username);
h.Password(settings.RabbitMq.Password);
});
var queueName = AssembleQueueName(settings);
var sp = services.BuildServiceProvider();
config.ReceiveEndpoint(queueName,
e =>
{
e.Consumer(() => new MessageConsumer());
e.AutoDelete = true;
});
}));
});
Unfortunately this does not work for me because I need have the ServiceProvider in my consumer class so therefore I'm doing the following instead (Worker.cs):
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var queueName = _settings.RabbitMq.ServicePrefixQueueName + "-" + _settings.ServiceId;
var messageHandler = _busControl.ConnectReceiveEndpoint(queueName, x =>
{
x.Consumer<MessageConsumer>(_serviceProvider);
});
await messageHandler.Ready;
_workerWitness.IsWorkerReady = true;
}
But here I don't know how to set the AutoDelete flag. Is it even possible?
If you follow the documentation, on configuring consumers with a container, you would see that you can configure your consumers so that they are resolved from the container as shown below (your code, updated to be correct):
services.AddMassTransit(x =>
{
x.AddConsumer<MessageConsumer>();
x.UsingRabbitMq((context, config) =>
{
config.Host(settings.RabbitMq.Host, settings.RabbitMq.Port,
settings.RabbitMq.VirtualHost, h =>
{
h.Username(settings.RabbitMq.Username);
h.Password(settings.RabbitMq.Password);
});
var queueName = AssembleQueueName(settings);
config.ReceiveEndpoint(queueName, e =>
{
e.AutoDelete = true;
e.ConfigureConsumer<MessageConsumer>(context);
});
}));
});
Related
I have a asp.net core web api application that runs great like this:
class Program
{
/// <summary>
/// Main method
/// </summary>
static void Main(string[] args)
{
// pass this as a parameter to specify what database I will like to use
Func<IServiceProvider, IMyDatabase> GetDatabaseFactory = provider =>
{
// used for testing purposes. In production I will use the real DB
return new MyDummyDatabase();
}
// create on a method so that it can be unit tested
WebApplication app = CreateMyAppWebApiApplication(GetDatabaseFactory);
// run application
app.Run();
}
}
And here is the method CreateMyAppWebApiApplication.
/* I removed a lot of stuff I just want to illustrate the idea. Moreover, I have hardocoded a lot of stuff for testing purposes. Once it works I will move it to a configuration file.*/
static WebApplication CreateMyAppWebApiApplication(StartAspDotNetParameters parameters)
{
var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(k =>
{
var port = 8888;
k.Listen(System.Net.IPAddress.Any, port, listenOptions =>
{
// Enable support for HTTP1 and HTTP2 (required if you want to host gRPC endpoints)
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps();
});
});
#region Add IoC dependencies
// .. code some dependencies I need for the controlers
#endregion
// add controllers
var mvcBuilder = builder.Services.AddControllers();
// serialize enums as string
mvcBuilder.AddJsonOptions(opts =>
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())
);
// Configure Swagger/OpenAPI. More info: https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
// code to configure...
});
WebApplication? app = builder.Build();
#region Configure the HTTP request pipeline.
// first middleware to intercept swagger.json file
// I have hardocded the path for testing purposes
app.Use(async (HttpContext context, Func<Task> next) =>
{
if (requestUrl.EndsWith("myapp-swagger.json"))
{
var content = File.ReadAllText(#"T:\repos\.....\myapp-swagger.json.json");
context.Response.ContentLength = content.Length;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(content);
return;
}
else
{
// else execute next middleware
await next();
}
});
// enable swagger
app.UseSwagger();
// change swager endpoint
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "documentation";
c.SwaggerEndpoint("/myapp-swagger.json", "MY API");
});
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// This will run the application
//// execute endpoint
//app.Run();
return app;
#endregion
}
The things important to note about this method are:
// I changed swagger default endpoint
app.UseSwaggerUI(c =>
{
c.RoutePrefix = "documentation";
c.SwaggerEndpoint("/myapp-swagger.json", "MY API");
});
// AND
// first middleware to intercept swagger.json file
// I have hardocded the path for testing purposes
app.Use(async (HttpContext context, Func<Task> next) =>
{
if (requestUrl.EndsWith("myapp-swagger.json"))
{
var content = File.ReadAllText(#"T:\repos\.....\myapp-swagger.json.json");
context.Response.ContentLength = content.Length;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(content);
return;
}
else
{
// else execute next middleware
await next();
}
});
Anyways that code works great.
Now here is the problem:
When I try to run that same code from a Tests project like this:
[Fact]
public async Task TestUserPermissions_IntegrationTest()
{
// pass the same dummyDatabase
WebApplication app = CreateMyAppWebApiApplication(provider =>
{
// used for testing purposes. In production I will use the real DB
return new MyDummyDatabase();
});
loginWorked = false;
var taskLogin = Task.Run(async () =>
{
// make sure app starts by waiting 5 seconds
await Task.Delay(5000);
using var client = new HttpClient();
var json = #"{ 'username':'tono', 'password':'myPassword'}".Replace("'", "\"");
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result = await client.PostAsync("https://localhost:8888/api/LoginController/Login", content);
Console.WriteLine(result.StatusCode);
loginWorked = result.StatusCode == 200;
});
// run application
app.Run();
await taskLogin ;
Assert.True(loginWorked);
}
The app runs but I am not able to consume the API when running in the Test project
Finally found the answer. The controllers where not being found because I was running the project from a different assembly. This solution for stackoverflow made my Test pass:
https://stackoverflow.com/a/59121354/637142
In other words I ended up adding this code
// add controllers
var mvcBuilder = builder.Services.AddControllers();
// add controllers from this assembly. This is needed in case we are calling this method from unit tests project.
mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(typeof(MyCustomController).Assembly));
I have the following error when I start the masstransit with azure bus.Start(); on my function StartService() I have configuring my azure with masstransit and autofact. The error:
MassTransit.Azure.ServiceBus.Core.ServiceBusConnectionException
HResult=0x80131500
Message=ReceiveTransport faulted: sb://softbaire-amilkar.servicebus.windows.net/;SharedAccessKeyName=**REMOVED**;SharedAccessKey=**REMOVED**/TeamTimeManager
Source=mscorlib
configuration with masstransit:
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.AddConsumer<TeamTimeManager>();
cfg.UsingAzureServiceBus((context, conf) =>
{
var settings = new HostSettings
{
ServiceUri = new Uri("sb://softbaire-amilkar.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=UeIC0z5RPCt25SjnWdss2ssP5a6msUKNJxmLnBpm26g="),
TokenProvider = TokenProvider.CreateManagedIdentityTokenProvider()
};
conf.Host(settings);
conf.ConfigureEndpoints(context);
});
});
return builder.Build();
}
this is where I start the service and I get the error:
public void StartService()
{
var container = CreatorContainer.ConfigureContainer();
var bus = container.Resolve<IBusControl>();
if (host != null)
{
host.Close();
}
host = new ServiceHost(typeof(TeamTimeManager));
utilHost = new ServiceHost(typeof(TeamTimeUtilityManager));
bus.Start();
source.TraceInformation("Starting TeamTimeManager Azure Bus...");
host.Open();
source.TraceInformation("TeamTimeManager Started!");
utilHost.Open();
utilSource.TraceInformation("Starting TeamTimeUtilityManager...");
}
UPDATE
this problem is solved when I comment on the line:
cfg.AddConsumer<TeamTimeManager>();
if I add a queue or a subscription the problem appears again
BUG
https://github.com/Azure/azure-sdk-for-net/issues/8627
It's likely permissions. MassTransit requires Manage, and you're configuring the managed identity token provider.
Remove the shared access credentials from the connection string, since they would conflict with the managed identity provider.
Make sure the service identity has Manage permissions on the namespace.
Well, the problem was in the token provider.
The problem was that the token generated was connected to azure but without any queue or topic register, the problem started when I wanted to register queues and topics, when I wanted to generate the connection with the queue or a topic I got an error because the token was invalid for my user(weird), so... I changed the method to generate the token and everything started to work correctly.
Before:
var settings = new HostSettings
{
ServiceUri = new Uri("sb://xxxx-busazure.servicebus.windows.net"),
TokenProvider = TokenProvider.CreateManagedIdentityTokenProvider()
};
After: now I'm using CreateSharedAccessSignatureTokenProvider() you need to send as parameters the "SharedAccessKeyName" and the "SharedAccessKey"
var settings = new HostSettings
{
ServiceUri = new Uri("sb://xxxxx-busazure.servicebus.windows.net"),
TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider("RootManageSharedAccessKey", "xxxxxxxxxxxxxxx=")
};
queues and everything is running smoothly, Final configuration method:
public static IContainer ConfigureContainer()
{
var builder = new ContainerBuilder();
builder.AddMassTransit(cfg =>
{
cfg.SetKebabCaseEndpointNameFormatter();
cfg.AddServiceBusMessageScheduler();
cfg.AddConsumer<TeamTimeManager>();
cfg.UsingAzureServiceBus((context, conf) =>
{
conf.UseServiceBusMessageScheduler();
var settings = new HostSettings
{
ServiceUri = new Uri("sb://amilkar-busazure.servicebus.windows.net"),
TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider("RootManageSharedAccessKey", "xxxxxxxxxxxxxxxxxxxxxx")
};
conf.Host(settings);
conf.ReceiveEndpoint("team-time-manager", e =>
{
e.ConfigureConsumer<TeamTimeManager>(context);
});
conf.ConfigureEndpoints(context);
});
});
return builder.Build();
}
The repository is available here: https://github.com/ranouf/TestingWithDotNetCore3_0/tree/WithDatabase
I'm looking for a way to upgrade from .Net Core 2.0 to 3.0 my Integration Tests.
I really try my best to be able to inject a damned service, as you can see, I tried every where I could:
var hostBuilder = new HostBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddXunit(Output);
})
.ConfigureServices(services =>
{
services.AddAutofac();
services.AddSingleton<IMyService, MyService>(); //Here
})
.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();//Here
})
.ConfigureWebHost(webHost =>
{
// Add TestServer
webHost
.UseStartup<TestStartup>()
.UseTestServer()
.ConfigureServices(services =>
{
services.AddSingleton<IMyService, MyService>();//Here
services.AddAutofac();
services
.AddControllers()
.AddApplicationPart(typeof(TestStartup).Assembly);
})
.ConfigureTestServices(services =>
{
services.AddSingleton<IMyService, MyService>();//Here
services.AddAutofac();
services
.AddControllers()
.AddApplicationPart(typeof(TestStartup).Assembly);
})
.ConfigureTestContainer<ContainerBuilder>(builder =>
{
builder.RegisterType<MyService>().As<IMyService>().InstancePerLifetimeScope();//Here
});
});
But guess what? It doesnt work at all ... I really don't know how I can make the HostBuilder understand ...
Of course I tried to alternativaly keep only one way to inject my service
So when I want to get the Service:
Host = hostBuilder.Start();
Server = Host.GetTestServer();
Client = Host.GetTestClient();
using (var scope = Host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var myService = services.GetRequiredService<MyService>();
}
catch (Exception ex)
{
Output.WriteLine("HOST: " + ex.Message);
}
}
using (var scope = Server.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var myService = services.GetRequiredService<MyService>();
}
catch (Exception ex)
{
Output.WriteLine("SERVER: " + ex.Message);
}
}
The error message is:
The requested service 'MyAPI.Services.MyService' has not been
registered. To avoid this exception, either register a component to
provide the service, check for service registration using
IsRegistered(), or use the ResolveOptional() method to resolve an
optional dependency.
How do you succeed to inject a service in HostBuilder?
You have registered that whenever someone asks for IMyService give them an instance of MyService - but you are asking for MyService directly and DI doesn't know anything about it.
You need to change your registration to:
builder.RegisterType<MyService>().InstancePerLifetimeScope();
Or ask for the interface not the class:
services.GetRequiredService<IMyService>();
Also, you only need to register it in the ConfigureContainer method.
I am using RabbitMQ MassTransit for service bus implementation in my .Net core solution. I have created a queue by the name log.service. After lot many efforts I was finally able to push the messages in the queue and can see them in management tool but when I am listening to the same queue in another microservice project, I am unable to do so. I have pushed the messages in the bus from Authentication service and want to log the event in Logging service. Please help!
Here is my authentication-StartUp.cs
var buildr = new ContainerBuilder();
buildr.RegisterType<LoggingCommandConsumer>();
buildr.Register(c =>
{
return Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri("rabbitmq://localhost/"), h =>
{
h.Username("guest");
h.Password("guest");
});
sbc.ExchangeType = ExchangeType.Direct;
sbc.ReceiveEndpoint(host, "log.service", e =>
{
e.Consumer<LoggingCommandConsumer>();
});
});
})
.As<IBusControl>()
.As<IBus>()
.As<IPublishEndpoint>()
.SingleInstance();
buildr.Populate(services);
ApplicationContainer = buildr.Build();
return new AutofacServiceProvider(ApplicationContainer);
Here is my logging-StartUp.cs:
var buildr = new ContainerBuilder();
buildr.RegisterType<LoggingCommandConsumer>();
buildr.Register(context =>
{
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost/"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ReceiveEndpoint(host, "log.service", e =>
{
e.Consumer<LoggingCommandConsumer>();
});
});
return busControl;
})
.SingleInstance()
.As<IBusControl>()
.As<IBus>();
buildr.Populate(services);
ApplicationContainer = buildr.Build();
return new AutofacServiceProvider(ApplicationContainer);
Here I am starting the Bus in both the StartUp.CS
var bus = ApplicationContainer.Resolve<IBusControl>();
var busHandle = TaskUtil.Await(() => bus.StartAsync());
lifetime.ApplicationStopping.Register(() => busHandle.Stop());
Here I am sending the message to end points from authentication controller:
var sendToUri = new
Uri("rabbitmq://localhost/log.servicebind=true&queue=log.service");
var endPoint = await _bus.GetSendEndpoint(sendToUri);
await endPoint.Send<ILoggingCommand>(new
{
XCorrelationId = "asd",
M4SId = "M4SId",
Host = "asdasd",
Level = "Level",
Time = "2019-01-02T07:06:43.722Z",
Message = "Message",
Other = "Other"
});
return Ok();
When I try to get the above message in log.service bus in rabbitMQ management tool, I am able to do so...but not able to listen it in logging-startup.cs
Your endpoint has a queue defined by "log.service" + Guid.NewGuid().ToString() but you send messages to the log.service queue.
I don't really see the point of adding the guid to the endpoint address, what are you trying to achieve?
if you define your endpoint as cfg.ReceiveEndpoint(host, "log.service", ep => <configuration> it should work. You need to uncomment your consumer.
I'm trying to figure out the easiest way to test my Service Registrations method for my framework. I'm creating dynamic services my registration looks like so:
var messageHubConfig = new DynamicHubServiceConfiguration<Message, MessageDTO>();
messageHubConfig.SetDynamicHubOptions<AstootContext>(async (context, dto) =>
{
return await context.ConversationSubscriptions
.Where(x => x.ConversationId == dto.ConversationId
&& x.IsSubscribed)
.Distinct()
.Select(x => x.User.UniqueIdentifier)
.ToListAsync();
});
messageHubConfig.RequiresDynamicValidator = false;
messageHubConfig.EventMapping.AddCreateEvent(async (sp, obj, dto) =>
{
var conversationService = sp.GetService<IRestEzService<Conversation, ConversationDTO>>();
var conversationDTO = await conversationService.Get(new object[] { dto.ConversationId });
var hubTaskQueue = sp.GetService<IHubServiceTaskQueue>();
hubTaskQueue.QueueDynamicCreate(conversationDTO);
}).When(async (sp, dto) => {
var context = sp.GetService<AstootContext>();
return await context.Conversations.Where(x => x.Id == dto.ConversationId).Where(x => x.Messages.Count == 1).AnyAsync();
});
//Registers service with a hub
restConfiguration.RegisterRestService(typeof(IMessageDTOService),
typeof(MessageDTOService),
messageHubConfig);
Inside of my Register Rest Service Method I have a lot of different services Getting registered e.g:
services.AddTransient(restServiceType, (IServiceProvider serviceProvider) =>
{
var restService = (IRestEzService<TEntity, TDTO>)
ActivatorUtilities.CreateInstance(serviceProvider, restServiceImplementationType);
serviceOption.EventMapping?.Register(serviceProvider, restService);
return restService;
});
How can I be assure that my factory configuration is being registered properly, How can I create a Service Collection for testing?
Create a ServiceCollection,
var services = new ServiceCollection();
call your registration function and then assert that your restServiceType was added.
Next build a provider from the service collection, resolve the restServiceType
var provider = services.BuildServiceProvider();
var restService = provider.GetRequiredService(restServiceType);
and assert that it is created as desired.
The GetRequiredService extension method will throw an exception if the service is unable to resolve the target type.
Now that is based solely on what is currently being shown in your example as I am unaware of any other dependencies.
Based on #Nkosi's answer, a quick test that all Servives are wired up and in a particular order:
// Arrange
var services = new ServiceCollection();
// Act
var provider = services.AddBaseServices(); // Whatever service you have...
// Assert
Assert.AreEqual(27, provider.Count);
// Run this code once and copy the output into this test...
for(int i = 0; i < provider.Count; i++)
{
System.Diagnostics.Debug.WriteLine($"Assert.AreEqual(\"{provider[i].ServiceType.Name}\", provider[{i}].ServiceType.Name);");
}