As .NET developers, the tools we have are pretty impressive. You can choose from numerous UI frameworks such as UWP, WPF, Windows Forms, and various Web client UI frameworks like Angular and React. And you have multiple options for interacting with databases, such as Entity Framework, Dapper, and others.
What about business logic? How do you organize your business logic into a maintainable layer with clear separation of concerns from the presentation and data layers? Business logic is at least as important as the UI or data, and yet it's the layer of every app that has little or no rigor in terms of architecture or repeatable coding patterns.
This is the purpose of the MIT-licensed open-source framework CSLA .NET: to provide first-class framework support for business logic, like your options at the UI and data access layers. CSLA got its start in the mid-1990s as a freeware framework supporting DCOM, and in the context of a freeware project, I reimplemented it for .NET in 2002. Of course, these days, “freeware” is better known as open source, and the license was switched to the widely accepted MIT license many years ago. You can find CSLA .NET at https://cslanet.com and on GitHub.
CSLA .NET Application Architecture
CSLA .NET is designed to support a specific architecture, as shown in Figure 1. This architecture has clean separation between the interface and interface control layers, typically, something like HTML or JSON and controllers or viewmodels. And it has clean separation between the data access and data storage layers, often Entity Framework or ADO.NET and SQL Server. The part of the architecture CSLA .NET focuses on is the business layer in the center.
Like many developers, you may find it difficult to totally separate all business logic into its own layer that includes validation, authorization, and algorithmic processing. It's incredibly easy to allow business logic to leak into the presentation layers, especially validation and authorization. And it also takes a lot of discipline to prevent data access logic from leaking up into the business or presentation layers.
Separation of concerns is one of the best techniques you can use to improve the maintainability and long-term reusability of your code. Design patterns like MVC and MVVM are specifically focused on separation of concerns, showing how to separate views, interface control, and UI event handling into their own components. Interestingly enough, neither of these patterns speak to how or where to put business logic: just that the logic shouldn't go in the views, controllers, or viewmodels.
When it comes to implementing business logic, you might do what most people do and end up putting some or all of it in a controller or viewmodel, even though that directly violates those design patterns. Or you might create a function library or set of services that are invoked by the controller or viewmodel. And that's a very good solution to be sure!
CSLA takes a different approach, by making the model itself into a repository for business logic. This is particularly beneficial when using .NET, because CSLA also makes your model bindable against every UI framework supported by .NET (from Windows Forms through ASP.NET to Blazor with WebAssembly). If your model supports data binding, and also encapsulates all of your business logic, you'll find that it's very easy to create highly interactive user experiences while still maintaining complete separation of concerns between the presentation layers and the business layer.
It's also the case that CSLA is very prescriptive about how and where you invoke your data access layer. Again, the intent is to help you preserve separation of concerns by formalizing the ideas of a business layer and a data access layer. CSLA doesn't care what technologies you use to interact with the database(s), so you can use Dapper, Entity Framework, raw ADO.NET, or whatever other technologies work for you. The important part is that CSLA helps you keep your data access code in its own layer, separate from the business or presentation layers.
A Simple Blazor App
The best way to show you CSLA is to build an app, and the most modern type of UI technology at the moment is Blazor using WebAssembly to run .NET in any modern browser. Building this app currently requires the use of Visual Studio 2019 Preview and .NET Core 3.1 Preview. You can find the latest code for this example on GitHub in the CSLA .NET repo (https://github.com/MarimerLLC/csla/tree/master/Samples/BlazorExample).
Keep in mind that the same architecture, business code, and data access concepts apply unchanged to any other .NET UI framework, including ASP.NET Razor Pages, MVC, WPF, Xamarin, and others. The /Samples directory in the CSLA GitHub repo include examples of these other UI frameworks as well.
Solution Structure
Most client-side Blazor apps include a client-side project that compiles to WebAssembly, and this code runs in the browser on the client device. And they have a server-side project that's used to deploy the code to the client, as well as exposing service endpoints. They also have a shared project containing code that you want to deploy to both client and server. This is illustrated in Figure 2.
This is the same model CSLA that .NET has used over its 23+ years, and so it's a natural fit to use with Blazor. I've always recommended having a Class Library project that builds a DLL to contain business logic, with that assembly deployed to client and server.
Also notice that this solution has a DataAccess project to help maintain the separation of concerns between the business layer (the BlazorExample.Shared project) and the code used to interact with the data store. In this example, the data store is a mock in-memory database built using collections, but it could as easily be a SQL Server accessed via Dapper. I won't discuss the data access layer in this article, although I'll show how and where it's invoked.
The Business Layer
The BlazorExample.Shared project contains three business classes: PersonList, PersonInfo, and PersonEdit. The PersonList and PersonInfo classes work together to provide a read-only list of information about the people available to the user, and they are used by the ListPersons.razor page. The PersonEdit class exposes read-write properties and encapsulates the business rules necessary to allow the user to edit information about a person. It's used by the EditPerson.razor page.
There are also some rule classes that implement business rules: InfoText, CheckCase, LetterCount, and NoZAllowed. One of the most important features of CSLA is its rules engine, which honors rules from the System.ComponentModel.DataAnnotations namespace as well as rules implemented as classes.
Because the BlazorExample.Shared project uses CSLA, it references the Csla NuGet package.
The PersonInfo class is very simple, exposing read-only properties using the CSLA property declaration model as shown in Listing 1.
Listing 1: Read-Only Name Property
public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(nameof(Name));
[Display(Name = "Person name")]
public string Name
{
get { return GetProperty(NameProperty); }
private set { LoadProperty(NameProperty, value); }
}
CSLA is prescriptive about property declarations, so they all follow the same pattern. This increases readability and maintainability of code, and it reinforces the idea that business rules should be implemented using the CSLA rules engine rather than random code in a property setter. Notice the use of the Display attribute to provide a friendly name for the property. This is an example of how CSLA supports the use of the System.ComponentModel.DataAnnotations attributes.
The more interesting class is PersonEdit, because it implements read-write properties that have business rules attached. For example, the next snippet shows a PersonEdit property.
public static readonly PropertyInfo<string> NameProperty = RegisterProperty<string>(nameof(Name));
[Display(Name = "Person name")]
[Required]
public string Name
{
get { return GetProperty(NameProperty); }
set { SetProperty(NameProperty, value); }
}
The coding pattern for this property is essentially the same as for the read-only property, so you can see the consistency in how properties are declared. You should also notice that this property uses the Required attribute to indicate that this is a required property.
Other rules are attached to properties in the AddBusinessRules method as shown in the next snippet:
protected override void AddBusinessRules()
{
base.AddBusinessRules();
BusinessRules.AddRule(new InfoText(NameProperty, "Person name (required)"));
BusinessRules.AddRule(new CheckCase(NameProperty));
BusinessRules.AddRule(new NoZAllowed(NameProperty));
BusinessRules.AddRule(new LetterCount(NameProperty, NameLengthProperty));
}
You can see that a rule is attached to a property by creating an instance of the rule and providing the static PropertyInfo descriptor as a parameter. Some rules require other parameters as well.
Some rules provide information, warnings, or error validation messages to the user. Error level messages, like the Required attribute, also prevent the business domain object from being saved, as the object is considered invalid. Listing 2 shows how easy it is to implement a warning rule. Rule classes implement the IBusinessRule interface or subclass the BusinessRule base class. In any case, they implement an Execute or ExecuteAsync method containing the rule.
Listing 2: Rule to Return Warning Message
protected override void Execute(IRuleContext context)
{
var text = (string)ReadProperty(context.Target, PrimaryProperty);
if (string.IsNullOrWhiteSpace(text)) return;
var ideal = text.Substring(0, 1).ToUpper();
ideal += text.Substring(1).ToLower();
if (text != ideal)
context.AddWarningResult("Check capitalization");
}
You can see how this rule reads the property value from the target business object and then checks to see if it has proper capitalization. If the capitalization isn't ideal, a warning message is returned so the UI can display it to the user as appropriate for the current user experience.
But rules aren't just for validation; they can also manipulate property values. Listing 3 shows a simple rule that counts the number of characters in one string property and updates the value of a different int property with that length.
Listing 3: LetterCount Rule Implementation
protected override void Execute(IRuleContext context)
{
var text = (string)ReadProperty(context.Target, PrimaryProperty);
var count = text.Length;
context.AddOutValue(AffectedProperties[1], count);
}
This rule also reads the property value of the primary property, and then it asks the CSLA rules engine to set the value of another property (AffectedProperties[1]) to the length of the primary property.
But rules aren't just for validation; they can also manipulate property values.
You can see how these rules are abstracted, in that they don't know about the type of business domain object against which they're running. They just know to read some properties, update other properties, or return error, warning, or information text. The goal is that you can create and test your rule classes, and then attach those rules to properties in any business class where the property should apply.
Interacting with the Data Access Layer
The final aspect of the business layer is that it relies on CSLA to formalize how and when to invoke the data access layer. For example, Listing 4 shows how the PersonEdit class implements a CSLA-invoked method to get data from the data access layer.
Listing 4: Fetch Method in PersonEdit Class
[Fetch]
private void Fetch(int id, [Inject]DataAccess.IPersonDal dal)
{
var data = dal.Get(id);
using (BypassPropertyChecks)
Csla.Data.DataMapper.Map(data, this);
BusinessRules.CheckRules();
}
The CSLA framework manages the lifetime of domain objects, and invokes methods attributed with Create, Fetch, Insert, Update, and Delete attributes (among others) as appropriate. You're responsible for implementing these methods to interact with your data access layer to perform the requested action. In this example, notice how a reference to the data access layer is provided via dependency injection as defined in the standard application Startup.cs file.
The data access layer is invoked to get the requested data, and that data is copied into the properties of the business domain object. Then business rules are run against the newly loaded data so that any broken rules will be visible to the user.
Now let's see how these business classes are used to create a Blazor UI through their support for data binding.
The Interface and Interface Control Layers
The BlazorExample.Client project implements a client-side Blazor app that can run in any modern “evergreen” browser, including Chrome, Firefox, Safari, and Edge; on desktop, laptop, tablet, and phone devices. Because Blazor apps are built using .NET, the assembly from the BlazorExample.Shared can be deployed to the client device, providing a highly interactive user experience.
Blazor apps use a variant of the Razor syntax that has been used by ASP.NET MVC for many years. Each Blazor page is defined in a file with a .razor extension. For example, the Pages/EditPerson.razor page provides the UI for creating or editing a person based on the PersonEdit business class. This next snippet shows how the page navigation, namespaces, and dependency injection is set up.
@page "/EditPerson"
@page "/EditPerson/{id}"
@using BlazorExample.Shared
@inject Csla.Blazor.ViewModel<PersonEdit> vm
@inject NavigationManager NavigationManager
@attribute [Authorize(Roles = "Admin")]
Although CSLA is primarily focused on the business layer, CSLA does provide some helper types to streamline interaction with each type of .NET UI framework. This project references the Csla.Blazor NuGet package and so has access to these helper types, including a ViewModel class that can be injected into pages when declared in the standard Startup.cs file. This ViewModel type abstracts common repetitive code that you would have to write in nearly every page.
The page uses Razor syntax to build the UI for the user. This includes the use of data binding against the ViewModel and PersonEdit business type. Listing 5 shows how the Name property is displayed to the user by using a TextInput Blazor component.
Listing 5: Binding to the Name property from the PersonEdit Object
@if (vm.GetPropertyInfo<string>(nameof(vm.Model.Name)).CanRead)
{
<tr>
<td>@(vm.GetPropertyInfo<string>(nameof(vm.Model.Name)).FriendlyName)</td>
<td>
<TextInput Property="@(vm.GetPropertyInfo<string>(nameof(vm.Model.Name)))" />
</td>
</tr>
}
The code in Listing 5 illustrates how the separation of concerns between the presentation layers and business layer helps create a flexible user experience. Notice that this code block is only displayed if the current user can read the property, thanks to the CanRead property exposed by the ViewModel type. You can also see how the FriendlyName property is used to display the friendly name of the property from the Display attribute in the PersonEdit business class. And the TextInput component is provided with information about the property the user can edit.
The TextInput component is a custom UI component I created based on the Blazor component model. It's implemented in the Shared/TextInput.razor file as shown in Listing 6.
Listing 6: TextInput Blazor UI Component
<div>
<input @bind-value="Property.Value" @bind-value:event="oninput" disabled="@(!Property.CanWrite)" /><br />
<span class="text-danger">@Property.ErrorText</span>
<span class="text-warning">@Property.WarningText</span>
<span class="text-info">@Property.InformationText</span>
</div>
@code {
[Parameter]
public Csla.Blazor.PropertyInfo<string> Property { get; set; }
}
Blazor components are the mechanism by which you create reusable bits of UI. This component requires a Csla.Blazor.PropertyInfo object as a parameter, and it uses that object as a binding source for the various UI elements in the Razor markup.
You can see how the input element is bound to the Value property, with the @bind-value:event=“oninput” parameter indicating that the underlying model should be updated on each keystroke. Also notice how the input element will be disabled if the current user isn't authorized to change the property value by checking the CanWrite value provided by CSLA. Finally, you should notice how the error, warning, and informational text values are displayed to the user. These are the values generated by the rules attached to the Name property in the PersonEdit business class.
The important thing you should understand is that neither the EditPerson page nor the TextInput component have any knowledge of business rules. No validation, authorization, or algorithmic processing is implemented in the presentation layer. All that business logic is implemented in the business layer, and the presentation layer is solely responsible for altering the UI so the user has a great experience based on the information provided by CSLA and your business code.
Back in the EditPerson.razor page, there's one last detail I want you to see, and that is how the Save button is only enabled if the business object can be saved. This is shown in Listing 7.
Like in the previous code, you can see how the element's disabled property is set based on the IsSavable property provided by CSLA. By default, CSLA considers a business object “savable” if there are no error rules broken, the object has been changed, and the current user is authorized to save the object.
Listing 7: Auto-Disabling the Save Button
<button @onclick="vm.SaveAsync" disabled="@(!vm.Model.IsSavable)">Save person</button>
Figure 3 is an example of the Blazor app in action, where I've entered a Name value that triggers all the rules. Notice how the Save button is disabled because the Name property currently violates an error-severity validation rule.
Conclusion
The purpose of CSLA .NET is to provide a first-class home for business logic, similar to the first-class UI and data access experiences provided by the .NET ecosystem. You can use CSLA to create consistent, maintainable business logic that encapsulates validation, authorization, and algorithmic processing. The resulting business assembly can run anywhere you can run .NET, including Linux and Windows servers, containers, Windows clients, and mobile clients. And now with client-side Blazor, you can run your business logic in any modern browser on any device.