Setup Plugin – Part 4

Setup Plugin – Part 4

Last time I had finished walking through the setup plugin. I had almost finished walking through the quick start sample, for the library, but I left the last part for now. Let’s go look at that now:

I have mentioned previously that the setup plugin dynamically generates the UI using a POCO class. I have also mentioned that the POCO type is typically decorated with various attributes, in order for the form generator to know how to render the object as HTML.


If my form generator isn’t familiar, try reading HERE, for more information.


In your project, what that POCO class looks like, and how it’s decorated, is up to you. In the case of my quick sample though, the class looks like this:

[RenderFluentValidationValidator]
[RenderValidationSummary]
[RenderMudTabs]
public class SetupVM
{
    [RenderMudTabPanel]
    public DatabaseVM Database { get; set; }

    public SetupVM()
    {
        // Set default values.
        Database = new DatabaseVM();
    }
}

So let’s look at this one class at a time, starting with SetupVM. The class doesn’t derive from anything, so it’s a true POCO type. It does contain a number of special attributes, that are all tied back to various forms related libraries. For instance, the RenderFluentValidationValidator attribute is part of my CG.Blazor.Forms._FluentValidations library, and it directs the form generator to inject a fluent style validator into the form. The RenderValidationSummary attribute directs the form generator to inject a validation summary component in the form.

The RenderMudTabs attribute directs the form generator to wrap the object’s HTML inside a MudTabs component. That attribute, and all the other MudBlazor attributes, are all part of my CG.Blazor.Forms._MudBlazor library.

The Database property, on SetupVM is a reference to a DatabaseVM object. Notice that the property is decorated with a RenderMudTabPanel attribute. That attribute directs the form generator to wrap the object’s HTML inside a MudTabPanel component. Let’s go look at the DatabaseVM type next:

public class DatabaseVM
{
    [RenderMudRadioGroup(Options = "SqlServer, Mongo")]
    public string Selected { get; set; }

    [RenderObject(VisibleExp = "x => x.Selected=\"SqlServer\"")]
    public SqlServerModel SqlServer { get; set; }

    [RenderObject(VisibleExp = "x => x.Selected=\"Mongo\"")]
    public MongoModel Mongo { get; set; }

    public DatabaseVM()
    {
        // Set default values.
        Selected = nameof(SqlServer);
        SqlServer = new SqlServerModel();
        Mongo = new MongoModel();
    }
}

The DatabaseVM class is another POCO type. This time the properties on the class are decorated with various MudBlazor type attributes. If I had wanted to generate a form using some other UI library – Syncfusion, for instance – I would only have needed to decorate the properties with Syncfusion specific attributes. The form generator itself is UI agnostic and will happily render whatever is needed.

For this sample, the form renders a radio group for the Selected property. It then renders one of two sub objects, depending on the state of the Selected property. Notice the LINQ expression on the VisibleExp property of the RenderObject attributes. Those expressions are evaluated at runtime and they are why we only see mongo options when mongo is selected, and we only see sqlserver options when sqlserver is selected.

Let’s look at the MongoModel and SqlServerModel types next:

public class MongoModel
{
    [RenderMudTextField]
    [ProtectedProperty]
    public string ConnectionString { get; set; } = "mongodb://localhost:27017";
}

public class SqlServerModel
{
    [RenderMudTextField]
    [ProtectedProperty]
    public string ConnectionString { get; set; } = "Server=.;Database=CG.Blazor.Setup.QuickStart;Trusted_Connection=True;";

    [RenderMudSlider(Label = "Command Timeout", Min = 0, Max = 90)]
    public int? CommandTimeout { get; set; } = 30;

    [RenderMudRadioGroup(Options = "None, CircuitBreaker, WaitAndRetry")]
    public string RetryPattern { get; set; } = "None";
}

As we can see, there’s absolutely no magic going on. It’s just POCO classes with properties that are decorated with various form generator attributes. Notice, on these two classes, SqlServerModel, and MongoModel, we’ve also included the ProtectedProperty attribute on the ConnectionString property. That was done to demonstrate that those properties are to be encrypted, at rest. It also demonstrates that the encryption is transparently decrypted, inside the setup plugin, so that we see the plain text while we are editing. That’s right, no more manually decrypting JSON when you want to edit a property! You’re welcome.

The only other thing to cover, code-wise, is the validator for the SetupVM class. That class looks like this:

public class SetupVMValidator : AbstractValidator<SetupVM>
{
    public SetupVMValidator()
    {
        // Ensure the Database property is populated.
        RuleFor(x => x.Database)
            .NotNull()
            .NotEmpty()
            .WithMessage("Database settings are missing!");

        // Rules for validating the Database property.
        RuleSet("Database", () =>
        {
            // Ensure a database type is selected.
            RuleFor(x => x.Database.Selected)
                .NotNull()
                .NotEmpty()
                .WithMessage("Must select a database type!");
                
            // Ensure the Mongo and SqlServer properties are populated.
            RuleFor(x => x.Database.Mongo)
                .NotNull()
                .WithMessage("Mongo settings are missing!");
            RuleFor(x => x.Database.SqlServer)
                .NotNull()
                .WithMessage("SqlServer settings are missing!");

            // If SqlServer is selected, ensure the connection is populated.
            RuleFor(x => x.Database.SqlServer.ConnectionString)
                .NotNull()
                .NotEmpty()
                .When(x => x.Database.Selected == "SqlServer")
                .WithMessage("SqlServer connnection string is required!");

            // If Mongo is selected, ensure the connection is populated.
            RuleFor(x => x.Database.Mongo.ConnectionString)
                .NotNull()
                .NotEmpty()
                .When(x => x.Database.Selected == "Mongo")
                .WithMessage("Mongo connnection string is required!");
        });
    }
}

Of course, validation is completely optional, but in this case, I wanted to demonstrate how easily it could be added into the mix. In this case, SetupVMValidator derives from the AbstractValidator base class, which is part of the Blazored.FluentValidation library. As we can see, the constructor for SetupVMValidator contains a variety of validations for the SetupVM object. Those validations are called, at submit time, via the Blazored.FluentValidation library. Which means, when the user tries to save the setup form, these validations are called.

One thing that I’d like to get across is, this POCO type, SetupVM, is just something I used for my quick start demo. You are free to create your own POCO type, for your setup, for your project.

In my mind, a setup will probably always be a small subset of all the possible configuration values that might go into making a Blazor website work. In other words, just those settings that might need to be deferred until the first time the application is run. For instance, things like what sort, and/or type of database to connect to, what kind of email provider to integrate with, etc.

I don’t see trying to edit every conceivable configuration setting, only because there are so many and the possible variations are so huge. For instance, Serilog is a logging package that I love to use, but, Serilog is also ridiculously configurable, and that means it’s probably not a good candidate for this approach. Also, it probably doesn’t make sense to edit the logging in a UI since, by the time the UI can be shown, the logging decisions have already been made. I guess timing is everything.


Here’s what the quick start setup form looks like:

This is with minimal styling (almost none, really). It’s really just a demo, after all. Also, if MudBlazor is not your thing, I also support several other UI libraries.


So where do I see this package going? Not sure, really. To me, it’s more of a concept than a tool, at least for now. I do see promise in it though, or I wouldn’t have taken the time to build it, much less blog about it. I like the idea of dealing with configuration settings at a higher level, in a more visual fashion. It’s unclear to me though, right now, whether that’s practical in the long term, or not.

I’ll be shocked if this library doesn’t change as I start to use it. It’s so new that there’s bound to be bugs and design flaws. I’ll post all my changes to GITHUB and write about them, as I have the time.

Thanks for reading this series of blog posts.


The source code for the CG.Blazor.Setup project is available, for free, HERE.

The CG.Blazor.Setup NUGET package is available, for free, HERE.


Photo by freestocks on Unsplash