feat: Add ExcludeFromMultiTenantResolution() and ExcludeFromMultiTenantResolutionAttribute for endpoints.
Usage:
app.MapOpenApi()
.ExcludeFromMultiTenantResolution();
Darn, my second commit automatically added itself to the PR.
@dotnet-policy-service agree
thanks!
I went back and forth on going with BypassMultiTenantResolution as opposed to ExcludeFrom. I chose ExcludeFrom because of an existing process, but I'm more keen on Bypass. Thought I'd throw that out there, incase it's a discussion.
This PR has been labeled inactive because it has been open 30 days with no activity. This will be closed in 7 days without further activity.
edit: No this will not be closed--sorry about that!
@wxwyz can you give some use cases where this would be beneficial. Also do you have any unit tests or documentation file updates to go along with it?
@AndrewTriesToCode Unfortunately I mixed two commits here. I will try to cover both real quick, let me know if you'd like any expansions. I have not created any documentation files. However, I did add a unit test for both additions, included in the PR. Were you looking for something more specific on either, or both?
I originally coded ExcludeFromMultiTenantResolution for OpenApi related endpoints. However, I could easily see it also being used for some more generalized endpoints that need to do work across tenants. i.e. Administrative dashboards or reports.
The original usage posted above was:
app.MapOpenApi()
.ExcludeFromMultiTenantResolution();
It could also be done within a controller, using the attribute:
// Here --> [ExcludeFromMultiTenantResolution]
public class DashboardController : Controller
{
// Or here --> [ExcludeFromMultiTenantResolution]
public ActionResult Index()
{
return View();
}
}
The primary reason I created the ShortCircuit was due to the fact that the only current way to catch a tenant resolution failure was from within the non web specific core library. If you try to handle the failure there, in a web specific manner (i.e. using the context to write a problem details response), MultiTenantMiddleware would still propagate the request, which isn't what you would want. This can also lead to exceptions due to the response being started and further middleware would fail. For instance kestrel middleware would throw exceptions because the response had already been started (the error is almost verbatim). I also didn't want to limit the need to short circuiting to my limited imagination, so I liked the idea of making it configurable by the user.
Usage for that would look like:
builder.Services.AddMultiTenant<TenantInfo>()
.WithHostStrategy()
.WithConfigurationStore()
.ShortCircuitWhenTenantNotResolved();
One thing to note, is if you use the ShortCircuitWhenTenantNotResolved, and you have endpoints that do not require a tenant (i.e. Dashboard or reporting), then ExcludeFromMultiTenantResolution becomes a necessity, otherwise the endpoints would actually never be reached. So the two go hand in hand in that scenario.
@wxwyz I reviewed it all and it looks great. The short circuiting is something I'd been noodling a while so I like that too.
Last request would be that you add a section to the ConfigurationAndUsage doc called "ASP.NET Core Features" with these two items briefly described with a basic example like above.
Followup question: what sort of response does the client expect? Would it make sense in the options to specify the type of response, e.g. 404 or 500?
Sure thing, give me a few days to find some time to flesh out some documentation.
As for the response, I ran with 400, bad request. However, I think there's a few 400 level errors that make sense. I wouldn't suggest 500 because this isn't a server error.
I handled the problem details response within OnTenantResolveCompleted. Which, other than passing the context through (as an object), doesn't know anything about the web. So I'm not sure anything in these commits really pertains to how a user generates a response. It just allows the user to short circuit, which doesn't do anything with the response. Was this just an open ended question?
Just an open ended question. Using the event makes sense for that. On May 30, 2025, at 1:25 PM, wxwyz @.***> wrote:wxwyz left a comment (Finbuckle/Finbuckle.MultiTenant#935) Sure thing, give me a few days to find some time to flesh out some documentation. As for the response, I ran with 400, bad request. However, I think there's a few 400 level errors that make sense. I wouldn't suggest 500 because this isn't a server error. I handled the problem details response within OnTenantResolveCompleted. Which, other than passing the context through (as an object), doesn't know anything about the web. So I'm not sure anything in these commits really pertains to how a user generates a response. It just allows the user to short circuit, which doesn't do anything with the response. Was this just an open ended question?
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were assigned.Message ID: @.***>
@AndrewTriesToCode I've been thinking more about your question. I'm not sure handling the response, even setting a status code would be my choice of action (this is your library though :)), as there are any number of ways a user may want to act. However, we could throw another Func on the ShortCircuitOptions that allows the user to handle the response right there during short curcuiting. This could be a better place for the logic as well, as the HttpContext could be strongly typed and allow users to handle a tenant not resolve in both a non-web and web way from two different places. Better adhering to the singularity principle.
Quick thought on how it would look:
// During app startup
builder.Services.AddMultiTenant<TenantInfo>()
.WithHostStrategy()
.WithConfigurationStore()
.ShortCircuitWhenTenantNotResolved(async (httpContext, multiTenantContext) =>
{
...
});
// In middleware
if (options?.Predicate is null || !options.Predicate(multiTenantContext))
await next(context);
else if (options.OnShortCircuit is not null)
await options.OnShortCircuit(context, multiTenantContext);
In thinking about it further, the most common use case and request I get is to simply redirect to a set url when no tenant is found. I think this pairs well with your PR here since you'd want that url to not resolve tenant as it would likely cause a loop never ending tenant resolution failures. So in his case it would short circuit and also return a http redirect the desired url. If you want go ahead and add something like that or I'll get it in a future update building off of what you have here.
I had to resolve a conflict, .ConfigureAwait(false) was added to await next(context). I removed it, as there is no synchronization context in ASP.NET Core.
I have an idea on how we could provide OnShortCircuit with RedirectTo as the catch all. If you'd like me to implement it, let me know and I'll create another feature branch for that.
@wxwyz looking good. Planning to add this to the next release within the month.
What is your idea with RedirectTo? I see currently you have support for it as an option -- can you add a short blurb about that into the docs in your short circuit section?
Going to go ahead and include this in a release now. Thanks!
Hey @AndrewTriesToCode, so I believe I added all the necessary details about the new functionality in the docs as you had asked. Is there something you feel is missing? I'd be happy to address it.
Nope I tweaked it all a bit and released yesterday