Sitecore Microsoft Dependency Injection error "Service has been disposed, cannot create object" - dependency-injection

I am using Sitecore 8.2 Update 4 with Helix framework also using Microsoft Extension Dependency Injection. I have performed few steps for DI:
1. Created DI project in Foundation layer.
2. I have created on pipeline with name Habitat.Foundation.DI.RegisterControllers inside
App_config/Include/zFoundation
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"><sitecore> <services> <configurator type=" Habitat.Foundation.DI.RegisterControllers, Habitat.Foundation.DI" /></services></sitecore></configuration>
namespace Habitat.Foundation.DI{ public class RegisterControllers : IServicesConfigurator
{
public void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddMvcControllers("*.Feature.*");
}
}}
namespace Habitat.Foundation.DI.Extensions{
public static class ServiceCollectionExtensions
{
public static void AddMvcControllers(this IServiceCollection serviceCollection, params string[] assemblyFilters)
{
var assemblies = GetAssemblies.GetByFilter(assemblyFilters);
AddMvcControllers(serviceCollection, assemblies);
}
public static void AddMvcControllers(this IServiceCollection serviceCollection, params Assembly[] assemblies)
{
var controllers = GetTypes.GetTypesImplementing<IController>(assemblies)
.Where(controller => controller.Name.EndsWith("Controller", StringComparison.Ordinal));
foreach (var controller in controllers)
serviceCollection.AddTransient(controller);
controllers = GetTypes.GetTypesImplementing<ApiController>(assemblies)
.Where(controller => controller.Name.EndsWith("Controller", StringComparison.Ordinal));
foreach (var controller in controllers)
serviceCollection.AddTransient(controller);
}
}}
public static class GetAssemblies
{
public static Assembly[] GetByFilter(params string[] assemblyFilters)
{
var assemblyNames = new HashSet<string>(assemblyFilters.Where(filter => !filter.Contains('*')));
var wildcardNames = assemblyFilters.Where(filter => filter.Contains('*')).ToArray();
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(assembly =>
{
var nameToMatch = assembly.GetName().Name;
if (assemblyNames.Contains(nameToMatch)) return true;
return wildcardNames.Any(wildcard => IsWildcardMatch(nameToMatch, wildcard));
})
.ToArray();
return assemblies;
}
/// <summary>
/// Checks if a string matches a wildcard argument (using regex)
/// </summary>
private static bool IsWildcardMatch(string input, string wildcards)
{
return Regex.IsMatch(input, "^" + Regex.Escape(wildcards).Replace("\\*", ".*").Replace("\\?", ".") + "$",
RegexOptions.IgnoreCase);
}
}public static class GetTypes {
public static Type[] GetTypesImplementing<T>(params Assembly[] assemblies)
{
if (assemblies == null || assemblies.Length == 0)
return new Type[0];
var targetType = typeof(T);
return assemblies
.Where(assembly => !assembly.IsDynamic)
.SelectMany(GetExportedTypes)
.Where(type => !type.IsAbstract && !type.IsGenericTypeDefinition && targetType.IsAssignableFrom(type))
.ToArray();
}
private static IEnumerable<Type> GetExportedTypes(Assembly assembly)
{
try
{
return assembly.GetExportedTypes();
}
catch (NotSupportedException)
{
// A type load exception would typically happen on an Anonymously Hosted DynamicMethods
// Assembly and it would be safe to skip this exception.
return Type.EmptyTypes;
}
catch (ReflectionTypeLoadException ex)
{
// Return the types that could be loaded. Types can contain null values.
return ex.Types.Where(type => type != null);
}
catch (Exception ex)
{
// Throw a more descriptive message containing the name of the assembly.
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
"Unable to load types from assembly {0}. {1}", assembly.FullName, ex.Message), ex);
}
}
}
As I am using Glass Mapper so I want to use ISitecoreContext, for that I have register ISitecoreContext in Foundation/ORM for that I have performed these actions:
1. Created patch file inside Foundation/ORM
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"><sitecore><services>
<configurator type="Habitat.Foundation.ORM.DI.RegisterContainer, Habitat.Foundation.ORM" />
</services> </sitecore>
public class RegisterContainer : IServicesConfigurator
{
public void Configure(IServiceCollection serviceCollection)
{
serviceCollection.AddTransient<ISitecoreContext, SitecoreContext>();
}
}
Accesssing in controller like this
private readonly ISitecoreContext _sitecoreContext;
public TestController(ISitecoreContext sitecoreContext)
{
_sitecoreContext = sitecoreContext;
}
I accessing in two classes one way is _sitecoreContext.GetItem(Context.Site.ContentStartPath) and another way is _sitecoreContext.GetRootItem() then it started throwing error “{"Service has been disposed, cannot create object"}” in _sitecoreContext.GetItem(Context.Site.ContentStartPath) but working for
_sitecoreContext.GetRootItem()
When I update AddTransient to AddSingleton then it works for some time for both but for after some time it started giving me null value of _sitecoreContext.
Please help me.

Related

Some services are not able to be constructed using the library : NetCore.AutoRegisterDi

I am developing asp.net core 3.1 GraphQL based APIs. I used the below reference article to setup automatic DI configuration in the API layer and have used the nuget package : NetCore.AutoRegisterDi
https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/
Here goes the code details:
Code:
Startup.cs:
public virtual void ConfigureServices(IServiceCollection services) => services
.AddGraphQLResolvers()
.AddProjectRepositories();
ProjectServiceCollectionExtensions.cs
public static class ProjectServiceCollectionExtensions
{
public static IServiceCollection AddProjectRepositories(this IServiceCollection services) =>
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
.Where(c => c.Name.EndsWith("Persistence"))
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
public static IServiceCollection AddGraphQLResolvers(this IServiceCollection services) =>
services
.AddScoped<ICountriesResolver, CountriesResolver>()
.AddScoped<ICountryGroupsResolver, CountryGroupsResolver>()
.AddScoped<IDisclaimerResolver, DisclaimerResolver>();
}
Here in the above CommonService is part of the Service Layer that ends with Persistence.
CountriesResolver.cs
public class CountriesResolver : Resolver, ICountriesResolver
{
private readonly ICountryService _countryService;
private readonly IHttpContextAccessor _accessor;
private readonly IDataLoaderContextAccessor _dataLoaderContextAccessor;
public CountriesResolver(ICountryService countryService, IHttpContextAccessor accessor, IDataLoaderContextAccessor dataLoaderContextAccessor)
{
_countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
_accessor = accessor;
_dataLoaderContextAccessor = dataLoaderContextAccessor;
}
public void Resolve(GraphQLQuery graphQLQuery)
{
var language = _accessor.HttpContext.Items["language"] as LanguageDTO;
graphQLQuery.FieldAsync<ResponseGraphType<CountryResultType>>("countriesresponse", arguments: new QueryArguments(new QueryArgument<IdGraphType>{Name = "pageNo", Description = "page number"}, new QueryArgument<IdGraphType>{Name = "pageSize", Description = "page size"}), resolve: async context =>
{
var pageNo = context.GetArgument<int>("pageNo") == 0 ? 1 : context.GetArgument<int>("pageNo");
var pageSize = context.GetArgument<int>("pageSize") == 0 ? 100 : context.GetArgument<int>("pageSize");
if (language != null)
{
var loader = _dataLoaderContextAccessor.Context.GetOrAddLoader("GetAllCountries", () => _countryService.GetAllCountriesAsync(language, pageNo, pageSize));
var list = await context.TryAsyncResolve(async c => await loader.LoadAsync());
return Response(list);
}
return null;
}
, description: "All Countries data");
}
}
ICommonService.cs
using Author.Query.Persistence.DTO;
using System.Threading.Tasks;
namespace Author.Query.Persistence.Interfaces
{
public interface ICommonService
{
LanguageDTO GetLanguageFromLocale(string locale);
Task<LanguageDTO> GetLanguageFromLocaleAsync(string locale);
}
}
CommonService.cs
namespace Author.Query.Persistence
{
public class CommonService : ICommonService
{
private readonly AppDbContext _dbContext;
private readonly IOptions<AppSettings> _appSettings;
private readonly IMapper _mapper;
private readonly ICacheService<Languages, LanguageDTO> _cacheService;
public CommonService(AppDbContext dbContext, IOptions<AppSettings> appSettings, IMapper mapper, ICacheService<Languages, LanguageDTO> cacheService)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_appSettings = appSettings;
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
}
//public Languages GetLanguageFromLocale(string locale)
public LanguageDTO GetLanguageFromLocale(string locale)
{
return GetLanguagesByFilter(GetFilterValues(locale, true).ToArray());
}
}
}
ICountryService.cs
namespace Author.Query.Persistence.Interfaces
{
public interface ICountryService
{
Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize);
Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId);
}
}
CountryService.cs
namespace Author.Query.Persistence
{
public class CountryService : ICountryService
{
private readonly AppDbContext _dbContext;
private readonly IOptions<AppSettings> _appSettings;
private readonly ICacheService<Images, ImageDTO> _cacheService;
public CountryService(TaxathandDbContext dbContext, IOptions<AppSettings> appSettings, ICacheService<Images, ImageDTO> cacheService)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
_appSettings = appSettings;
}
public async Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize)
{
var localeLangId = language.LanguageId;
var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
// By default pick the localLanguage value
var countries = await GetAllCountriesDataAsync(localeLangId, pageNo, pageSize);
// If localLanguage data is not available then pull the data based on default language
if (countries.Countries.Count == 0)
{
countries = await GetAllCountriesDataAsync(dftLanguageId, pageNo, pageSize);
}
return countries;
}
public async Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId)
{
var localeLangId = language.LanguageId;
var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
//var country = new CountryDTO();
// By default pick the localLanguage value
var country = await GetCountryDetailsAsync(countryId, localeLangId);
// If localLanguage data is not available then pull the data based on default language
if (country == null)
{
country = await GetCountryDetailsAsync(countryId, dftLanguageId);
}
return country;
}
private async Task<CountryDTO> GetCountryDetailsAsync(int countryId, int languageId)
{
var images = await _cacheService.GetAllAsync("imagesCacheKey");
var country = await _dbContext.Countries.AsNoTracking().FirstOrDefaultAsync(c => c.CountryId.Equals(countryId) && c.IsPublished.Equals(true) && c.LanguageId.Equals(languageId));
if (country == null)
{
return null;
}
var countryDTO = new CountryDTO{Uuid = country.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.SVGImageId)).FilePath, DisplayName = country.DisplayName, DisplayNameShort = country.DisplayName, Name = Helper.ReplaceChars(country.DisplayName), Path = Helper.ReplaceChars(country.DisplayName), CompleteResponse = true};
return countryDTO;
}
private async Task<CountryResult> GetAllCountriesDataAsync(int languageId, int pageNo, int pageSize)
{
var countryList = new CountryResult();
var images = await _cacheService.GetAllAsync("imagesCacheKey");
var countries = await _dbContext.Countries.Where(cc => cc.IsPublished.Equals(true) && cc.LanguageId.Equals(languageId)).Select(c => new
{
c.CountryId, c.DisplayName, c.PNGImageId, c.SVGImageId
}
).OrderByDescending(c => c.CountryId).Skip((pageNo - 1) * pageSize).Take(pageSize).AsNoTracking().ToListAsync();
if (countries.Count == 0)
{
return null;
}
countryList.Countries.AddRange(countries.Select(co => new CountryDTO{Uuid = co.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.SVGImageId)).FilePath, DisplayName = co.DisplayName, DisplayNameShort = co.DisplayName, Name = Helper.ReplaceChars(co.DisplayName), Path = Helper.ReplaceChars(co.DisplayName), CompleteResponse = true}));
return countryList;
}
}
}
Error:
System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountriesResolver':
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.) (Error while validating the service descriptor 'ServiceType:
Author.Query.New.API.GraphQL.Resolvers.ICountryGroupsResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryGroupService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver'.) (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.IDisclaimerResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.IDisclaimerService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at Author.Query.New.API.Program.Main(String[] args) in /src/QueryStack/Author.Query.New.API/Program.cs:line 15
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType:
Author.Query.New.API.GraphQL.Resolvers.CountriesResolver':
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.
Inner Exception 2:
InvalidOperationException: Unable to resolve service for type
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.
Can anyone help me to know how to fix this issue?
The error says that :
Unable to resolve service for type
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'
ICountryService is never registered. AddGraphQLResolvers registers only ICountriesResolver, ICountryGroupsResolver and IDisclaimerResolver.
The method AddProjectRepositories only registers classes whose name ends in Persistence that appear in the same namespace as CommonService. It never registers CommonService itself.
The service must be registered, eg with :
services.AddScoped<ICommonService, CommonService>();
Your method AddProjectRepositories() does not register CountryService, because the Where filter in RegisterAssemblyPublicNonGenericClasses() looks at classes (Type), not at namespaces.
So perhaps you can change your filter:
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
.Where(c => c.Name.EndsWith("Service")) // <-- Change "Persistence" to "Service"
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
That should register all classes that end with "Service" in the Assembly that contains CommonService.
For me it was a simple mistake. I had a class with the same name in another namespace and referenced the wrong class/forgot to delete the duplicate class and add the correct one to my startup.
I had a service called UserService in myapp.Utils and another in myapp.Services. I was referencing myapp.Utils when I meant to delete that one and only use myapp.Services. I was incorrectly injecting the one in myapp.Utils when my controllers were set up to use the one in myapp.Services.

Unity.MVC ASP.Net Controller Constructor not Called

I'm attempting to use the latest version on Unity.Mvc but for some reason my controller never gets called. I have the following controller
public class AdminApiController : ApiController
{
private IUserRepository userRepository;
public AdminApiController(IUserRepository userRepository)
{
this.userRepository = userRepository;
}
[Route("api/AdminApi/{lastName}/{currentUserId}")]
public IEnumerable<UserDetail> GetUsers(string lastName, string currentUserId)
{
return userRepository.Search(lastName, currentUserId)
.Select(u => new UserDetail { Id = u.Id, Name = u.FirstName + " " + u.LastName });
}
}
and the latest version of Unity.Mvc appears to use a UnityWebActivator class to set up the container, i.e.,
public static class UnityWebActivator
{
/// <summary>Integrates Unity when the application starts.</summary>
public static void Start()
{
var container = UnityConfig.GetConfiguredContainer();
FilterProviders.Providers.Remove(FilterProviders.Providers.OfType<FilterAttributeFilterProvider>().First());
FilterProviders.Providers.Add(new UnityFilterAttributeFilterProvider(container));
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
// TODO: Uncomment if you want to use PerRequestLifetimeManager
// Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
}
/// <summary>Disposes the Unity container when the application is shut down.</summary>
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
and in my unity config I have the following
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
/// <summary>
/// Gets the configured Unity container.
/// </summary>
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
/// <summary>Registers the type mappings with the Unity container.</summary>
/// <param name="container">The unity container to configure.</param>
/// <remarks>There is no need to register concrete types such as controllers or API controllers (unless you want to
/// change the defaults), as Unity allows resolving a concrete type even if it was not previously registered.</remarks>
public static void RegisterTypes(IUnityContainer container)
{
// NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
// container.LoadConfiguration();
// TODO: Register your types here
// container.RegisterType<IProductRepository, ProductRepository>();
container.RegisterType<IUserDatabase, UserDatabase>();
container.RegisterType<IUserRepository, UserRepository>();
}
}
which registers my in-memory repo, i.e.,
public class UserRepository : IUserRepository
{
IUserDatabase db;
public UserRepository(IUserDatabase db)
{
this.db = db;
}
public IEnumerable<User> Search(string lastName, string currentUserId)
{
if (Exists(currentUserId))
{
return GetByLastName(lastName);
}
return new List<User>();
}
private IEnumerable<User> GetByLastName(string lastName)
{
return db.Users.Where(u => u.LastName == lastName);
}
private bool Exists(string id)
{
return db.Users.Any(u => u.Id == id);
}
}
where
public class UserDatabase : IUserDatabase
{
private User[] users = new User[]
{
new User("1", "John", "Smith"),
new User("2", "Fred", "Smith"),
new User("3", "John", "Doe")
};
public IEnumerable<User> Users
{
get
{
return users;
}
}
}
When I start the application I can see that my types are being registered using breakpoints, but when I put a breakpoint in the constructor of AdminApiController it never gets called. Subsequently the controller is never created and I get an Internal Server error when I try to use the controller.
What am I missing with the setup to ensure AdminApiController is instantiated and being injected with objects from the container?

With Ninject, how do you specify different lifetimes depending on the binding?

In our MVC application we predominantly use Ninject to inject dependencies into controllers. As such, our default lifetime scope is InRequestScope(). We have now added an IHttpModule that uses common dependencies as the controllers (i.e., UserService). The problem is that HttpModules can be pooled by ASP.NET and IIS and reused over multiple requests. Because the injected dependencies are set to InRequestScope, subsequent requests will often get an ObjectDisposedException when referencing the injected dependencies inside the HttpModule. How can I specify InRequestScope() for the UserService when injecting into a controller and InScope() when injecting into an HttpModule.
Here is a simplified version of our registration:
public static class NinjectWebCommon
{
private static readonly Bootstrapper bootstrapper = new Bootstrapper();
public static void Start(){
DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
bootstrapper.Initialize(CreateKernel);
}
public static void Stop(){
bootstrapper.ShutDown();
}
private static IKernel CreateKernel(){
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
kernel.Bind<IHttpModule>().To<CustomModule>(); // needs a UserService
kernel.Bind<IUserService>().To<UserService>().InRequestScope(); // injected into controllers and the CustomModule
DependencyResolver.SetResolver(new Services.NinjectDependencyResolver(kernel));
return kernel;
}
}
Check out .When() on the binding syntax. You can use it to specify a specific scope for a service under specific circumstances. here's an example:
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
var scope1 = new object();
var scope2 = new object();
kernel.Bind<IWidget>().To<WidgetA>().InScope(c => scope1);
kernel.Bind<IWidget>().To<WidgetA>().WhenInjectedInto<WidgetController>().InScope(c => scope2);
var service = kernel.Get<GeneralWidgetService>();
service.Print();
service = kernel.Get<GeneralWidgetService>();
service.Print();
var controller = kernel.Get<WidgetController>();
controller.Print();
controller = kernel.Get<WidgetController>();
controller.Print();
// when scope2 changes, WidgetController gets a new widget, while WidgetService continues getting the existing widget
scope2 = new object();
service = kernel.Get<GeneralWidgetService>();
service.Print();
controller = kernel.Get<WidgetController>();
controller.Print();
}
}
public class WidgetController
{
private readonly IWidget _widget;
public WidgetController(IWidget widget)
{
_widget = widget;
}
public void Print()
{
Console.WriteLine("WidgetController ID: " + _widget.ID);
}
}
public class GeneralWidgetService
{
private readonly IWidget _widget;
public GeneralWidgetService(IWidget widget)
{
_widget = widget;
}
public void Print()
{
Console.WriteLine("GeneralWidgetService ID: " + _widget.ID);
}
}
public interface IWidget
{
string ID { get; }
string Name { get; }
}
public class WidgetA : IWidget
{
private readonly string _id = Guid.NewGuid().ToString();
public string ID { get { return _id; } }
public string Name { get { return "AAAAAAAAAAAAAAAAA"; } }
}
Output:
GeneralWidgetService ID: 09f61af7-c70d-45fe-834a-6cc94e1e3c40
GeneralWidgetService ID: 09f61af7-c70d-45fe-834a-6cc94e1e3c40
WidgetController ID: 2c2bf05f-d251-41be-b9e0-224f02839ead
WidgetController ID: 2c2bf05f-d251-41be-b9e0-224f02839ead
GeneralWidgetService ID: 09f61af7-c70d-45fe-834a-6cc94e1e3c40
WidgetController ID: 519a2930-5b71-4cbb-b84e-a1d712ec5398

Web API, Light Inject and Passing a Static Dictionary to the data layer

We have a multi-database solution and are passing the connection string to a factory function like so:
container.Register<IDbContextFactory>(
f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
new PerScopeLifetime());
ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).
public class ClientConfig
{
private static ConcurrentDictionary<string, string> ConnectionStringManager
{
get;
set;
}
// etc.
}
My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.
Edit : The question was initially tagged with Autofac
With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :
public class ConnectionStringParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
valueProvider = null;
if (pi.ParameterType == typeof(String)
&& String.Equals(pi.Name, "connectionString",
StringComparison.OrdinalIgnoreCase))
{
valueProvider = () =>
{
// get connectionstring based on HttpContext.Current.Request.Url.Host
return String.Empty;
};
}
return valueProvider != null;
}
}
Then register your Parameter using a Module
public class ConnectionStringModule : Autofac.Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += registration_Preparing;
}
private void registration_Preparing(Object sender, PreparingEventArgs e)
{
Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() };
e.Parameters = e.Parameters.Concat(parameters);
}
}
Module you have to register inside your container using
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.
By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.
public interface IConnectionstringProvider
{
String ConnectionString { get; }
}
public class ConnectionStringProvider : IConnectionstringProvider
{
public ConnectionStringProvider(Strong host)
{
// get connectionstring based on host
this._connectionString = String.Empty;
}
private readonly String _connectionString;
public String ConnectionString
{
get { return this._connectionString; }
}
}
Inside your Parameter you will have to change the valueProvider by
valueProvider = () =>
{
return context.Resolve<IConnectionstringProvider>().ConnectionString;
};
And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
}
private static void container_ChildLifetimeScopeBeginning(
Object sender, LifetimeScopeBeginningEventArgs e)
{
String host = HttpContext.Current.Request.Url.Host;
ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
.As<IConnectionstringProvider>()
.SingleInstance();
childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
}
}
Of course there is many way to do it but you have the idea

Autofac and injection of instances

In a ASP.NET MVC project I'm working on I have the following piece of code that basically inject instances to specific methods within my assemblies.
So in the application root I have a class that register the instances like this and finally handles the injection.
ApplicationServiceProvider serviceProvider = ApplicationServiceProvider.CreateDefaultProvider();
serviceProvider.RegisterInstance(GlobalConfiguration.Configuration);
serviceProvider.RegisterInstance(GlobalFilters.Filters);
serviceProvider.RegisterInstance(RouteTable.Routes);
serviceProvider.RegisterInstance(BundleTable.Bundles);
serviceProvider.Distribute();
Now when I want to access these instances from the assemblies, I have to create some handler (method) and mark it with the following attribute 'ApplicationServiceHandler' like in the following example.
[ContractVerification(false)]
public static class RouteConfiguration
{
[ApplicationServiceHandler]
public static void Register(RouteCollection routes)
{
}
}
This is part of the extensibility layer in the project which is currently working pretty good.
Now, I'm new to Autofac and I wonder whether I can use Autofac to do the work for me rather than using my own implementation (which I provided below) that probably does it less efficient and handles less cases that are already covered by Autofac.
I noticed Autofac have a RegisterInstance method but I'm not sure how to tell it to inject the instances to methods flagged with 'ApplicationServiceHandler' attribute, I'm not not sure it's even the correct method but based on the name it seems like the right one.
Any kind of help is greatly appreciated, thank you.
EDIT: Here is the code that I'm using to achieve this without Autofac in my project.
ApplicationServiceHandlerAttribute.cs
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ApplicationServiceHandlerAttribute : Attribute
{
}
ApplicationServiceHandler.cs
public sealed class ApplicationServiceHandler
{
private readonly MethodInfo _method;
private readonly object[] _args;
public ApplicationServiceHandler(MethodInfo method, object[] args)
{
Contract.Requires(method != null);
Contract.Requires(args != null);
_method = method;
_args = args;
}
public void Invoke()
{
_method.Invoke(null, _args);
}
[ContractInvariantMethod]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void ObjectInvariant()
{
Contract.Invariant(_method != null);
Contract.Invariant(_args != null);
}
}
ApplicationServiceProvider.cs
public sealed class ApplicationServiceProvider
{
private readonly IEnumerable<Assembly> _assemblies;
private readonly Dictionary<Type, object> _instances;
public ApplicationServiceProvider(IEnumerable<Assembly> assemblies)
{
Contract.Requires(assemblies != null);
_assemblies = assemblies;
_instances = new Dictionary<Type, object>();
}
public static ApplicationServiceProvider CreateDefaultProvider()
{
Contract.Ensures(Contract.Result<ApplicationServiceProvider>() != null);
return new ApplicationServiceProvider(PackageLoader.ReferencedAssemblies);
}
public void Distribute()
{
foreach (var handler in GetHandlers())
{
Contract.Assume(handler != null);
handler.Invoke();
}
}
public IEnumerable<ApplicationServiceHandler> GetHandlers()
{
Contract.Ensures(Contract.Result<IEnumerable<ApplicationServiceHandler>>() != null);
if (_instances.Count == 0)
{
yield break;
}
foreach (var asm in _assemblies)
{
IEnumerable<MethodInfo> methods = GetMethods(asm);
foreach (var method in methods)
{
ParameterInfo[] #params = method.GetParameters();
if (#params.Length > 0)
{
int instanceCount = 0;
object[] args = new object[#params.Length];
for (int i = 0; i < #params.Length; i++)
{
ParameterInfo param = #params[i];
var instance = GetInstance(param);
if (instance != null)
{
instanceCount++;
args[i] = instance;
}
}
if (instanceCount > 0)
{
yield return new ApplicationServiceHandler(method, args);
}
}
}
}
}
public bool RegisterInstance(object instance)
{
Contract.Requires(instance != null);
return AddInstance(instance);
}
private static ApplicationServiceHandlerAttribute GetApplicationServiceHandlerAttribute(MethodInfo method)
{
ApplicationServiceHandlerAttribute attribute = null;
try
{
attribute = method.GetCustomAttribute<ApplicationServiceHandlerAttribute>(false);
}
catch (TypeLoadException)
{
// We don't need to do anything here for now.
}
return attribute;
}
private static IEnumerable<Type> GetDefinedTypes(Assembly assembly)
{
Contract.Requires(assembly != null);
Contract.Ensures(Contract.Result<IEnumerable<Type>>() != null);
try
{
return assembly.DefinedTypes;
}
catch (ReflectionTypeLoadException ex)
{
return ex.Types.Where(type => type != null);
}
}
/// <summary>
/// Gets the methods that are marked with <see cref="ApplicationServiceHandlerAttribute"/> from the assembly.
/// </summary>
/// <remarks>
/// Eyal Shilony, 21/11/2012.
/// </remarks>
/// <param name="assembly">
/// The assembly.
/// </param>
/// <returns>
/// The methods that are marked with <see cref="ApplicationServiceHandlerAttribute"/> from the assembly.
/// </returns>
private static IEnumerable<MethodInfo> GetMethods(Assembly assembly)
{
Contract.Requires(assembly != null);
Contract.Ensures(Contract.Result<IEnumerable<MethodInfo>>() != null);
const TypeAttributes STATIC_TYPE_ATTRIBUTES = TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit;
var methods = (from type in GetDefinedTypes(assembly)
where type.Attributes == STATIC_TYPE_ATTRIBUTES
from method in type.GetMethods().AsParallel()
where GetApplicationServiceHandlerAttribute(method) != null
select method).ToArray();
return methods;
}
private bool AddInstance(object instance)
{
Type type = instance.GetType();
return AddInstance(type, instance);
}
private bool AddInstance(Type type, object instance)
{
if (!_instances.ContainsKey(type))
{
_instances.Add(type, instance);
return true;
}
return false;
}
private object GetInstance(ParameterInfo param)
{
object instance = null;
Type paramType = param.ParameterType;
if (_instances.ContainsKey(paramType))
{
instance = _instances[paramType];
}
else
{
foreach (var type in _instances.Keys.Where(type => type.IsSubclassOf(paramType)))
{
instance = _instances[type];
break;
}
}
return instance;
}
}
i hope , i have understood you correctly.if what you mean is marking a class as dependency with attributes then you can do it by creating custom attribute.following is an example of implementing such an attribute :
public class DependencyAttribute : Attribute
{
public DependencyAttribute()
{
}
//The type of service the attributed class represents
public Type ServiceType { get; set; }
//Optional key to associate with the service
public string Key { get; set; }
public virtual void RegisterService(AttributeInfo<DependencyAttribute> attributeInfo, IContainer container)
{
Type serviceType = attributeInfo.Attribute.ServiceType ?? attributeInfo.DecoratedType;
Containerbuilder builder = new ContainerBuilder();
builder.RegisterType(attributeInfo.DecoratedType).As(serviceType).Keyed(
attributeInfo.Attribute.Key ?? attributeInfo.DecoratedType.FullName);
builder.Update(container)
}
}
then you must find all types marked with this attribute and call the RegisterService method of these attributes.
public class DependencyAttributeRegistrator
{
public DependencyAttributeRegistrator()
{
}
public IEnumerable<AttributeInfo<DependencyAttribute>> FindServices()
{
//replace this line with you'r own
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (Type type in types)
{
var attributes = type.GetCustomAttributes(typeof(DependencyAttribute), false);
foreach (DependencyAttribute attribute in attributes)
{
yield return new AttributeInfo<DependencyAttribute> { Attribute = attribute, DecoratedType = type };
}
}
}
public void RegisterServices(IEnumerable<AttributeInfo<DependencyAttribute>> services)
{
foreach (var info in services)
{
//replace following two line with you'r own global container
var builder = new ContainerBuilder();
IContainer container = builder.Build();
info.Attribute.RegisterService(info, container);
}
}
}

Resources