Your browser may have trouble rendering this page. See supported browsers for more information.

|<<>>|118 of 273 Show listMobile Mode

Mini-applications and utilities with Quino

Published by marco on

Updated by marco on

In several articles last year[1], I went into a lot of detail about the configuration and startup for Quino applications. Those posts discuss a lot about what led to the architecture Quino has for loading up an application.

 Some of you might be wondering: what if I want to start up and run an application that doesn’t use Quino? Can I build applications that don’t use any fancy metadata because they’re super-simple and don’t even need to store any data? Those are the kind of utility applications I make all the time; do you have anything for me, you continue to wonder?

As you probably suspected from the leading question: You’re in luck. Any functionality that doesn’t need metadata is available to you without using any of Quino. We call this the “Encodo” libraries, which are the base on which Quino is built. Thanks to the fundamental changes made in Quino 2, you have a wealth of functionality available in just the granularity you’re looking for.

Why use a Common Library?

Instead of writing such small applications from scratch—and we know we could write them—why would we want to leverage existing code? What are the advantages of doing this?

  • Writing code that is out of scope takes time away from writing code that is in scope.
  • Code you never write has no bugs.
  • It also doesn’t require maintenance or documentation.
  • While library code is not guaranteed to be bug-free, it’s probably much better off than the code you just wrote seconds ago.
  • Using a library increases the likelihood of having robust, reliable and extendible code for out-of-scope application components.
  • One-off applications tend to be maintainable only by the originator. Applications using a common library can be maintained by anyone familiar with that library.
  • Without a library, common mistakes must be fixed in all copies, once for each one-off application.
  • The application can benefit from bug fixes and improvements made to the library.
  • Good practices and patterns are encouraged/enforced by the library.

What are potential disadvantages?

  • The library might compel a level of complexity that makes it take longer to create the application than writing it from scratch
  • The library might force you to use components that you don’t want.
  • The library might hamstring you, preventing innovation.

A developer unfamiliar with a library—or one who is too impatient to read up on it—will feel these disadvantages more acutely and earlier.

Two Sample Applications

Let’s take a look at some examples below to see how the Encodo/Quino libraries stack up. Are we able to profit from the advantages without suffering from the disadvantages?

We’re going to take a look at two simple applications:

  1. An application that loads settings for Windows service-registration. We built this for a customer product.
  2. The Quino Code Generator that we use to generate metadata and ORM classes from the model

Windows Service Installer

The actual service-registration part is boilerplate generated by Microsoft Visual Studio[2], but we’d like to replace the hard-coded strings with customized data obtained from a configuration file. So how do we get that data?

  • The main requirement is that the user should be able to indicate which settings to use when registering the Windows service.
  • The utility could read them in from the command line, but it would be nicer to read them from a configuration file.

That doesn’t sound that hard, right? I’m sure you could just whip something together with an XMLDocument and some hard-coded paths and filenames that would do the trick.[3] It might even work on the first try, too. But do you really want to bother with all of that? Wouldn’t you rather just get the scaffolding for free and focus on the part where you load your settings?

Getting the Settings

The following listing shows the main application method, using the Encodo/Quino framework libraries to do the heavy lifting.

[NotNull]
public static ServiceSettings LoadServiceSettings()
{
  ServiceSettings result = null;
  var transcript = new ApplicationManager().Run(
    CreateServiceConfigurationApplication,
    app => result = app.GetInstance<ServiceSettings>()
  );

  if (transcript.ExitCode != ExitCodes.Ok)
  {
    throw new InvalidOperationException(
      "Could not read the service settings from the configuration file." + 
      new SimpleMessageFormatter().GetErrorDetails(transcript.Messages)
    );
  }

  return result;
}

If you’ve been following along in the other articles (see first footnote below), then this structure should be very familiar. We use an ApplicationManager() to execute the application logic, creating the application with CreateServiceConfigurationApplication and returning the settings configured by the application in the second parameter (the “run” action). If anything went wrong, we get the details and throw an exception.

You can’t see it, but the library provides debug/file logging (if you enable it), debug/release mode support (exception-handling, etc.) and everything is customizable/replaceable by registering with an IOC.

Configuring the Settings Loader

Soooo…I can see where we’re returning the ServiceSettings, but where are they configured? Let’s take a look at the second method, the one that creates the application.

private static IApplication CreateServiceConfigurationApplication()
{
  var application = new Application();
  application
    .UseSimpleInjector()
    .UseStandard()
    .UseConfigurationFile("service-settings.xml")
    .Configure<ServiceSettings>(
      "service", 
      (settings, node) =>
      {
        settings.ServiceName = node.GetValue("name", settings.ServiceName);
        settings.DisplayName = node.GetValue("displayName", settings.DisplayName);
        settings.Description = node.GetValue("description", settings.Description);
        settings.Types = node.GetValue("types", settings.Types);
      }
    ).RegisterSingle<ServiceSettings>();

  return application;
}
  1. First, we create a standard Application, defined in the Encodo.Application assembly. What does this class do? It does very little other than manage the main IOC (see articles linked in the first footnote for details).
  2. The next step is to choose an IOC, which we do by calling UseSimpleInjector(). Quino includes support for the SimpleInjector IOC out of the box. As you can see, you must include this support explicitly, so you’re also free to assign your own IOC (e.g. one using Microsoft’s Unity). SimpleInjector is very lightweight and super-fast, so there’s no downside to using it.
  3. Now we have an application with an IOC that doesn’t have any registrations on it. How do we get more functionality? By calling methods like UseStandard(), defined in the Encodo.Application.Standard assembly. Since I know that UseStandard() pulls in what I’m likely to need, I’ll just use that.[4]
  4. The next line tells the application the name of the configuration file to use.[5]
  5. The very next line is already application-specific code, where we configure the ServiceSettings object that we want to return. For that, there’s a Configure method that returns an object from the IOC along with a specific node from the configuration data. This method is called only if everything started up OK.
  6. The final call to RegisterSingle makes sure that the ServiceSettings object created by the IOC is a singleton (it would be silly to configure one instance and return another, unconfigured one).

Basically, because this application is so simple, it has already accomplished its goal by the time the standard startup completes. At the point that we would “run” this application, the ServiceSettings object is already configured and ready for use. That’s why, in LoadServiceSettings(), we can just get the settings from the application with GetInstance() and exit immediately.

Code Generator

The code generator has a bit more code, but follows the same pattern as the simple application above. In this case, we use the command line rather than the configuration file to get user input.

Execution

The main method defers all functionality to the ApplicationManager, passing along two methods, one to create the application, the other to run it.

internal static void Main()
{
  new ApplicationManager().Run(CreateApplication, GenerateCode);
}

Configuration

As before, we first create an Application, then choose the SimpleInjector and some standard configuration and registrations with UseStandard(), UseMetaStandardServices() and UseMetaTools().[6]

We set the application title to “Quino Code Generator” and then include objects with UseSingle() that will be configured from the command line and used later in the application.[7] And, finally, we add our own ICommandSet to the command-line processor that will configure the input and output settings. We’ll take a look at that part next.

private static IApplication CreateApplication(
  IApplicationCreationSettings applicationCreationSettings)
{
  var application = new Application();

  return
    application
    .UseSimpleInjector()
    .UseStandard()
    .UseMetaStandardServices()
    .UseMetaTools()
    .UseTitle("Quino Code Generator")
    .UseSingle(new CodeGeneratorInputSettings())
    .UseSingle(new CodeGeneratorOutputSettings())
    .UseUnattendedCommand()
    .UseCommandSet(CreateGenerateCodeCommandSet(application))
    .UseConsole();
}

Command-line Processing

The final bit of the application configuration is to see how to add items to the command-line processor.

Basically, each command set consists of required values, optional values and zero or more switches that are considered part of a set.

The one for i simply sets the value of inputSettings.AssemblyFilename to whatever was passed on the command line after that parameter. Note that it pulls the inputSettings from the application to make sure that it sets the values on the same singleton reference as will be used in the rest of the application.

The code below shows only one of the code-generator–specific command-line options.[8]

private static ICommandSet CreateGenerateCodeCommandSet(
  IApplication application)
{
  var inputSettings = application.GetSingle<CodeGeneratorInputSettings>();
  var outputSettings = application.GetSingle<CodeGeneratorOutputSettings>();

  return new CommandSet("Generate Code")
  {
    Required =
    {
      new OptionCommandDefinition<string>
      {
        ShortName = "i",
        LongName = "in",
        Description = Resources.Program_ParseCommandLineArgumentIn,
        Action = value => inputSettings.AssemblyFilename = value
      },
      // And others…
    },
  };
}

Code-generation

Finally, let’s take a look at the main program execution for the code generator. It shouldn’t surprise you too much to see that the logic consists mostly of getting objects from the IOC and telling them to do stuff with each other.[9]

I’ve highlighted the code-generator–specific objects in the code below. All other objects are standard library tools and interfaces.

private static void GenerateCode(IApplication application)
{
  var logger = application.GetLogger();
  var inputSettings = application.GetInstance<CodeGeneratorInputSettings>();

  if (!inputSettings.TypeNames.Any())
  {
    logger.Log(Levels.Warning, "No types to generate.");
  }
  else
  {
    var modelLoader = application.GetInstance<IMetaModelLoader>();
    var metaCodeGenerator = application.GetInstance<IMetaCodeGenerator>();
    var outputSettings = application.GetInstance<CodeGeneratorOutputSettings>();
    var modelAssembly = AssemblyTools.LoadAssembly(
      inputSettings.AssemblyFilename, logger
    );

    outputSettings.AssemblyDetails = modelAssembly.GetDetails();

    foreach (var typeName in inputSettings.TypeNames)
    {
      metaCodeGenerator.GenerateCode(
        modelLoader.LoadModel(modelAssembly, typeName), 
        outputSettings,
        logger
      );
    }
  }
}

So that’s basically it: no matter how simple or complex your application, you configure it by indicating what stuff you want to use, then use all of that stuff once the application has successfully started. The Encodo/Quino framework provides a large amount of standard functionality. It’s yours to use as you like and you don’t have to worry about building it yourself. Even your tiniest application can benefit from sophisticated error-handling, command-line support, configuration and logging without lifting a finger.


[1] See Encodo’s configuration library for Quino Part 1, Part 2 and Part 3 as well as API Design: Running and Application Part 1 and Part 2 and, finally, Starting up an application, in detail.
[2]

That boilerplate looks like this:

var fileService = new ServiceInstaller();
fileService.StartType = ServiceStartMode.Automatic;
fileService.DisplayName = "Quino Sandbox";
fileService.Description = "Demonstrates a Quino-based service.";
fileService.ServiceName = "Sandbox.Services";

See the ServiceInstaller.cs file in the Sandbox.Server project in Quino 2.1.2 and higher for the full listing.

[3]

The standard implementation of Quino’s ITextKeyValueNodeReader supports XML, but it would be trivial to create and register a version that supports JSON (QNO-4993) or YAML. The configuration file for the utility looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<config>
  <service>
    <name>Quino.Services</name>
    <displayName>Quino Utility</displayName>
    <description>The application to run all Quino backend services.</description>
    <types>All</types>
  </service>
</config>
[4]

If you look at the implementation of the UseStandard method[10], it pulls in a lot of stuff, like support for BCrypt, enhanced CSV and enum-value parsing and standard configuration for various components (e.g. the file log and command line). It’s called “Standard” because it’s the stuff we tend to use in a lot of applications.

But that method is just a composition of over a dozen other methods. If, for whatever reason (perhaps dependencies), you don’t want all of that functionality, you can just call the subset of methods that you do want. For example, you could call UseApplication() from the Encodo.Application assembly instead. That method includes only the support for:

  • Processing the command line (ICommandSetManager)
  • Locating external files (ILocationManager)
  • Loading configuration data from file (IConfigurationDataLoader)
  • Debug- and file-based logging (IExternalLoggerFactory)
  • and interacting with the IApplicationManager.

If you want to go even lower than that, you can try UseCore(), defined in the Encodo.Core assembly and then pick and choose the individual components yourself. Methods like UseApplication() and UseStandard() are tried and tested defaults, but you’re free to configure your application however you want, pulling from the rich trove of features that Quino offers.

[5]

By default, the application will look for this file next to the executable. You can configure this as well, by getting the location manager with GetLocationManager() and setting values on it.

You’ll notice that I didn’t use Configure<ILocationManager>() for this particular usage. That’s ordinarily the way to go if you want to make changes to a singleton before it is used. However, if you want to change where the application looks for configuration files, then you have to change the location manager before it’s used any other configuration takes place. It’s a special object that is available before the IOC has been fully configured. To reiterate from other articles (because it’s important), the order of operations we’re interested in here are:

  1. Create application (this is where you call Use*() to build the application)
  2. Get the location manager to figure out the path for LocationNames.Configuration
  3. Load the configuration file
  4. Execute all remaining actions, including those scheduled with calls to Configure()

If you want to change the configuration-file location, then you have to get in there before the startup starts running—and that’s basically during application construction. Alternatively, you could also call UseConfigurationDataLoader() to register your own object to actually load configuration data and do whatever the heck you like in there, including returning constant data. :-)

[6] The metadata-specific analog to UseStandard() is UseMetaStandard(), but we don’t call that. Instead, we call UseMetaStandardServices(). Why? The answer is that we want the code generator to be able to use some objects defined in Quino, but the code generator itself isn’t a metadata-based application. We want to include the IOC registrations required by metadata-based applications without adding any of the startup or shutdown actions. Many of the standard Use*() methods included in the base libraries have analogs like this. The Use*Services() analogs are also very useful in automated tests, where you want to be able to create objects but don’t want to add anything to the startup.
[7] Wait, why didn’t we call RegisterSingle()? For almost any object, we could totally do that. But objects used during the first stage of application startup—before the IOC is available—must go in the other IOC, accessed with SetSingle() and GetSingle().
[8] The full listing is in Program.cs in the Quino.CodeGenerator project in any 2.x version of Quino.
[9] Note that, once the application is started, you can use GetInstance() instead of GetSingle() because the IOC is now available and all singletons are mirrored from the startup IOC to the main IOC. In fact, once the application is started, it’s recommended to use GetInstance() everywhere, for consistency and to prevent the subtle distinction between IOCs—present only in the first stage of startup—from bleeding into your main program logic.
[10] If you have the Quino source code handy, you can look it up there, but if you have ReSharper installed, you can just F12 on UseStandard() to decompile the method. In the latest DotPeek, the extension methods are even displayed much more nicely in decompiled form.