asp.net-mvc-3 - unity - web api inversion of control
MVC, EF-instancia singleton de DataContext solicitud por web en Unity (9)
Tengo una aplicación web MVC 3, donde estoy usando Entity Framework para el acceso a los datos. Además, hice un uso simple del patrón de repositorio, donde, por ejemplo, todas las cosas relacionadas con el producto se manejan en el "ProductRepository" y todas las cosas relacionadas con el usuario se manejan en el "UserRepository".
Por lo tanto, estoy usando el contenedor UNITY, para hacer una instancia singleton del DataContext, que inyecto en cada uno de los repositorios. Una búsqueda rápida en Google, y todos le recomiendan que NO use una instancia singleton del DataContext, ya que podría ocasionarle pérdidas de memoria en el futuro.
Entonces, inspirado por este post, la respuesta es hacer una instancia singleton del DataContext para cada solicitud web (¡corríjanme si me equivoco!)
Sin embargo, UNITY no admite el administrador de por vida "solicitud por web". Sin embargo, es posible implementar su propio administrador de por vida personalizado, que maneja esto por usted. En realidad, esto se discute en esta publicación:
Contexto individual por llamada (solicitud web) en Unity
La pregunta es: ahora he implementado el administrador de por vida personalizado como se describe en la publicación anterior, pero no estoy seguro si esta es la manera de hacerlo. También me pregunto dónde está dispuesta la instancia del contexto de datos en la solución provista. ¿Me estoy perdiendo algo?
¿Hay realmente una mejor manera de resolver mi "problema"?
¡Gracias!
** Información adicional sobre mi implementación **
Lo siguiente son fragmentos de mi Global.asax, Controller y Repository. Esto da una imagen clara de mi implementación.
Global.asax
var container = new UnityContainer();
container
.RegisterType<ProductsRepository>(new ContainerControlledLifetimeManager())
.RegisterType<CategoryRepository>(new ContainerControlledLifetimeManager())
.RegisterType<MyEntities>(new PerResolveLifetimeManager(), dbConnectionString)
Controlador
private ProductsRepository _productsRepository;
private CategoryRepository _categoryRepository;
public ProductsController(ProductsRepository productsRepository, CategoryRepository categoryRepository)
{
_productsRepository = productsRepository;
_categoryRepository = categoryRepository;
}
public ActionResult Index()
{
ProductCategory category = _categoryRepository.GetProductCategory(categoryId);
.
.
.
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_productsRepository.Dispose();
_categoryRepository.Dispose();
}
Repositorio de productos
public class ProductsRepository : IDisposable
{
private MyEntities _db;
public ProductsRepository(MyEntities db)
{
_db = db;
}
public Product GetProduct(Guid productId)
{
return _db.Product.Where(x => x.ID == productId).FirstOrDefault();
}
public void Dispose()
{
this._db.Dispose();
}
Controlador de fábrica
public class UnityControllerFactory : DefaultControllerFactory
{
IUnityContainer _container;
public UnityControllerFactory(IUnityContainer container)
{
_container = container;
}
protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, String.Format("The controller for path ''{0}'' could not be found" +
"or it does not implement IController.",
requestContext.HttpContext.Request.Path));
}
return _container.Resolve(controllerType) as IController;
}
}
Información de adición Hola, publicaré enlaces adicionales con los que me cruzo, en relación con el tema relacionado y las sugerencias de solución:
- http://cgeers.wordpress.com/2009/02/21/entity-framework-objectcontext/#objectcontext
- http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx
- adjuntar linq a sql datacontext para httpcontext en la capa de negocios
- http://weblogs.asp.net/shijuvarghese/archive/2008/10/24/asp-net-mvc-tip-dependency-injection-with-unity-application-block.aspx
- http://msdn.microsoft.com/en-us/library/bb738470.aspx
A partir de Unity 3, ya existe un administrador de por vida incorporado por solicitud http.
Un LifetimeManager que se aferra a la instancia que se le otorga durante la vigencia de una única solicitud HTTP. Este administrador de por vida le permite crear instancias de tipos registrados que se comportan como singleton dentro del alcance de una solicitud HTTP. Ver comentarios para información de uso importante.
Comentarios de MSDN
Aunque el administrador de por vida PerRequestLifetimeManager funciona correctamente y puede ayudar a trabajar con dependencias con estado o sin seguridad dentro del alcance de una solicitud HTTP, generalmente no es una buena idea usarlo cuando se puede evitar , ya que a menudo puede conducir a malas prácticas o errores difíciles de encontrar en el código de la aplicación del usuario final cuando se usa incorrectamente.
Se recomienda que las dependencias que registre sean sin estado y, si es necesario compartir un estado común entre varios objetos durante la vigencia de una solicitud HTTP, puede tener un servicio sin estado que almacene y recupere explícitamente este estado utilizando la colección Elementos de el objeto actual.
Las observaciones dicen que incluso si se ve obligado a usar un contexto único por servicio (servicio de fachada), debe mantener sus llamadas de servicio sin estado.
Unity 3 es para .NET 4.5 por cierto.
Creo que el código de muestra que se muestra en NerdDinner: DI en MVC usando Unity para su HttpContextLifetimeManager
debe satisfacer sus necesidades.
En Unity3, si quieres usar
PerRequestLifetimeManager
UnityPerRequestHttpModule
registrar UnityPerRequestHttpModule
Lo hago usando WebActivatorEx, el código es el siguiente:
using System.Linq;
using System.Web.Mvc;
using Microsoft.Practices.Unity.Mvc;
using MyNamespace;
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(UnityWebActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(UnityWebActivator), "Shutdown")]
namespace MyNamespace
{
/// <summary>Provides the bootstrapping for integrating Unity with ASP.NET MVC.</summary>
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();
}
}
}
Lo resolví usando Castle.DynamicProxy. Necesitaba que ciertas dependencias se inyectaran "a pedido", lo que significaba que debían resolverse en el momento del uso, no en el momento de la creación de "Depender".
Para hacer esto configuro mi contenedor así:
private void UnityRegister(IUnityContainer container)
{
container.RegisterType<HttpContextBase>(new OnDemandInjectionFactory<HttpContextBase>(c => new HttpContextWrapper(HttpContext.Current)));
container.RegisterType<HttpRequestBase>(new OnDemandInjectionFactory<HttpRequestBase>(c => new HttpRequestWrapper(HttpContext.Current.Request)));
container.RegisterType<HttpSessionStateBase>(new OnDemandInjectionFactory<HttpSessionStateBase>(c => new HttpSessionStateWrapper(HttpContext.Current.Session)));
container.RegisterType<HttpServerUtilityBase>(new OnDemandInjectionFactory<HttpServerUtilityBase>(c => new HttpServerUtilityWrapper(HttpContext.Current.Server)));
}
La idea es que proporcione un método para recuperar la instancia "a pedido". El lambda se invoca cada vez que se utiliza alguno de los métodos de la instancia. El objeto Dependiente en realidad contiene una referencia a un objeto con proxy, no al objeto en sí.
OnDemandInjectionFactory:
internal class OnDemandInjectionFactory<T> : InjectionFactory
{
public OnDemandInjectionFactory(Func<IUnityContainer, T> proxiedObjectFactory) : base((container, type, name) => FactoryFunction(container, type, name, proxiedObjectFactory))
{
}
private static object FactoryFunction(IUnityContainer container, Type type, string name, Func<IUnityContainer, T> proxiedObjectFactory)
{
var interceptor = new OnDemandInterceptor<T>(container, proxiedObjectFactory);
var proxyGenerator = new ProxyGenerator();
var proxy = proxyGenerator.CreateClassProxy(type, interceptor);
return proxy;
}
}
OnDemandInterceptor:
internal class OnDemandInterceptor<T> : IInterceptor
{
private readonly Func<IUnityContainer, T> _proxiedInstanceFactory;
private readonly IUnityContainer _container;
public OnDemandInterceptor(IUnityContainer container, Func<IUnityContainer, T> proxiedInstanceFactory)
{
_proxiedInstanceFactory = proxiedInstanceFactory;
_container = container;
}
public void Intercept(IInvocation invocation)
{
var proxiedInstance = _proxiedInstanceFactory.Invoke(_container);
var types = invocation.Arguments.Select(arg => arg.GetType()).ToArray();
var method = typeof(T).GetMethod(invocation.Method.Name, types);
invocation.ReturnValue = method.Invoke(proxiedInstance, invocation.Arguments);
}
}
No quiero desanimarte innecesariamente y, por supuesto, experimentar, pero si sigues adelante y utilizas instancias únicas de DataContext, asegúrate de hacerlo.
Puede parecer que funciona bien en su entorno de desarrollo pero podría no cerrar bien las conexiones. Esto será difícil de ver sin la carga de un entorno de producción. En un entorno de producción con alta carga, las conexiones no expuestas causarán grandes pérdidas de memoria y, a continuación, un alto nivel de CPU al intentar asignar nueva memoria.
¿Has considerado lo que estás obteniendo de una conexión por patrón de solicitud? ¿Cuánto rendimiento se obtiene al abrir / cerrar una conexión una vez más, digamos 3-4 veces en una solicitud? Vale la pena la molestia? Además, esto hace que la carga lenta no funcione (leer las consultas de la base de datos en su vista). Ofensas mucho más fáciles de hacer.
Lo siento si esto fue desalentador. Ve por él si realmente ves el beneficio. Solo te estoy advirtiendo que podría ser contraproducente si te equivocas, así que ten cuidado. Algo como el perfilador de entidades será invaluable para hacerlo bien, le dice la cantidad de conexiones abiertas y cerradas, entre otras cosas muy útiles.
Sí , no comparto contexto y uso un contexto por solicitud. También puede verificar las preguntas vinculadas en esa publicación para ver todos los problemas que un contexto compartido causó.
Ahora sobre la Unidad. La idea de PerCallContextLifetimeManager
funciona pero creo que la implementación provista no funcionará para más de un objeto. Debe usar PerHttpRequestLifetimeManager
directamente:
public class PerHttpRequestLifetime : LifetimeManager
{
// This is very important part and the reason why I believe mentioned
// PerCallContext implementation is wrong.
private readonly Guid _key = Guid.NewGuid();
public override object GetValue()
{
return HttpContext.Current.Items[_key];
}
public override void SetValue(object newValue)
{
HttpContext.Current.Items[_key] = newValue;
}
public override void RemoveValue()
{
var obj = GetValue();
HttpContext.Current.Items.Remove(obj);
}
}
Tenga en cuenta que la Unidad no dispondrá de contexto para usted. También tenga en cuenta que la implementación predeterminada de UnityContainer
nunca llamará RemoveValue
método RemoveValue
.
Si su implementación resuelve todos los repositorios en una sola llamada Resolve
(por ejemplo, si sus controladores reciben instancias de repositorios en el constructor y usted está resolviendo controladores), no necesita este administrador de por vida. En tal caso, use build-in (Unity 2.0) PerResolveLifetimeManager
.
Editar:
Veo un problema bastante grande en la configuración provista de UnityContainer
. Está registrando ambos repositorios con ContainerControllerLifetimeManager
. Este administrador de por vida es una instancia de Singleton por vida útil del contenedor. Esto significa que ambos repositorios se instanciarán solo una vez y la instancia se almacenará y reutilizará para llamadas posteriores. Por eso no importa qué vida asignó a MyEntities
. Se inyecta a los constructores de repositorios que se llamarán solo una vez. Ambos repositorios utilizarán aún esa única instancia de MyEntities
creada durante su construcción = usarán una única instancia para toda la vida útil de su AppDomain
. Ese es el peor escenario que puedes lograr.
Reescribe tu configuración de esta manera:
var container = new UnityContainer();
container
.RegisterType<ProductsRepository>()
.RegisterType<CategoryRepository>()
.RegisterType<MyEntities>(new PerResolveLifetimeManager(), dbConnectionString);
¿Por qué esto es suficiente? Está resolviendo el controlador que depende de los repositorios pero no se necesita ninguna instancia de repositorio más de una vez para que pueda usar el TransientLifetimeManager
predeterminado que creará una nueva instancia para cada llamada. Debido a que se llama al constructor del repositorio y se debe resolver la instancia de MyEntities
. Pero usted sabe que múltiples repositorios pueden necesitar esta instancia, por lo que la configurará con PerResolveLifetimeManager
=> cada resolución del controlador producirá solo una instancia de MyEntities
.
Vi preguntas y respuestas hace algunas veces. Está fechado. Unity.MVC3 tiene administrador de tiempo de vida como HierarchicalLifetimeManager.
container.RegisterType<OwnDbContext>(
"",
new HierarchicalLifetimeManager(),
new InjectionConstructor(connectionString)
);
y funciona bien
Yo propondría resolverlo así: http://forums.asp.net/t/1644386.aspx/1
Atentamente
PerRequestLifetimeManager clases PerRequestLifetimeManager y UnityPerRequestHttpModule están en el paquete Unity.Mvc que tiene una dependencia en ASP.NET MVC. Si no desea tener esa dependencia (por ejemplo, está utilizando la API web) tendrá que copiar y pegar en su aplicación.
Si haces eso, no olvides registrar el HttpModule.
Microsoft.Web.Infrastructure.DynamicModuleHelper.DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));
Editar: incluiré las clases aquí antes de que CodePlex se cierre:
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web;
using Microsoft.Practices.Unity.Mvc.Properties;
using Microsoft.Practices.Unity.Utility;
namespace Microsoft.Practices.Unity.Mvc
{
/// <summary>
/// Implementation of the <see cref="IHttpModule"/> interface that provides support for using the
/// <see cref="PerRequestLifetimeManager"/> lifetime manager, and enables it to
/// dispose the instances after the HTTP request ends.
/// </summary>
public class UnityPerRequestHttpModule : IHttpModule
{
private static readonly object ModuleKey = new object();
internal static object GetValue(object lifetimeManagerKey)
{
var dict = GetDictionary(HttpContext.Current);
if (dict != null)
{
object obj = null;
if (dict.TryGetValue(lifetimeManagerKey, out obj))
{
return obj;
}
}
return null;
}
internal static void SetValue(object lifetimeManagerKey, object value)
{
var dict = GetDictionary(HttpContext.Current);
if (dict == null)
{
dict = new Dictionary<object, object>();
HttpContext.Current.Items[ModuleKey] = dict;
}
dict[lifetimeManagerKey] = value;
}
/// <summary>
/// Disposes the resources used by this module.
/// </summary>
public void Dispose()
{
}
/// <summary>
/// Initializes a module and prepares it to handle requests.
/// </summary>
/// <param name="context">An <see cref="HttpApplication"/> that provides access to the methods, properties,
/// and events common to all application objects within an ASP.NET application.</param>
[SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Validated with Guard class")]
public void Init(HttpApplication context)
{
Guard.ArgumentNotNull(context, "context");
context.EndRequest += OnEndRequest;
}
private void OnEndRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
var dict = GetDictionary(app.Context);
if (dict != null)
{
foreach (var disposable in dict.Values.OfType<IDisposable>())
{
disposable.Dispose();
}
}
}
private static Dictionary<object, object> GetDictionary(HttpContext context)
{
if (context == null)
{
throw new InvalidOperationException(Resources.ErrorHttpContextNotAvailable);
}
var dict = (Dictionary<object, object>)context.Items[ModuleKey];
return dict;
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.Practices.Unity.Mvc;
namespace Microsoft.Practices.Unity
{
/// <summary>
/// A <see cref="LifetimeManager"/> that holds onto the instance given to it during
/// the lifetime of a single HTTP request.
/// This lifetime manager enables you to create instances of registered types that behave like
/// singletons within the scope of an HTTP request.
/// See remarks for important usage information.
/// </summary>
/// <remarks>
/// <para>
/// Although the <see cref="PerRequestLifetimeManager"/> lifetime manager works correctly and can help
/// in working with stateful or thread-unsafe dependencies within the scope of an HTTP request, it is
/// generally not a good idea to use it when it can be avoided, as it can often lead to bad practices or
/// hard to find bugs in the end-user''s application code when used incorrectly.
/// It is recommended that the dependencies you register are stateless and if there is a need to share
/// common state between several objects during the lifetime of an HTTP request, then you can
/// have a stateless service that explicitly stores and retrieves this state using the
/// <see cref="System.Web.HttpContext.Items"/> collection of the <see cref="System.Web.HttpContext.Current"/> object.
/// </para>
/// <para>
/// For the instance of the registered type to be disposed automatically when the HTTP request completes,
/// make sure to register the <see cref="UnityPerRequestHttpModule"/> with the web application.
/// To do this, invoke the following in the Unity bootstrapping class (typically UnityMvcActivator.cs):
/// <code>DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));</code>
/// </para>
/// </remarks>
public class PerRequestLifetimeManager : LifetimeManager
{
private readonly object lifetimeKey = new object();
/// <summary>
/// Retrieves a value from the backing store associated with this lifetime policy.
/// </summary>
/// <returns>The desired object, or null if no such object is currently stored.</returns>
public override object GetValue()
{
return UnityPerRequestHttpModule.GetValue(this.lifetimeKey);
}
/// <summary>
/// Stores the given value into the backing store for retrieval later.
/// </summary>
/// <param name="newValue">The object being stored.</param>
public override void SetValue(object newValue)
{
UnityPerRequestHttpModule.SetValue(this.lifetimeKey, newValue);
}
/// <summary>
/// Removes the given object from the backing store.
/// </summary>
public override void RemoveValue()
{
var disposable = this.GetValue() as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
UnityPerRequestHttpModule.SetValue(this.lifetimeKey, null);
}
}
}