Integration Events in Modular Monolith - c#

I am trying to implement a multitenant (using a database per tenant) modular monolith that uses integration events to communicate between modules.
I am attempting to implement the integration events functionality and have the following scenario:
A new integration event is raised via an HTTP request
The integration event is published to the event bus
An integration event handler receives the message from the bus and modifies a domain object
The modification to the domain object causes a domain event to be raised
A domain event handler receives the domain event and performs some read operations on the database
This all works fine if I ignore the multitenant requirement, I am struggling to understand how to implement this for multiple tenants. I am using Finbuckle in my API and constructor injecting ITenantInfo into my DbContext to retrieve the correct connection string for the current tenant.
When I try to move the scenario to multitenant:
Step 1 is easy enough – I can simply pass a tenant identifier in the integration event.
Step 2 is unaffected – the message is published to the event bus.
Step 3 is tricky because it is asynchronous. If it was within the scope of the HTTP request, I would be able to inject the domain repository into the constructor of my integration event handler and retrieve the domain object from the database. However, because it is triggered from the event bus, the tenant identifier must be extracted from the message and manually resolved. Once I have resolved the tenant, I can manually create an instance of the domain repository and get the domain object from the database.
Step 4 is not affected; the domain object is updated as usual.
Step 5 is where I am struggling. I am constructor injecting the DbContext into the domain event handler which, before I introduced integration events, worked because ITenantInfo was resolved by the Finbuckle middleware and DI. The problem I have now is that I do not know how to resolve the tenant when the domain event is triggered via an integration event. My domain event does not contain a tenant identifier and there is no HttpContext therefore I cannot resolve ITenantInfo to provide the database connect string.
To clarify, the final step of my scenario requires the domain event handler to read data from the database via a DbContext. This is possible within the scope of an HTTP request because Finbuckle will resolve the ITenantInfo to provide the connection string. When outside the scope of an HTTP request, I do not know how to resolve the tenant because my domain event does not contain any tenant specific information.
How can I identify the tenant in a domain event handler when the domain event is raised as a side effect of an integration event?

For anybody that sees this in the future I was overthinking the problem.
I was able to resolve ITenantInfo using Scoped Filters in MassTransit.
For example:
public class MyTenantInfoConsumeFilter<T>
: IFilter<ConsumeContext<T>>
where T : class
{
readonly MyTenantInfo _tenantInfo;
private readonly MultiTenantStoreDbContext _dbContext;
public MyTenantInfoConsumeFilter(MyTenantInfo tenantInfo, MultiTenantStoreDbContext dbContext)
{
_tenantInfo = tenantInfo;
_dbContext = dbContext;
}
public Task Send(ConsumeContext<T> context, IPipe<ConsumeContext<T>> next)
{
if (context.Message is IntegrationEventBase message)
{
Map(_dbContext.TenantInfo.FirstOrDefault(tenant => tenant.Identifier == message.TenantIdentifier));
}
return next.Send(context);
}
public void Probe(ProbeContext context)
{
}
private void Map(MyTenantInfo? tenantInfo)
{
if (tenantInfo is null) return;
_tenantInfo.Id = tenantInfo.Id;
_tenantInfo.Identifier = tenantInfo.Identifier;
_tenantInfo.Name = tenantInfo.Name;
_tenantInfo.ConnectionString = tenantInfo.ConnectionString;
}
}
Then register the services using something like:
services.AddScoped<Finbuckle.MultiTenant.ITenantInfo>(sp => sp.GetService<MyTenantInfo>()!);
services.AddMassTransit(x =>
{
x.AddScoped<MyTenantInfo>();
x.UsingAzureServiceBus((context, cfg) =>
{
cfg.Host(hostContext.Configuration.GetConnectionString("AzureServiceBusConnection"));
cfg.ConfigureEndpoints(context);
cfg.UseConsumeFilter(typeof(MyTenantInfoConsumeFilter<>), context);
});
});

Related

asp.net core 3.1 service lifetime and Observable sobscriptions in Signalrhub

I think I`m bit lost:
I have a signalr hub which gets a "scoped" service ("subscription-service") injected via DI.
This "subscription-service" service gets a hubxontext and additional "scoped" domain-specific services injected, and tries to subscribe to the different domain-specific observables, that the services provides.
In the individual callbacks from the observables, I want to call methods on the hubcontext, to be able to notify the client when CRUD operations has been done.
My proble is that the callbacks are only fired IF the injected domain-services are of "singletons" lifetime.
In my Startup.cs I have configured all my services as "scoped":
services.AddScoped<IMaterialTypeService, MaterialTypeService>();
services.AddScoped<IMaterialTypeRepository, MaterialTypeDbRepository>();
services.AddScoped<WebApiSubscriptionService>();
services.AddScoped<IEmailService, EmailService>();
services.AddScoped<INotificationService, NotificationService>();
The method for wiring up the subscriptions (in the "subscription-service") is as following:
private void Startup()
{
_notificationService.Notifications.Subscribe(notification =>
{
_hubContext.Clients.All.Notify(notification);
});
_materialTypeService.MaterialTypesChanged.Subscribe(materialTypes =>
{
var matTypes = _mapper.Map<IList<MaterialTypeResponseDto>>(materialTypes);
_hubContext.Clients.All.MaterialTypesChanged(matTypes);
});
}
What am I missing here?
Why wouldnt the callback inside the subscription being called?
All services ar scoped and triggered by t he same web request.
My design is that I want to have n number of domain services, and when something happens (CRUD) inside any of the domain services, I want them to push an Observable message, som that the subscription-service mentioned above, can subscribe to this Observable, and in the callback, push the message via signalr.

How to dynamically create and inject services in ASP.NET 5?

I'm in a situation where the classic functionality of vnext's DI container is not enough to provide me with the correct functionality. Let's say I have a DataService that gets data from a database like this:
public class DataService : IDataService, IDisposable {
public List<MyObject> GetMyObjects()
{
// do something to fetch the data...
return myObjects;
}
}
I can then register this service in the DI container during the configuration phase in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(typeof(IDataService), typeof(DataService));
}
This ensures the correct lifecylce of the service (one per request scope), however, I need the service to access a different database when a different request is made. For simplicity reasons, let's say the following scenario applies:
when a request to my Web API is made, the DataService will access the currently logged in user, which contains a claim called Database which contains the information which database to use.
the DataService is then instantiated with the correct database connection.
In order to get the second step to work, I have created a constructor for the DataService like this:
public DataService(IHttpContextAccessor accessor)
{
// get the information from HttpContext
var currentUser = accessor.HttpContext.User;
var databaseClaim = currentUser.Claims.SingleOrDefault(c => c.Type.Equals("Database"));
if (databaseClaim != null)
{
var databaseId = databaseClaim.Value;
// and use this information to create the correct database connection
this.database = new Database(databaseId);
}
}
By using the currently logged in user and his claims, I can ensure that my own authentication middleware takes care of providing the necessary information to prevent attackers from trying to access the wrong database.
Of course adding the IDisposable implementation is required to cleanup any database connections (and gets called correctly using the scope lifecycle).
I can then inject the DataService into a controller like this
public MyController : Controller
{
private IDataService dataService;
public MyController(IDataService dataService)
{
this.dataService = dataService;
}
}
This all works fine so far.
My questions now are:
Is there another way to create the instance other than using the constructor of the DataService? Maybe accessing the object the IServiceCollection provides in a different place other than during the configration phase which runs only once? Maybe using my own OWIN middleware?
Is this method really safe? Could two requests made at the same time accidentally end up with the DataServiceintended for the other request and therefore end up giving out the wrong data?
What you have is fine.
Is there another way to create the instance other than using the constructor of the DataService? Maybe accessing the object the IServiceCollection provides in a different place other than during the configration phase which runs only once? Maybe using my own OWIN middleware?
Not really. You can use delegate registration but it's the same problem.
Is this method really safe?
Yes
Could two requests made at the same time accidentally end up with the DataServiceintended for the other request and therefore end up giving out the wrong data?
Nope. The IHttpContextAcessor uses AsyncLocal (http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html) to provide access to the "current" http context.

SignalR and ASP.NET Identity's UserManager class lifetime

In my MVC application, I user SignalR for communication between users. Basically, a client calls a method on the hub, which calls a method on a repository, which then saves the message to the database and the hub notifies the other client of the new message.
I had used the GetOwinContext() method during these calls from the client to get the current instance of UserManager and ApplicationDbContext, by using the GetUserManager<UserManager>() and Get<ApplicationDbcontex>() extension methods, respectively. However, I have noticed that calls from the same connection use the same context, which is, obviously, not a very good thing. I went ahead and changed my repository so it is like this now:
public XyzRepository() //constructor
{
db = ApplicationDbContext.Create(); //static method that generates a new instance
}
private ApplicatonDbContext db { get; set; }
private UserManager UserManager
{
get
{
return new UserManager(new UserStore<ApplicationUser>(db)); //returns a new UserManager using the context that is used by this instance of the repository
}
}
Since I reference the ApplicationUser objects using the UserManager (using FindByIdAsync(), etc, depending on the design), it is extremely important to use the context I currently work with for the UserStore of the UserManager's current instance. The repository is created once per request, which seems to apply to each SignalR calls as intended. While I have experienced no problems with this design so far, after reading about the issue (in this article), particularly this line:
"In the current approach, if there are two instances of the UserManager in the request that work on the same user, they would be working with two different instances of the user object.", I decided to ask the community:
Question: what is the preferred way to use ASP.NET Identity's UserManager class with SignalR, if it is imperative that I use the same instance of DbContext for my repository's methods that the UserManager's UserStore uses?
I think the preferred way is to use an Inversion of Control container and constructor-inject dependencies with some kind of lifetime scope. Here is another question that you might want to look into:
Using Simple Injector with SignalR
It is preferable that your DbContext instance live as long as the current web request. IoC containers have facilities that let you register DbContext instances with per web request lifetimes, but you need to set up the IoC container so that it can manage the construction of the Hub classes to achieve this. Some IoC containers (like SimpleInjector) will also automatically dispose of the DbContext at the end of the web request for you, so you don't need to wrap anything in a using block.
As for the UserManager, XyzRepository, etc, I think those can also have per-web-request lifetime, or even transient lifetimes. Ultimately, I don't see why you wouldn't be able to achieve something like this:
public class MyXyzHub : Hub
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly MessageRepository _messageRepository;
public MyXyzHub(UserManager<ApplicationUser> userManager,
MessageRepository messageRepository)
{
_userManager = userManager;
_messageRepository= messageRepository;
}
public void sendMessage(string message)
{
var user = _userManager.FindByIdAsync(...
_messageRepository.CreateAndSave(new Message
{
Content = message, UserId = user.Id
});
Clients.All.receiveMessage(message, user.Name);
}
}
If you wire up your IoC container the right way, then every time the Hub is constructed, it should reuse the same ApplicationDbContext instance for the current web request. Also with your current code, it looks like XyzRepository is never disposing of your ApplicationDbContext, which is another problem that an IoC container can help you out with.

Making an object accessible by Service Layer without passing as Parameter in MVC4 App

I'm building a multi-tenant MVC app where there's a single app pool and single database. I have a Tenant table, and each of my models has a TenantId identified.
Each Tenant has a string "Url" that identifies the full URL used to access that tenant's data.
I can access this from my BaseController with the following (rough approximation):
HttpRequest request = HttpContext.Current.Request;
Uri requestUrl = request.Url;
_tenant = _tenantService.GetTenantByUrl(requestUrl);
Now, I'm at a point where I need to pass the Tenant into the service layer to perform business logic. One way I can do this is to go across every single method across all services (~200 methods) and add a Tenant parameter. I'd have to touch every call to the service layer, and every service layer method. This would work, but it's tedious and muddles the code.
For example, one of my methods before:
public void DeleteUserById(int userId)
{
using (var db = CreateContext())
{
var user = db.Users.FirstOrDefault(u => u.UserId.Equals(userId));
InternalDeleteUser(db, user);
}
}
After (if I pass in the Tenant):
public void DeleteUserById(Tenant tenant, int userId)
{
using (var db = CreateContext())
{
var user = tenant.Users.FirstOrDefault(u => u.UserId.Equals(userId));
InternalDeleteUser(db, user);
}
}
What I'm trying to achieve (by setting the tenant from my BaseController, one layer up):
public void DeleteUserById(int userId)
{
using (var db = CreateContext())
{
var user = _tenant.Users.FirstOrDefault(u => u.UserId.Equals(userId));
InternalDeleteUser(db, user);
}
}
Is there any way I can use my BaseService (all other services inherit from this) or some other pattern to define the Tenant from the Controller, and have the service methods pick it up, without passing it as a parameter to each one? This way I need only touch the base controller (or maybe even global.asax), and nothing else.
Put simply: How can I make an object accessible to all services by defining it from an MVC controller, without passing it directly to the service?
I guess what you´re saying about having a base service (see Layer Supertype) makes sense. That base class will have a dependency on an interface defined in the same service layer (e.g. IUserSession, IContext or whatever) and that interface will have a method or property that will return your Tenant.
The implementation of this interface will reside in your web application and it will do something as what you described, obtaining the data from the HttpContext.
If you have a background process, console application or whatever that does not run on a web context, you will have a different implementation that will create the Tenant based on any other criteria that you want.
So to summarize, you will have in your service layer:
abstract class BaseService
{
protected IContext Context {get; private set;}
public BaseService(IContext context)
{
Context = context;
}
}
public interface IContext
{
Tenant GetTenant();
}
Then in your web layer you´ll have:
public IWebContext : IContext
{
public Tenant GetTenant()
{
//your code to return create the tenant based on the url.
}
}
Hope this helps.
I have the same 'problem' since I'm building a multi tenant app as well. However, I solved it quite simple, IMO: every repository/service has defined a TenantId property, that must be set when that service is used. TenantId is a value object and it will throw if null.
Now, the point is any of the repos/services can be used outside the request, for example in a background thread or app. I am using a message driven approach so any required info (like tenant id) is part of the message and thus available for the consumer of the service (the message handler). Another benefit is testability.
I advice against coupling your service to a request specific object like HttpContext, Session or Cache.

Sharing ServiceStack ICacheClient with SignalR

I'm trying to share the elements in cache between ServiceStack OOB ICacheClient and a SignalR Hub, but I'm getting the following error when I try to get the user session in the OnDisconnected event
Only ASP.NET Requests accessible via Singletons are supported
I have no issues accessing the session in the OnConnected event, so far this is what I've done:
public class HubService:Hub
{
private readonly IUserRepository _userRepository;
private readonly ICacheClient _cacheClient;
public HubService(IUserRepository userRepository,ICacheClient cacheClient)
{
_userRepository = userRepository;
_cacheClient = cacheClient;
}
public override System.Threading.Tasks.Task OnConnected()
{
var session = _cacheClient.SessionAs<AuthUserSession>();
//Some Code, but No error here
return base.OnConnected();
}
public override System.Threading.Tasks.Task OnDisconnected()
{
var session = _cacheClient.SessionAs<AuthUserSession>();
return base.OnDisconnected();
}
}
I'm using simple injector and my ICacheClient is registered as singleton:
Container.RegisterSingle<ICacheClient>(()=>new MemoryCacheClient());
the question is how do I register requests as singletons in SS? what am I missing on SignalR event?
Edit:
what I tried to expain for register requests in SS is because if there's a possibility to register SS IHttpRequest using a container and set the lifestyle as singleton due to the exception message, it seems like httpContext and IHttprequest are null by the OnDisconnected event
the SS code is the following:
public static string GetSessionId(IHttpRequest httpReq = null)
{
if (httpReq == null && HttpContext.Current == null)
throw new NotImplementedException(OnlyAspNet); //message
httpReq = httpReq ?? HttpContext.Current.Request.ToRequest();
return httpReq.GetSessionId();
}
what I'm trying to do is to store a list of connected users using ICacheClient and I just want to remove the connectionID from the list when a user get disconnected.
Edit:
it seems like according to danludwig post
"There is an interesting thing about SignalR... when a client disconnects from a
hub (for example by closing their browser window), it will create a
new instance of the Hub class in order to invoke OnDisconnected().
When this happens, HttpContext.Current is null. So if this Hub has any dependencies that are >registered per-web-request, something will probably go wrong."
the description above perfectly match my situation
I am no SingalR expert, but based on my experience with it and simple injector, I don't think you can get at Session (or Request, or HttpContext for that matter) during OnDisconnected. It kind of makes sense if you think about it though -- when a client disconnects from a hub, SignalR no longer has access to a session ID, there is no request, there is no more communication with the client. OnDisconnected is basically telling you "here, do something with this ConnectionId because the client it belonged to has gone away." Granted the user may come back, and then you can get access to the web goodies (session, request, etc, as long as you are IIS hosted) during OnReconnected.
I was having similar problems getting some simpleinjector dependencies to have correct lifetime scope during these 3 Hub connection events. There was 1 dependency that I wanted to register per http request, and it worked for everything except OnDisconnected. So I had to fenagle the SI container to use http requests when it could, but use a new lifetime scope when the dependency was needed during an OnDisconnected event. If you care to read it, I have a post here that describes my experiences. Good luck.

Categories