1 2 3 4 5 6 7 8 9 10 11
Quino Release Notes

The following is a complete list of all Quino release notes, from newest to oldest. See the roadmap for future releases.

v5.0: Bootstrap IOC, authorization driver, improve dependencies

The summary below describes major new features, items of note and breaking changes. The full list of issues is in the release notes below and is available to those with access to the Encodo issue tracker.

Notes

Quino 4 was released on 11. August, 2017. Quino 4.1.7 brings that venerable line to a close. Products are encouraged to upgrade to the this version as the next stable build with the improvements outlined below.

Highlights

Breaking changes

  • ApplicationBasedTestsBase.CreateApplication() has a new parameter (bool isFixtureApplication). Applications that override this method will have to adjust their definition.
  • PathTools methods are deprecated. In some cases, these methods have been marked as obsolete (true), which causes a compile error. The obsolete message indicates which method to use instead.
  • ICommandSetManager.Apply() no longer has a IValueTools parameter
  • RegisterStandardConnectionServices has been renamed to RegisterApplicationConnectionServices
  • IValueTools.EnumParser is no longer settable; register your object/implementation in the Bootstrap instead
  • Methods from DataMetaExpressionFactoryExtensions has been removed. Instead of creating expressions directly with the expression factory, you should use the query or query table.
  • IQueryCondition no longer has a type parameter (it used to be IQueryCondition<TSelf>)
v4.1.2: Expressions and code-generation

The summary below describes major new features, items of note and breaking changes. The full list of issues is in the release notes and is available to those with access to the Encodo issue tracker.

Highlights

  • Fix code-generation for modules with no classes (QNO-5722)
  • Restore ExpressionConstants.Now (QNO-5720)
  • Improve backwards-compatibility for wrappers (QNO-5744)

Breaking changes

  • None
v4.0: New modeling API, expanded UI support and data improvements

The summary below describes major new features, items of note and breaking changes. The full list of issues is in the release notes below and is available to those with access to the Encodo issue tracker.

Highlights

Metadata & Modeling

Most of the existing metadata-building API has been deprecrated and replaced with a fluent API that is consistent and highly extensible.

UI

Data

Code-generation

  • Improve compatibility of generated code with StyleCop/best practices (QNO-5252, QNO-5584, QNO-5515)
  • Add support for integrating interfaces into generated code (QNO-5585)
  • Finalize support for including generated code in a separate assembly. Generated code can now be in a separate assembly from the modeling code.
  • Removed WinformDx code generator (QNO-5324)

General

  • Improve debugging with Quino sources and Nuget packages (QNO-5473)
  • Improve directory-services integration (QNO-5421)
  • Reworked the plugins system (QNO-2525)
  • Improve assembly-loading in tests and tools (QNO-5538, QNO-5571)
  • Improve registration API for external loggers; integrate Serilog (QNO-5591)
  • Improve schema-migration logging (QNO-5586)
  • Allow customization of exception and message formatting (QNO-5551, QNO-5550)

Breaking changes

Metadata & Modeling

  • The Encodo.Quino.Builders.Extensions namespace has been removed. All members were moved to Encodo.Quino.Meta or Encodo.Quino.Builders instead.
  • The assembly Quino.Meta.Standard no longer exists and may have to be removed manually if Nuget does not remove it for you.
  • Added default CreateModel() to MetaBuilderBasedModelBuilderBase
  • Added empty constructor to MetaBuilderBasedModelBuilderBase
  • GetSubModules() and GetModules() now returns IMetaModule instead of IModuleAspect
  • Even deprecated versions of AddSort(), AddSortOrderProperty(), AddEnumeratedClass(), AddValueListProperty() all expect a parameter of type IMetaExpressionFactory or IExpressionConstants now.

Data

  • The IDataSessionAwareList is used instead of IMetaAwareList
  • Two constructors of DataList have been made private
  • GenericObject.DoSetDedicatedSession() is no longer called or overridable
  • None of the classes derived from AuthenticatorBase accept an IApplication as constructor parameters anymore. Instead, use the Application or Session to create the authenticator with GetInstance<TService>(). E.g. if before you created a TokenAuthenticator with this call, new TokenAuthenticator(Application), you should now create the TokenAuthenticator with Application.GetInstance<TokenAuthenticator>(). You are free also to call the new constructor directly, but construction using the IOC is strongly recommended.
  • The constructor for DataSession has changed; this shouldn't cause too many problems as applications should be using the IDataSessionFactory to construct instances anyway.
  • DataGenerators have changed considerably. Implement the IDataGenerator interface instead of using the DataGenerator base class.
  • The names of ISchemaDifference have changed, so the output of a migration plan will also be different. Software that depended on scraping the plan to determine outcomes may no longer work.
  • Default values are no longer implicitly set. A default value for a required property will only be supplied if one is set in the model. Otherwise, a NULL-constraint violation will be thrown by the database. Existing applications will have to be updated: either set a default value in the metadata or set the property value before saving objects.

Code-generation

  • The generated filename for builders has changed from "Extensions.cs to "Builders.cs". When you regenerate code for the V2 format, you will have include the new files and remove the old ones from your project.
  • Data-language-specific properties are no longer generated by default because there is no guarantee that languages are available in a given application, You can still enable code-generation by calling SetCodeGenerated() on the multi-language or value-list property
  • The generated MetaModelBuilder classes are no longer generated. QNO-5515

General

  • LanguageTools.GetCaption() no longer defaults to GetDescription() because this is hardly ever what you wanted to happen.
  • CaptionExtensions are now in CaptionTools and are no longer extension methods on object.
  • ReflectionExtensions are now in ReflectionTools and are also no longer extension methods on object.

Expressions

  • Redeclared Operation<> with new method signature

Windows-specific

Some Windows-specific functionality has been moved to new assemblies. These assemblies are automatically included for Winform and WPF applications (as before). Applications that want to use the Windows-specific functionality will have to reference the following packages:

  • For WindowsIdentity-based code, use the Encodo.Connections.Windows package and call UseWindowsConnectionServices()
  • For ApplicationSettingsBase support, use the Encodo.Application.Windows package and call UseWindowsApplication()
  • For Directory Services support, use the Encodo.Security.Windows package and call UseWindowsSecurityServices().
v3.1: New metadata builder API

The summary below describes major new features, items of note and breaking changes. The full list of issues is also available for those with access to the Encodo issue tracker.

Highlights

This release is a "bridge" release that has the entire new Metadata API as well as the older version, which is marked as obsolete. It is intended that projects upgrade to this version only temporarily in order to more easily migrate to the 4.0 Metadata API. At that point, projects should immediately upgrade to Quino 4.0, from which all obsolete methods have been removed. Once 4.0 is available, there will be no more bug-fix releases for this release.

Metadata construction

  • Remove MetaId/Guid parameters from all metadata-building APIs
  • Remove all dependencies and requirement for the MetaBuilder; make MetaBuilder obsolete.
  • Drastically reduce the surface of the MetadataBuilder and base classes and improve dependency-resolution
  • Replaced AddClassWithDefaultPrimaryKey("A") with AddClass("A").AddId()
  • Added AddForeignKeys() step to builders and AddForeignKey() APIs
  • Moved a lot of the builder-related extension methods from Quino.Meta to Quino.Builders.
  • Added API to make it easier to add paths: AddPath(classA.FromOne(), classTwo.ToMany("OneId"))
  • Fixed generation of extension classes (e.g. PunchclockUser).

Data & Schema

  • Made all GenericObject constructors without an IDataSession obsolete.
  • Removed last reference to the GlobalContext from the data driver
  • Improved flexibility of caching subsystem and added default global/immutable-data cache (in addition to the session-based cache).
  • Fixed schema-migration for SQL Server: not-null constraints are now properly added and trigger-migration works with pure SQL statements and has been drastically simplified.

Configuration

  • Added BootstrapServices to the application to clearly delineate which objects are needed during the boostrap phase of configuration and startup
  • Re-added all satellite assemblies for all Quino Nuget packages (ch-DE translations)

GUI

  • Replaced all usages of DevExpress TreeList with GridControl/GridView.
  • Added a lot of WPF controls and application implementation with a Sandbox.WPF project

Breaking changes

  • PersistentEventHandlerAspect has been renamed to PersistentEventHandlerAspectBase

Roadmap

The next step is to bring out the 4.0 release, which will include the following features,

  • Remove all currently obsolete code
  • Reshape the metadata API to be more task-based and to significantly reduce the surface revealed to the application developer. This includes a drastic reduction of extension methods in Quino.Meta
  • Finish implementation and support for layouts everywhere (specifically as they are used for titles).
  • Split Quino.Data.Backend out of Quino.Data to reduce the surface exposed to application developers.
  • Deprecate IMetaReadable, IMetaWritable and IPersistable and replace with a more appropriate and slimmer API.
Quino Retrospective and Roadmap

History

Before taking a look at the roadmap, let's quickly recap how far we've come. An overview of the release schedule shows a steady accretion of features over the years, as driven by customer or project needs.

image

The list below includes more detail on the releases highlighted in the graphic.1

  • 0.1: Proof of concept with metadata, PostgreSql (data and schema-migration) and Winforms UI
  • 1.0: First customer product with PostgreSql, DevExpress Winforms UI and Reporting
  • 1.0.5: MS-SQL driver (parity with PostgreSql driver)
  • 1.5.0: Remoting data driver; require .NET 4.0
  • 1.6.0: Mongo/NoSQL data driver
  • 1.8.0: Rewrite data driver to use sessions
  • 1.8.5: Support improved metadata-generation pattern
  • 1.9.0: Add plugin/overlay support
  • 1.10.0: Require .NET 4.5; add JSON-based remoting protocol; Windows-service support
  • 1.13.0: Rewrite security API
  • v2.0-beta1: Rewrite configuration, logging and schema-migration APIs
  • v2.0-beta2: Add V2 generated-code format
  • 2.0: Finish configuration/IOC rewrite; produce NuGet packages for delivery
  • 2.2: Stabilize Winform; support aliased tables in queries
  • 3.0: Rewrite MetaBuilder API; improve support for plugins

We took 1.5 years to get to v1. The initial major version was to signify the first time that Quino-based code went into external production.2

After that, it took 6.5 years to get to v2. Although we added several large products that use Quino, we were always able to extend rather than significantly change anything in the core. The second major version was to signify sweeping changes made to address technical debt, to modernize certain components and to prepare for changes coming to the .NET platform.

It took just 5 months to get to v3 for two reasons:

  1. Although we were able to make a lot of planned changes in v23, we had to leave some breaking changes for future versions.4
  2. We now strictly adhere to the rule that a breaking change anywhere in the software's API -- and Quino's API surface is large -- leads automatically to a major-version change.5

Roadmap

So that's where we've been. Where are we headed?

As you can see above, Quino is a very mature product that satisfies the needs of a wide array of software on all tiers. What more is there to add?

Quino's design has always been driven by a combination of customer requirements and what we anticipated would be customer requirements.

We're currently working on the following features.

Modeling improvements

This work builds on the API changes made to the MetaBuilder in v3. We're creating a more fluent, modern and extensible API for building metadata. We hope to be able to add these changes incrementally without introducing any breaking changes.6

WPF / VSG

A natural use of the rich metadata in Quino is to generate user interfaces for business entities without have to hand-tool each form. From the POC onward, Quino has included support for generating UIs for .NET Winforms.

Winforms has been replaced on the Windows desktop with WPF and UWP. We've gotten quite far with being able to generate WPF applications from Quino metadata. The screenshots below come from a pre-alpha version of the Sandbox application included in the Quino solution.

imageimageimageimage

You may have noticed the lovely style of the new UI.7 We're using a VSG designed for us by Ergosign, for whom we've done some implementation work in the past.

.NET Core

If you've been following Microsoft's announcements, things are moving quickly in the .NET world. There are whole new platforms available, if you target your software to run on them. We're investigating the next target platforms for Quino. Currently that means getting the core of Quino -- Quino.Meta and its dependencies -- to compile under .NET Core.

imageAs you can see in the screenshot, we've got one of the toughest assemblies to compile -- Encodo.Core. After that, we'll try for running some tests under Linux or OS X. The long-term goal is to be able to run Quino-based application and web servers on non-Windows -- and, most importantly, non-IIS -- platforms.8

These changes will almost certainly cause builds using previous versions to break. Look for any additional platform support in an upcoming major-version release.



  1. There were, of course, more minor and patch releases throughout, but those didn't introduce any major new functionality.

  2. Punchclock, our time-entry and invoicing software -- and Quino "dogfood (When a developer uses their own code for their own daily needs. Being a user as well as a developer creates the user empathy that is the hallmark of good software.)" product -- had been in use internally at Encodo earlier than that.

  3. E.g. splitting the monolithic Encodo and Quino assemblies into dozens of new, smaller and much more focused assemblies. Reorganizing configuration around the IOC and rewriting application startup for more than just desktop applications was another sweeping change.

  4. One of those breaking changes was to the MetaBuilder, which started off as a helper class for assembling application metadata, but became a monolithic and unavoidable dependency, even in v2. In v3, we made the breaking changes to remove this component from its central role and will continue to replace its functionality with components that more targeted, flexible and customizable.

  5. In the years between v1 and v2, we used the minor-version number to indicate when breaking changes could be made. We also didn't try as hard to avoid breaking changes by gracefully deprecating code. The new approach tries very hard to avoid breaking changes but accepts the consequences when it's deemed necessary by the team.

  6. That is, when users upgrade to a version with the newer APIs, they will get obsolete warnings but their existing code will continue to build and run, as before the upgrade. In this way, customers can smoothly upgrade without breaking any builds.

  7. You may also have noticed that the "Sandbox Dialog View" includes a little tag in it for the "XAML Spy", a tool that we use for WPF development. Just so you know the screenshots aren't faked... :-)

  8. As with the WPF interface, we're likely to dogfood all of these technologies with Punchclock, our time-tracking and invoicing system written with Quino. The application server and web components that run on Windows could be migrated to run on one of our many Linux machines instead.

v3.0: Metadata builders and code-generation improvements

The summary below describes major new features, items of note and breaking changes. The full list of issues is also available for those with access to the Encodo issue tracker.

Highlights

Breaking changes

  • IDataSession and IApplication now directly implement the IServiceRequestHandler and helper methods that used to extend IApplication now extend this interface instead, so calls like GetModel() can now be executed against an IApplication or an IDataSession. Many methods have been moved out of the IServiceRequestHandler interface to extension methods declared in the Encodo.IOC namespace. This move will require applications to update the usings. ReSharper will automatically find the correct namespace and apply it for you.
  • Similarly, the extension method ApplicationExtensions.GetInstance() has been replaced with a direct implementation of the IServiceRequestHandler by IApplication.
  • MetaBuilder.Include() has been replaced with Dependencies.Include()
  • When you call the new version of CreateModel(), you can no longer call CreateMainModule() because the main module is set up automatically. Although the call is marked as obsolete, it can only be combined with the older overload of the CreateModel(). Using it with the newer overload will cause a runtime error as the main module is added to the model twice.
  • The various methods to create paths with the MetaBuilder have been replaced by AddPath(). To rewrite a path, use the following style:
Builder.AddPath(
  Elements.Classes.A.FromOne("Id"), 
  Elements.Classes.B.ToMany("FileId"), 
  path => path.SetMetaId(new Guid("...")).SetDeleteRule(MetaPathRule.Cascade),
  idx => idx.SetMetaId(new Guid("..."))
);
v2.2: Winform fixes and Query Improvements

The summary below describes major new features, items of note and breaking changes. The full list of issues is also available for those with access to the Encodo issue tracker.

Highlights

  • Lots of bug fixes and improvements for the Winform UI and German translations with the release of Punchclock on this version. (QNO-5162, QNO-5159, QNO-5158, QNO-5157, QNO-5156, QNO-5140, QNO-5155, QNO-5145, QNO-5111, QNO-5107, QNO-5106, QNO-5104, QNO-5015)
  • DateTimeExtensions.GetDayOfWeek() had a leap-day bug (QNO-5051)
  • Fixed how the hash code for GenericObjects is calculated, which fixes sorting issues in grids, specifically for non-persisted or transient objects (QNO-5137)
  • Improvements to the IAccessControl API for getting groups and users and testing membership (QNO-5133)
  • Add support for query aliases (e.g. for joining the same table multiple times) (QNO-531) This changes the API surface only minimally. Applications can pass an alias when calling the Join method, as shown below,
query.Join(Metadata.Project.Deputy, alias: "deputy")

You can find more examples of aliased queries in the TestAliasedQuery(), TestJoinAliasedTables(), TestJoinChildTwice() defined in the QueryTests testing fixture.

  • Add a standalone IQueryAnalyzer for optimizations and in-memory mini-drivers (QNO-4830)

Breaking changes

  • ISchemaManager has been removed. Instead, you should retrieve the interface you were looking for from the IOC. The possible interfaces you might need are IImportHandler, IMappingBuilder, IPlanBuilder or ISchemaCommandFactory.

  • ISchemaManagerSettings.GetAuthorized() has been moved to ISchemaManagerAuthorizer.

  • The hash-code fix for GenericObjects may have an effect on the way your application sorts objects.The IParticipantManager (base interface of IAccessControl) no longer has a single method called GetGroups(IParticipant). This method was previously used to get the groups to which a user belongs and the child groups of a given group. This confusing double duty for the API led to an incorrect implementation for both usages. Instead, there are now two methods:

    • IEnumerable<IGroup> GetGroups(IUser user): Gets the groups for the given user
    • IEnumerable<IGroup> GetChildGroups(IGroup group): Gets the child groups for the given group

The old method has been removed from the interface because (A) it never worked correctly anyway and (B) it conflicts with the new API.

Mini-applications and utilities with Quino

In several articles last year1, 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.

imageSome 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 Studio2, 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.


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.

<?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>

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.

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. :-)


  1. See Encodos 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:

  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:

  4. If you look at the implementation of the UseStandard method10, 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.

  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.

  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.