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?
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);
(....)
mamy możliwość takich rejestracji tylko dla klas i interfejsów
Oraz, w kazdym sensownym IoC mozliwosc rejestracji modulow.
@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.
@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.
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 :)
Słabo wygląda to podawanie nazw plików. Nie da się normalnie, jak w moim przykładzie?
@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