This article describes a library to help write and run business logic in a .NET Core application where you are using Entity Framework Core (EF Core) library for database accesses. This follows on from my article “Architecture of Business Layer working with Entity Framework (Core and v6) – revisited”, where I describe my pattern for building business logic and showed something called a BizRunner. This article details a library called EfCore.GenericBizRunner that provides a much more comprehensive solution to the BizRunner described in that first article.
The EfCore.GenericBizRunner library is available as a NuGet package, and its source, with examples, is available on GitHub at https://github.com/JonPSmith/EfCore.GenericBizRunner. The project is an open-source (MIT licence).
Other articles about business logic and GenericBizRunner:
- Architecture of Business Layer working with Entity Framework (Core and v6)
- A library to run your business logic when using Entity Framework Core. (this article)
- How to write good, testable ASP.NET Core Web API code quickly.
- Wrapping your business logic with anti-corruption layers – NET Core.
UPDATE: Version 3 of GenericBizRunner is out. Now works well with ASP.NET Core’s Web API.
The aims of this article
- To tell you why the GenericBizRunner library/framework is useful.
- To show you where you might use the GenericBizRunner library in your application.
- To show you how to use the GenericBizRunner library via two examples in an ASP.NET Core application.
- To provide you with links to the GitHub repo GenericBizRunner , which contains the code of the library, an example an ASP.NET Core web application you can run locally to try the business logic code out, and its README file and the documentation in the project’s Wiki.
NOTE: I refer to the GenericBizRunner package as a “library”, because you can download it and install it. However, according to Neal Ford et al, technically the GenericBizRunner package is a framework because it calls your code, while a library is called by your code. I therefore use the term “framework” in contexts where that term is helpful.
I’m going assume you are familiar with .NET, EF Core and ASP.NET Core, and this article only worth reading if you are using Entity Framework Core (EF Core) library for database accesses in your business logic.
NOTE: This is a long article covering all the library’s capabilities, so if you just want to see an example of what it can do then I suggest to go straight away to Example 1. Or you can look at the Quick Start guide in the GitHub Wiki.
Setting the scene
According to Eric Evan’s, of Domain-Drive Design fame, “The heart of software is its ability to solve domain(business)-related problems for its users”. He also goes on to say “When the domain(business problem) is complex, this is a difficult task, calling for the concentrated effort of talented and skilled people”. I totally agree and I take business logic very seriously, which is why I have built a framework to help manage and run my business logic.
Over the years I have built some quite complex business logic and I have learnt that writing business logic is hard work. During those years I have progressively improved my business logic pattern to its current form, which I describe in the article “Architecture of Business Layer working with Entity Framework (Core and v6) – revisited” and in chapter 4 of my book “Entity Framework Core in Action” (use my discount code fccefcoresmith to get 37% off my book).
This article covers the next stage, where I have taken the common parts of my pattern and made them into a library. The diagram below describes a layered architecture, with the EfCore.GenericBizRunner framework acting as a Command, Mediator and Adapter software pattern.
If you aren’t familiar with that sort of layered architecture I suggest you read the article “Architecture of Business Layer working with Entity Framework (Core and v6) – revisited” where I describe how and why I split up my code in this way.
What does the library/framework do?
As you can see by the diagram above, I try to isolate the business logic from the other layers. The EfCore.GenericBizRunner framework helps this by providing much of the code you need to isolate the business logic, yet easily use it in your presentation layer. The key elements of the framework are:
- Better Dependency Injection: It allows the business logic to be used as a service that can be injected into a ASP.NET Core action using dependency injection (DI).
- Templates: It provides a templates for the business logic which include error handling and the definition on the particular in/out, sync/async etc. options the business logic needs.
- Anti-corruption layer: It provides isolation of the business logic from the presentation, via mapping DTOs (data transfer objects, also known and ViewModels in ASP.NET).
- Controls database commits: It manages the call to EF Core’s SaveChanges method, with optional validation, so that the business logic doesn’t have to handle the database or any errors it produces.
The diagram below shows the GenericBizRunner framework being used to run the business logic for handling a customer’s e-commerce order, which I describe in Example 1. The GenericBizRunner framework acts as a ServiceLayer (see previous diagram) to isolate, adapt and control the business logic. (Click figure to see a bigger version).
I should also say that the EfCore.GenericBizRunner framework is a rewrite of a private library I built for EF6.x back in 2015. The EF6.x framework was private because it was quite complex and hard to explain, but I have used it in several projects. My experience from using the original framework has helped me to create a better GenericBizRunner library, and I have been able to take advantage of the improvements in ASP.NET Core and EF Core. The EfCore.GenericBizRunner framework is still complex inside, but is easier to use that the original EF6.x framework.
There are many options in the library so I am going to explain what it does with two example pieces of business logic that are implemented in an ASP.NET Core application in the GitHub repo.
Example 1: Basic call of business logic
In my first example I want to show you all the steps in building your business logic and then calling it. To help me with this I am going to look at the business logic for placing an order in an e-commerce site that sells books. Order processing is typically quite complex, with requests to external systems. My example business logic isn’t that complicated as I am only adding the order to the database, but shows the how the GenericBizRunner works. Here is a screenshot of the checkout page.
When the Purchase button is pressed then the ASP.NET Core action PlaceOrder is called. This action method obtains a BizRunner service instance, linked to my PlaceOrderAction business class, via the injection of a the GenericBizRunner library’s IActionService interface. Let’s look at the ASP.NET Core Action called PlaceOrder.
[HttpPost] [ValidateAntiForgeryToken] public IActionResult PlaceOrder(PlaceOrderInDto dto, [FromServices]IActionService<IPlaceOrderAction> service) { if (!ModelState.IsValid) { //model errors so return to checkout page, showing the basket return View("Index", FormCheckoutDtoFromCookie(HttpContext)); } //This runs my business logic using the service injected into the Action's service parameter var order = service.RunBizAction<Order>(dto); if (!service.Status.HasErrors) { //If successful I need to clear the line items in the basket cookie ClearCheckoutCookie(HttpContext); //Then I show the order to confirm that it was placed successfully return RedirectToAction("ConfirmOrder", "Orders", new { order.OrderId , Message = "Your order is confirmed" }); } //Otherwise errors, so I need to redisplay the basket from the cookie var checkoutDto = FormCheckoutDtoFromCookie(HttpContext); //This copies the errors to the ModelState service.Status.CopyErrorsToModelState(ModelState, checkoutDto); return View("Index", checkoutDto); }
Some of the key lines are:
- Line 4: The [FromService] attribute tells ASP.NET Core to use dependency injection to resolve the interface I have given. This creates what I call a BizRunner, with the business class instance also injected into that BizRunner via the IPlaceOrderAction interface generic part.
- Line 13. This is where I use the BizRunner service to execute the business logic. The order processing business logic takes in a class called PlaceOrderDto (defined in the BizLogic layer), and outputs an Order class, which is the database entity class, i.e. it holds the information written to the database.
- Line 15: The BizRunner feeds back any errors that the business logic raises, or any validation errors found when writing to the database. You can see me checking whether I have errors or not, and taking the appropriate actions.
- Line 27: You may notice the extension method called CopyErrorsToModelState, which I wrote and is part of the EfCore.GenericServices.AspNetCore library. This takes the errors returned by the business logic, which is as a collection of ValidationResults, and copies these errors into the ModelState so ASP.NET can display this back to the user, hopefully against the actual field that caused the problem. If you were using a WebAPI you would need a method to return these errors to the calling application.
What business logic templates does it have?
The GenericBizRunner library provides a template for your business logic. There are twelve combinations of business logic formats, each represented by an interface:
Interface | In | Out | Async? | Write to Db |
IGenericAction<TIn, TOut> | Yes | Yes | No | No |
IGenericActionAsync<TIn, TOut> | Yes | Yes | Yes | No |
IGenericActionWriteDb<TIn, TOut> | Yes | Yes | No | Yes |
IGenericActionWriteDbAsync<TIn, TOut> | Yes | Yes | Yes | Yes |
IGenericActionInOnly<TIn> | Yes | No | No | No |
IGenericActionInOnlyAsync<TIn> | Yes | No | Yes | No |
IGenericActionInOnlyWriteDb<TIn> | Yes | No | No | Yes |
IGenericActionInOnlyWriteDbAsync<TIn> | Yes | No | Yes | Yes |
IGenericActionOutOnly<TOut> | No | Yes | No | No |
IGenericActionOutOnlyAsync<TOut> | No | Yes | Yes | No |
IGenericActionOutOnlyWriteDb<TOut> | No | Yes | No | Yes |
IGenericActionOutOnlyWriteDbAsync<TOut> | No | Yes | Yes | Yes |
As you can see, the GenericBizAction library provides interfaces that handle all the different types of business logic you could write. The library decodes what the business logic needs and checks that the data/call you use matches its interface.
The business logic is expected to implement the GenericBizAction’s IBizActionStatus interface which the business logic can use to report errors and other status items to the BizRunner. The GenericBizAction library provides an abstract class called BizActionStatus, which the business logic can inherit to provide that functionality. Here is an example of an In-and-Out, synchronous method that doesn’t write to the database, with all the key component parts pointed out (The business logic class shown is one I use in my unit testing, so the code inside the method is very simple).
The code for the order processing business logic is much more complicated and you can find it in the GutHub repro under PlaceOrderAction.cs . Also, if you clone the GitHub project and run the application locally the ASP.NET Core web app allows you to try out the order example just described and the next example below.
Example 2: Using the anti-corruption layer
In my second example I want to show you how the anti-corruption layer part of the GenericBizRunner framework works. The term “anti-corruption layer” is a Domain-Driven Design concept, and is normally about related to separating bounded contexts (see this article on the DDD use of this terms). I am not using my anti-corruption layer between bounded contexts, but to stop the Presentation Layer concepts/data from having any impact on my business logic.
NOTE: See Wrapping your business logic with anti-corruption layers – NET Core article for longer discussion on the anti-corruption concept.
To show the anti-corruption layer in action I have built a piece of business logic that allows the delivery date of an order to be changed. The business logic implements certain business rules to apply to any change, and ultimately it would also need to access an external site, such as a courier service, to check if the delivery date is possible.
Before I describe the code, with its use of the anti-corruption layer feature, let me first show you a screenshot of the ChangeDelivery page that the user would see.
To make this work properly the Presentation layer need to show a dropdownlist, which isn’t something that the business logic should be even thinking about – its a Presentation layer problem. This is where the GenericBizRunner library provides a couple of classes that provide the anti-corruption layer feature.
These anti-corruption layer classes are abstract classes that you can inherit, which then allows you to deal with the presentation layer item (known as the presentation-focused DTO). The abstract class makes you define the business logic’s associated class, known as the business-focused DTOs. You will see this in action later in this example, but lets look at stages in the “Change Delivery” example.
This screenshot shows the date that an order is going to be delivered on being changed from 16/1/2018 (see second line) to 17/1/2018. The stages in this are:
- Show the ChangeDeliver input page – the ChangeDelivery HTTP GET request
- Get the user’s input and run the business logic – ChangeDelivery HTTP POST request
- Copy the presentation-side DTO to the business DTO and run business logic
- Show a confirmation page if successful, or errors if failed.
Let’s look at the code for each of these sections, and see where and how the anti-corruption feature works.
1. Showing the ChangeDelivery input page
To produce the page shown in the screenshot above I need to read the current order and then display a list of possible dates in a dropdown. I do this by adding some properties for dropdown list, etc. to the a DTO class called WebChangeDeliverDto. But how do these properties get filled in? This is where the abstract class in the GenericBizRunner library called GenericActionsToBizDto comes in.
The class GenericActionsToBizDto has number of features, but the one I want to start with is the SetupSecondaryData method. This is called by the BizRunner to set up the presentation properties. Here is the code from my WebChangeDeliverDto class.
public class WebChangeDeliveryDto : GenericActionToBizDto<BizChangeDeliverDto, WebChangeDeliveryDto> { public int OrderId { get; set; } public string UserId { get; set; } public DateTime NewDeliveryDate { get; set; } //--------------------------------------------- //Presentation layer items // … various properties removed to focus on the SelectList public SelectList PossibleDeliveryDates { get; private set; } protected override void SetupSecondaryData(DbContext db, IBizActionStatus status) { var order = db.Set<Order>() .Include(x => x.LineItems).ThenInclude(x => x.ChosenBook) .SingleOrDefault(x => x.OrderId == OrderId); if (order == null) { status.AddError("Sorry, I could not find the order you asked for."); return; } // … other code removed to focus on creating SelectList PossibleDeliveryDates = new SelectList( FormPossibleDeliveryDates(DateTime.Today)); var selected = PossibleDeliveryDates .FirstOrDefault(x => x.Text == NewDeliveryDate.ToString("d")); if (selected != null) selected.Selected = true; } //... other code left out }
The part I want you to see is the SetupSecondaryData method, at the bottom of the class. This is executed by two BizRunner methods:
- GetDto<T>: This creates the DTO class, optionally sets some properties (see next code example below) and the runs the the SetupSecondaryData
- ResetDto: If you have errors and want to re-display the page, you need to run the SetupSecondaryData method again, which the BizRunner’s ResetDto method does.
The idea is that the SetupSecondaryData method sets up any properties in the DTO which are needed to build the page shown to the user.
The code below displays the ChangeDelivery page. You can see it using the BizRunner’s GetDto<T> method to set up the presentation data.
public IActionResult ChangeDelivery(int id, [FromServices]IActionService<IChangeDeliverAction> service) var dto = service.GetDto<WebChangeDeliveryDto>(x => { x.OrderId = id; x.UserId = GetUserId(HttpContext); }); service.Status.CopyErrorsToModelState(ModelState, dto); return View(dto); }
Some of the key lines are 4 to 8, where the service.GetDto<WebChangeDeliveryDto> method is called. This will:
- Create a new instance of the WebChangeDeliveryDto
- Executes the action I have provided in the parameter, which sets properties in the new WebChangeDeliveryDto
- Then it runs the SetupSecondaryData method in the WebChangeDeliveryDto class, which I described earlier.
NOTE: The code I wrote in the the SetupSecondaryData method reports an error if the order was missing. I did this to show you how something like that would work. I needed to call the CopyErrorsToModelState extension method to copy any errors found to ASP.NET Core’s ModelState.
2. Get the user’s input and run the business logic
Once the user has selected the new delivery date and pressed the Change button the ASP.NET Core’s POST Action method is called. This does certain checks, but the primary thing we are interested here is the running of the ChangeDeliverAction business class, which you can see in line 17 of the code below.
[HttpPost] [ValidateAntiForgeryToken] public IActionResult ChangeDelivery(WebChangeDeliveryDto dto, [FromServices]IActionService<IChangeDeliverAction> service) { if (!ModelState.IsValid) { //I have to reset the DTO, which will call SetupSecondaryData //to set up the values needed for the display service.ResetDto(dto); //return to the same view to show the errors return View(dto); } //This runs my business logic using the service injected in the //Action's service parameter service.RunBizAction(dto); //… rest of action left out. I deal with that in section 3. }
3. Copy the presentation-side DTO to the business DTO and run business logic
One of my rules is that the Business Logic data classes should be isolated from other layers in the application. This is a separation of concerns principle, and helps me to focus just on the business issue I am trying to solve. To help me with this the GenericBizRunner library provides the abstract GenericActionToBizDto class for input and the abstract GenericActionFromBizDto class for output. These provide the anti-corruption layer between the business logic code and the presentation code.
In this “Change Delivery” example I needed a dropdownlist of potential delivery dates, which is a presentation-focused item so I produced a presentation-focused input class called WebChangeDeliveryDto (which you have seen already when I was talking about the SetupSecondaryData method). This class inherits from the abstract GenericActionToBizDto class, and right at the top you need to define which business logic DTO this class is linked to, in this case the BizChangeDeliveryDto, as you can see this in the class definition below.
public class WebChangeDeliveryDto : GenericActionToBizDto< BizChangeDeliverDto, WebChangeDeliveryDto> { … other code left out
The abstract class uses this definition to set up a mapping, via the AutoMapper library, between a presentation-layer focused DTO and the business-focused DTO. So, in this “Change Delivery” example, when the BizRunner is asked to run some business logic it notices that the input isn’t the BizChangeDeliverDto class that the business logic needs, but is a class that inherits from GenericActionToBizDto class. It then uses the CopyToBizData method inside the GenericActionToBizDto class to copy only the properties that the business logic’s DTO needs from the input class.
The default mapping that AutoMapper uses is “copy similar named properties”, which in this case only copies the first three properties to the business input class called BizChangeDeliveryDto. This is shown in the figure below.
This simple copy-by-name works 80% of the time, but the abstract classes have plenty of options to allow you to tweak the copy – see the article Using AutoMapper: Creating Mappings.
4. Show a confirmation page if successful, or errors if failed
Finally, I want to cover what happens when the business logic returns. This depends on what happened with the business logic. Let me show you the second half for the ChangeDeliver POST action.
[ValidateAntiForgeryToken] public IActionResult ChangeDelivery(WebChangeDeliveryDto dto, [FromServices]IActionService<IChangeDeliverAction> service) { //… first part of the action removed for clarity service.RunBizAction(dto); if (!service.Status.HasErrors) { //We copy the message from the business logic to show return RedirectToAction("ConfirmOrder", "Orders", new { dto.OrderId, message = service.Status.Message }); } //Otherwise errors, so I need to redisplay the page to the user service.Status.CopyErrorsToModelState(ModelState, dto); //I reset the DTO, which will call SetupSecondaryData i set up the display props service.ResetDto(dto); return View(dto); //redisplay the page, with the errors }
Success path
If the business logic, and the write to the database, were successful then the service.Status.HasErrors property is false. In that case I want to show a confirmation page. In this example I include the service.Status.Message property, which contains a string defined by my business logic – in this example it says “Your new delivery date is 17/01/18”.
The Message property is one way to return confirmation messages when the business logic finishes successfully. If there were no errors it carries a success message, otherwise it has a message saying there were n errors. Note: there are several default messages for success and failure cases – see this documentation on the service.Status property.
Failure path
If there are errors, you normally what to redisplay the GET page, with a list of errors. You have seen the CopyErrorsToModelState extension method in the first example – it copies the Errors into the ASP.NET ModelState. So that the errors will be displayed when the view is shown.
This “Change Delivery” example also needs to re-run the SetupSecondaryData method, so that the properties in the View are set up. I do that using the BizRunner’s ResetDto method (see line 19 in the code above).
Other features not covered in this article
This article is already very long, so I have left out several parts. Here is a list of features, with links to the documentation in the project’s Wiki.
- Using GenericBizRunner with ASP.NET Web API. Version 3 of GenericBizRunner works well with ASP.NET Web API and Swagger. There is also a support library called EfCore.GenericServices.AspNetCore to support building Web API responses.
- Setting up via Dependency Injection (DI). .NET Core uses DI a lot, and GenericBizRunner is designed to work with DI. You do need to set up the GenericBizRunner’s DI in the ASP.NET Startup
- Database validation. By default, the BizRunner will apply validation to any data added/updated by business logic. This can be turned off globally and/or per business class.
- How to use multiple DbContexts with GenericBizRunner. One of the biggest concepts of Domain-Drive Design is approach is bounded contexts. You can use bounded contexts in your database by having multiple DbContext’s (see my book, section 10.6) and GenericBizRunner supports this.
- Configuration options for GenericBizRunner. There are a few options for configuring things inside GenericBizRunner.
Conclusion
Well done if you get to the end, as it’s a big article! The GenericBizRunner has many options to make it versatile but that does make the documentation quite long (this was one reason I didn’t make the original EF6.x GenericBizRunner library open-source – too much documentation needed!). Hopefully this article, and the runnable example application in the EfCore.GenericBizRunner GitHub repo, will make it easier for you to understand the GenericBizRunner framework.
The core concept is that writing business logic is difficult so isolating the business logic code from other layers allows the developer to concentrate on the business/domain problem they are trying to solve (see chapter 4 of my book for more on this concept). The GenericBizRunner helps me in five main ways:
- DI-friendly. It is designed to make accessing business logic very simple, via specific interfaces and dependency injection.
- Templates: It’s set of interfaces provides a common format for your business logic patterns, including help on error reporting and status feature.
- Controls database writes: It provides a harness with can run any business logic that implements one of the GenericBizRunner’s interfaces, with optional writing to an EF Core database.
- Anti-corruption layer feature: It allows the business logic to be isolated from the presentation layer through a copy system that extracts the data the business logic needs from a presentation-focused class.
This new EF Core version of the GenericBizRunner framework has several improvements from using the original EF6.x version I built some years ago. The experience of using that first framework showed me that the GenericBizRunner library was useful, so I put the time into building the EF Core version, and finally biting the bullet on writing the documentation. You can find out more about the EF Core library via the EfCore.GenericBizRunner GitHub repo and its Wiki.
Other useful articles/books
- Article Architecture of Business Layer working with Entity Framework (Core and v6) – revisited
- How to write good, testable ASP.NET Core Web API code quickly.
- Wrapping your business logic with anti-corruption layers – NET Core.
- The book Entity Framework in Action, by Jon P Smith (use my discount code fccefcoresmith to get 37% off my book).
- The book Domain-Drive Design, by Eric Evan’s
- Article Using .NET Generics with a type derived at runtime (some of the internal techniques I use to build the EF6.x library, and some performance timings: Note: .NET Core is faster!)
Happy coding!
Hi! I have been studying your approach to layered architecture, also bought your book “Entity Framework Core In Action”. I am a bit confused about the purpose of your GenericBizRunner framework, as it seems to me that there is some overlapping with GenericServices. Can GenericBizRunner and GenericServices be used together or they serve the same purpose? This is not clear for those who have read your other posts on your architectural approach.
Hi Marco,
Its a good question. The simplistic answer is: GenericBizRunner is for business logic and GenericServices is for CRUD. But its never that simple, because there isn’t a simple dividing line between business logic and CRUD. Also, with DDD-styled entity classes you can put business logic inside a class method/ctor, which can be called by GenericServices.
To help you decide what you want to do here is how I use these two libraries.
– If it is CRUD, with only validation checks, then I use GenericServices (with a few expections 🙂 ).
– If there are business rules, or calls to other services, then I use GenericBizRunner.
The other big thing is that the DDD methods that GenericServices calls cannot (at the moment) be async – only the database accesses can be async. But GenericBizRunner handles async methods which means if your business code needs to be called async, then you need GenericBizRunner.
Thanks a lot for your quick answer, Jon. You confirm the impression I had in mind. Kudos for the all the work you do, I am learning a lot from your posts and book.