If you are going to create relatively large ASP.NET Core web application, it might be a good time to think about making it modular and extendable. The common idea is that decoupled software is better than monolithic one, because it is easier to develop it (especially where teamwork is important), easier to test, easier to localize and fix the problems, and easier to support.
For example, you could split regular e-commerce web application into the frontend module and the backend one. In its turn, backend module might consist of warehouse, delivery, and statistics submodules. In the future, it will be very easy to add, let’s say, accounting submodule, without need to look at the other modules source code. Also, it makes it easy to combine the product from parts depending on the customers requirements.
Good news is that it is extremely simple to build modular and extendable ASP.NET Core web application using the ExtCore framework. ASP.NET Core itself already contains some features that make this task easier (take a look at the application parts feature for example), but ExtCore framework provides more specific things like assembly and type discovering, working with the views and resources, different storage types support, server-side evens etc. Today we will try it in action.
We are going to create small accounting web application which will consist of 4 extensions: Barebone, Incomes, Expenses, and Balance. UI and data model will be modular too. The result will look like this:
When I start developing new modular web application I usually think what the common parts that will be shared across all the extensions are? In case of our web application we need to provide ability for the extensions to add their own styles and scripts to the resulting HTML, and menu items to the main menu. We will describe these 3 elements as the StyleSheet, Script, and MenuItem classes:
One more IExtensionMetadata interface will be used to combine them all in the single extension-related container. Each extension will be able to implement this interface to provide its own objects:
Now our web application can find all the existing implementations of the IExtensionMetadata interface (we will see how to do that below) and get all the styles, scripts, and menu items the extensions has provided. Good. Usually I put all the things like this into an Infrastructure project. Let’s create it and put these files there.
Also, all the extensions should look similar way. In other words, they all must use the same styles to look unified. And we should avoid styles duplicating, so the best idea is to move everything related to the shared UI to the separated Barebone (of course you can choose any other name you like) extension.
This extension is the central one. It defines a lot of shared things (except ones we have put into the Infrastructure project): shared views (_Layout.cshtml, _ViewImports.cshtml, _ViewStart.cshtml, and others), shared styles and scripts, base controller and view model classes etc. This extension also contains the view components that generate the styles, scripts, and menu (as we have discussed above) HTML. They are invoked inside the _Layout.cshtml view. Let’s take a look at the MenuViewComponent class and MenuViewModel view model and its factory for example:
As you can see, the MenuViewModelFactory class uses ExtCore framework’s ExtensionManager class to get all the instances of the IExtensionMetadata interface implementations. It gets menu items from all of them and then sort these menu items by the position. ExtCore makes types discovering very simple. Styles and scripts rendering works in a similar way. And this is the implementation of the IExtensionMetadata that is used by the Barebone extension to include shared styles and scripts to all the extensions:
Standard libraries like jQuery are also included here.
The final thing is done by the Barebone extension is registering of the default web application MVC routes (so all other extensions will use them). To do that we need to implement the ExtCore.Mvc.Infrastructure.Actions.IUseMvcAction interface (this all is related to the ExtCore.Mvc extension and you can learn more by looking at the ExtCore samples):
The main extension is done. Other extensions will provide controllers, view models, views, data models, and, optionally, styles, scripts, and menu items.
As we have discussed above, we will have 4 extensions in this application. Barebone one is ready, so we are going to create Incomes, Expenses, and Balance. Incomes and Expenses extensions will consist of 4 projects each. For example, these are the projects of the Incomes extension:
Incomes project will contain controller, view models and views that are required to display the list of the incomes and to create a new one. Also it contains the ExtensionMetadata class that provides one menu item for the Barebone extension:
It is important, that all the views, styles, scripts, images, and other resources should be added to the project as resources using the .csproj file. Otherwise ExtCore framework will not be able to discover them.
Incomes.Data.Entities project contains the only one Expense entity class:
Incomes.Data.Abstractions project contains the IExpenseRepository interface that describes the repository to work with the Expense entity:
This interface should be implemented once for each of the supported storage types (in our case it is only a SQLite database).
Finally, the Expenses.Data.EntityFramework.Sqlite project contains the ExpenseRepository class (implementation of the IExpenseRepository interface for a SQLite database):
Also, it contains the EntityRegistrar class:
This is implementation of the IEntityRegistrar interface, which is the part of the ExtCore.Data.EntityFramework extension. It registers all the entities from all the extension within the same data context.
That’s all for this extension. The Expenses extension is almost the same.
The Balance extension has implicit dependencies on the Incomes and Expenses ones. It knows about the Income and Expense entity classes, knows about their repository interfaces (but it doesn’t know anything about their implementations!). So, it uses that repositories to get the total amount of all the incomes and all the expenses and calculates the sum:
For now we just created few ASP.NET Core projects. We didn’t think about how they will be executed and used. The main advantage of using the ExtCore framework is that you can just use any .NET Core package (DLL or NuGet) as an extension. Just put the DLL-file into the extensions folder or add implicit NuGet reference and that’s it, all the controllers, views, styles, scripts, images and other resources will be resolved automatically! All you need to do is to call the AddExtCore and UseExtCore methods inside your web application's Startup class.
Create the WebApplication (main web application) project and change the Startup class to look like this:
Also, we need to add some ExtCore NuGet packages as the dependencies:
Now rebuild the solution and put DLL-files from all the extension projects inside the extensions folder of the main web application. Run your web application and you will have the result as on the first screenshot.
Now you can see how easy it is to create modular and extendable web application on ASP.NET Core using the ExtCore framework. I have created the demo project for this post. Feel free to ask if you have any questions!
You can find more ExtCore samples on its GitHub page.