I am wondering how to set up the netcore dependency container for mvc with one instance per user.
According to https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection#service-lifetimes-and-registration-options there are currently only three methods of specifying a lifetime: singleton(one instance per application), scoped (one instance shared within a HttpRequest), transient (one instance per DI instance request)
Has someone attempted to do create on instance per user yet? I would be curious how it's done - if not i will probably dig through the docs at some point to see how it can be done and share the solution.
Create instance on first user request and keep it alive (for next requests) until with some expiration timeout... This looks like a Sessions.
You may register your service with factory method and analyze current Session inside.
If this is an asp.net core application, middleware that is automatically added to the middleware pipeline handles creating a new DI scope at the start of a request, and disposing of that scope at the end of a request. This scope is stored in HttpContext. This scope will be used when injecting MVC controllers etc. Therefore if you want to have per-user services injected into your MVC controllers / action methods, you'll need to replace this scope in HttpContext with your own one built for the current user. You'd have to do this with middleware which would have to run after the authentication middleware (so after the current user was established). Your custom middleware would look at the current authenticated user, and the GetOrCreate the IServiceProvider (container) held in some cache with probably a sliding expiry. With the per user IServiceProvider in hand, it would then create a scope for the current request and replace the scope currently in HttpContext with this user specific one, also ensuring its disposed of at the end of the request. The thing is, when building the per user container, if you create a new ServiceCollection for each user and register a few services and build and cache that IServiceProvider for that user, you won't be able to resolve any services that you've only registered at the application level I.e on startup. This is where the concept of child containers are handy, which microsoft doesnt implement out of the box, but you can use if you switch to using another DI provider like Autofac. Autofac provides an implementation of IServiceProvider and the ability to spawn child containers. If you used this mechanism you could create a child container for each user, which means it would still be able to resolve all your higher level services, but now you can also resolve the user specific services. If you do all this, you'll be able to have per user services injected.
It's a fair amount of work. If there is enough interest I'd consider adding this feature to my multitenancy library as it already does something similar to create per tenant containers: https://github.com/dazinator/Dotnettency
something like that?
var shoppingLists = new Dictionary < string,
ShoppingListStateContainer > ();
services.AddTransient < ShoppingListStateContainer > (s = >{
var userName = s.GetRequiredService < AuthenticationStateProvider > ().GetAuthenticationStateAsync().GetAwaiter().GetResult().User.Identity.Name ? ?"null";
lock(s) {
if (!shoppingLists.ContainsKey(userName)) shoppingLists.Add(userName, new ShoppingListStateContainer());
}
return shoppingLists[userName];
});
Related
I am currently reading about DDD and have a issue how to implement a certain validation.
Scenario:
I have an entity Group that contains a list of Members which in turn consists of a User and a MemberState (Member, Admin). The entity has a method MakeAdmin(User user) that turns a member into an admin.
The rule is: Only if the current user is admin of the group, it is allowed to turn the member to an admin.
I have three possible implementations in mind, with some caveats each.
Variant 1
Group enitity gets a dependency IUserContext injected via constructor to get the current user. With that the entity can check if the current user is admin and is allowed to switch the member state.
Caveat: I don't like that the domain entity has such a dependency injected and I don't think that is correct in a DDD view.
Variant 2
Method MakeAdmin() gets an additional parameter which turns it into MakeAdmin(User user, User currentUser). That removes the requirement for the IUserContext dependency in the entity.
Caveat: I am not sure if it is correct at all that the entity validates this rule, because it is not really an invariant of the entity.
Variant 3
Validate this rule in an application service, and only call MakeAdmin(User user) if validation passed.
Caveat: I consider the rule domain specific, so I think it is not correct to put it in application layer.
So what would be the best option, or is there some totally different variant?
My suggestion would be to go with your third option: Authorise access in the application/integration layer and then call into the domain.
This goes for any domain functionality really. The domain should not concern itself with whether the action may or may not be performed based on authorisation but rather only with domain concerns. If that authorisation action happens to be a domain concern in a particular Identity & Access Control bounded context that it would make sense but you would not run into those too often.
I have an asp.net core project. two role exist in application. user role with limited access that can act in site, and admin role with complete access that can act in site and admin panel.
for some of security reason I want Idle timeout of admin been short (about 30 min), and user role time out 1 day.
for user role as a default I set this code
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromDays(1);
options.CookieName = "Session";
});
how can config admin role IdleTimeout?
Simply, you can't. Session timeout is global. However, you could implement a layer on top of it that manually times out the session. For example, when the session is started, you could add a session var with the current date and time. Then, you could add a global action filter that checks this session var and compares the datetime with your custom timeout for the particular user role. If the custom timeout has been exceeded, you can then destroy the session manually (Session.Abandon()). In order for this to work, your global session timeout will have to be the longest timeout possible (presumably for admins), and then your custom timeout(s) would then be for any roles with shorter timeout periods.
I guess the SessionMiddleware should be modified to setup different session's idle times.
Explanation:
AddSession registers a SessionOptions object as singleton in DI. This object is injected into SessionMiddleware. Unfortinately, it is injected into a constuctor. As we know, a middleware is constructed at app startup. Per-request dependency injection is possible to Invoke method only, and SessionMiddleware doesn't have the Invoke method accepting SessionOptions.
So the plan is:
Fork SessionMiddleware and implement Invoke(HttpContext, SessionOptions) method.
After AddSession call, replace SessionOptions registration.
For example:
services.Replace(new ServiceDescriptor(typeof(SessionOptions),
provider =>
{
//you could resolve another dependencies here and determine user's role.
var service = provider.GetService<FooService>();
return new SessionOptions();
},
ServiceLifetime.Scoped));
in the WebApi I can get the current user using AspIdentity that way
User.Identity.GetUserId<long>();
However, The service Layer doesn't know about the AspIdentity. And I don't want to passe the current user as paramettre for all the methodes.
In other word, what pattern I can use to get the current user wherever I want.
The reason for this is to log all the activity the current user is doing in the application.
APIs are intended to be stateless, you may use the current logged in user, but you may have a 3rd party application accessing your API. Because it is stateless the current user is handled differently than with standard web calls.
Long story short: however you're authenticating your API users, you need to pass the username/userID down into the service layer. The good news is that once you have this setup, you can use the same service layer methods for API and non-API calls.
Finally this is how I implemented it.
In the Framework Layer (a comun independent project) I added this:
public interface ICurrentContextProvider<T>
{
T GetCurrentUser();
}
In the API I Added the implementation of this interface, something like :
public ApplicationUser GetCurrentUser()
{
return _usersService.FindByIdAsync(Convert.ToInt64(HttpContext.Current.User.Identity.GetUserId())).Result;
}
And As the all other layers know about the Framework Layer I can call it anywhere without any reference to AspIdentity and without passing the Current user to all my functions.
Note: I make it a generic because of the following:
1-Framework (common layer) doesn't know about application user
2-I can get any other type from the context like the ApplicationUserId which is Long.
I have a question about the field in service is shared by user or not? My grails version is 2.3.4.
Now I have a controller with two actions and trying to set and get value from service field.
//This is a controller
class TestController{
def testService
def setValue(){
testService.setValue("123")
}
def getValue(){}
println testService.getValue()
}
}
//This is a service
class TestService{
def var
def setValue(def value){
var = value
}
def getValue(){}
return var
}
}
In other words, if several users are using the action getValue in the controller, do they share the var in the service or not?
Thank you!
Yes, by default all services are singletons, so there is only one instance of service per webapp, but functions inside are not synchronised:
By default, access to service methods is not synchronised, so nothing prevents concurrent
execution of those methods. In fact, because the service is a
singleton and may be used concurrently, you should be very careful
about storing state in a service. Or take the easy (and better) road
and never store state in a service.
You can change this behaviour by placing a service in a particular
scope. The supported scopes are:
prototype - A new service is created every time it is injected into another class
request - A new service will be created per request
flash - A new service will be created for the current and next request only
flow - In web flows the service will exist for the scope of the flow
conversation - In web flows the service will exist for the scope of the conversation. ie a root flow and its sub flows
session - A service is created for the scope of a user session
singleton (default) - Only one instance of the service ever exists
Here's my question: I'm writing a platform which I will be giving to the customers to implement their projects with. So in my platform I have created a SessionService in which I have methods like getCurrentSession, getAttribute, setAttribute, etc. Before spring-session my getCurrentMethod looked like this:
#Override
public HttpSession getCurrentSession() {
if (this.session == null) {
final ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(true); // true == allow create
}
return this.session;
}
which worked perfectly fine, although it looks ugly and have no backing like redis. Now I want to migrate to spring-session and I was hoping to use the SessionRepository to find the current session of the user, however I can only see a getSession(String id) in there. I believe the id is stored in the cookie, so to use it I will probably have to pass the HttpServletRequest object from my controllers, to my facades, to the service layer which is very near the db layer. This looks like a very bad idea to me, so my question would be: is there any way to get the currentSession near the db layer? One way I would think is to write an interceptor that will be invoked the controllers, which will set the current session in the repository, or the service maybe? I'm just not sure this is the right way to go.
Obtaining the Session Id from Service Layer
You can use the RequestContextHolder to retrieve the session id, set attributes, and remove attributes.
The RequestContextHolder is typically setup using RequestContextListener or RequestContextFilter. Spring Session does NOT work with RequestContextListener because there is no way for Spring Session to wrap the request before the RequestContextListener is invoked.
Unfortunately, this means for Spring Boot applications, RequestContextHolder does not work out of the box. To work around it you can create a RequestContextFilter Bean. See spring-boot/gh-2637 for updates on this issue.
Should I be putting this in session?
Just because it is easy to put a lot of objects in session and it is stored in Redis does not mean it is the right thing to do.
Keep in mind that the entire session is retrieved on every request. So while Redis is fast, this can have a significant impact if there are lots of objects in session. Obviously the implementation can be optimized for your situation, but I think the concept of session generally holds this property.
A general rule of thumb is, "Do I need this object for over 95% of my requests?" (read this as almost all of my requests). If so, it may be a candidate for session. In most cases, the object should be security related if it fits this criteria.
Should I access session id from ThreadLocal in the service layer?
This is certainly open for debate as code is as much of an art as it is a science.
However, I'd argue that you should not be obtaining the session id from thread locale variables throughout your architecture. Doing this feels a bit like obtaining a "Person id" and obtaining the current "Person id" from the HttpServletRequest in a ThreadLocale. Instead, values should be obtained from the controller and passed into your service layer.
Your code does not need changing. It will return the Spring Session session object.
Though it is generally better to inject the HttpSession from the controller, or use session-scoped beans and #SessionAttribute than to have such a session service in the first place.