We interrupt this usual flow of entertainment posts to bring you a special PSA. We take you live, now, to our studios in Burbank. Over to you, Chet.

This post is going to be really niche (and really long, sorry), but as this is uncharted territory for me figuring this out took quite a while, between false starts, partial information, and a good few days of trial and error. Since I had such a hard time finding solid answers to what I thought would be a simple and prevalent question, I wanted to codify what I have learned here where I can.

Preamble (Yes, it’s One of Those Posts)

So I’m working on YAP (yet another project), the specifics of which are unimportant for this post (no, really). What is important is that I am working with ASP.NET Core, which is the next-to-last iteration of Microsoft’s .NET platform. Core, unlike the older .NET Framework embraces Microsoft’s new-found love of playing nice (thanks to Satya Nadella) with other platforms. Framework ran on Windows machines, whereas Core can be compiled to run on Windows, Macs, Linux, Android, and other operating systems, meaning that as a .NET developer (specifically a .NET web developer) I am no longer limited to targeting only Windows machines. Like any new venture, getting started is an uphill climb, and a lot of things that I can do by muscle memory in Framework are different or just slightly off-center with Core.

Model-View-Controller, Entity Framework, and Data

The pattern I’m most used to working with is the “model-view-controller” paradigm. In this scheme, abbreviated MVC, web pages are “views” which get their marching orders from “controllers” who in turn get data and business logic results from “models”. Models represent things like business logic or database tables or more often than not, custom-designed “data couriers” who translate data from the back-end into something nicer that the front end can use to work with.

Traditionally I have worked with SQL Server, and traditionally I’ve hooked my sites up to use stored procedures residing in the database. A database is a collection of data, arranged in columns which live in tables. You can ask the database for data from one or more tables by writing queries. A stored procedure is a small script which takes in parameters and returns data via queries. For example, if my app needs to get a list of users, I would call a stored procedure that has been written specifically to return a list of user names, their office location, phone number, email address, and supervisor name. That data may live in several tables, so querying stored procedures from a website’s data layer puts processing duties on the database, leaving the website to handle the tasks related to using that data.

Now, though, there’s Entity Framework. Rather than using stored procedures housed in the database, EF connects to the database and, using analogous constructs that are either built first and used to create a database, or are built from a database structure, can query the database using a query language called LINQ. If there’s a “users” table in my database, then my application will have a “users” class which has the same fields as the table it’s named after. EF allows me to use these classes to write queries in C# — my native language — and not T-SQL — which I can stumble through well enough. This makes things easier for me, and removes the need to back up stored procedures somewhere in case the database blows up or someone overwrites it (as happens with alarming regularity at my day job, but as a means to refresh development and QA environments).

So that’s a crash course in MVC and EF, and hopefully I’ve explained it well enough to continue to the real point of this post.

Separation of Concerns

The idea of “Separation of Concerns” centers on the belief that when putting together a product, the product’s inner workings should be divided up so that no one part is doing any more than it should. Throwing all Javascript into one massive file is convenient for a small site or for developers who can’t or won’t be bothered, but breaking out AJAX functionality from UI updating keeps things tidy; future-you (or other developers) won’t have to plow through lines and lines of unrelated code to find something specific, and it’s obvious which file is dedicated to what purpose.

Most of my sites are small enough that I don’t bother with a high level of SoC. The logic is specific to that app, and it’s easier to contain UI, business logic, and data manipulation in one project for purposes of maintenance and portability. But with this personal project, I wanted to learn to do things “the Core way”, which is where Dependency Injection comes in. I won’t rehash what DI means, so if you want to know you can revisit my epiphany on the subject.

OK, here’s the take-away. In this project, I wanted to completely separate the data access portion from the website portion. This means two projects, a web app and a database class library, the first referencing the second. Normally this wouldn’t be an issue in Framework, but I was having a hell of a time trying to create a business logic layer in Core within my web app that injects this separate database class library. After days of pawing through various articles and questions on StackOverflow, I finally figure out what I needed to do.

Data Project

I created my new data project as a class library, but there was an issue: .NET 5 had just been released (literally, the version flipped over as I was getting down to work on this project) and I couldn’t create a class library that worked with my existing web project. I had to create the library as a .NET Standard 2.1, which means nothing except to those for whom it means something, so let’s move on.

This class library contains several “code-first” classes. This means that I write the code first, and the code handles the construction of the database. I have about 12 classes right now that represent some of the data I’ll need.

In addition, the library contains a data context class. The data context class handles the representation of the individual data classes to the rest of the application. It also handles “seeding” during database creation. I seeded some dummy data in there for testing, but this process is more useful for populating reference tables with data that needs to be present from day one (as during development we might need to trash the database on occasion, and don’t want to have to manually re-enter testing data each time).

Web Project

I had originally started the project using the “whole shebang” design of throwing MVC and data access into one project, so I had the framework kind of up and running. I added my data layer project as a dependency to the web project so I could access the contents of the data layer. But this was not enough.

In the web app’s startup.cs file, I had to add a special reference to the data library so it can use information that the web project provides, specifically the connection string that allows connection to the database. Normally this is straightforward, but because the consumer was another class library, this setup needed a different format.

services.AddDbContext<ADContext>(options =>
{
    options.UseSqlServer
      (Configuration.GetConnectionString
         ("CONN_STR_NAME"),
assembly =>          assembly.MigrationsAssembly
    (typeof(ADContext).Assembly.FullName));
});

Registering my context from the data library (ADContext) as a service, I am indicating where the connection string is (appsettings.json of the web project) and am referring to the assembly (compiled class library) where ADContext resides.

With this in hand, I had to create a web project business logic layer that accesses the data layer. First, I need an interface.

public interface IBusinessLayer{
   //definitions go here
}

Then I created a working layer which implements the interface.

public class BusinessLayer: IBusinessLayer {
   //implementation of definitions from interface
}

The problem I had was that the normal DI pattern injects the data context into the web controller. You can see this in action in my previous post. I didn’t want to do this, because then I’d have to pass the context down into an instance of my BusinessLayer, thereby completely negating any benefit of DI I might have wanted. I just could not find anyone who gave simple examples of how to inject classes into non-controller constructs, until I did.

To set it up, we have to go back to startup.cs and right below where we set our service definition of AddDbContext, we need to add this:

services.AddScoped<IBusinessLayer, BusinessLayer>();

Now, our business layer implementation can look like this:

public class BusinessLayer: IBusinessLayer
{
    //Our local database context
    private readonly ADContext _context;

    public BusinessLayer(ADContext context)
    {
        _context = context;
    }

    ...
}

Normally the use of ADContext as an argument to a class constuctor would happen at the controller level, but instead, our controller looks like this:

public class HomeController: Controller
{
    private readonly IBusinessLayer _businessLayer;
    public HomeController(IBusinessLayer businessLayer)
    {
        _businessLayer = businessLayer;
    }
}

Notice that we’re not passing a specific type (BusinessLayer) into the constructor, but the interface (IBusinessLayer). This means that we’re injecting any class which adheres to the standards set by IBusinessLayer. We’ve moved our injection down one level, so the database context is injected into the BusinessLayer, and BusinessLayer is injected into the controller via IBusinessLayer. When I want to call a method within the BusinessLayer, say to get my list of users, I can call it like this from the controller:

public List<User> GetUsers()
{
    //_businessLayer is scoped at class level, so I have access to
    //it here, and I didn't have to pass it into this function!
    List<User> _users = _businessLayer.GetUsers();
    return _users;
}

Theoretically at this point, I could keep layering separations of concern into the web project, although this falls apart if I try to create a vertical construct of web project > business layer project > data access project. That’s a search for another day.

I guess if you were expecting a bigger bang for the buck spent reading this massive wall of text, I’m sorry. In hindsight, this seems like a stupidly simple situation with a stupidly simple solution, but the problem I was having was that I was working with a new way of looking at familiar things — .NET Core instead of the .NET Framework I’ve been using for the past 20 years or so. The whole services thing is new to me, and because it’s very specific in what it does, finding a case online that matched what I was trying to do with the specific issues I was having was insanely difficult. Hindsight is 20/20, thankfully, and now that I have implemented this in testing I understand it enough to feel comfortable using it in other projects going forward. That is, assuming I make it through this current project.

Scopique

Owner and author.

Leave a Reply

Your email address will not be published. Required fields are marked *