Page 29 - MSDN Magazine, June 2019
P. 29
First, download the code at github.com/polterguy/magic/releases. Unzip the file and open magic.sln in Visual Studio. Start your debugger, and notice how you already have five HTTP REST endpoints in Swag- ger UI. Where did these HTTP endpoints originate? Well, let’s look at the code, because the answer to that question may surprise you.
Look Mom, No Code!
The first thing you’ll notice as you start browsing the code is that the ASP.NET Core Web project itself is literally empty. This is possible due to an ASP.NET Core feature that allows you to dynamically include controllers. If you want to see the internals behind this, you can check out the Startup.cs file. Basically, it’s dynamically adding each controller from all the assemblies in your folder into your AppDomain. This simple idea allows you to reuse your controllers and to think in a modularized way as you compose your solutions. The ability to reuse controllers across multiple projects is step 1 on your way to becoming a super-DRY practitioner.
Open up the web/controller/magic.todo.web.controller proj- ect and look at the TodoController.cs file. You’ll notice that it’s empty. So where did these five HTTP REST endpoints come from? The answer is through the mechanism of object-oriented programming (OOP) and C# generics. The TodoController class inherits from CrudController, passing in its view model and its database model. In addition, it’s using dependency injection to create an instance of the ITodoService, which it hands over to the CrudController base class.
Because the ITodoService interface inherits from ICrudService with the correct generic class, the CrudController base class hap- pily accepts your service instance. In addition, at this point it can already use the service polymorphistically, as if it were a simple ICrudService, which of course is a generic interface with parame- terized types. This provides access to five generically defined service methods in the CrudController. To understand the implications of this, realize that with the following simple code you’ve literally created all the CRUD operations you’ll ever need, and you’ve propagated them from the HTTP REST layer, through the service layer, into the domain class hierarchy, ending up in the relational database layer. Here’s the entire code for your controller endpoint:
[Route("api/todo")]
public class TodoController : CrudController<www.Todo, db.Todo> {
public TodoController(ITodoService service) : base(service)
{} }
This code gives you five HTTP REST endpoints, allowing you to create, read, update, delete and count database items, almost magi- cally. And your entire code was “declared” and didn’t contain a sin- gle line of functionality. Now of course it’s true that code doesn't generate itself, and much of the work is done behind the scenes, but the code here has become “Super-DRY.” There’s a real advantage to working with a higher level of abstraction. A good analogy would be the relationship between a C# if-then statement and the under- lying assembly language code. The approach I’ve outlined is simply a higher level of abstraction than hardcoding your controller code.
In this case, the www.Code type is your view model, the db.Todo type is your database model and ITodoService is your service msdnmagazine.com
implementation. By simply hinting to the base class what type you want to persist, you have arguably finished your job. The service layer again is equally empty. Its entire code can be seen here:
public class TodoService : CrudService<Todo>, ITodoService {
public TodoService([Named("default")] ISession session)
: base(session, LogManager.GetLogger(typeof(TodoService)))
{}
Zero methods, zero properties, zero fields, and yet there’s still a complete service layer for your TODO items. In fact, even the service interface is empty. The following shows the entire code for the service interface:
public interface ITodoService : ICrudService<Todo> {}
Again,empty!Yetstill,simsalabim,abrakadabra,andyouhave a complete TODO HTTP REST Web API application. If you open up the database model, you’ll see the following:
public class Todo : Model {
public virtual string Header { get; set; } public virtual string Description { get; set; } public virtual bool Done { get; set; }
}
Once again, there’s nothing here—just a couple of virtual prop- erties and a base class. And yet, you’re able to persist the type into your database. The actual mapping between your database and your domain type occurs in the TodoMap.cs class inside the magic.todo.model project. Here, you can see the entire class:
public class TodoMap : ClassMap<Todo> {
public TodoMap() {
Table("todos");
Id(x => x.Id);
Map(x => x.Header).Not.Nullable().Length(256); Map(x => x.Description).Not.Nullable().Length(4096); Map(x => x.Done).Not.Nullable();
This code instructs the ORM library to use the todos table, with the Id property as the primary key, and sets a couple of additional properties for the rest of the columns/properties. Notice that when you started this project, you didn’t even have a database. This is because NHibernate automatically creates your database tables if they don’t already exist. And because Magic by default is using SQLite, it doesn’t even need a connection string. It’ll automatically create a file- based SQLite database at a relative file path, unless you override its connection settings in appsettings.config to use MySQL or MSSQL.
Believe it or not, your solution already transparently supports almost any relational databases you can imagine. In fact, the only line of actual code you would need to add to make this thing function can be found in the magic.todo.services project, inside the Config- ureNinject class, which simply binds between the service interface and the service implementation. So, arguably, you added one line of code, and got an entire application as the result. Following is the only line of actual “code” used to create the TODO application:
public class ConfigureNinject : IConfigureNinject {
public void Configure(IKernel kernel, Configuration configuration) {
}
} }
}
// Warning, this is a line of C# code!
kernel.Bind<ITodoService>().To<TodoService>(); }
June 2019 25