Castle.Windsor lifestyle depending on context? - c#

I have a web application where many components are registered using .LifestylePerWebRequest(), now I've decided to implement Quartz.NET, a .NET job scheduling library, which executes in separate threads, and not the Request thread.
As such, HttpContext.Current yields null. My services, repositories, and IDbConnection were instanced so far using .LifestylePerWebRequest() because it made it easier to dispose of them when the requests ended.
Now I want to use these components in both scenarios, during web requests I want them to remain unaffected, and in non-request contexts I want them to use a different Lifestyle, I figure I can handle the disposing myself, but how should I go about it for choosing a lifestyle for the components based on the current context?
Currently I register services (for example), like this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
I figure I should be using some kind of extension method but I just don't see it..

You should use Hybrid Lifestyle from castleprojectcontrib.
An hybrid lifestyle is one that actually blends two underlying lifestyles: a main lifestyle and a secondary lifestyle. The hybrid lifestyle first tries to use the main lifestyle; if it's unavailable for some reason, it uses the secondary lifestyle. This is commonly used with PerWebRequest as the main lifestyle: if the HTTP context is available, it's used as the scope for the component instance; otherwise the secondary lifestyle is used.

Don't use the same components. In fact, in most scenarios I've seen the "background processing" doesn't even make sense to be in the web process to begin with.
Elaborating based on the comments.
Shoehorning background processing in the web pipeline is compromising your architecture to save a few $ on a EC2 instance. I would strongly suggest to think about this again, but I digress.
My statements still stands, even if you're putting both components in the web process they are two different components used in two different contexts and should be treated as such.

I've had a very similar problem recently - I wanted to be able to run initialisation code based off my container in the Application startup, when HttpContext.Request does not yet exist. I didn't find any way of doing it, so I modified the source of the PerWebRequestLifestyleModule to allow me to do what I wanted. Unfortunately it didn't seem possible to make this change without recompiling Windsor - I was hoping I would be able to do it in an extensible way so I could continue to use the main distribution of Windsor.
Anyway, to make this work, I modified the GetScope function of the PerWebRequestLifestyleModule so that if it was NOT running in a HttpContext (or if HttpContext.Request throws an exception, like it does in Application_Start) then it will look for a Scope started from the container instead. This allows me to use my container in Application_Start using the following code:
using (var scope = container.BeginScope())
{
// LifestylePerWebRequest components will now be scoped to this explicit scope instead
// _container.Resolve<...>()
}
There's no need to worry about explicitly disposing of things, because they will be disposed when the Scope is.
I've put the full code for the module below. I had to shuffle a couple of other things around within this class for it to work, but it's essentially the same.
public class PerWebRequestLifestyleModule : IHttpModule
{
private const string key = "castle.per-web-request-lifestyle-cache";
private static bool allowDefaultScopeOutOfHttpContext = true;
private static bool initialized;
public void Dispose()
{
}
public void Init(HttpApplication context)
{
initialized = true;
context.EndRequest += Application_EndRequest;
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var scope = GetScope(application.Context, createIfNotPresent: false);
if (scope != null)
{
scope.Dispose();
}
}
private static bool IsRequestAvailable()
{
if (HttpContext.Current == null)
{
return false;
}
try
{
if (HttpContext.Current.Request == null)
{
return false;
}
return true;
}
catch (HttpException)
{
return false;
}
}
internal static ILifetimeScope GetScope()
{
var context = HttpContext.Current;
if (initialized)
{
return GetScope(context, createIfNotPresent: true);
}
else if (allowDefaultScopeOutOfHttpContext && !IsRequestAvailable())
{
// We're not running within a Http Request. If the option has been set to allow a normal scope to
// be used in this situation, we'll use that instead
ILifetimeScope scope = CallContextLifetimeScope.ObtainCurrentScope();
if (scope == null)
{
throw new InvalidOperationException("Not running within a Http Request, and no Scope was manually created. Either run from within a request, or call container.BeginScope()");
}
return scope;
}
else if (context == null)
{
throw new InvalidOperationException(
"HttpContext.Current is null. PerWebRequestLifestyle can only be used in ASP.Net");
}
else
{
EnsureInitialized();
return GetScope(context, createIfNotPresent: true);
}
}
/// <summary>
/// Returns current request's scope and detaches it from the request context.
/// Does not throw if scope or context not present. To be used for disposing of the context.
/// </summary>
/// <returns></returns>
internal static ILifetimeScope YieldScope()
{
var context = HttpContext.Current;
if (context == null)
{
return null;
}
var scope = GetScope(context, createIfNotPresent: true);
if (scope != null)
{
context.Items.Remove(key);
}
return scope;
}
private static void EnsureInitialized()
{
if (initialized)
{
return;
}
var message = new StringBuilder();
message.AppendLine("Looks like you forgot to register the http module " + typeof(PerWebRequestLifestyleModule).FullName);
message.AppendLine("To fix this add");
message.AppendLine("<add name=\"PerRequestLifestyle\" type=\"Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor\" />");
message.AppendLine("to the <httpModules> section on your web.config.");
if (HttpRuntime.UsingIntegratedPipeline)
{
message.AppendLine(
"Windsor also detected you're running IIS in Integrated Pipeline mode. This means that you also need to add the module to the <modules> section under <system.webServer>.");
}
else
{
message.AppendLine(
"If you plan running on IIS in Integrated Pipeline mode, you also need to add the module to the <modules> section under <system.webServer>.");
}
#if !DOTNET35
message.AppendLine("Alternatively make sure you have " + PerWebRequestLifestyleModuleRegistration.MicrosoftWebInfrastructureDll +
" assembly in your GAC (it is installed by ASP.NET MVC3 or WebMatrix) and Windsor will be able to register the module automatically without having to add anything to the config file.");
#endif
throw new ComponentResolutionException(message.ToString());
}
private static ILifetimeScope GetScope(HttpContext context, bool createIfNotPresent)
{
var candidates = (ILifetimeScope)context.Items[key];
if (candidates == null && createIfNotPresent)
{
candidates = new DefaultLifetimeScope(new ScopeCache());
context.Items[key] = candidates;
}
return candidates;
}
}

Ok, I figured out a very clean way to do this!
First of all we'll need an implementation of IHandlerSelector, this can select a handler based on our opinion on the matter, or remain neutral (by returning null, which means "no opinion").
/// <summary>
/// Emits an opinion about a component's lifestyle only if there are exactly two available handlers and one of them has a PerWebRequest lifestyle.
/// </summary>
public class LifestyleSelector : IHandlerSelector
{
public bool HasOpinionAbout(string key, Type service)
{
return service != typeof(object); // for some reason, Castle passes typeof(object) if the service type is null.
}
public IHandler SelectHandler(string key, Type service, IHandler[] handlers)
{
if (handlers.Length == 2 && handlers.Any(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest))
{
if (HttpContext.Current == null)
{
return handlers.Single(x => x.ComponentModel.LifestyleType != LifestyleType.PerWebRequest);
}
else
{
return handlers.Single(x => x.ComponentModel.LifestyleType == LifestyleType.PerWebRequest);
}
}
return null; // we don't have an opinion in this case.
}
}
I made it so the opinion is very limited on purpose. I'll be having an opinion only if there are exactly two handlers and one of them has PerWebRequest lifestyle; meaning the other one is probably the non-HttpContext alternative.
We need to register this selector with Castle. I do so before I start registering any other components:
container.Kernel.AddHandlerSelector(new LifestyleSelector());
Lastly I wish I had any clue as to how I could copy my registration to avoid this:
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerWebRequest()
);
container.Register(
AllTypes
.FromAssemblyContaining<EmailService>()
.Where(t => t.Name.EndsWith("Service"))
.WithService.Select(IoC.SelectByInterfaceConvention)
.LifestylePerThread()
);
If you can figure out a way to clone a registration, change the lifestyle and register both of them (using either container.Register or IRegistration.Register), please post it as an answer here! :)
Update: In testing, I need to uniquely name the identical registrations, I did so like this:
.NamedRandomly()
public static ComponentRegistration<T> NamedRandomly<T>(this ComponentRegistration<T> registration) where T : class
{
string name = registration.Implementation.FullName;
string random = "{0}{{{1}}}".FormatWith(name, Guid.NewGuid());
return registration.Named(random);
}
public static BasedOnDescriptor NamedRandomly(this BasedOnDescriptor registration)
{
return registration.Configure(x => x.NamedRandomly());
}

I don't know whats happening behind the scenes in .LifestylePerWebRequest(); but this is what I do for "Context per request" scenarios:
Check HttpContext for session and if exists pull the context from .Items.
If it doesn't exist pull your context from System.Threading.Thread.CurrentContext.
Hope this helps.

Related

How do I check if a dependency injection service was injected in ASP.NET Core?

Let's say I register a scoped service to my ASP.NET 6 web application in Program.cs:
services.AddScoped<MyRequestService>();
Now let's say I have some custom middleware that runs after MVC routing:
app.UseRouting();
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
await Next(); // <-- here a controller gets instantiated based on MVC routing
// and *MIGHT* have 'MyRequestService' injected, but
// it's *NOT GUARANTEED*
// what do I put here to check if 'MyRequestService' was injected into the
// controller that was chosen by MVC?
});
Now, after the call to await Next() in the middleware, I would like to check if MyRequestService was used by the controller that was chosen by MVC. How can I do this?
I cannot use Context.RequestServices.GetRequiredService<MyRequestService>() since that will just instantiate the service if it isn't already instantiated. I need to check if the controller injected it, without inadvertently instantiating it.
Instead of trying to scan the DI container by reflection, I would try to use the HttpContext class. It has properties like Features and Items. Both are able to hold arbitrary data within the scope of the current context. Your service could for example add an entry with a specific key (e.g. a const string) into the Items dictionary with any data needed within the value and in your middleware you check after the return of Next() if the dictionary contains the specified key. If yes, your service was used.
In that case the default mechanism of ASP is used and no reflection is needed.
This is what I would do, it is not tested but I think this concept should work and it is easier to read than reflexion in my opinion:
// Scoped
MyRequestService {
constructor(MyServiceMonitor monitor) {
monitor.AddResolved(this.GetType().Name);
}
}
// Scoped
MyServiceMonitor {
List<string> types;
AddResolved(string type) {
types.Add(type);
}
IsResolved(string name) {
return types.Contains(name);
}
}
// Check in the delegate
context.RequestServices.GetRequiredService<MyServiceMonitor>().IsResolved("MyRequestService");
I used reflection per Jonesopolis's suggestion in the comments, but instead of using reflection on controllers, I'm using it to get at the internal dependency injection object cache. I'm caching the ResolvedServices PropertyInfo using a Lazy<PropertyInfo> object. This works, but I don't like it. Hopefully ASP.NET will have a public accessor for this soon:
app.UseRouting();
var ResolvedServicesPropLoader = new Lazy<PropertyInfo>(delegate ()
{
var ProviderType = Type.GetType("Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope, Microsoft.Extensions.DependencyInjection");
if (ProviderType == null) { throw new Exception("service caching class type could not be found"); }
var TheProperty = ProviderType.GetProperty("ResolvedServices", BindingFlags.NonPublic | BindingFlags.Instance);
return TheProperty ?? throw new Exception("could not find the cached services property using reflection");
});
app.Use(async delegate (HttpContext Context, Func<Task> Next)
{
await Next();
var InjectedServices = (ICollection<object>?)((IDictionary?)ResolvedServicesPropLoader.Value.GetValue(Context.RequestServices))?.Values;
if (InjectedServices == null) { throw new Exception("cached services collection is null"); }
if (InjectedServices.Where(x => x is MyRequestService).Any())
{
Console.WriteLine("service was injected");
}
});

How to initialize scoped dependencies for consumers using MassTransit filters?

I would like to initialize some dependencies resolved from the MassTransit serviceProvider in the same way Asp.Net Core does with the pipeline's middlewares.
In particular I would like to inspect the incoming message before the consumer is called and extract the tenant from it (I'm currently working on a multitenant web application with single database per tenant).
With this informations I need to initialize some scoped instances (Ef Core DbContext for example).
I know that I can inject them in the Consumer through constructor but this means that I must do that everytime I write a new one, so I suppose that a filter should be the right place (correct me if I'm wrong).
The problem raises when I need to access the current consumer scope to resolve the dependencies that I need. I was thinking that the behavior of the MassTransit' pipeline was similar to the Asp.Net one regarding middleware injection but I was probably wrong.
I haven't found any documentation on how to do that clearly without cluttering the code of the filter, so any suggestion is going to be really appreciated.
This is the filter that I need to modify:
public class TenantContextInitializerFilter<T> : IFilter<T> where T : class, ConsumeContext
{
public void Probe(ProbeContext context) { }
public async Task Send(T context, IPipe<T> next)
{
//Resolve scoped instance here and do something before Consumer is called
var connectionStringProvider = scope.GetService<IConnectionStringProvider>();
await next.Send(context);
}
}
public class RegistrationsDeliveredEventConsumer : IConsumer<IRegistrationsDelivered>
{
private readonly IConnectionStringProvider _connectionStringProvider;
public RegistrationsDeliveredEventConsumer(IConnectionStringProvider connectionStringProvider)
{
//This should be the same instance that has been resolved in the filter' Send() method
_connectionStringProvider = connectionStringProvider;
}
public async Task Consume(ConsumeContext<IRegistrationsDelivered> context)
{
}
}
This is a simplified example of my code but this should be enough
There's two facets to consider: 1) are filters registered as services/pulled from the service collection when using the ASP.NET Core integration and 2) what lifetime do the filters have if they are. I'm not familiar with the MassTransit ASP.NET Core integration, but it looks like you should be good based on a cursory review. You'll need to confirm that both of those requirements are met.
For dependency injection, in general, constructor injection is the way to go unless there's a very specific need to do something different, which does not seem to be the case here. In short, you need a constructor for your filter.
What exactly you need to inject is a function of the lifetime of the filter. If it has a transient lifetime, then you can inject your scoped dependencies directly. If it has a singleton lifetime, then you'll need to inject IServiceProvider instead, and do the following whenever you need to use one of those dependencies:
using (var scope = _serviceProvider.CreateScope())
{
var dep = scope.ServiceProvider.GetRequiredService<MyDependency>();
// do something with `dep`
}
Here's a draft... I'm sure there are missing pieces, so let me know if you have questions.
public class TenantContextInitializerFilter<T> : IFilter<T> where T : class, ConsumeContext
{
private readonly Func<string, IDbConnection> _dbContextAccessor;
public void Probe(ProbeContext context) { }
public TenantContextInitializerFilter(Func<string, IDbConnection> dbContextAccessor)
{
_dbContextAccessor = dbContextAccessor;
}
public async Task Send(T context, IPipe<T> next)
{
var tenantId = ""; // place holder
using (var dbContext = _dbContextAccessor(tenantId))
{
//... do db logic
}
await next.Send(context);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IConnectionStringProvider>(
provider => null /* TODO figure out how to fetch scoped instance from a cache or some storage mechanism*/);
services.AddScoped(provider =>
{
IDbConnection Accessor(string tenantId)
{
if (provider.GetService<IConnectionStringProvider>()
.TryGetConnectionString(tenantId, out var connectionString, out var providerName))
return new SqlConnection(connectionString);
throw new Exception();
}
return (Func<string, IDbConnection>)Accessor;
});
}
}

In Autofac, how do you register the Owned<B> relationship when A is a single instance and B is registered using InstancePerRequest?

I am using Autofac version 3.5.2.
I want to register a singleton class A that needs B until some point in the future. How do you do this if B is registered using InstancePerRequest?
Using Owned<B> directly does not work because the per request lifetime scope does not exist. You get the DependencyResolutionException as seen here.
One solution is to make A have a direct dependency on ILifetimeScope. A begins a scope with the MatchingScopeLifetimeTags.RequestLifetimeScopeTag when it needs an instance of B.
using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var b = scope.Resolve<B>();
b.DoSomething();
}
I do not like this approach since it is the service locator pattern. See this fiddle.
A second solution is to register B as InstancePerRequest and in the correct owned scope for B. Registering B would look like this: builder.RegisterType<B>().InstancePerRequest(new TypedService(typeof(B)));
The full example is here.
I like the second solution better but it also has problems:
It feels like a code smell that when registering B’s dependencies that they have to know to register themselves in B’s owned scope.
It does not scale well when B has a lot of dependencies and those dependencies have dependencies etc. All these dependencies need the extra registration as in problem 1 above.
How would you recommend solving this problem? Thanks for the help.
Update
I have moved the OwnedPerRequest<T> related code into this answer below.
This answer is based on Nicholas' comment
and his blog post on IRegistrationSource
It is based on the existing Owned<T> class
and its registration source.
public class OwnedPerRequest<T> : Owned<T>
{
public OwnedPerRequest(T value, IDisposable lifetime) : base(value, lifetime) { }
}
public class OwnedPerRequestInstanceRegistrationSource : IRegistrationSource
{
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
if (service == null)
throw new ArgumentNullException(nameof(service));
if (registrationAccessor == null)
throw new ArgumentNullException(nameof(registrationAccessor));
var swt = service as IServiceWithType;
if (swt == null
|| !(swt.ServiceType.IsGenericType
&& swt.ServiceType.GetGenericTypeDefinition() == typeof(OwnedPerRequest<>)))
return Enumerable.Empty<IComponentRegistration>();
var ownedInstanceType = swt.ServiceType.GetGenericArguments()[0];
var ownedInstanceService = swt.ChangeType(ownedInstanceType);
return registrationAccessor(ownedInstanceService)
.Select(r =>
{
var rb = RegistrationBuilder.ForDelegate(swt.ServiceType, (c, p) =>
{
var lifetime = c.Resolve<ILifetimeScope>().BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag);
try
{
var value = lifetime.ResolveComponent(r, p);
return Activator.CreateInstance(swt.ServiceType, value, lifetime);
}
catch
{
lifetime.Dispose();
throw;
}
});
return rb
.ExternallyOwned()
.As(service)
.Targeting(r)
.CreateRegistration();
});
}
public bool IsAdapterForIndividualComponents => true;
public override string ToString() => "OwnedPerRequestInstanceregistrationSource";
}
I think what you want to do is inject a Func<Owned<B>> which you invoke when you need a B. This removes the service locator pattern and I'm pretty sure is identical to this in functionality to this
using (var scope = this.lifetimeScope.BeginLifetimeScope(MatchingScopeLifetimeTags.RequestLifetimeScopeTag))
{
var b = scope.Resolve<B>();
b.DoSomething();
}
If you inject a Func<Owned<B>> the usage would look like this:
public void DoSomethingThatUsesB()
{
//_bFactory is your Func<Owned<B>>
using(var b = _bFactory.Invoke())
{
... (use b)
}
}
If the lifetime structure of your app is quite simple (i.e. you don't nest further lifetime scopes under "request" that need to resolve the shared B), then you might just switch B to InstancePerLifetimeScope():
builder.RegisterType<B>().InstancePerLifetimeScope();
This will be largely equivalent to per-request, but will also allow the Owned<B> to be successfully resolved by a singleton.
The only caveat is that you have to be careful not to accidentally take a dependency on B from singletons elsewhere, since this will no longer be detected as an error. It's possible to protect against this using a custom IComponentLifetime but probably not worth the effort unless you do it a lot.

Autofac shared objects require different registrations per controller but InstancePerApiControllerType won't work

As detailed in InstancePerApiControllerType not working, I am unable to use the InstancePerApiControllerType to configure my solution. The answer provided there works so long as I am directly injecting a ConnectionContext into the controller, or otherwise know that a class is only used by a specific controller. Unfortunately that is not the case in my situation:
ControllerA -> EngineA -> RepositoryA -> GenericEntityAccessor
ControllerB -> EngineB -> RepositoryB -> GenericEntityAccessor
The issue is when we come in through ControllerA, GenericEntityAccessor needs "string A" and from ControllerB it needs "string B".
Of course, the real situation is a little more complicated and there are some bad practices such as code that directly "news"-up a ConnectionContext (it's legacy code). I'm currently exploring providing another component that provides the connection string that is injected via Autofac and configured in the controller using Lazy, but the bad practices are causing problems there also (i.e. once I start to change things in the interface, all the dominoes start to fall over and I end up 15 classes later wondering how I got there).
Are there any patterns, techniques, etc. that address this type of thing? I can't imagine it's all that uncommon.
UPDATE:
To provide a few more specifics, since I'm having some trouble getting this to work, in general we have the following hierarchy, showing which scopes I've applied
Controller -> InstancePerApiRequest()
I*Repository -> ?
I*Manager -> ?
I*Builder -> ?
I*Adapter -> ?
ISqlServerConnectionContext -> ?
IConnectionContextCache -> InstancePerApiRequest()
I've got a number of components that directly take ISqlServerConntectionContext and I'm trying to provide it like so:
container.Register(c =>
{
var connectionContextCache = c.Resolve<IConnectionContextCache>();
var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;
return connection;
}).As<ISqlServerConnectionContext>().InstancePerDependency();
Unfortunately at that point I'm getting a null for CurrectConnectionContext. My guess at this point is I've got some component that isn't rooted from the controller and I'm currently going through the dependencies manually attempting to find it (AFAIK the isn't a way for my to find out which object triggered Autofac to attempt to provide the ISqlServerConnectionContext when I'm debugging).
UPDATE 2:
It turns out I did have some issues where I was registering things improperly, and creating a dependency on ISqlServerConnectionContext for DocumentController, even though it did not have one (this was created through the delegate for something it did depend on).
Now I've got a circular reference that I'm pretty sure I've created myself in the registrations:
container.Register(x =>
{
if (x.IsRegistered<HttpRequestMessage>())
{
var httpRequestMethod = x.Resolve<HttpRequestMessage>();
var tokenHelper = x.Resolve<ITokenHelper>();
var token = tokenHelper.GetToken(httpRequestMethod);
return token ?? new NullMinimalSecurityToken();
}
return new NullMinimalSecurityToken();
}).As<IMinimalSecurityToken>().InstancePerApiRequest();
container.Register(c =>
{
var connectionContextCache = c.Resolve<IConnectionContextCache>();
var token = c.Resolve<IMinimalSecurityToken>();
var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;
connection.Token = token;
return connection;
}).As<ISqlServerConnectionContext>().InstancePerApiRequest();
The problem is ISqlServerConnectionContext has a property of type IMinimalSecurityToken which is optional, and definitely not used when the ISqlServerConnectionContext is being used to look up IMinimalSecurityToken, which depends on ISqlServerConnectionContext through ITokenHelper.
UPDATE 3:
For completeness, in order to solve my circular reference problem I needed to use named services, and use a SqlServerConnectionContext that did not have the IMinimalSecurityToken property set for the IOAuthTokenManager registration. Now I'm getting the dreaded
No scope with a Tag matching 'AutofacWebRequest' is visible
error, but I think that warrants a new question if I'm not able to solve it.
container.Register(c =>
{
var productId = WellKnownIdentifierFactory.Instance.GetWellKnownProductIdentifier(WellKnownProductIdentifiers.RESTSearchService);
var connectionString = ConfigurationManager.AppSettings[AppSettingsNames.DatabaseConnection];
var newConnectionContext = new SqlServerConnectionContext(connectionString) { ProductID = productId };
newConnectionContext.Open();
return newConnectionContext;
}).Named<ISqlServerConnectionContext>("OAuthTokenConnectionContext").InstancePerApiRequest();
container.Register(c => new SqlServerBuilderFactory(c.ResolveNamed<ISqlServerConnectionContext>("OAuthTokenConnectionContext"))).Named<IBuilderFactory>("OAuthTokenBuilderFactory").InstancePerApiRequest();
container.Register(c =>new OAuthTokenManager(c.ResolveNamed<IBuilderFactory>("OAuthTokenBuilderFactory"))).As<IOAuthTokenManager>().InstancePerApiRequest();
This can be solved using AutoFac's support for object graph lifetime scoping.
Cache the current SqlServerConnectionContext in an object scoped to the lifetime of your controller.
Within the SqlServerConnectionContext factory type, once the connection is created assign it to the backing field of the current lifetime-scoped cache
Any types scoped within the lifetimes scope of a controller can then access the connection associated with that controller through the cache
The only complexities I can think of are:
If the controller is not actually the root of a lifetime scope for all types with a dependency on a specific connection. I.e. if they fall outside the lifetime of the controller.
If any of the dependencies are registered as single instance. In which case they will not be able to resolve the Cache as it is currently implemented as it is PerApiRequest.
For example:
public interface ISqlServerConnectionContextCache
{
ISqlServerConnectionContext CurrentContext { get; set; }
}
public class SqlServerConnectionContextScopeCache : ISqlServerConnectionContextCache
{
public ISqlServerConnectionContext CurrentContext { get; set; }
}
public interface ISqlServerConnectionContextFactory
{
ISqlServerConnectionContext Create();
}
// The factory has the cache as a dependancy
// This will be the first use of the cache and hence
// AutoFac will create a new one at the scope of the controller
public class SqlServerConnectionContextFactory : ISqlServerConnectionContextFactory
{
private string _connectionString;
private ISqlServerConnectionContextCache _connectionCache;
public SqlServerConnectionContextFactory(ISqlServerConnectionContextCache connectionCache,
string connectionString)
{
_connectionCache = connectionCache;
_connectionString = connectionString;
}
public ISqlServerConnectionContext Create()
{
var connectionContext = new SqlServerConnectionContext(_connectionString);
connectionContext.Open();
_sqlServerConnectionContextProvider.CurrentContext = connectionContext;
return connectionContext;
}
}
public class MyController : ApiController
{
private ISqlServerConnectionContext _sqlServerConnectionContext;
public MyController(Func<string, ISqlServerConnectionContextFactory> connectionFactory)
{
_sqlServerConnectionContext = connectionFactory("MyConnectionString");
}
}
// As the cache is lifetime scoped it will receive the single instance
// of the cache associated with the current lifetime scope
// Assuming we are within the scope of the controller this will receive
// the cache that was initiated by the factory
public class MyTypeScopedByController
{
public MyTypeScopedByController(ISqlServerConnectionContextCache connectionCache)
{
var sqlServerConnectionContext = connectionCache.CurrentContext;
}
}
// AutoFac wiring
builder.RegisterType<SqlServerConnectionContextScopeCache>()
.As<ISqlServerConnectionContextCache>()
.InstancePerApiRequest();
builder.RegisterType<SqlServerConnectionContextFactory>()
.As<ISqlServerConnectionContextFactory>()
.InstancePerDependency();

On how I access my DataContext (and whether it's wrong)

I use the following, static class to access the data context in my application
public static class DataContext
{
internal const string _contextDataKey = "dataContext";
/// <summary>
/// Returns a unique data context that lives for the duration of the request, which can be from ASP.NET or a WCF service
/// </summary>
/// <returns>The entity data model context for the current request</returns>
public static EntityDataModelContext GetDataContext()
{
IPersistanceContainer state;
if (HttpContext.Current != null)
{
state = new AspNetPersistanceContainer();
}
else if (OperationContext.Current != null)
{
state = new WcfPersistanceContainer();
}
else
{
state = new StaticPersistanceContainer(); // this container is thread-unsafe.
}
EntityDataModelContext edm = state.Get<EntityDataModelContext>(_contextDataKey);
if (edm == null)
{
edm = new EntityDataModelContext();
state.Store(_contextDataKey, edm);
}
return edm;
}
}
Forget about the other containers, which are for WCF and Console application simple-tests respectively, here's the ASP.NET container:
internal class AspNetPersistanceContainer : IPersistanceContainer
{
public T Get<T>(string key) where T : class
{
if (HttpContext.Current.Items.Contains(key))
return (T)HttpContext.Current.Items[key];
return null;
}
public void Store(string key, object value)
{
HttpContext.Current.Items[key] = value;
}
}
When I need to access the context I just invoke DataContext.GetDataContext() and do my DB-accessing, I never add any using statements.
If I add a using statement, the context is good for one use, and the next time I try to use it, it's disposed of. Raising an exception.
If I don't, like right now, it makes me kind of unhappy, I feel like it's not the right thing to do either, not disposing of it.
So I was wondering what would be the correct thing to do here.
Is this design flawed, and should I abandon it altogether?
Should I just figure out a way to re-create the context whenever it's disposed of?
Should I just leave the design as is, and that's fine?
Maybe the design is "fine enough", are there any books that you'd recommend I read on the subject? I feel like my skills on back-end architecture are rather on the lacking side.
In an asp.net application one solution can be like this :
Create your context when a request begins
Dispose it when the request ends
Here's an article that discuss this approach (for NHibernate session management but it's almost the same for EF )

Categories