I’ve recently blogged about how CSLA 5 supports Blazor and how it supports Uno. Clearly I am bullish on WebAssembly.
However, there remains a lot of value in server-side web development, and CSLA 5 also supports ASP.NET Core MVC, so I want to blog about that as well.
This post describes code in the Samples/MvcExample solution in the CSLA repo.
Configuration
As is typical with ASP.NET, the first step to getting anything working is to configure the app in the Startup.cs
class.
ConfigureServices
In the ConfigureServices
method three CSLA-related things are configured:
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new Csla.Web.Mvc.CslaModelBinderProvider());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddCsla();
services.AddTransient(typeof(DataAccess.IPersonDal), typeof(DataAccess.PersonDal));
The AddMvc
call is enhanced to specify the use of the CSLA CslaModelBinderProvider
. I’ll discuss this later, but for now it is enough to understand that this is a model binder that enables data binding between MVC views and CSLA domain objects.
The AddCsla
method does basic configuration of CSLA for use in ASP.NET Core.
The AddTransient
method has nothing really to do with CSLA: it exists to support dependency injection of my data access layer upon request.
However it does have a little to do with CSLA, since one of the new features of CSLA 5 is support for dependency injection within your data portal methods. For example, here’s the method in the PersonEdit
business domain class that invokes the DAL to retrieve data:
[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();
}
Notice how the dal
parameter is injected into the method, based on the IPersonDal
type being configured in Startup.cs
.
Configure
In the Configure
method of Startup.cs
there’s one line of code for CSLA:
app.UseCsla();
This configures CSLA for use in ASP.NET Core.
With the configuration complete it is easy to leverage the CSLA support for ASP.NET Core MVC.
Creating Controllers
The BusinessLibrary
project contains all the business domain types, and the DataAccess
project implements the data access layer. Those are all standard CSLA implementations of types, so I won’t focus on them in this post, other than to note that the PersonEdit
class includes a number of business rules, and we’ll want to show the results of those rules to the end user.
The web project has a PersonController
and associated set of CRUD views, created using the standard Visual Studio tooling for creating controllers and views.
The majority of the code and Razor markup in those files was created by Visual Studio, not by hand. However, there are some hand-crafted parts.
Controller Base Type
The PersonController
class inherits from a base class provided by CSLA:
public class PersonController : Csla.Web.Mvc.Controller
This base class adds some helper methods designed to simplify interaction with CSLA domain objects.
Model Binding
Remember in Startup.cs
we configured a CSLA model binder. That model binder enables business domain types to be data bound to the views on postback events.
You can see the controller helper methods and model binding in use in the postback Edit
method in the controller:
// POST: Person/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit(int id, PersonEdit person)
{
try
{
LoadProperty(person, PersonEdit.IdProperty, id);
if (await SaveObjectAsync<PersonEdit>(person, true))
return RedirectToAction(nameof(Index));
else
return View(person);
}
catch
{
return View(person);
}
}
Notice that a PersonEdit
type is a parameter of the method. This is possible because the CSLA model binder was used to map the postback data from the browser into a domain object. A special model binder is required because CSLA domain objects implement business rules, authorization rules, and other behaviors that make simple binding impossible.
LoadProperty Helper Method
The LoadProperty
method allows you to put a value into a property, even if that is normally a readonly property. For example, the Id
property is normally readonly because it is a primary key from the database. In the stateless web world however, it is necessary to “reload” that property with the value from the browser on postback.
The alternative would be to reload the domain object from the database. And sometimes that might be the right answer, but it does require a database call to get the data - and we (at least in theory) already have all the data in the postback from the browser.
So in this case the data from the browser is used to load the PersonEdit
object via model binding, and to provide the Id
property value to the object via LoadProperty
.
SaveObjectAsync Helper Method
The SaveObjectAsync
method is part of the controller base class. It abstracts the process of saving a domain object, and displaying any validation rule results, or exception results, to the end user by integrating with the standard ASP.NET MVC validation display infrastructure.
ASP.NET MVC includes functionality to project client-side JavaScript code based on DataAnnotations
attributes, and to display errors generated server-side on postback. The SaveObjectAsync
method leverages that pre-built functionality, along with the powerful CSLA rules engine, to display rule results way beyond the limited capabilities of DataAnnotations
attributes (though they are supported as well).
Creating Views
The various views in the Views/Person
folder are all generated by Visual Studio. The CSLA domain types expose bindable properties, and hide metastate properties such as IsValid
, so the Visual Studio view generation tooling works as expected.
Standard Validation
Notice, in the Edit
view, how the generated markup includes an asp-validation-for
element:
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
Thanks to the SaveAsyncObject
helper method, all validation rules that are associated with the Name
property will be shown to the user via this built-in MVC mechanism.
That includes DataAnnotation
attribute rules, plus any other rules created using the CSLA rules engine. For example, the PersonEdit
class includes a rule that the Name
property can’t contain a “Z”.
This is powerful, because with no extra code in the controller or view, all the power of the CSLA rules engine comes into play when building an MVC web site.
CSLA Html Extensions
A CSLA business domain object exposes a rich set of metastate about the object and each property, metastate that goes way beyond the basic validation supported by MVC. To help you leverage that metastate while creating a view, CSLA provides extensions to the standard Html
object in Razor.
In the Edit
view the complete markup for the Name
property looks like this:
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
<span class="text-danger">@Html.ErrorFor(model => model.Name)</span>
<span class="text-warning">@Html.WarningFor(model => model.Name)</span>
<span class="text-info">@Html.InformationFor(model => model.Name)</span>
</div>
WarningFor
displays warning messages, and InformationFor
displays information messages. These are features of CSLA, designed to provide a richer experience to the user.
The resulting user experience, with a number of these triggered, looks like this:
In practice you use either asp-validation-for
or Html.ErrorFor
. The difference is that asp-validation-for
displays one (of possibly many) validation errors, while ErrorFor
displays all validation errors for the property. In this example you see the error text twice because I’m demonstrating both options.
As you can see, it is possible to create a much richer user experience by tapping into the Html
extensions and leveraging the CSLA rules engine.
Conclusion
The functionality described in this post isn’t really new. CSLA has supported ASP.NET MVC for many years. However, CSLA 5 brings this support forward to ASP.NET Core 3 for use in modern server-side web development.