Download as pdf or txt
Download as pdf or txt
You are on page 1of 10

ASP.

NET Core Web API | Part 1 GenericBizRunner

A library to run your business logic when using Entity Framework Core
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).

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: 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.

Página 1 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

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).

Página 2 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

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.

Página 3 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

1
2 [HttpPost]
3 [ValidateAntiForgeryToken]
4 public IActionResult PlaceOrder(PlaceOrderInDto dto,
5 [FromServices]IActionService<IPlaceOrderAction> service)
6 {
7 if (!ModelState.IsValid)
{
8
//model errors so return to checkout page, showing the basket
9 return View("Index", FormCheckoutDtoFromCookie(HttpContext));
10 }
11
12 //This runs my business logic using the service injected into the Action's service parameter
13 var order = service.RunBizAction<Order>(dto);
14
15 if (!service.Status.HasErrors)
16 {
17 //If successful I need to clear the line items in the basket cookie
18 ClearCheckoutCookie(HttpContext);
19 //Then I show the order to confirm that it was placed successfully
20 return RedirectToAction("ConfirmOrder", "Orders",
new { order.OrderId , Message = "Your order is confirmed" });
21 }
22
23 //Otherwise errors, so I need to redisplay the basket from the cookie
24 var checkoutDto = FormCheckoutDtoFromCookie(HttpContext);
25 //This copies the errors to the ModelState
26 service.Status.CopyErrorsToModelState(ModelState, checkoutDto);
27 return View("Index", checkoutDto);
28 }
29

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?


Página 4 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

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 .

Página 5 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

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:

1. Show the ChangeDeliver input page – the ChangeDelivery HTTP GET request
2. Get the user’s input and run the business logic – ChangeDelivery HTTP POST request
3. Copy the presentation-side DTO to the business DTO and run business logic
4. 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.
1 public class WebChangeDeliveryDto :
GenericActionToBizDto<BizChangeDeliverDto, WebChangeDeliveryDto>
2 { public int OrderId { get; set; }
3 public string UserId { get; set; }
4 public DateTime NewDeliveryDate { get; set; }

Página 6 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

5 //---------------------------------------------
6 //Presentation layer items
7 // … various properties removed to focus on the SelectList
8 public SelectList PossibleDeliveryDates { get; private set; }
9 protected override void SetupSecondaryData(DbContext db, IBizActionStatus status)
10 {
11 var order = db.Set<Order>()
12 .Include(x => x.LineItems).ThenInclude(x => x.ChosenBook)
13 .SingleOrDefault(x => x.OrderId == OrderId);
14
if (order == null)
15 {
16 status.AddError("Sorry, I could not find the order you asked for.");
17 return;
18 }
19
20 // … other code removed to focus on creating SelectList
21
22 PossibleDeliveryDates = new SelectList(
23 FormPossibleDeliveryDates(DateTime.Today));
24 var selected = PossibleDeliveryDates
25 .FirstOrDefault(x => x.Text == NewDeliveryDate.ToString("d"));
26 if (selected != null)
selected.Selected = true;
27
}
28 //... other code left out
29 }
30
31
32
33
34

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.
1
2 public IActionResult ChangeDelivery(int id,
3 [FromServices]IActionService<IChangeDeliverAction> service)
4 var dto = service.GetDto<WebChangeDeliveryDto>(x =>
5 { x.OrderId = id;
x.UserId = GetUserId(HttpContext);
6 });
7 service.Status.CopyErrorsToModelState(ModelState, dto);
8 return View(dto);
9 }
10

Some of the key lines are 4 to 8, where the service.GetDto<WebChangeDeliveryDto> method is called. This will:

1. Create a new instance of the WebChangeDeliveryDto

Página 7 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

2. Executes the action I have provided in the parameter, which sets properties in the new WebChangeDeliveryDto
3. 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.
1 [HttpPost]
2 [ValidateAntiForgeryToken]
3 public IActionResult ChangeDelivery(WebChangeDeliveryDto dto,
4 [FromServices]IActionService<IChangeDeliverAction> service)
5 {
6 if (!ModelState.IsValid)
7 {
8 //I have to reset the DTO, which will call SetupSecondaryData
9 //to set up the values needed for the display
10 service.ResetDto(dto);
11 //return to the same view to show the errors
12 return View(dto);
13 }
14
15 //This runs my business logic using the service injected in the
16 //Action's service parameter
17 service.RunBizAction(dto);
18
19 //… rest of action left out. I deal with that in section 3.
20 }

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.

1 public class WebChangeDeliveryDto :

Página 8 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

2 GenericActionToBizDto< BizChangeDeliverDto, WebChangeDeliveryDto>


3 {
4 … 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.
1 [ValidateAntiForgeryToken]
2 public IActionResult ChangeDelivery(WebChangeDeliveryDto dto,
3 [FromServices]IActionService<IChangeDeliverAction> service)
4 { //… first part of the action removed for clarity
5 service.RunBizAction(dto);
6 if (!service.Status.HasErrors)
7 { //We copy the message from the business logic to show
8
return RedirectToAction("ConfirmOrder", "Orders",
9
new { dto.OrderId, message = service.Status.Message });
10
}
11
12 //Otherwise errors, so I need to redisplay the page to the user
13 service.Status.CopyErrorsToModelState(ModelState, dto);
14 //I reset the DTO, which will call SetupSecondaryData i set up the display props
15 service.ResetDto(dto);
16 return View(dto); //redisplay the page, with the errors
17 }

Página 9 de 10
ASP.NET Core Web API | Part 1 GenericBizRunner

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.

Página 10 de 10

You might also like