Mercurial > pub > ImplabNet
diff Implab/ServiceLocator.cs @ 40:fe33f4e02ad5
improved tracing
added text listeners (file,console)
author | cin |
---|---|
date | Tue, 15 Apr 2014 17:52:09 +0400 |
parents | |
children | 9dd6a896a385 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/ServiceLocator.cs Tue Apr 15 17:52:09 2014 +0400 @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Implab { + /// <summary> + /// Коллекция сервисов, позволяет регистрировать и получать сервисы. + /// </summary> + public class ServiceLocator: Component, IServiceLocator, IServiceProvider { + // запись об сервисе + struct ServiceEntry { + public object service; // сервис + public bool shared; // признак того, что сервис НЕ нужно освобождать + public Func<object> activator; // активатор сервиса при первом обращении + public List<Type> associated; // ссылки на текущую запись + public Type origin; // ссылка на оригинальную запись о сервисе + } + + // словарь существующих сервисов + Dictionary<Type, ServiceEntry> m_services = new Dictionary<Type,ServiceEntry>(); + + /// <summary> + /// Получает объект предоставляющий сервис <typeparamref name="T"/>. + /// </summary> + /// <typeparam name="T">Тип запрашиваемого сервиса</typeparam> + /// <returns>Объект, реализующий сервис</returns> + /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception> + public T GetService<T>() { + return (T)GetService(typeof(T)); + } + + + /// <summary> + /// Пытается получить указанный сервис, в случае, если компонента не предоставляет требуемый сервис + /// не возникает исключений. + /// </summary> + /// <typeparam name="T">Тип требуемого сервиса.</typeparam> + /// <param name="service">Объект реализующий сервис, или <c>default(T)</c> если такового нет.</param> + /// <returns><c>true</c> - сервис найден, <c>false</c> - сервис не зарегистрирован.</returns> + public bool TryGetService<T>(out T service) { + AssertNotDisposed(); + + try { + service = GetService<T>(); + return true; + } catch(KeyNotFoundException) { + service = default(T); + return false; + } + } + + /// <summary> + /// Получает объект предоставляющий сервис <paramref name="serviceType"/> + /// </summary> + /// <param name="serviceType">Тип запрашиваемого сервиса</param> + /// <returns>Объект, реализующий сервис</returns> + /// <exception cref="KeyNotFoundException">Сервис не зарегистрирован</exception> + public object GetService(Type serviceType) { + if (serviceType == null) + throw new ArgumentNullException("serviceType"); + AssertNotDisposed(); + + ServiceEntry se; + if (!m_services.TryGetValue(serviceType, out se)) { + // ищем ближайщий объект, реализующий нужный сервис + Type pt = null; + foreach (var t in m_services.Keys) + if (serviceType.IsAssignableFrom(t) && (pt == null || t.IsAssignableFrom(pt))) + pt = t; + if (pt == null) + throw new ApplicationException(String.Format("{0} doesn't provide {1} service",this,serviceType)); + + var pe = m_services[pt]; + + // найденная запись может ссылаться на оригинальную запись с сервисом + if(pe.origin != null) { + pt = pe.origin; + pe = m_services[pt]; + } + + // добавляем список с обратными ссылками + if (pe.associated == null) + pe.associated = new List<Type>(); + + pe.associated.Add(serviceType); + + // обновляем родительскую запись + m_services[pt] = pe; + + // создаем запись со ссылкой + se = new ServiceEntry { + service = pe.service, + origin = pt, + shared = true // предотвращаем множественные попытки освобождения + }; + + m_services[serviceType] = se; + } + + // запись содержит в себе информацию о сервисе + if (se.service != null) + return se.service; + + // текущая запись является ссылкой + if (se.origin != null) { + se.service = GetService(se.origin); + m_services[serviceType] = se; + return se.service; + } + + // текущая запись не является ссылкой и не имеет информации о сервисе + // она должна сожержать информацию об активации + if (se.activator != null) { + se.service = se.activator(); + + m_services[serviceType] = se; + + return se.service; + } + + throw new Exception("Unable to create a service instance"); + } + + /// <summary> + /// Регистрирует фабрику для активации сервиса по первому требованию. + /// </summary> + /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam> + /// <param name="activator">Фабрика для создания/получения объекта, предоставляющего сервис.</param> + /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception> + /// <remarks>При освобождении сервис-локатора, сервисы полученные в результате активации также будут освобождены.</remarks> + public void Register<T>(Func<T> activator) { + if (activator == null) + throw new ArgumentNullException("activator"); + + AssertNotDisposed(); + + Unregister(typeof(T)); + + m_services[typeof(T)] = new ServiceEntry { + activator = () => activator() as object + }; + } + + /// <summary> + /// Регистрирует объект, предоставляющий сервис. + /// </summary> + /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam> + /// <param name="service">Объект, предоставляющий сервис.</param> + /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception> + /// <remarks>Сервис-локатором не управляет временем жизни объекта для зарегистрированного сервиса.</remarks> + public void Register<T>(T service) { + Register(service, true); + } + + /// <summary> + /// Регистрирует объект, предоставляющий сервис. + /// </summary> + /// <typeparam name="T">Тип регистрируемого сервиса.</typeparam> + /// <param name="service">Объект, предоставляющий сервис.</param> + /// <param name="shared">Признак того, что объект является разделяемым и сервис-локатор не должен его освобождать.</param> + /// <exception cref="InvalidOperationException">Указанный сервис уже зарегистрирован.</exception> + public void Register<T>(T service, bool shared) { + if (service == null) + throw new ArgumentNullException("service"); + + AssertNotDisposed(); + + Unregister(typeof(T)); + + m_services[typeof(T)] = new ServiceEntry { service = service, shared = shared }; + } + + public void Unregister(Type serviceType) { + if (serviceType == null) + throw new ArgumentNullException("serviceType"); + + AssertNotDisposed(); + + ServiceEntry se; + if (m_services.TryGetValue(serviceType, out se)) { + // освобождаем ресурсы + if (se.service != null && !se.shared) + ((IDisposable)se.service).Dispose(); + m_services.Remove(serviceType); + + // убираем связанные записи + if (se.associated != null) + foreach (var item in se.associated) + m_services.Remove(item); + } + } + + /// <summary> + /// Освобождает зарегистрированные сервисы (которые требуется освобоить). + /// </summary> + /// <param name="disposing">Призанак того, что нужно освободить ресурсы.</param> + protected override void Dispose(bool disposing) { + if (disposing) { + + foreach (var entry in m_services.Values) + if (!entry.shared && entry.service is IDisposable) + ((IDisposable)entry.service).Dispose(); + + } + base.Dispose(disposing); + } + } +} \ No newline at end of file