This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.


Mini-applications and utilities with Quino


In several articles last year<fn>, 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. <img attachment="folder_utily.png" align="right">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. <h>Why use a Common Library?</h> 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 <b>advantages</b> of doing this? <ul> Writing code that is <i>out of</i> scope takes time away from writing code that is <i>in</i> 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 <i>just wrote seconds ago</i>. 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. </ul> What are potential <b>disadvantages</b>? <ul> The library might compel a level of complexity that makes it take <i>longer</i> 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. </ul> 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. <h>Two Sample Applications</h> 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: <ol> An application that loads settings for Windows service-registration. We built this for a customer product. The <i>Quino Code Generator</i> that we use to generate metadata and ORM classes from the model </ol> <h>Windows Service Installer</h> The actual service-registration part is boilerplate generated by Microsoft Visual Studio<fn>, 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? <ul> 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. </ul> That doesn't sound that hard, right? I'm sure you could just whip something together with an <c>XMLDocument</c> and some hard-coded paths and filenames that would do the trick.<fn> 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? <h level="3">Getting the Settings</h> The following listing shows the main application method, using the Encodo/Quino framework libraries to do the heavy lifting. <code> [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; } </code> If you've been following along in the other articles (see first footnote below), then this structure should be very familiar. We use an <c>ApplicationManager()</c> to execute the application logic, creating the application with <c>CreateServiceConfigurationApplication</c> 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. <h level="3">Configuring the Settings Loader</h> Soooo...I can see where we're returning the <c>ServiceSettings</c>, but where are they configured? Let's take a look at the second method, the one that creates the application. <code> 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; } </code> <ol> First, we create a standard <c>Application</c>, defined in the <c>Encodo.Application</c> 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). The next step is to choose an IOC, which we do by calling <c>UseSimpleInjector()</c>. Quino includes support for the <i>SimpleInjector</i> IOC out of the box. As you can see, you must include this support <i>explicitly</i>, so you're also free to assign your own IOC (e.g. one using Microsoft's Unity). <i>SimpleInjector</i> is very lightweight and super-fast, so there's no downside to using it. 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 <c>UseStandard()</c>, defined in the <c>Encodo.Application.Standard</c> assembly. Since I know that <c>UseStandard()</c> pulls in what I'm likely to need, I'll just use that.<fn> The next line tells the application the name of the configuration file to use.<fn> The very next line is already application-specific code, where we configure the <c>ServiceSettings</c> object that we want to return. For that, there's a <c>Configure</c> 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. The final call to <c>RegisterSingle</c> makes sure that the <c>ServiceSettings</c> object created by the IOC is a singleton (it would be silly to configure one instance and return another, unconfigured one). </ol> 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 <c>ServiceSettings</c> object is already configured and ready for use. That's why, in <c>LoadServiceSettings()</c>, we can just get the settings from the application with <c>GetInstance()</c> and exit immediately. <h>Code Generator</h> 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. <h level="3">Execution</h> The main method defers all functionality to the <c>ApplicationManager</c>, passing along two methods, one to create the application, the other to run it. <code> internal static void Main() { new ApplicationManager().Run(CreateApplication, GenerateCode); } </code> <h level="3">Configuration</h> As before, we first create an <c>Application</c>, then choose the <i>SimpleInjector</i> and some standard configuration and registrations with <c>UseStandard()</c>, <c>UseMetaStandardServices() </c> and <c>UseMetaTools()</c>.<fn> We set the application title to "Quino Code Generator" and then include objects with <c>UseSingle()</c> that will be configured from the command line and used later in the application.<fn> And, finally, we add our own <c>ICommandSet</c> to the command-line processor that will configure the input and output settings. We'll take a look at that part next. <code> 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(); } </code> <h level="3">Command-line Processing</h> 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 <kbd>i</kbd> simply sets the value of <c>inputSettings.AssemblyFilename</c> to whatever was passed on the command line after that parameter. Note that it pulls the <c>inputSettings</c> 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.<fn> <code> 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> <h level="3">Code-generation</h> 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.<fn> I've highlighted the code-generator--specific objects in the code below. All other objects are standard library tools and interfaces. <code> private static void GenerateCode(IApplication application) { var logger = application.GetLogger(); <hl>var inputSettings = application.GetInstance<codegeneratorinputsettings>();</hl> if (!inputSettings.TypeNames.Any()) { logger.Log(Levels.Warning, "No types to generate."); } else { var modelLoader = application.GetInstance<imetamodelloader>(); var metaCodeGenerator = application.GetInstance<imetacodegenerator>(); <hl>var outputSettings = application.GetInstance<codegeneratoroutputsettings>();</hl> 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 ); } } } </code> 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. <hr> <ft>See Encodo’s configuration library for Quino <a href="{app}view_article.php?id=412">Part 1</a>, <a href="{app}view_article.php?id=413">Part 2</a> and <a href="{app}view_article.php?id=414">Part 3</a> as well as API Design: Running and Application <a href="{app}view_article.php?id=422">Part 1</a> and <a href="{app}view_article.php?id=426">Part 2</a> and, finally, <a href="{app}view_article.php?id=412">Starting up an application, in detail</a>.</ft> <ft>That boilerplate looks like this: <code>var fileService = new ServiceInstaller(); fileService.StartType = ServiceStartMode.Automatic; fileService.DisplayName = "Quino Sandbox"; fileService.Description = "Demonstrates a Quino-based service."; fileService.ServiceName = "Sandbox.Services"; </code> See the <c>ServiceInstaller.cs</c> file in the <c>Sandbox.Server</c> project in Quino 2.1.2 and higher for the full listing.</ft> <ft>The standard implementation of Quino's <i>ITextKeyValueNodeReader</i> supports XML, but it would be trivial to create and register a version that supports JSON (<a href="">QNO-4993</a>) or YAML. The configuration file for the utility looks like this: <code> <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> </code></ft> <ft>If you look at the implementation of the <c>UseStandard</c> method<fn>, 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 <i>do</i> want. For example, you could call <c>UseApplication()</c> from the <c>Encodo.Application</c> assembly instead. That method includes only the support for: <ul> Processing the command line (<c>ICommandSetManager</c>) Locating external files (<c>ILocationManager</c>) Loading configuration data from file (<c>IConfigurationDataLoader</c>) Debug- and file-based logging (<c>IExternalLoggerFactory</c>) and interacting with the <c>IApplicationManager</c>. </ul> If you want to go even lower than that, you can try <c>UseCore()</c>, defined in the <c>Encodo.Core</c> assembly and then pick and choose the individual components yourself. Methods like <c>UseApplication()</c> and <c>UseStandard()</c> 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. </ft> <ft>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 <c>GetLocationManager()</c> and setting values on it. You'll notice that I didn't use <c>Configure<ilocationmanager>()</c> 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 <i>before</i> 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: <ol> Create application (this is where you call <c>Use*()</c> to build the application) Get the location manager to figure out the path for <c>LocationNames.Configuration</c> Load the configuration file Execute all remaining actions, including those scheduled with calls to <c>Configure()</c> </ol> 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 <c>UseConfigurationDataLoader()</c> to register your own object to actually load configuration data and do whatever the heck you like in there, including returning constant data. :-)</ft> <ft>The metadata-specific analog to <c>UseStandard()</c> is <c>UseMetaStandard()</c>, but we don't call that. Instead, we call <c>UseMetaStandardServices()</c>. 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 <i>IOC registrations</i> required by metadata-based applications without adding any of the startup or shutdown actions. Many of the standard <c>Use*()</c> methods included in the base libraries have analogs like this. The <c>Use*Services()</c> 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.</ft> <ft>Wait, why didn't we call <c>RegisterSingle()</c>? 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 <i>other</i> IOC, accessed with <c>SetSingle()</c> and <c>GetSingle()</c>.</ft> <ft>The full listing is in <c>Program.cs</c> in the <c>Quino.CodeGenerator</c> project in any 2.x version of Quino.</ft> <ft>Note that, once the application is started, you can use <c>GetInstance()</c> instead of <c>GetSingle()</c> 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 <i>recommended</i> to use <c>GetInstance()</c> 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.</ft> <ft>If you have the Quino source code handy, you can look it up there, but if you have ReSharper installed, you can just <kbd>F12</kbd> on <c>UseStandard()</c> to decompile the method. In the latest <i>DotPeek</i>, the extension methods are even displayed much more nicely in decompiled form.</ft>