23

I'm struggling to find a graceful way to keep my DAL layer separate to my MVC/UI layer in ASP.NET 5 thanks to the new built in Dependency Injection which I want to use.

For example I have an ASP.NET 5 project, a Business Layer project and a Data Access Project where I have various Entity Framework code such as entities and contexts. in ASP.NET 5 to get the context set up and targeting a database the main documentation is suggesting I do something like this in my StartUp.cs class

services.AddEntityFramework()
    .AddSqlServer()
    .AddDbContext<BookContext>(options =>
    {
        options.UseSqlServer(Configuration.Get("Data:ConnectionString"));
    });

This means that I now have to reference my DAL in what is basically my UI layer, something which for years has always been bad practice according to the various experts and blog posts around.

One way I have worked around this is to create two new projects, one is a CompositeRoot project which contains factory classes to generate my business classes which then access the DAL and also a Utilities project with a Configuration class in which has a ConnectionString property which I can pass into my Context, I then use the built in DI to wire everything up and avoid referencing my DAL in my UI layer. But I've come across issues with the latest version of Entity Framework (beta 7) as it now doesn't seem possible to specify a connection string either in the constructor of the context or in the overrideable OnConfiguration method. Plus, all the documentation so far doesn't seem to care about this mixing of concerns at all. Is this just the way we do things now? Trust that developers won't do 'bad' things like reference DAL classes in the UI directly? Or is there a pattern people are employing to keep things SOLID with this new built in DI/configuration for ASP.NET 5?

davidmdem
  • 3,763
  • 2
  • 30
  • 33
BenM
  • 4,218
  • 2
  • 31
  • 58
  • *"This means that I now have to reference my DAL in what is basically my UI layer"*. This is incorrect. This code is *not* in your UI layer; this is your *composition root layer*. You are mistaking layers (logical artifacts) for assemblies (deployment artifacts) and you implicitly chose to have two layers in one single assembly. For more in depth explanation, please read this answer: https://stackoverflow.com/a/9505530/264697. – Steven Sep 16 '15 at 06:52
  • JK, yes I do deem this acceptable and not too broad as I am referring to a specific problem with a specific framework. And thank you Steven, I will look into your post. I think my main problem is that Microsoft have forced their project template to contain all of the MVC items (controllers/views) and also the composition in the StartUp class meaning that you have to have a project level reference to your DAL if your EF code is contained there and you don't have a separate composition root. – BenM Sep 16 '15 at 07:46
  • Why do you say you can't separate it? Why cant you create a separate project called Database. Then BookContext is just a reference to that project. Finally the options parameter overrides the connection string you set. – hidden Dec 18 '15 at 17:52
  • Just in case you dint know hot to set up your console app : http://docs.asp.net/en/latest/dnx/console.html#creating-a-console-app – hidden Dec 18 '15 at 18:15

8 Answers8

10

If you ask 10 civil architects to build you a bridge, you’ll end up having 10 different architectures. Not one will be the same and not one will be better than the other.

Regardless of how many best practices and design patterns they apply, each architect will justify their idea. Some will be overzealous while others will keep it simple and get the job done. Amongst other things, budget, delivery dates and craftsmanship will have a direct impact on type of architecture you decide.

The same rule applies with software architects.

I’ve seen my fair share of architects having the best intentions in the world only to realize that the UI layer has a dependency on the DAL. Perhaps the reasoning behind this is that:

  • They haven't thought of it
  • They don't really care about it
  • It facilitates DI since you see every layer which in turn makes it easy to map Interfaces to their Implementation.

Back in MVC 5, I had the following layers:

-Contoso.Core (Class Library)
-Contoso.Data (Class Library)
-Contoso.Service (Class Library)
-Contoso.Web (asp.net MVC 5)
-Contoso.Web.Configuration (Class Library)

The Web.Configuration layer had a dependency on Core, Data and Service. This layer is where I would configure my DI stuff.

The Web layer did not have a dependency on the Data layer and to get stuff started, I was using the Bootstrapper Nuget Package.

Perhaps you could achieve something similar with ASP.NET 5

I’d love for Microsoft (or anyone) to create a working project template that is more geared towards enterprise level samples using a decoupled approach or even something like an Onion Architecture sample.

In the end, whatever I consider like a reasonable decoupled architecture might be too much for some while not enough for others...

Feel free to share your findings as I’m curious to know how you’ve managed to make it work.

Vlince
  • 5,885
  • 7
  • 45
  • 62
  • "I’d love for Microsoft (or anyone) to create a working project template that is more geared towards enterprise level samples using a decoupled approach or even something like an Onion Architecture sample." Have you looked at https://github.com/ardalis/CleanArchitecture? – keipala Nov 30 '22 at 09:05
3

Is this really a major problem?

You UI layer will have a dependency on your data layer and there will be some reference somewhere.

The StartUp.cs adds various services and configuration, so most of these references are in one place.

Mark Redman
  • 24,079
  • 20
  • 92
  • 147
2

You can probably get around that using a Service Locator.

For example, you have an interface in your root/core/abstractions project:

public interface IServiceConfiguration
{
    void ConfigureServices(IServiceCollection services, IConfigurationRoot configuration);
}

In your StartUp.cs, find all types that implements IServiceConfiguration and use that to register your external services.

public void ConfigureServices(IServiceCollection services)
{
    var currentAssembly = typeof(Startup).Assembly;

    // Limit to project assemblies
    var @namespace = currentAssembly.FullName.Split(',')[0].Split('.')[0];
    var assemblies = currentAssembly.GetReferencedAssemblies()
                            .Where(a => a.Name.StartsWith(@namespace, StringComparison.Ordinal))
                            .Select(a => Assembly.Load(a));

    foreach (var assembly in assemblies)
    {
        // Assembly.ExportedTypes is not supported in dnxcore50 so you need to take it off your frameworks node in project.json.
        foreach (var config in assembly.ExportedTypes.Where(t => typeof(IServiceConfiguration).IsAssignableFrom(t) && !t.IsAbstract))
        {
            ((IServiceConfiguration)Activator.CreateInstance(config)).ConfigureServices(services, Configuration);
        }
    }

    // More service registrations..
}

And in your DAL, you can add a class that will register EF:

public sealed class EntityFrameworkServiceConfiguration : IServiceConfiguration
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddEntityFramework()
                    .AddSqlServer()
                    .AddDbContext<BookContext>(options =>
                    {
                        options.UseSqlServer(Configuration.Get("Data:ConnectionString"));
                    });
    }
} 

Not everyone would need this type of decoupling though. In some ways, it makes more sense to just register everything in StartUp.cs. You would've referenced your DAL in the web project anyway (unless everything is through dynamic discovery) and that means that it already knows something about your DAL.

Dealdiane
  • 3,984
  • 1
  • 24
  • 35
0

I commented above but I figure many people might have the same question. enter image description here

Add the brand new ClassLibraryPackage

Edit you project.json file

{
  "version": "1.0.0-*",
  "description": ":)",
  "authors": [ "JJVC" ],
  "tags": [ "" ],
  "projectUrl": "JJVCblog.com",
  "licenseUrl": "",

  "frameworks": {
    "dnx451": {
      "frameworkAssemblies": {
      },
      "dependencies": {
        "EntityFramework.Commands": "7.0.0-rc1-final"
      }
    }
  },

  "commands": {
    "ef": "EntityFramework.Commands"
  },

  "dependencies": {
    "EntityFramework.MicrosoftSqlServer": "7.0.0-rc1-final",
    "Microsoft.AspNet.Identity": "3.0.0-rc1-final",
    "Microsoft.AspNet.Identity.EntityFramework": "3.0.0-rc1-final"
  }
}

Do your code first things

    public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole,string>
        {

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {

                optionsBuilder.UseSqlServer(@"server=localhost;Database=Whatever;Integrated Security=True;MultipleActiveResultSets=True;Connect Timeout=60;");
            }
            DbSet<ForgotPasswordResetHistory> ForgotPasswordResetHistory { get; set; }

            protected override void OnModelCreating(ModelBuilder builder)
            {

                base.OnModelCreating(builder);
                base.OnModelCreating(builder);

            }



        }
 public class ForgotPasswordResetHistory
    {
        [Key]
        public int ForgotPasswordResetHistoryId { get; set; }
        public bool UserTriedToResetPassword { get; set; }
        public DateTime LinkExpirationDate { get; set; }
        public string SecretCodeForTheLink { get; set; }
        public DateTime PasswordRecoveryRequestDate { get; set; }
        public ApplicationUser ApplicationUser { get; set; }
        public int Test { get; set; }
    }

Launch cmd prompt in the context of your folder. dnu restore dnx ef migration whateverurmigrationnameis dnx ed database update

Now go to your "web" project and a reference to your db. In your web project "project.json" you should see it invoked under dependencies.

 "ConsoleApp2.Database": "1.0.0-*"

Good luck!

hidden
  • 3,216
  • 8
  • 47
  • 69
0

I faced the same issue while working on a demo for MVC 6 today. after some research/trial and error, when i removed

"EntityFramework.SqlServer": "7.0.0-beta8"

from project.json file everything worked fine with me.

0

I was at the 2016 Global Azure Bootcamp recently and the sample project that we worked on (purely from an Azure perspective) has these separations of concerns laid out very well. I haven't spent a lot of time dissecting the project, but if anyone wants to look and see if this is a suitable N-Tier split please post back. The link is http://opsgilitytraining.blob.core.windows.net/public/modern-cloud-apps-student.zip. The separations are laid out as follows:

  • Contoso.Apps.SportsLeague.Web Contoso Sports League e-commerce application
  • Contoso.Apps.SportsLeague.Admin Contoso Sports League call center admin application
  • Contoso.Apps.SportsLeague.WorkerRole Handles receipt generation for orders
  • Contoso.Apps.SportsLeague.Data Data tier
  • Contoso.Apps.SportsLeague.Offers API for returning list of available products
  • Contoso.Apps.PaymentGateway API for payment processing
Reza
  • 5,314
  • 5
  • 21
  • 35
0

The reason that there is a reference to the data layer from the UI layer is because we need to register the DbContext (if you are using EF) in the IoC container.

You can get around this project/package reference using MEF (Manage Extensibility Framework). Check implementation of MEF in .NET Core as it has change from the previous .NET Framework.

In MEF, you can do it by convention or by configuration.

alltej
  • 6,787
  • 10
  • 46
  • 87
-1

you can implement extension methods for IServiceCollection in your business layer and in your data access layer.

then in Web Startup you could call the extension methods on your business layer which in turn call the extension methods on your data layer

so this code could be in the data layer extension(s):

services.AddEntityFramework()
                    .AddSqlServer()
                    .AddDbContext<BookContext>(options =>
                    {
                       options.UseSqlServer(Configuration.Get("Data:ConnectionString"));
                   });

that would things separated and the web only needs using statements in the startup file for references to the business layer

Joe Audette
  • 35,330
  • 11
  • 106
  • 99
  • 4
    I think the point he's trying to make is that by UI/Web shouldn't know or care about EF. – Brian Feb 12 '16 at 17:35