Autofac – registrando classes automaticamente

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Hoje vamos criar um conjunto de classes e interfaces, que vai nos ajudar a manter um código mais organizado em projetos que usem o Autofac, além de nos ajudar a registrar todas as classes de todos os class libraries (assemblies) que utilizarmos em nosso projeto. A idéia é que com uma linha de código, a aplicação procure em todos os assemblies do projeto por classes que implementem uma interface definida por nós, e rode o código de todas essas classes de forma automática, assim não importa quantos assemblies você referencia no seu projeto, fica simples e fácil para não esquecer de carregar todo mundo.

Nós vamos usar como base para nosso exemplo a aplicação que construímos no post Autofac – utilizando com wcf que está disponível no GitHub: https://github.com/rdakar/autofac-wcf. Baixe a aplicação, e crie uma nova class library com o nome ContainerRegister na mesma solution. Utilizando o NuGet Packager Manager instale o pacote do Autofac na nova class library.

Feito isso, vamos agora criar uma interface chamada IRegisterDependency com o seguinte código:

using Autofac;

namespace ContainerRegister.DependencyManager
{
    public interface IRegisterDependency
    {
        void Register(ContainerBuilder builder);

        int Order { get; }
    }
}

Essa é a interface que deverá ser implementada pelas classes que farão o registro das classes/interfaces que desejarmos no container do Autofac. Ou seja, toda classe que implementar IRegisterDependency será chamada de forma automática, independente do assembly que ela estiver.

Agora vamos criar uma interface chamada IAssemblyLoader com o código abaixo:

using System.Collections.Generic;
using System.Reflection;

namespace ContainerRegister.DependencyManager
{
    public interface IAssemblyLoader
    {
        IList<Assembly> GetAssemblies();
    }
}

Essa interface é necessária, para abstrairmos como queremos carregar os assemblies que vamos procurar por classes que implementem a interface IRegisterDependency. Nós teremos três implementações diferentes para essa interface, isso porque de acordo com o tipo de aplicação que tivemos (webforms, wcf, winforms, etc) a forma que devemos obter os assemblies muda.

A primeira classe que vamos criar que implementa IAssemblyLoader é a AppDomainLoader:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace ContainerRegister.DependencyManager
{
    public class AppDomainLoader : IAssemblyLoader
    {
        public IList<Assembly> GetAssemblies()
        {
            return AppDomain.CurrentDomain.GetAssemblies().ToList();
        }
    }
}

Ela não deve ser usada em aplicações web, devendo ser usada apenas para aplicações windows. essa classe carrega todos os assemblies do domínio da aplicação.

A próxima classe que vamos implementar é a BuildManagerLoader:

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Compilation;

namespace ContainerRegister.DependencyManager
{
    public class BuildManagerLoader : IAssemblyLoader
    {
        public IList<Assembly> GetAssemblies()
        {
            return BuildManager.GetReferencedAssemblies().Cast<Assembly>().ToList();
        }
    }
}

Essa classe sim, deve ser usada em aplicações web, e não deve ser usada em aplicações winforms. Repare que ela utiliza o assmebly System.Web, então não se esqueça de adicioná-lo nas referencias de nossa class library.

A última classe que vamos implementar é a ReferencedAssemblyLoader:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace ContainerRegister.DependencyManager
{
    public class ReferencedAssemblyLoader : IAssemblyLoader
    {
        public IList<Assembly> GetAssemblies()
        {
            return (from file in Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory)
                    where Path.GetExtension(file) == ".dll"
                    select Assembly.LoadFrom(file)).ToList();
        }
    }
}

Essa classe vai devolver todos os assemblies referenciados no diretório da aplicação, independente de estarem em uso ou não pela aplicação no momento, então pode ser uma boa idéia utilizá-la em aplicações windows em que seja necessário olhar todos assemblies do diretório, e não apenas os de domínio da aplicação.

Agora vem a parte mais complexa, vamos criar a classe que vai varrer a lista de assemblies, retornada pelas classes acima, e vai procurar em cada um desses assemblies por classes que implementem a interface IRegisterDependency, e caso achem, vai retornar uma lista com todas as classes que implementem IRegisterDependency.

O código dela é meio grande, e segue abaixo:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

namespace ContainerRegister.DependencyManager
{
    public class AppTypeFinder
    {
        private bool ignoreReflectionErrors = true;
        private IList<IAssemblyLoader> listLoader;

        public AppTypeFinder(IList<IAssemblyLoader> loaders)
        {
            this.listLoader = loaders;
        }

        public IEnumerable<Type> FindClassesOfType<T>(bool onlyConcreteClasses = true)
        {
            return FindClassesOfType(typeof(T), onlyConcreteClasses);
        }

        public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, bool onlyConcreteClasses = true)
        {
            return FindClassesOfType(assignTypeFrom, GetAssemblies(), onlyConcreteClasses);
        }

        public IEnumerable<Type> FindClassesOfType<T>(IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true)
        {
            return FindClassesOfType(typeof(T), assemblies, onlyConcreteClasses);
        }

        public IEnumerable<Type> FindClassesOfType(Type assignTypeFrom, IEnumerable<Assembly> assemblies, bool onlyConcreteClasses = true)
        {
            var result = new List<Type>();
            try
            {
                foreach (var a in assemblies)
                {
                    Type[] types = null;
                    try
                    {
                        types = a.GetTypes();
                    }
                    catch
                    {
                        if (!ignoreReflectionErrors)
                        {
                            throw;
                        }
                    }
                    if (types != null)
                    {
                        foreach (var t in types)
                        {
                            if (assignTypeFrom.IsAssignableFrom(t) || (assignTypeFrom.IsGenericTypeDefinition && DoesTypeImplementOpenGeneric(t, assignTypeFrom)))
                            {
                                if (!t.IsInterface)
                                {
                                    if (onlyConcreteClasses)
                                    {
                                        if (t.IsClass && !t.IsAbstract)
                                        {
                                            result.Add(t);
                                        }
                                    }
                                    else
                                    {
                                        result.Add(t);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (ReflectionTypeLoadException ex)
            {
                var msg = string.Empty;
                foreach (var e in ex.LoaderExceptions)
                    msg += e.Message + Environment.NewLine;

                var fail = new Exception(msg, ex);
                Debug.WriteLine(fail.Message, fail);

                throw fail;
            }
            return result;
        }

        private IList<Assembly> GetAssemblies()
        {
            var addedAssemblyNames = new List<string>();
            var assemblies = new List<Assembly>();

            foreach(IAssemblyLoader loader in listLoader)
                AddAssemblies(addedAssemblyNames, assemblies, loader);

            return assemblies;
        }

        private void AddAssemblies(List<string> addedAssemblyNames, List<Assembly> assemblies, IAssemblyLoader loader)
        {
            foreach (Assembly assembly in loader.GetAssemblies())
            {
                if (!addedAssemblyNames.Contains(assembly.FullName))
                {
                    assemblies.Add(assembly);
                    addedAssemblyNames.Add(assembly.FullName);
                }
            }
        }

        private bool DoesTypeImplementOpenGeneric(Type type, Type openGeneric)
        {
            try
            {
                var genericTypeDefinition = openGeneric.GetGenericTypeDefinition();
                foreach (var implementedInterface in type.FindInterfaces((objType, objCriteria) => true, null))
                {
                    if (!implementedInterface.IsGenericType)
                        continue;

                    var isMatch = genericTypeDefinition.IsAssignableFrom(implementedInterface.GetGenericTypeDefinition());
                    return isMatch;
                }
                return false;
            }
            catch
            {
                return false;
            }
        }
    }
}

O que nós fizemos foi declarar uma variável que representa uma lista de IAssemblyLoader, e recebâ-la no construtor da classe. No método GetAssemblies, nós vamos varrer essa lista, e chamar o método AddAssemblies, que irá pegar a lista com todos assemblies de nossa aplicação, de acordo com a classe passada para IAssemblyLoader, e irá verificar se aquele assembly já está em nossa lista, pois só queremos adicionar o assembly uma vez, mesmo que usemos mais de um tipo de IAssemblyLoader. E por fim, o método GetAssemblies vai retornar uma lista com todos assemblies encontrados.

O método FindClassesOfType possui alguns overloads, e é responsável por varrer a lista de assemblies e verificar se em cada assembly existe alguma classe do tipo que ele recebe como parâmetro, que no nosso caso, será IRegisterDependency, e existindo alguma classe do tipo desejado, ele vai retornar uma lista com as classes encontradas.

Agora vamos criar uma classe chamada AutofacRegisterAll, que será responsável por pegar a lista de classes retornada pela classe AppTypeFinder, instanciar cada uma dessas classes, chamar o método Register de cada uma delas, e devolver um IContainer do Autofac. Segue o código:

using Autofac;
using System;
using System.Collections.Generic;
using System.Linq;

namespace ContainerRegister.DependencyManager
{
    public static class AutofacRegisterAll
    {
        public static IContainer RegisterDependencies(IList<IAssemblyLoader> loaders)
        {
            ContainerBuilder builder = new ContainerBuilder();
            AppTypeFinder typeFinder = new AppTypeFinder(loaders);
            var drTypes = typeFinder.FindClassesOfType<IRegisterDependency>();
            var drInstances = new List<IRegisterDependency>();
            foreach (var drType in drTypes)
                drInstances.Add((IRegisterDependency)Activator.CreateInstance(drType));

            drInstances = drInstances.AsQueryable().OrderBy(t => t.Order).ToList();
            foreach (var dependencyRegistrar in drInstances)
                dependencyRegistrar.Register(builder);

            return builder.Build();
        }
    }
}

O que fazemos aqui é criar um ContainerBuilder, que deve ser passado como parâmetro para o método Register, também criamos o AppTypeFinder, e com a lista retornada por ele, instanciamos as classes e chamamos seu método Register.

Com isso nossa class library ContainerRegister está pronta. Agora vamos utilizá-la em nosso WCF. Então, no projeto AutofacDemoWCF, vamos criar uma classe chamada RegisterDependencyWCF que implemente a interface IRegisterDependency conforme abaixo:

using ContainerRegister.DependencyManager;
using Autofac;

namespace AutofacDemoWCF
{
    public class RegisterDependencyWCF : IRegisterDependency
    {
        public int Order => 1;

        public void Register(ContainerBuilder builder)
        {
            builder.RegisterType<AutofacDemoWCF.Service1>();
            builder.RegisterType<Soma>().As<ICalculo>().InstancePerLifetimeScope();
        }
    }
}

Observe que estamos registrando as classes/interfaces que antes estavam sendo registradas no método Application_Start do Global.asax. Agora altere o método Application_Start do Gloabal.asax para ficar assim:

protected void Application_Start(object sender, EventArgs e)
{
    AutofacHostFactory.Container = AutofacRegisterAll.RegisterDependencies(new IAssemblyLoader[] { new BuildManagerLoader() });
}

O que estamos fazendo aqui, é chamando nossa classe AutofacRegisterAll, passando como parâmetro um BuildManagerLoader, e atribuindo o container que ela retorna ao AutofacHostFactory.Container. Pronto, agora rode a aplicação e veja o resultado, repare que a classe RegisterDependencyWCF está presente no assembly AutofacDemoWCF.dll, e ela foi executada pela classe AutofacRegisterAll que está no assembly ContainerRegister.

Dessa forma cada assembly do seu projeto, fica responsável por registrar suas classes/interfaces, e com apenas uma chamada à classe AutofacRegisterAll, sua aplicação vai procurar e registrar todas as classes que ela precisa, sem você precisar ficar se preocupando de chamar cada uma delas na mão, prático não?

Como de costume o código completo da aplicação está no GitHub: https://github.com/rdakar/autofac-registrando-automaticamente.

É possível fazer algumas melhorias na classe AppTypeFinder, como criar padrões de assembly que devem ser ignorados, Vou evoluir esse exemplo, e subir no GitHub em breve e talvez como um pacote no NuGet, fiquem ligados.

Facebooktwittergoogle_plusredditpinterestlinkedinmail

1 thought on “Autofac – registrando classes automaticamente

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *