No Consumer code executed when publishing via a base class - .net-core

I have a base class IntegrationEvent and all other business events inherited from that.
public abstract class IntegrationEvent
{
public Guid Id { get; private set; }
public DateTimeOffset OccuredOn { get; private set; }
protected IntegrationEvent()
{
this.Id = Guid.NewGuid();
this.OccuredOn = DateTimeOffset.Now;
}
}
public sealed class StudentRegisteredIntegrationEvent : IntegrationEvent
{
public Guid StudentId { get; set; }
public string FullName { get; set; }
public StudentRegisteredIntegrationEvent(Guid studentId, string fullName)
{
StudentId = studentId;
FullName = fullName;
}
}
then I've created a consumer :
public sealed class StudentRegisteredConsumer: IConsumer<StudentRegisteredIntegrationEvent>
{
private readonly ILogger<StudentRegisteredConsumer> _logger;
public StudentRegisteredConsumer(ILogger<StudentRegisteredConsumer> logger)
{
_logger = logger;
}
public Task Consume(ConsumeContext<StudentRegisteredIntegrationEvent> context)
{
_logger.LogWarning("========== Message Received +==========================");
_logger.LogInformation($"Sending notification to {context.Message.FullName}");
_logger.LogWarning("========== Message Received +==========================");
return Task.CompletedTask;
}
}
In the producer side I have a List<IntegrationEvent> list and I publish them via IPublishEndpoint but it does not routed to the correct queue, instead it just creates another exchange, Sample.Abstraction.Domain:IntegrationEvent. How can I tell MassTransit not to use the base class and instead use the real type class? I also tried with ISendEndpointProvider but they are routed again to another queue, student-registered-integration-event_skipped queue, since there is no consumer available for the base class.
here is the log on the consumer side:
[00:33:21 DBG] Configuring endpoint student-registered-integration-event, Consumer: Sample.University.Notification.Consumers.StudentRegisteredConsumer
[00:33:21 DBG] Declare exchange: name: student-registered-integration-event, type: fanout, durable
[00:33:21 DBG] Declare exchange: name: Sample.IntegrationEvents:StudentRegisteredIntegrationEvent, type: fanout, durable
[00:33:21 DBG] Bind exchange: source: Sample.IntegrationEvents:StudentRegisteredIntegrationEvent, destination: student-registered-integration-event
[00:33:21 DBG] Declare queue: name: student-registered-integration-event, durable
[00:33:21 DBG] Bind queue: source: student-registered-integration-event, destination: student-registered-integration-event
[00:33:21 DBG] Prefetch Count: 16
[00:33:21 DBG] Consumer Ok: rabbitmq://localhost/wrapperizer/student-registered-integration-event - amq.ctag-nqSrJ0A5UQZCXg3tIr9Hfg
I have no clue how to configure, I also used ConsumerDefinition<StudentRegisteredConsumer> but to no avail, here is the code:
public sealed class StudentRegisteredConsumerDefinition : ConsumerDefinition<StudentRegisteredConsumer>
{
public StudentRegisteredConsumerDefinition()
{
const string eventName = nameof(StudentRegisteredIntegrationEvent);
var sanitized = KebabCaseEndpointNameFormatter.Instance.SanitizeName(eventName);
this.EndpointName = sanitized;
}
}
on producer side to get the uri for send endpoint:
var eventName = logEvt.IntegrationEvent.GetType().Name;
var sanitized = KebabCaseEndpointNameFormatter.Instance.SanitizeName(eventName);
var uri = new Uri($"exchange:{sanitized}");
var sender = await _sendEndpointProvider.GetSendEndpoint(uri);
await sender.Send(logEvt.IntegrationEvent);
I know the above-code is kinda the default behavior of MT, but without that, I have no correct queues and exchanges. any solution will be appreciated.

First, you can do this entirely with Publish, there is no need to configure all of the things you've done to try to work around the issue. You can configure your consumers by convention, and let them get configured on their own endpoints. The one part you missed is the way the messages are published.
From your List<IntegrationEvent>, my suspicion is that you were calling Publish<T>(T message) where T was inferred to be IntegrationEvent. This is why you're only getting messages on that exchange. You need to use the Publish(object message) overload, so that the appropriate type is determined.
You can simply assign each message in the list to object, and then call Publish with that object. Or, you can force the use of the overload:
await Task.WhenAll(events.Select(x => bus.Publish((object)x, x.GetType()));
That way, MassTransit will use the object type to call the appropriate generic overload.

Related

Avoid - An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full

I get following error from an Azure Function App when using cosmos DB. I have got the same with HttpClient but seemed to solve that by doing HttpClient static. Can you solve the same problem just by making the CosmosDB client static? Something like:
public class DocRepoCoach
{
public string ConnStr { get; set; }
public Container XX1Container { get; set; }
public Container XX2Container { get; set; }
**public static CosmosClient Client { get; set; }**
public DocRepoCoach(string connectionString)
{
ConnStr = connectionString;
var options = new CosmosClientOptions() { AllowBulkExecution = true, MaxRetryAttemptsOnRateLimitedRequests = 1000 };
Client = new CosmosClient(ConnStr, options);
XX1Container = Client.GetContainer("XXXAPI", "XX");
XX2Container = Client.GetContainer("XXXAPI", "XX");
}
}
Yes, please make it static.
The recommended practice with Azure functions is to use a Singleton client for the lifetime of your application. The CosmosClient can manage connections when you use a static client.
Below are the recommendations
Do not create a new client with every function invocation.
Do create a single, static client that every function invocation can use.
Consider creating a single, static client in a shared helper class if different functions use the same service.
These are also documented here on Azure docs

ASP.NET IConfigurationSection is returning null objects for POCO

I looked through various solutions posted on StackOverflow -- many were outdated.
The intent is to use IConfigurationSection.Get to return a POCO object from a json section via JsonConfigurationExtensions.
The simplest case:
IConfigurationBuilder builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{hostEnvironment.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
IConfiguration configuration = builder.Build();
return configuration.GetSection("ServerConfiguration").Get<ServerConfiguration>();
And a nested POCO:
public class ServerConfiguration
{
public Authentication Authentication { get; internal set; }
}
public class Authentication
{
public DatabaseConfiguration UserDatabase { get; internal set; }
}
public class DatabaseConfiguration
{
public string ConnectionString { get; internal set; }
public string DatabaseName { get; internal set; }
}
The result is a null object.
In order to "clean up" my code at inception, I actually did not include the set property declarations as it wasn't needed in previous Json to POCO handlers. However, even when declaring these handlers (typically non-public) the ASP.NET implementation for Json file processing was always returning null although retrieving the individual key pairs from the section was successful.
The answer was buried in a response in the ASP.NET forum:
https://github.com/aspnet/Configuration/issues/394#issuecomment-444683884
The resulting change in the code:
1) Make sure there is a declaration of a set handler (internal, protected, private).
2) Specify BindOptions => BindNonPublicProperties.
return configuration.GetSection("ServerConfiguration")
.Get<ServerConfiguration>(c => c.BindNonPublicProperties = true);

.NET Core SignalR: How to accomplish resource-based authorization?

All my SignalR clients connect using a JWT bearer token. I utilize the [Authorize] attribute in my SignalR Hub.
This token contains a userId which can be used to check if a user has read access on the resource through the resource's users property which contains a List<PuppyUserPermission> that look like this:
public class PuppyUserPermission
{
public string userId { get; set; }
public bool read { get; set; }
public bool write { get; set; }
}
The question is: how do I connect the dots here? Ideally, instead of something like
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
await Clients.All.SendAsync(pup);
}
}
I would so something like the following (this is more pseudo code than anything else, as I don't use valid methods):
[Authorize]
public class PuppyHub : Hub
{
public async Task SendPuppy(Puppy pup)
{
var clients = Puppy.users.Where(u => u.read == true);
await clients.SendAsync(pup);
}
}
Basically, I'd like to ensure that the clients recieving the Puppy object via SignalR would be authorized users on the resource. Problem is, Clients is just a list of string client IDs, and I'm not sure how to go about tying them to actual users on my Puppy resource.
How do I go about achieving this?
From the beginning, I had the feeling that the answer lay in IUserIdProvider, but I didn't see how that would work for multiple users.
I finally found the answer, but it'll definitely need some cleanup.
First, create your own implementation of IUserIdProvider as follows:
public class MyUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
var username = connection.User.Claims.Where(x => x.Type == "THE_CLAIM_YOU_WANT_TO_USE_TO_IDENTIFY_USERS").First().Value;
return username;
}
}
Next, register it using DI:
services.AddSingleton<IUserIdProvider, MyUserIdProvider >();
Now, when you want to send events from the server, use DI in your constructor to pull down an instance of your SignalR Hub as per usual:
private IHubContext<PuppyHub> puppyHub { get; }
public UsersController(IHubContext<PuppyHub> _puppyHub)
{
puppyHub = _puppyHub;
}
Then, where when you want to tell your clients about the new Puppy:
// ... typical controller code
// assume we have a var, puppy, with a list of authorized users
// use System.Linq to get a list of userIds where the user is authorized to read the puppy
var authorizedUsers = (IReadOnlyList<string>)puppy.users.Where(x => x.permissions.read == true).Select(i => i._id).ToList();
// send the new puppy to the authorized users
await puppyHub.Clients.Users(authorizedUsers).SendAsync("SendPuppy", puppy);
And viola! You have now done resource-based authorization with SignalR.

MassTransit. Consume equal objects defined in different namespaces

First of all, excuse my English, it's very bad. I am using MassTransit with Azure Service Bus for asynchronous communication between microservices. By their own definition, and to avoid generating dependencies between them, messages sent between different microservices are defined in each of them, that is, they are part of different namespaces. The automatic management of MassTransit causes queues and topics to be managed by the object type, which prevents the microservices that consume a message from receiving the messages sent by the microservice publisher. The same thing happens with two classes with the same properties in the same namespace but with a different class name.
Is there any way to solve this?
The options that have occurred to me are:
Remove the namespace from the endpoint of the destination address, naming it only with the name of the class.
That MassTransit can manage the creation of queues and topics based on the serialization of the object, instead of managing it based on the object type (perhaps through some type of wrapping object?)
I leave an example that I hope can help you in understanding the problem.
//FIRST PROGRAM - MESSAGE CONSUMER
namespace Consumer
{
public class Example
{
public string PropOne { get; set; }
public string PropTwo { get; set; }
}
public class ExampleConsumer :
IConsumer<Example>
{
public List<Example> ConsumedTestObjectList { get; } = new List<Example>();
//THIS METHOD NEVER CALL !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
public Task Consume(ConsumeContext<ExampleConsumer> context)
{
ConsumedTestObjectList.Add(context.Message);
return Task.CompletedTask;
}
}
public class ConsumerProgram
{
public static void Main()
{
var bus = Bus.Factory.CreateUsingAzureServiceBus(sbc =>
{
var host = sbc.Host("connectionString", h => {});
});
sbc.ReceiveEndpoint(host, e =>
{
e.Consumer<ConsumerProgram.Example>(context =>
{
return Console.Out.WriteLineAsync($"Message Received: {JsonConvert.SerializeObject(context.Message)}");
});
});
bus.Start(); // This is important!
Console.WriteLine("Press any key to exit");
Console.ReadKey();
bus.Stop();
}
}
}
//SECOND PROGRAM - MESSAGE PUBLISHER
namespace Publisher
{
public class Example
{
public string PropOne { get; set; }
public string PropTwo { get; set; }
}
public class PublisherProgram
{
public static void Main()
{
var bus = Bus.Factory.CreateUsingAzureServiceBus(sbc =>
{
var host = sbc.Host("connectionString", h => {});
});
bus.Start(); // This is important!
//send new instance of Publisher.Example
var example = new Example() { PropOne = "1", PropTwo = "2" };
bus.Publish(example);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
bus.Stop();
}
}
}
Thank you very much.
regards
Borja
The message type, and the resulting name, are a key concept within MassTransit. If you want to avoid sharing assemblies between projects, that is fine, but you will need to match the entire interface (or class, in your case) name, including namespace, or it will not route properly.
Yes, you can override the entity name formatter to change how topics are named but it won't change the message type requirement for deserialization of the message (which happens, by type).
So the recommendation here is to use the same namespace for the contracts, even if they're in separate projects.

Dependency Injection on API to API with AutoRest

I been following the Swagger in Azure App Service tutorial and I notice the AutoREST code generation. In the tutorial, theres is an API and a DataAPI.
The TodoListAPI is a normal Web API.
The TodoListDataAPI is the one that is connected to a datasource, it is also a Web API and it is being consumed by TodoListAPI.
Using swagger autogerated codes are being imported to the TodoListAPI
partial interface ITodoListDataAPI: IDisposable
{
Uri BaseUri
{
get; set;
}
ServiceClientCredentials Credentials
{
get; set;
}
IToDoList ToDoList
{
get;
}
....
/// this seems to be the interface that is needed to be injected in the Controller
public partial interface IToDoList
{
Task<HttpOperationResponse<object>> DeleteByOwnerAndIdWithOperationResponseAsync(string owner, int id, CancellationToken cancellationToken = default(System.Threading.CancellationToken));
Task<HttpOperationResponse<ToDoItem>> GetByIdByOwnerAndIdWithOperationResponseAsync(string owner, int id, CancellationToken cancellationToken = default(System.Threading.CancellationToken));
Then in the ToDoListAPI controller it is being used like this
public class ToDoListController : ApiController
{
private string owner = "*";
private static ITodoListDataAPINewDataAPIClient()
{
var client = new TodoListDataAPI(new Uri(ConfigurationManager.AppSettings["ToDoListDataAPIUrl"]));
return client;
}
// GET: api/ToDoItemList
public async Task<IEnumerable<ToDoItem>> Get()
{
using (var client = NewDataAPIClient())
{
var results = await client.ToDoList.GetByOwnerAsync(owner);
....
}
}
}
Now the problem in this pattern is it is not testable because it directly consumes the DataAPI.
My question is, How can I make ITodoList to be used as dependency injection on the controller.
public class ToDoListController : ApiController
{
private readonly ITodoListDataAPI _todoListData;
private ToDoListController (IToDoList todoListData)
{
_todoListData = todoListData;
}
}
I also don't know what Autofoca DI library to use, there is Autofac and Autofac.WebApi in the nuget gallery and I am not sure what to use in these instance.
Thanks,

Resources