architektura - WEB, BLL, DALL i dependency injection

1

Standardowa architektura aplikacji webowej wygląda tak, że mamy projekt WEB, BLL i DAL. WEB ma referencję do BLL, a BLL ma referencję do DAL.
Co robicie gdy używacie dependency injection w postaci, np. castle windsor, ninject itp.? Tzn. wszelkie rejestracje żeby dla danego interfejsu został stworzony obiekt danej klasy robi się w projekcie WEB więc mamy możliwość takich rejestracji tylko dla klas i interfejsów, które są w projekcie WEB lub BLL, ale już nie w DAL, bo WEB nie ma referencji do DAL. Co więc robi się w takiej sytuacji, dodaje się po prostu referencję z WEB do DAL żeby dokonać stosownych rejestracji w WEB dla klas i interfejsów, które są w DAL?

0

Nie jestem pewien czy mam to samo na myśli co Ty ale ja to robię mniej więcej tak:
Pusta solucja w której mam 2 projekty. Pierwszy to WebAPI przykładowo (czy cokolwiek innego co piszesz), natomiast drugi to Class Library, w którym mam foldery Domain, Mapping, Interfaces oraz Repositories. W Interfaces trzymam interfejsy do repositoriow np. IUserRepository. Natomiast w Repositories trzymam implementacje tych repozytoriów np. UserRepository i w takiej klasie mam przykładowy kod:

 

 public class UserRepository: IUserRepository
    {
        private readonly ISession session;

        public UserRepository(ISession session)
        {
            this.session = session;
        }

        public IEnumerable<User> Users()
        {
            return this.session.Query<User>();
        }

następnie w pliku konfiguracyjnym (Global.asax) tego pierwszego projektu czyli WebAPI mam:

 protected void Application_Start()
        {
            const string ConnString = "Server=**************;"; //"Data Source=(local); Integrated Security=True; Initial Catalog=Blackhole";

            var factory = Fluently.Configure()
                     .Database(MsSqlConfiguration.MsSql2012.ConnectionString(ConnString))
                //.ExposeConfiguration(x => x.SetInterceptor(new SqlStatementInterceptor()))
                     .Mappings(m => m.FluentMappings.AddFromAssemblyOf<UserMap>())
                     .BuildSessionFactory();

            var builder = new ContainerBuilder();
            builder.Register(a => factory.OpenSession()).As<ISession>().SingleInstance();
            builder.RegisterType<UserRepository>().As<IUserRepository>();
                 
            builder.RegisterWebApiFilterProvider(GlobalConfiguration.Configuration);
            builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
            var container = builder.Build();

            GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
 

I teraz w folderze Domain trzymam klasy "tabele" np.:

namespace Domain.Domain
{
    public class User
    {
        public virtual int Id { get; set; }
        public virtual string Email { get; set; }
        public virtual string Password { get; set; }
        public virtual string Salt { get; set; }
    }
}

i w fodlerze Mapping:

namespace Domain.Mapping
{
    using FluentNHibernate.Mapping;
    using global::Domain.Domain;

    public class UserMap: ClassMap<User>
    {
        public UserMap()
        {
            this.Id(x => x.Id);
            this.Map(x => x.Email).Not.Nullable();
            this.Map(x => x.Password).Not.Nullable();
            this.Map(x => x.Salt).Not.Nullable();
        }
    }
}
 

na podstawie powyższego kodu robie DI i tworzę inne projekty jakie chce, przykładowo kontroler WebAPI będzie wyglądał następująco:

 
namespace WebAPI.Controllers
{
    using Domain.Domain;
    using Domain.Interfaces;
       
    public class ConfirmController : ApiController
    {
        private readonly IUserRepository userRepository;

        public ConfirmController(IUserRepository userR )
        {
             this.userRepository = userR;
        }

        public HttpResponseMessage Post([FromUri] string userId)
        {
            var User = this.userRepository.GetUserById(userId);
           
(....)

0

mamy możliwość takich rejestracji tylko dla klas i interfejsów

Oraz, w kazdym sensownym IoC mozliwosc rejestracji modulow.

2

@ne0, tak to ma każdy. Tylko po co aplikacji świadomość istnienia czegoś takiego jak FluentNH i jakichś repozytoriach?

@pytk, o ile dobrze rozumiem pytasz o odseparowanie warstw aplikacji, ale tak, żeby działało IoC. Ja to robię tak:

DataAccess:

using System.Configuration;
using Autofac;
using NHibernate;

namespace Janusz.DataAccess
{
    public class AutofacModule : Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            string connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
            var sessionFactory = new NhConfigurator(connectionString).GetSessionFactory();

            builder.RegisterInstance(sessionFactory).As<ISessionFactory>().SingleInstance();
            builder.Register(c => sessionFactory.OpenSession()).As<ISession>().InstancePerLifetimeScope();
        }
    }
}

BusinessLogic:

using System.Reflection;
using Autofac;
using Autofac.Extras.DynamicProxy2;
using Janusz.Infrastructure.NHibernate;

namespace Janusz.BusinessLogic
{
    public class AutofacModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<NhTransactionInterceptor>().AsSelf();

            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                .Where(t => t.Name.EndsWith("Service"))
                .EnableClassInterceptors()
                .InterceptedBy(typeof(NhTransactionInterceptor));
        }
    }
}

WebClient:

using System.Reflection;
using Autofac;
using Autofac.Integration.Mvc;

namespace Janusz.WebClient.App_Start
{
    public class AutofacModule : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterControllers(Assembly.GetExecutingAssembly());
        }
    }
}
using System;
using System.Web.Mvc;
using Autofac;
using Autofac.Integration.Mvc;

namespace Janusz.WebClient.App_Start
{
    public class AutofacConfig
    {
        public static IContainer Container;

        public void Configure()
        {
            var builder = new ContainerBuilder();
            builder.RegisterAssemblyModules(AppDomain.CurrentDomain.GetAssemblies());

            if (Container == null)
            {
                Container = builder.Build();
            }
            else
            {
                builder.Update(Container);
            }

            DependencyResolver.SetResolver(new AutofacDependencyResolver(Container));
        }
    }
}

Global.asax:

var autofac = new AutofacConfig();
autofac .Configure();

Z tego, co wiem, to w Windsorze też taki efekt jest osiągalny. Nie wiem jak z Ninjectem, ale dziwne by było, gdyby się tak nie dało.

0

@somekind - nie bardzo rozumiem, bo korzystam z ninject i aktualnie dzięki temu, że w projekcie WEB mam referencję do BLL i DAL robię przykładowo coś takiego - cały kod jest w projekcie WEB (w metodzie RegisterServices klasy NinjectWebCommon dodaje się własne moduły, mój własny moduł to klasa WebModule):

public static class NinjectWebCommon 
{
    private static readonly Bootstrapper bootstrapper = new Bootstrapper();

    /// <summary>
    /// Starts the application
    /// </summary>
    public static void Start() 
    {
        DynamicModuleUtility.RegisterModule(typeof(OnePerRequestHttpModule));
        DynamicModuleUtility.RegisterModule(typeof(NinjectHttpModule));
        bootstrapper.Initialize(CreateKernel);
    }
        
    /// <summary>
    /// Stops the application.
    /// </summary>
    public static void Stop()
    {
        bootstrapper.ShutDown();
    }
        
    /// <summary>
    /// Creates the kernel that will manage your application.
    /// </summary>
    /// <returns>The created kernel.</returns>
    private static IKernel CreateKernel()
    {
        var kernel = new StandardKernel();
        try
        {
            kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
            kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();

            RegisterServices(kernel);
            return kernel;
        }
        catch
        {
            kernel.Dispose();
            throw;
        }
    }

    /// <summary>
    /// Load your modules or register your services here!
    /// </summary>
    /// <param name="kernel">The kernel.</param>
    private static void RegisterServices(IKernel kernel)
    {
        // W TYM MIEJSCU W NINJECT DODAJE SIE MODULY
        kernel.Load(new WebModule());
    }        
}

// Moj wlasny modul:
    public class WebModule : NinjectModule
    {
        public override void Load()
        {
            Bind<DAL.IUnitOfWork>().To<DAL.UnitOfWork>().InRequestScope();
            Bind<BLL.ICommentsService>().To<BLL.CommentsService>();
        }
    }

Gdybym teraz z projektu WEB usunął referencję do projektu DAL, co chciałbym uczynić to ta linia:

Bind<DAL.IUnitOfWork>().To<DAL.UnitOfWork>().InRequestScope();

byłaby błędna.

0

Znalazłem rozwiązanie, w projekcie BLL daje:

public class BLLModule : NinjectModule
    {
        public override void Load()
        {
            Bind<ICommentsService>().To<CommentsService>();
        }
    }

W projekcie DAL daje:

public class DALModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();
        }
    }

I wtedy w projekcie WEB wystarczy:

private static void RegisterServices(IKernel kernel)
        {
            kernel.Load("Blog.DAL.dll");
            kernel.Load("Blog.BLL.dll");
        } 

I nie muszę już w Web dawać referencji do DAL, bo ninject sam wyszuka we wskazanych dllkach klasy dziedziczące po NinjectModule :D Kocham ninject :)

0

Słabo wygląda to podawanie nazw plików. Nie da się normalnie, jak w moim przykładzie?

0

@somekind - faktycznie jak teraz sprawdziłem istnieje niby taka możliwość, można to zrobić tak:

kernel.Load(AppDomain.CurrentDomain.GetAssemblies());

niestety wówczas dostaję błąd:
Another module (of type WebCommonNinjectModule) with the same name has already been loaded

A jedyna strona w google, która mi coś o tym mówi:
http://www.sunilrav.com/post/Error-loading-module-NinjectWebMvcMvcModule-of-type-MvcModule-Another-module-%28of-type-MvcModule%29-with-the-same-name-has-already-been-loaded
zaleca bezpośrednie wypisanie dll'ek - no może jednak ninject nie jest taki super, ale i tak lepsze wypisanie dll'ek niż konieczność posiadania referencji z WEB do DAL więc jakoś to przeżyję :D

1 użytkowników online, w tym zalogowanych: 0, gości: 1