Is it possible to inject an instance of object to

2019-05-24 05:29发布

问题:

I have created a plugin which inspects a param in the query string and loads up a user object based on this ID and populates any request DTO with it. (All my request DTO's inherit from BaseRequest which has a CurrentUser property)

public class CurrentUserPlugin : IPlugin
{
    public IAppHost CurrentAppHost { get; set; }

    public void Register(IAppHost appHost)
    {
        CurrentAppHost = appHost;
        appHost.RequestFilters.Add(ProcessRequest);
    }

    public void ProcessRequest(IHttpRequest request, IHttpResponse response, object obj)
    {
        var requestDto = obj as BaseRequest;

        if (requestDto == null) return;

        if (request.QueryString["userid"] == null)
        {
            throw new ArgumentNullException("No userid provided");
        }

        var dataContext = CurrentAppHost.TryResolve<IDataContext>();
        requestDto.CurrentUser = dataContext.FindOne<User>(ObjectId.Parse(requestDto.uid));

        if (requestDto.CurrentUser == null)
        {
            throw new ArgumentNullException(string.Format("User [userid:{0}] not found", requestDto.uid));
        }
    }
}

I need to have this User object available in my services but I don't want to inspect the DTO every time and extract from there. Is there a way to make data from plugins globally available to my services? I am also wondering if there is another way of instantiating this object as for my unit tests, the Plugin is not run - as I call my service directly.

So, my question is, instead of using Plugins can I inject a user instance to my services at run time? I am already using IoC to inject different Data base handlers depending on running in test mode or not but I can't see how to achieve this for User object which would need to be instantiated at the beginning of each request.

Below is an example of how I inject my DataContext in appHost.

container.Register(x => new MongoContext(x.Resolve<MongoDatabase>()));
container.RegisterAutoWiredAs<MongoContext, IDataContext>();

Here is an example of my BaseService. Ideally I would like to have a CurrentUser property on my service also.

public class BaseService : Service
{
    public BaseService(IDataContext dataContext, User user)
    {
        DataContext = dataContext;
        CurrentUser = user; // How can this be injected at runtime?
    }

    public IDataContext DataContext { get; private set; }
    public User CurrentUser { get; set; }
}

回答1:

Adding:

We don't want to use HttpContext inside of the Service because we want use Service in our tests directly.

Advantages for living without it

If you don't need to access the HTTP Request context there is nothing stopping you from having your same IService implementation processing requests from a message queue which we've done for internal projects (which incidentally is the motivation behind the asynconeway endpoint, to signal requests that are safe for deferred execution).

http://www.servicestack.net/docs/framework/accessing-ihttprequest

And we don't use http calls to run tests.

So our solution is:

public class UserService
    {
        private readonly IDataContext _dataContext;

        public UserService(IDataContext dataContext)
        {
            _dataContext = dataContext;
        }

        public User GetUser()
        {
            var uid = HttpContext.Current.Request.QueryString["userId"];

            return _dataContext.Get<User>(uid);
        }
    }

and

container.Register(x => new UserService(x.Resolve<IDataContext>()).GetUser()).ReusedWithin(ReuseScope.Request);

This is service signature:

public SomeService(IDataContext dataContext, User user) { }

Any suggestions?



回答2:

Have you thought about trying to use the IHttpRequest Items Dictionary to store objects. You can access these Items from any filter or service or anywhere you can access IHttpRequest. See the src for IHttpRequest.

Just be mindful of the order that your attributes, services and plugins execute and when you store the item in the Items dictionary.



回答3:

I need to have this User object available in my services but I don't want to inspect the DTO every time and extract from there

How will your application know about the user if you're not passing the 'userid' in the querystring? Could you store the user data in the Session? Using a Session assumes the client is connected to your app and persists a Session Id (ss-id or ss-pid cookie in ServiceStack) in the client that can be looked up on the Server to get the 'session data'. If you can use the Session you can retrieve the data from your service doing something like

base.Session["UserData"] or base.SessionAs<User>();

Note: you will need to save your User data to the Session

Is there a way to make data from plugins globally available to my services? but I can't see how to achieve this for User object which would need to be instantiated at the beginning of each request.

This sounds like you want a global request filter. You're kind of already doing this but you're wrapping it into a Plugin.