1

I have a DNX 4.5.1 ASP.NET 5 project called Foo and a class library project called Bar. Foo has a reference to Bar, and Bar references a nuget package called Baz.

From within Foo, I can use types defined in Baz, despite that nuget package not being referenced by Foo. It seems like Foo can access any type referenced by Bar. Why is that? Doesn't this completely break the concept of abstraction?

Bas
  • 1,946
  • 21
  • 38
  • 1
    Do classes in *Bar* have methods that return types declared in *Baz*? – Thorsten Dittmar Jun 16 '16 at 12:36
  • They do. Back in the old days, this would throw compiler error CS0012 ("The type is defined in an assembly that is not referenced"). Why is that no longer the case? – Bas Jun 16 '16 at 12:44
  • Only if you really call one of those methods in *Foo*. Do you? – Thorsten Dittmar Jun 16 '16 at 12:51
  • I do. Back in the old days, this would throw a compiler error, now it just silently references baz. Why this change? – Bas Jun 16 '16 at 13:04
  • Are you complaining about the fact that, overall, the system is now silently coping with what was previously a common mistake (referencing an assembly but not referencing other assemblies that are required). – Damien_The_Unbeliever Jun 16 '16 at 13:49
  • I'm not complaining, I'm asking why this behavior was changed. – Bas Jun 16 '16 at 14:11
  • @Bas: 1# You probably have a different assembly which references your Baz, The whole dependency chain will be downloaded and referenced automatically once you have one project/package that requires it. #2 Don't use DNX anymore, upgrade to RC2 which uses dotnet-cli tooling. DNX is deprecated and won't receive any updates and RC2 and all future versions of ASP.NET Core won't work on DNX anymore – Tseng Jun 20 '16 at 07:07

1 Answers1

1

No, it doesn't break abstraction as long as you install the Baz NuGet package in Foo as well, since you are declaring a dependency. The simple fact that the Baz library has public classes means any exe or dll file that references it can use those classes.

Since Foo requires Bar, and Bar requires Baz, the Baz.dll file should appear in the bin/ output folder when compiling Foo, since Foo needs classes in Bar. This means a simple using directive at the top of a file in Foo causes the CLR to import the Baz.dll file.

Now if Foo needs to directly use classes defined in Baz, I would recommend installing the Baz project as a dependency to Foo. It's OK for two libraries to need the same third party library. You just need to make sure both Foo and Bar need the same version.

If Foo and Bar need different versions, then I would recommend creating proxy classes or interfaces in Bar to encapsulate the behavior you need from Baz, so that the Foo project doesn't directly need to know about Baz.

Let's do a concrete example.

Example: A Blog web application using NHibernate

Without diving too deep or making any judgements about "best practices" this example revolves around an ASP.NET MVC project for a blog. It has a class library for the data access and business classes. The library installs the NHibernate NuGet packages.

  • Foo is the "Blog.Mvc" project, an ASP.NET MVC application

  • Bar is the "Blog.Core" project, a class library containing repository classes and interfaces, as well as the "Domain Models" or business classes.

  • Baz is NHibernate

So the question becomes:

Should the "Blog.Mvc" application directly uses classes from NHiberate?

No. The "Blog.Core" project should provide a layer of abstraction using repository interfaces:

Blog.Core

First, the "Domain Model" or business class:

public class Blog
{
    public virtual int Id { get; protected set; }
    public virtual string Name { get; set; }
}

The public interface for the blog repository:

public interface IBlogRepository
{
    Blog Find(int id);
}

Now the implementation of the interface, referencing classes and interfaces directly related to NHibernate:

public class BlogRepository : IBlogRepository
{
    private NHibernate.ISession session;

    private NHibernate.ISession Session
    {
        get
        {
            if (session == null)
                session = NHibernateSessionHelper.GetSession();

            return session;
        }
    }

    public Blog Find(int id)
    {
        return Session.Get<Blog>(id);
    }
}

The MVC project controllers use the repositories through their interface:

public class BlogsController : Controller
{
    public BlogsController(IBlogRepository repository)
    {
        this.repository = repository;
    }

    private IBlogRepository repository;

    public ActionResult Edit(int id)
    {
        Blog blog = repository.Find(id);

        if (blog == null)
            return HttpNotFound();

        BlogForm viewModel = new BlogForm(blog);

        return View(viewModel);
    }
}

While this might not be your exact situation, the Separation of Concerns outlined in this example makes your question moot.

If Foo requires classes in Baz, declare that as a dependency. If not, don't use classes declared in Baz in the Foo project. If Foo requires functionality from Bar which delegates some behavior to Baz, this doesn't break abstraction as long as Foo doesn't get passed any objects for classes defined in Baz. Bar needs to create it's own classes as either Data Transfer Objects, proxy classes, or some sort of object that implements an interface defined in Bar for consumption in Foo.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92
  • While this is all well, you have to note one problem which I have frequently: Foo references Bar, which references Baz. Baz is abstracted completely by Bar, so that Foo doesn't need a reference to Baz. However, when I build the solution, Bar.dll gets copied to Foo's `bin\debug` folder, but Baz.dll does not. Which can either be solved by some manual post-build thing or by referencing Baz from Foo... – Thorsten Dittmar Jun 16 '16 at 13:14
  • If Bar requires Baz, and Foo requires Bar, then the bin\Debug folder should have Baz.dll. Are you sure you are compiling using the "Debug" build configuration? – Greg Burghardt Jun 16 '16 at 17:59
  • Yes. I'm currently having this problem using NHibernate via nuget. – Thorsten Dittmar Jun 16 '16 at 18:18
  • Hm. You know, you might need to explicitly reference these third party DLLs so your project knows everyone it relies on, rather than assuming things. – Greg Burghardt Jun 16 '16 at 18:32
  • @ThorstenDittmar: You probably didn't abstract it well enough. If your abstracted library (Let's call it A) uses public types and these are referenced by a second assembly (let's call it B) in any of the following situations: **Public** or **protected** members, including the constructor, then it will have to add this reference to the App project (call it C), because this must be visible to C (i.e. if you subclass a type of B which has A types in it's parameters or constructor). To really abstract it you'd have to use these types only in private or internal members/constructors – Tseng Jun 20 '16 at 07:12
  • @Tseng It's not that the compiler is complaining about return types in a missing assembly, it's just that an assembly C referenced by an assembly B, which is referenced by A (without A having any other dependencies to C) will not be copied to A's output folder. This seems to be a known "problem". You can try this yourself by creating a solution and adding 3 projects where proj1 references proj2 and proj2 references proj3. If you compile that, proj3.dll will not be copied to proj1's \bin\debug folder. – Thorsten Dittmar Jun 20 '16 at 07:16
  • @ThorstenDittmar: Odd. Never experienced this and my usual constellation is Project.Contracts.dll < Project.Service/Infrastucture/Domain.dll < Project.Web and I never reference Project.Contracts.dll explicitly in the web application (both dnx based rc1-final as well as dotnet-cli based rc2-final). Usually the nuget should take care of resolving all the dependencies. Are you by chance using/mixing csproj and .xproj files? (i.e. having A and B as multi-target Class Library (package) and the web project as csproj – Tseng Jun 20 '16 at 08:46
  • @Tseng I don't think this is a nuget problem at all. As I said, you can try this yourself creating normal C# projects. – Thorsten Dittmar Jun 20 '16 at 08:47
  • @ThorstenDittmar: That's what I asked you. Are you referring to *.csproj projects or *.xproj? The question is about DNX/.NET Core/ASP.NET Core projects, which use *.xproj by default and project.json for package management and other stuff. It's **not** about ASP.NET 4.5 (MVC1/2/34/5) – Tseng Jun 20 '16 at 08:56
  • @Tseng It's all pure *.csproj projects. This entire discussion is a bit off topic here. – Thorsten Dittmar Jun 20 '16 at 09:04