Someone asked me for a quick example of how to get started with my business extensions. Even though I already blogged about my CG.Business library, before. No problem, I can see that there might still be room to explain some things.
I won’t cover library internals. There is already a post for that HERE.
The source code for the associated project is located HERE.
First let’s talk about why someone might want to use the CG.Business library. Here is a short list of what the library offers:
- A mechanism to dynamically load strategies at startup.
- A mechanism to dynamically load repositories at startup.
- Base types for common abstractions like models, stores, services, strategies, repositories, managers, directors, clients, etc. Most of these types are deliberately thin, but they still provide a handy-dandy starting point that will (hopefully) tie in with whatever future enhancements I add to the library, for these types.
All of this means, if you’re using strategies, or repositories, and you need to dynamically load those abstractions based off a standardized configuration block, then this library might interest you.
Or, if you’re using models, stores, services, strategies, repositories, managers, directors, clients and you want an existing set of interfaces, base classes, and supporting exceptions to leverage, then this library might also interest you. Although, frankly, if you don’t care about dynamically loading anything then it’s probably easier for you to just build from your own interfaces and base classes.
So now let’s do a quick walkthrough of a typical C# Blazor project, and as an example, we’ll use a repository class that we’ll implement in a separate assembly, so we can demonstrate dynamically loading things at runtime. Here goes …
Start by creating a standard C# Blazor project in Visual Studio:
You can call your project whatever you like. I’ll call mine BlazorApp1. When you’re ready, press the next button.
On the next screen pick whatever you like for a framework and authentication type. For my purposes, .NET 5.0 with no authentication works just fine. When you’re ready, press the “Create” button.
Visual Studio will then create your Blazor project. The result looks something like this, in Visual Studio:
This works, of course, but for our walkthrough we’ll need to add some additional projects to the solution. Let’s do that now. First, right click on the solution and choose the “Add” | “New Project” menu choice. That will pop up a dialog like this:
We’ll want to add a class library project to our solution. That project type just happens to be selected in my UI already. If it isn’t visible in your UI, look around until you find the project type: “Class Library”, then select it and hit the “Next” button.
That pops up another dialog that allows you to pick a name and location for your class library. You can, of course, choose anything you like. For my purposes here, the default name “ClassLibrary1” is fine, so I’ll stay with that.
Once again, we are asked to choose a framework. Assuming you stuck with .NET 5.0, like I did, then you can simply hit the “Create” button. Otherwise, pick an appropriate framework for your needs and then hit the “Create” button.
In my Visual Studio, the result looks like this after I added the new class library project to my solution. As we can see, the solution now has two projects: the BlazorApp1 project that we started with, and the ClassLibrary1 project that we added.
We also see that Visual Studio has helpfully created a class for us, in our new class library, called Class1. Normally, I just delete that class. You can keep it if you like but we won’t be using it in our walkthrough.
We’ll need another class library, in addition to the one we’ve already created, in our solution. Rather than go through those steps again, since nothing will change, really, I’ll simply skip ahead while you go back and add the second library now. I called mine ClassLibrary2. Start from where we right clicked on the solution, in Visual Studio, and chose the “Add” | “New Project” menu choice. Stop when you get back to here.
Ok, now the solution look like this:
Once again, we won’t need the Class1.cs files, in the new class library projects. Visual Studio adds those as part of the project creation process. You can delete them, if you like.
At this point we have all the projects we’ll need for our walkthrough. Now we need to setup some dependencies. Let’s start by adding a reference to both our class libraries from our BlazorApp1 project. Now, before we do that, let me explain. We won’t technically need to have a reference between our BlazorApp1 project and our ClassLibrary2 project, since my CG.Business library is fully capable of finding and loading assemblies at runtime. But, in order to make things easier, for the sake of our walkthrough, we’ll add the reference now. Just bear in mind we only did this here to keep things simple.
Right click on the BlazorApp1 project, in Visual Studio, and choose the “Add” | “Project Reference …” menu choice. That displays this popup dialog:
Make sure both libraries have checks before their name and press the “OK” button.
The next reference we’ll need to add is on the ClassLibrary2 project. To do that, right click on the ClassLibrary2 project, in Visual Studio, and choose the “Add” | “Project Reference …” menu choice. That will popup this dialog:
This time, make sure ONLY the ClassLibrary1 project has a check before it’s name, then press the “OK” button.
Now we have a basic server-side Blazor solution to work with. We know there’s no magic code or hidden steps anywhere because we got to this point, together. If you like, you can press F5, in Visual Studio, and verify to yourself that the solution comes up in the browser. Here is what mine looks like:
Shutdown your browser, if you chose to run the app. Now that we know we have a stable solution to build on, let’s start integrating in my CG.Business library.
First thing we’ll need to do is right click on the ClassLibrary1 project, in Visual Studio, and choose the “Manage Nuget Packages …” menu choice. That displays this window, in Visual Studio:
Type “CG.Business” into the search window, on the “Browse” tab, then select the “CG.Business” package and hit the “Install” button. Afterwards, accept any license dialogs that popup. That adds a reference to my CG.Business library, from the ClassLibrary1 project.
Once we have CG.Business linked into our solution, we can use it for the rest of the walkthrough.
For our walkthrough, we’re going to create a repository that returns the current date/time. Yes, I know, .NET has a method to do that already. Work with me here …
We’ll use that repository in our index page, to show the current server time. We’ll load our repository using a standard block in our appSettings.json file, and the extensions from the CG.Business library. CG.Business will take care of locating our repository assembly, and loading it at startup. Let’s get started!
The first thing we’ll need is a place holder for our repository. For that we’ll add an IRepository
interface to the ClassLibrary1 project. Do that now by right clicking on the ClassLibrary1 project and choosing the “Add” | “Class” menu choice. That displays this popup dialog:
Choose “Code”, under “Visual C# Items”, then choose the “Interface” type. After that, name the new file IRepository.cs and press the “Add” button.
Edit the resulting file to that it looks like this:
using System; namespace ClassLibrary1 { public interface IRepository { DateTime ServerTime(); } }
Next we need to add a concrete repository class to the ClassLibrary2 project. This is the part of the repository that CG.Business will automatically load for us. Add this part by right clicking on the ClassLibrary2 project and choosing the “Add” | “Class” menu choice. That displays the “Add New Item” dialog we saw before:
This time choose “Code”, under “Visual C# Items”, then choose the “Class” type. After that, name the new file Repository.cs and press the “Add” button.
After that, edit the new file so that it looks like this:
using System; namespace ClassLibrary2 { public class Repository : ClassLibrary1.IRepository { public DateTime ServerTime() { return DateTime.Now; } } }
Go back and make sure you added the IRepository
interface to the ClassLibrary1 project, and that you added the Repository
class to the ClassLibrary2 project. Also, make sure both types have that public
keyword applied to them – otherwise, you’ll get build errors later.
Alright, we now have a repository! Let’s modify the Index.razor page, inside the BlazorApp1 project, to use our new repository. The page itself is in the Pages subfolder. Edit it so that it looks like this:
The important edits here are the using and inject lines (3 and 4), and the H2 line (12). Obviously, I could have added the using statement (line 3) into the _Imports.razor file, but I’m just trying to keep everything short and focused here. Either way works though.
The next step is to write some extension methods to register and wire everything up. Now, I know what you might be thinking … “We don’t need extension methods for that! Just add the registration directly into the Startup class, in the BlazorApp1 project! Sheesh!”
Actually, we do need extension methods for that. Here’s why. While it’s true that we could register our repository type like this, in our BlazorApp1 project:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IRepository, Repository>(); } }
Doing so would create a dependency on our Repository type, from within our BlazorApp1 project. That, in turn, means our BlazorApp1 project would have a dependency on our ClassLibrary2 project. By dependency, I mean that, if we did this, our BlazorApp1 project would no longer compile without a hard reference to our ClassLibrary2 project. That’s fine, in itself, but if we wanted to be able to swap out repository assemblies then adding hard dependencies would make our lives more difficult.
Let me stop and explain. I know that I specifically added a reference between our BlazorApp1 project and our ClassLibrary2 project, earlier in this post. That may be confusing to some readers – after all, now I’m saying we don’t want dependencies between our BlazorApp1 project and our ClassLibrary2 project. Remember, I only added that particular dependency to make it easier, and simpler to get our walkthrough solution up and running. I’ll prove it all works without that reference, later. For now, just trust me when I say that we don’t want those dependencies now.
Well, alright, if we don’t want to create a dependency between BlazorApp1 and ClassLibrary2 then how are we going to get the Repository type registered?! The answer is, we’re going to use some extension methods and the CG.Business library. Let’s start by creating some classes to hold our extension methods.
Right click on the ClassLibrary2 project, in Visual Studio, and choose the “Add” | “Class…” menu choice. Name the file ServiceCollectionExtensions.cs. Here is what we’ll want that class to look like:
using ClassLibrary1; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace ClassLibrary2 { public static partial class ServiceCollectionExtensions { public static IServiceCollection AddMyRepositories( this IServiceCollection serviceCollection, IConfiguration configuration ) { serviceCollection.AddTransient<IRepository, Repository>(); return serviceCollection; } } }
In order to get this code to compile, you’ll need to add some additional NUGET packages. To do that, right click on the ClassLibrary1 project and choose the “Manage NUGET Packages…” menu choice. From there, add the Microsoft.Extensions.Configuration.Abstractions
and Microsoft.Extensions.Hosting.Abstractions
packages to the project. Follow the steps we did before, when we added the CG.Business NUGET package. You can verify the results by compiling the project now. Everything should compile without error.
If you have compile errors, at this point, go back and verify that you added the right packages to the ClassLibrary1 project. If you’re wondering why we added those packages to the ClassLibrary1 project, and not to ClassLibrary2 instead, it’s because of the way dependencies work in .NET. Specifically, in our walkthrough, ClassLibrary2 has a reference to ClassLibrary1, so ClassLibrary2 automatically gets access to any NUGET packages we add to ClassLibrary1. Pretty scary, huh?
Next we need another extension method, to call the one we just created. I can hear you now, thinking to yourself “What? We’re writing more extension methods?! This guy is crazy!” Hear me out, If we were to directly call our new AddMyRepositories
method, from within our BlazorApp1 project, we’d be right back where we started, with a dependency between the BlazorApp1 and ClassLibrary2 projects. Somehow, we need a way to call our AddMyRepositories
method without actually calling it ourselves. Crazy, right?
We’ll do that by using the CG.Business library, but, that library uses extension methods as integration points. For that reason, we’ll need another extension method, this time in our ClassLibrary1 library. Let’s do that now. Let’s also call that one ServiceCollectionExtensions
, to be consistent. Here is what that class should look like:
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace ClassLibrary1 { public static partial class ServiceCollectionExtensions { public static IServiceCollection AddMyLibrary( this IServiceCollection serviceCollection, IConfiguration configuration, ServiceLifetime serviceLifetime ) { serviceCollection.AddRepositories( configuration.GetSection("Repositories"), serviceLifetime ); return serviceCollection; } } }
Here is where the walkthrough starts to get interesting. See that method we’re calling? The one named AddRepositories
? Where did that come from? That’s not the extension method we just wrote, right? It turns out, AddRepositories
comes from the CG.Business library. This method is how we’ll call our AddMyRepositories
extension method, without actually calling it ourselves.
But how does AddRepositories
know about our AddMyRepositories
method?? That’s a great question! CG.Business uses a standardized block within the appSettings.json file (or wherever you get your configuration settings from), to determine which extension method to call on our behalf. For that to work, we have to add a section to the appSettings.json file, in the BlazorApp1 project. Here’s what that should look like:
{ "BlazorApp1": { "Repositories": { "Selected": "My", "My": { "AssemblyNameOrPath": "ClassLibrary2" } } }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
Ignore everything below the “Logging” section, that’s all standard Blazor stuff. Instead, focus on the “BlazorApp1” section. See the “Repositories” sub-section, under that? That’s the information we need to supply to the AddRepositories
method, in order for all of this to work. How do we do that?? …. Wait for it …
You thought I was going to say write another extension method, didn’t you? Hah! I knew it! No, this time we simply need to call our AddMyLibrary
method from the BlazorApp1’s Startup
class, like this:
using ClassLibrary1; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace BlazorApp1 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { // existing code remove for clarity services.AddMyLibrary( Configuration.GetSection("BlazorApp1") ); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // existing code remove for clarity } } }
I removed some of the clutter, in this code snippet, just for clarity. The point here is, we should call the AddMyLibrary
method, from within the Startup.ConfigureServices
method, like I’ve shown above. When we do that, we should pass in the configuration section from “BlazorApp1”, that we added to the appSettings.json file, in the BlazorApp1 project.
Once we’ve done that, with the help of the CG.Business library, we will, inadvertently, call the AddMyRepositories
method, in the ClassLibrary2 project.
I know, crazy, right?
What’s more, because CG.Business uses reflection to load and locate the AddMyRepositories
method, at runtime, and call it on our behalf, it means we haven’t introduced any dependencies on the ClassLibrary2 project.
Pretty cool, right?
Wait, what? You’re skeptical? I can’t believe that! Alright, let’s go prove that it all works. Set a breakpoint on line 14, of the ServiceCollectionExtensions
class, in the ClassLibrary2 project. Next, hit F5 to run the project. If everything is right, and you’ve followed all the steps as I’ve laid them out, you should see something like this:
That means, the extension method was called and we used it to register our concrete repository, from our application, without actually having to know anything about it, at compile time.
Next, release that breakpoint and hit F5 again. You should see this in the browser:
The server time itself will, of course, be different, but otherwise, your screen should look like mine.
So what did we do here?? We created a very simple server-side Blazor application and let it reference two class libraries that we added into the mix. The first class library, ClassLibrary1, held the interface for our demo repository, called IRepository
. The second class library, ClassLibrary2, held the concrete implementation for our demo repository, called Repository
. We then added a line of code to the Index.razor page, in the BlazorApp1 project, to inject our repository type, at runtime, and call the ServerTime
method to show us what the time is.
In order for everything work at runtime, the ASP.NET DI container that’s built into Blazor has to know about our repository types: IRepository
and Repository
. Since we wanted to avoid creating a dependency between our BlazorApp1 and ClassLibrary projects, we used the features of the CG.Business library, to call some extension methods that would register our types without creating any unwanted dependencies.
For the skeptics among us, we can prove that this all still works by removing the project reference in the BlazorApp1 project, to the ClassLibrary2 project. To do that, highlight this node (ClassLibrary2, as highlighted, below) in the Visual Studio solution explorer, and hit the delete key:
Next, go into the appSettings.json file, inside the BlazorApp1 project, and make this change:
Change: "AssemblyNameOrPath": "ClassLibrary2" To: "AssemblyNameOrPath": "..\\ClassLibrary2\\bin\\Debug\\net5.0\\ClassLibrary2.dll"
For this last bit, we need to change the assembly name, to a relative path, in the JSON file, so that the CG.Business library can find the ClassLibrary2.dll file, at runtime. BTW, that actual path, as shown above, may or may not work for you. It depends greatly on what you named your ClassLibrary2 project, and where you chose to put that project, in relation to your BlazorApp1 project. CG.Business internally uses standard .NET rules for locating assemblies, so, if you changed anything (projects name(s), location(s), etc) from my example, you’ll need to modify that path in order for this last step to work.
In any event, this path complexity is why I originally just added the project reference, in Visual Studio, between BlazorApp1 and ClassLibrary2 projects. That way it would just work without having to do any of this. But, I also wanted to prove that we didn’t need a project reference at runtime.
So that’s it! A walkthrough of how to get started using one of the features of the CG.Business library. How can you extend on this demo for your next project? Well, that’s up to you. One thing that springs to mind though, is that it’s now possible to quickly switch between repository assemblies without ever having to recompile anything. So, this would now be possible:
{ "BlazorApp1": { "Repositories": { "Selected": "MyType1", // Change types here. "MyType1": { "AssemblyNameOrPath": "ClassLibrary2.dll" // Put your MyType1 repository configuration settings here. }, "MyType2": { // Put your MyType2 repository configuration settings here. }, "MyType3": { // Put your MyType3 repository configuration settings here. } } }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" }
In other words, you could easily ship a product that supports N types of back-end storage, and configure your product to use any one of those types at setup, without needing to have a developer staff around to rewrite your back-end and recompile everything.
The source code for the associated project is located HERE.
Photo by Martin Adams on Unsplash