We continue our column on the topic ASP.NET Core by another publication from Dmitry Sikorsky ( DmitrySikorsky ) - the head of the company “Yubreinians” from Ukraine. This time, Dmitry continues to talk about his experience in developing a modular cross-platform framework based on ASP.NET Core. Previous articles from the column can always be read on the link #aspnetcolumn - Vladimir Yunev
In the
previous article, I already talked about ExtCore - a small framework for developing modular and extensible applications on ASP.NET Core. In this article I will try to stop in more detail on the process of developing an application based on it.
Main application
First of all, create a new empty project on ASP.NET Core 1.0:
As a result, we get a ready-to-use project. It remains only to delete the Project_Readme.html file. Now our solution browser should look something like this:
')
Tip! You can try everything yourself or by downloading the source code from GitHub https://github.com/ExtCore/ExtCore-Sample .
To connect the ExtCore framework to our project, you need to add links to the NuGet packages ExtCore.Infrastructure and ExtCore.WebApplication in project.json. Also, since in this example we will work with the database, we will add links to the ExtCore.Data extension components: (ExtCore.Data, ExtCore.Data.Abstractions, ExtCore.Data.EntityFramework.Sqlite, ExtCore.Data. Models.Abstractions). (We will also need links to packages familiar to MVC applications, like Microsoft.AspNet.Mvc.) As a result, our project.json should look like this:
{ "commands": { "web": "Microsoft.AspNet.Server.Kestrel" }, "dependencies": { "EntityFramework.Sqlite": "7.0.0-rc1-final", "ExtCore.Data": "1.0.0-alpha7", "ExtCore.Data.Abstractions": "1.0.0-alpha7", "ExtCore.Data.EntityFramework.Sqlite": "1.0.0-alpha7", "ExtCore.Data.Models.Abstractions": "1.0.0-alpha7", "ExtCore.Infrastructure": "1.0.0-alpha7", "ExtCore.WebApplication": "1.0.0-alpha7", "Microsoft.AspNet.Diagnostics": "1.0.0-rc1-final", "Microsoft.AspNet.Diagnostics.Entity": "7.0.0-rc1-final", "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final", "Microsoft.Extensions.Configuration.Abstractions": "1.0.0-rc1-final", "Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final", "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0-rc1-final" }, "exclude": [ "wwwroot" ], "frameworks": { "dnx451": { }, "dnxcore50": { } }, "publishExclude": [ "**.user", "**.vspscc" ], "version": "1.0.0-*", "webroot": "wwwroot" }
Now it only remains to inherit the Startup class from ExtCore.WebApplication.Startup:
public class Startup : ExtCore.WebApplication.Startup { public Startup(IHostingEnvironment hostingEnvironment, IApplicationEnvironment applicationEnvironment, IAssemblyLoaderContainer assemblyLoaderContainer, IAssemblyLoadContextAccessor assemblyLoadContextAccessor, ILibraryManager libraryManager) : base(hostingEnvironment, applicationEnvironment, assemblyLoaderContainer, assemblyLoadContextAccessor, libraryManager) { IConfigurationBuilder configurationBuilder = new ConfigurationBuilder() .AddJsonFile("config.json"); this.configurationRoot = configurationBuilder.Build(); } public override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); } public override void Configure(IApplicationBuilder applicationBuilder, IHostingEnvironment hostingEnvironment) { if (hostingEnvironment.IsEnvironment("Development")) { applicationBuilder.UseBrowserLink(); applicationBuilder.UseDeveloperExceptionPage(); applicationBuilder.UseDatabaseErrorPage(); } else { applicationBuilder.UseExceptionHandler("/"); } base.Configure(applicationBuilder, hostingEnvironment); } }
In the Startup class constructor, we initialize the configurationRoot variable defined in the ExtCore.WebApplication.Startup base class. This is necessary to provide the ExtCore framework with access to configuration parameters (in our case, the only source of configuration parameters is the config.json file). For example, the ExtCore.Data extension thus gets the Data: DefaultConnection: ConnectionString parameter (a database connection string). You can also configure other extensions (including your own).
Let's create a config.json file in the project root:
{ "Data": { "DefaultConnection": { "ConnectionString": "Data Source=../db.sqlite" } }, "Extensions": { "Path": "artifacts\\bin\\Extensions" } }
The Extensions: Path parameter defines the path that the folder with extensions (relative to the application root) is located in the file system.
That's all, at this moment we can build and run our application. We will get a 404 error and this will be correct, since we do not yet have routes or controllers.
Extensions
Now let's create 2 extensions. The first extension (ExtensionA) on its only page (the main page of our application) will simply display a list of all available extensions. We will also test the use of static content in the form of resources on the example of a CSS file. The second extension (ExtensionB) will display the records described by the model from the database. It's simple.
ExtensionA Extension
Create another WebApplication.ExtensionA project (note that this time it is a class library):
To conveniently separate projects in a solution related to various extensions, move our project to the solution folder with the name ExtensionA, after creating it.
First of all, let's edit project.json again. Add a link to ExtCore.Infrastructure (contains a description of the IExtension interface; in addition, ExtCore loads and uses only those assemblies that have a link to this package) and to Microsoft.AspNet.Mvc. In this extension, we will use the view and the CSS file added as resources (I described this in more detail in the previous article referenced above), so you must also add the appropriate entry. This is what should work:
{ "dependencies": { "ExtCore.Infrastructure": "1.0.0-alpha7", "Microsoft.AspNet.Mvc": "6.0.0-rc1-final" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "resource": [ "Styles/**", "Views/**" ], "version": "1.0.0-*" }
Next, we implement the IExtension interface:
public class ExtensionA : IExtension { private IConfigurationRoot configurationRoot; public string Name { get { return "Extension A"; } } public void SetConfigurationRoot(IConfigurationRoot configurationRoot) { this.configurationRoot = configurationRoot; } public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder applicationBuilder) { } public void RegisterRoutes(IRouteBuilder routeBuilder) { routeBuilder.MapRoute(name: "Extension A", template: "", defaults: new { controller = "ExtensionA", action = "Index" }); } }
In the RegisterRoutes method, we add a route for the main page of our application.
Now we will add a controller with a single Index method, which will transmit to the view a set of names of all extensions loaded by ExtCore, for which the ExtensionManager class is used:
public class ExtensionAController : Controller { public ActionResult Index() { return this.View(ExtensionManager.Extensions.Select(e => e.Name)); } }
In turn, the view displays this set as follows:
<ul> @foreach (var item in this.Model) { <li>@item</li> } </ul>
The last thing to do is add the typography.css style file to the Styles folder. Above, in the project.json file, we indicated that the entire contents of the Styles and Views folders will be added to the assembly as resources. ExtCore will detect these resources and make it possible to use them in a manner similar to the use of physical files. That is, we will be able to include our CSS file in any extension in this way:
<link href="Styles.typography.css" rel="stylesheet" />
It should only be borne in mind that the tree structure of the file system is transformed into a "flat" structure of text names (the register matters!).
Our extension ExtensionA is ready. To test it, it is enough to either add a link to it in the project.json of the main application, or compile it as a dll file and copy it to the folder with extensions (we previously specified it in config.json).
ExtensionB
Here we need as many as 4 new projects: WebApplication.ExtensionB, WebApplication.ExtensionB.Data.Abstractions, WebApplication.ExtensionB.Data.EntityFramework.Sqlite and WebApplication.ExtensionB.Data.Models. As in the first extension, group them in the solution folder (called ExtensionB).
WebApplication.ExtensionB
In this project we will post the implementation of the IExtension interface, the controller, the view models and the views.
The implementation of the IExtension interface is similar to that of the previous extension. Let's go straight to the controller:
public class ExtensionBController : Controller { private IStorage storage; public ExtensionBController(IStorage storage) { this.storage = storage; } public ActionResult Index() { return this.View(new IndexViewModelBuilder().Build(this.storage.GetRepository<IItemRepository>().All())); } }
Since in this extension we need to get some records from the database, we will use the ExtCore.Data extension features for this. In the controller's constructor, we will request the available implementation of the IStorage interface (which was previously detected and registered by the ExtCore.Data extension) from the ASP.NET DI built-in. Next, we will already request our own implementation of our own IItemRepository interface for a specific storage (in our case, this is a SQLite database) and call the All method to get all the records. Next, convert the models from the database to the view models for display in the view.
Instead of using representations as resources, in this extension we will use precompiled representations. To do this, add the RazorPreCompilation class to the / Compiler / PreProcess folder:
public class RazorPreCompilation : RazorPreCompileModule { protected override bool EnablePreCompilation(BeforeCompileContext context) => true; }
This will give us the opportunity to use our own (that is, declared inside our extension) classes for species models. (For more on precompiled views, see the previous article.)
WebApplication. ExtensionB. Data. Abstractions
This project contains a single repository interface for working with models of the Item type (see below):
public interface IItemRepository : IRepository { IEnumerable<Item> All(); }
In our example, the interface describes only one method to get all the records.
WebApplication. ExtensionB. Data. EntityFramework. Sqlite
In this project, we will implement the IItemRepository interface for a specific repository — the SQLite database:
public class ItemRepository : RepositoryBase<Item>, IItemRepository { public IEnumerable<Item> All() { return this.dbSet.OrderBy(i => i.Name); } }
Since the extension does not work directly with a specific implementation, but uses only abstractions, we can simultaneously support several types of storages and add new ones without having to change the code of the extension itself.
Also, registration of the models used in the extension and storage settings takes place here. To do this, use a class that implements the interface IModelRegistrar:
public class ModelRegistrar : IModelRegistrar { public void RegisterModels(ModelBuilder modelbuilder) { modelbuilder.Entity<Item>(etb => { etb.HasKey(e => e.Id); etb.Property(e => e.Id); etb.ForSqliteToTable("Items"); } ); } }
WebApplication.ExtensionB.Data.Models
In this project, we describe our only model - Item:
public class Item : IEntity { public int Id { get; set; } public string Name { get; set; } }
Each model must implement the ExtCore.Data.Models.Abstractions.IEntity interface.
Let's test the work of our new extension exactly as we did with ExtensionA.
Running and testing
Our application with two extensions is ready. Running it, we should see something like this:
findings
At the moment, we (I and a few interested guys) are actively developing this project and several others are already based on it. We will be very happy ideas, advice and criticism. Thank!
Link to sources:
https://github.com/ExtCore/ExtCore-Sample .
To authors
Friends, if you are interested in supporting the column with your own material, please write to me at
vyunev@microsoft.com to discuss all the details. We are looking for authors who can interestingly tell about ASP.NET and other topics.

about the author
Sikorsky Dmitry Alexandrovich
Jubreynians Company (
http://ubrainians.com/ )
Owner, Head
DmitrySikorsky