Injecting data directly into consumers that need it might work in some circumstances, but in most cases it is the consumer who determines what data he needs, by querying this data (asking for data using supplied arguments).
For those few cases that data can be injected directly (you can see them as parameterless queries), injecting data directly would make your DI configuration extremely complex. Take for instance a consumer that depends on the current time in the system. You could inject a DateTime into the consumer (or even a Lazy<DateTime>). Another consumer might need the date of birth of the current user, so this consumer also depends on an DateTime. But now you got ambiguity in the system, since the DateTime dependency has two meanings. DI containers are bad in handling this, and to resolve this, you will have to tell the container explicitly what DateTime means for each consumer that needs it. This results in a brittle configuration that is hard to contain.
For this latter case (the parameterless queries), the solution is to define unambiguous interfaces that can be resolved. In the example above you can define an ITimeProvider interface and a IUserContext interface. Each consumer can take a dependency on the correct interface.
For the former case (the parameterized queries), you don't need a framework for this; you need proper design.
You are talking about querying a database and web service and need a way to cache the returned data. So what you need is an abstraction for defining queries, and do it in a way that you can apply caching and other cross-cutting concerns to them, in a way that is pluggable and saves you from having to make any changes to your code.
An effective way to do this is by defining a interface that defines the query object (the input parameters of the query) + the return type, and an interface that defines the logic that handles that query:
public interface IQuery<TResult>
{
}
public interface IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
{
TResult Handle(TQuery query);
}
With these abstractions you can define a query object like this:
public class FindUsersBySearchTextQuery : IQuery<User[]>
{
public string SearchText { get; set; }
public bool IncludeInactiveUsers { get; set; }
}
This object defines a query that finds users by a search text and allows inclusion or exclusion of inactive users, and the query result is an array of User objects.
The logic that executes this query can be implemented as follows:
public class FindUsersBySearchTextQueryHandler
: IQueryHandler<FindUsersBySearchTextQuery, User[]>
{
private readonly NorthwindUnitOfWork db;
public FindUsersBySearchTextQueryHandler(
NorthwindUnitOfWork db)
{
this.db = db;
}
public User[] Handle(FindUsersBySearchTextQuery query)
{
return (
from user in this.db.Users
where user.Name.Contains(query.SearchText)
where user.IsActive || query.IncludeInactiveUsers
select user)
.ToArray();
}
}
And why exactly does this solve the problem you have? This solves your problem because consumers can depend on the IQueryHandler<FindUsersBySearchTextQuery, User[]> interface, while you can wrap IQueryHandler<T> implementations with decorators, without anyone to know. Writing a decorator that caches the result in the tread-local stogage is a no-brainer:
public class TlsCachingQueryHandlerDecorator<TQuery, TResult>
: IQueryHandler<TQuery, TResult>
where TQuery : IQuery<TResult>
where TResult : class
{
[ThreadStatic]
private static TResult cache;
private readonly IQueryHandler<TQuery, TResult> decorated;
public ValidationQueryHandlerDecorator(
IQueryHandler<TQuery, TResult> decorated)
{
this.decorated = decorated;
}
public TResult Handle(TQuery query)
{
return cache ?? (cache = this.decorated.Handle(query));
}
}
Any solid DI container allows you to register these decorators and allow you to register decorators conditional (based on the type information of the decorated type). This allows you to place this caching decorator only on types that can safely be cached (according to your conditions).
You can find more information about this model in this article: Meanwhile… on the query side of my architecture.