2 3 4 5 6 7 8 9 10 11 12
v1.8.6: Bug fix release for v1.8.5 (no new features)

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

No known breaking changes

Archive of Quino release notes are now online

For a long time, we maintained our release notes in a wiki that's only accessible for Encodo employees. For the last several versions -- since v1.7.6 -- we've published them on the web site, available for all to see.1 Because they reveal the progress and history of Quino quite nicely, we've made all archival release notes available on the web site as well.

Browse to the Quino folder in the blogs to see them all. Some highlights are listed below:

  • v0.1 -- November 2007: Even the very first version already included the core of Quino: metadata, basic schema migration and an automatically generated Winforms-based UI
  • v0.5 -- July 2008: Introduced reporting integration, based on the DevExpress reporting system and including a report-designer UI
  • v1.0 -- April 2009: The 1.0 release coincided with the initial release of the first suite of applications built with Quino2 and included a lot of performance tweaks (e.g. caching)
  • v1.0.5 -- September 2009: Included the first version of the MS-SQL driver, with support for automated schema migration (putting it more or less in the same class as the PostgreSql driver).
  • v1.5.0 -- October 2010: Quino officially moved to .NET 4.0 and VS2010
  • v1.6.0 -- March 2011: Included an initial verison of a Mongo/NoSQL data driver (without schema-migration support)
  • v1.7.0 -- February 2012: Included a radical rewrite of the startup and shutdown mechanism, including configuration, service locator registration as well as the feedback sub-system
  • v1.7.6 -- August 2012: Quino moved to NuGet for all third-party package integration, with NAnt integration for all common tasks
  • v1.8.0 -- August 2012 and 1.8.1 -- September 2012: The data-driver architecture was overhauled to improve support for multiple databases, remoting, cloud integration, custom caching and mini-drivers
  • v1.8.3 -- October 2012: Introduced a user interface to help analyze data traffic (which integrated with the statistics maintained by the data driver) for Winform applications
  • v1.8.5 -- November 2012: The latest available release -- as of the end of 2012 -- includes an overhaul of the metadata-generation pattern with an upgrade path for applications as well as support for running against a local database using the Mongo data driver.
  • v1.8.63: Coming up in the next release, is support for plugins and model overlays to allow an application to load customizations without recompiling.


  1. Please note that the detailed release notes include links to our issue-tracking system, for which a login is currently required.

  2. The initial version of Quino was developed while working on a suite of applications for running a medium-sized school. The software includes teacher/student administration, curriculum management, scheduling and planning as well as integration with mail/calendar and schedule-display systems. By the time we finished, it also included a web interface for various reporting services and an absence-tracking and management system is going to be released soon.

  3. The release notes were not available at publication time.

Quino Projekt-Template für Visual Studio 2012

Einleitung

Visual Studio bietet die Möglichkeit, zusätzlich zu den von Haus aus mitgelieferten Projekt-Templates eigene Templates zu erstellen und diese dann zu verwenden. Dies ist von Vorteil, wenn häufig ähnliche Projekte erstellt werden und das Projektsetup verhältnismässig aufwändig ist. Diese Voraussetzugen treffen auf unser hauseigenenes Framework Quino bestens zu: Für jede neue Quino Applikation muss ein Model erstellt werden, was jeweils einige Code- und Konfigurationsdateien erfordert. Dies nimmt, insbesondere wenn man es zum ersten Mal macht, schnell einige Stunden Zeit in Anspruch bis alles wie gewünscht läuft.

Mit einem Projekt-Template ist dies deutlich einfacher: Neues Projekt erstellen, die gewünschten Module wählen und schon wird eine lauffähige Quino Applikation mit einem einfachen Model erstellt. Darauf aufbauend kann man dann die eingene Applikation implementieren.

Die Erstellung eines eigenen Projekt-Templates ist aber, vor Allem wenn es etwas umfangreicher ist und zusätzlich einen eigenen Wizard haben soll, mit einigen Fallstricken versehen. Auch sind die Informationen dazu auf MSDN und generell im Internet eher spärlich und teilweise verwirrend. Um das gewonnene Know-How mit anderen Entwicklern zu teilen haben wir eine kleine Anleitung verfasst. Diese ist zwar auf Quino zugeschnitten, kann aber natürlich auch auf eigene Bedürfnisse angepasst werden.

image

Ziel

Es soll ein Projekt-Template erstellt werden, das eine lauffähige Quino Applikation generiert. Dabei sollen die Dateinamen sowie die verwendeten Namespaces dem Namen des Projektes angepasst werden. Ausserdem soll das Projekt-Template beim Generieren des Projekts einen Wizard anzeigen, in dem verschiedenen Quino Module an- oder abgewählt werden können. Momentan sind dies Core für das Model und Winforms für eine Winforms Oberfläche. Für jedes gewählte Modul wird ein Projekt in der Solution erstellt und jeweils alle benötigten Referenzen richtig gesetzt.

Vorbereitung

Diese Anleitung geht davon aus, dass Visual Studio 2012 mit allen aktuellen Updates installiert ist. Zusätzlich wird das Microsoft Visual Studio 2012 SDK benötigt. Dies stellt den Projekttyp Project Template zur Verfügung. Ausserdem ist es ratsam, ein Projekt zu erstellen das genau dem Projekt entspricht, das nachher generiert werden soll.

Template

Ein Projekt-Template ist im Grunde nichts anderes als eine Zip Datei, in welche alle benötigten Dateien gepackt sind. Von zentraler Bedeutung sind hier die .vstemplate-Dateien, von welchen jedes Projekt-Template mindestens eine beinhalten muss. Innerhalb einer .vstemplate-Datei kann dann wiederum auf andere .vstemplate-Dateien verlinkt werden um so Subprojekte zu generieren. Ausserdem sind in den .vstemplate-Dateien die Metadaten der einzelnen Projekte hinterlegt. Dies ist für die Haupt-.vstemplate-Datei besonders wichtig, da diese Daten im New 'Project Dialog' von Visual Studio angezeigt werden. Dazu gehören der Name des Projekts, eine kurze Beschreibung, ein Icon sowie eine grössere Grafik welche beispielsweise einen Screenshot oder ein Logo enthalten kann. Ausserdem sind alle Dateien enthalten, welche später in das neue Projekt eingefügt werden. Alle Dateien können mit Platzhalter versehen werden, welche dann bei der Generierung des Projekts durch die entsprechenden Werte ersetzt werden.

Dateisystem (nicht vollständig):

QuinoTemplate.vstemplate
__PreviewImage.png
__TemplateIcon.png
Core/Core.vstemplate
Core/Quino.Core.vstemplate
Core/App/QuinoConfiguration.cs
Core/Models/QuinoModelClasses.cs
...
Core/Models/Generators/QuinoCoreGenerator.cs
...
Winform/Winform.vstemplate
Winform/Quino.Winform.App.csproj
Winform/Program.cs
Winform/data-configuration.xml
...
Winform/Forms/MainForm.cs
...

QuinoTemplate.vstemplate:

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="ProjectGroup">
  <TemplateData>
    <Name>Quino Application</Name>
    <Description>A quino application with different modules.</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType>
    </ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>MyQuinoApplication</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
    <Icon>__TemplateIcon.png</Icon>
    <PreviewImage>__PreviewImage.png</PreviewImage>
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="$quinoapplicationname$.Core">
        Core\Core.vstemplate
      </ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="$quinoapplicationname$.Winform">
        Winform\Winform.vstemplate
      </ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>
</VSTemplate>

Die Namen der Projekte können durch Platzhalter beeinflusst werden.

Core.vstemplate (stellvertretend für die Subprojekte):

<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project">
  <TemplateData>
    <Name>Quino Core</Name>
    <Description>Provides a quino core project containing the model generation</Description>
    <ProjectType>CSharp</ProjectType>
    <ProjectSubType>
    </ProjectSubType>
    <SortOrder>1000</SortOrder>
    <CreateNewFolder>true</CreateNewFolder>
    <DefaultName>MyQuinoApplication.Core</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <LocationField>Enabled</LocationField>
    <EnableLocationBrowseButton>true</EnableLocationBrowseButton>
  </TemplateData>
  <TemplateContent>
    <Project
      TargetFileName="$quinoapplicationname$.Core.csproj"
      ProjectName="$quinoapplicationname$.Core"
      File="Quino.Core.csproj" 
      ReplaceParameters="true">
      <Folder Name="App" TargetFolderName="App">
        <ProjectItem ReplaceParameters="true" TargetFileName="$quinoapplicationname$Configuration.cs">
          QuinoConfiguration.cs
        </ProjectItem>
      </Folder>
      <Folder Name="Models" TargetFolderName="Models">
        <Folder Name="Generators" TargetFolderName="Generators">
          <ProjectItem ReplaceParameters="true" TargetFileName="$quinoapplicationname$CoreGenerator.cs">
            QuinoCoreGenerator.cs
          </ProjectItem>
          ...
        </Folder>
        <ProjectItem ReplaceParameters="true" TargetFileName="$quinoapplicationname$ModelClasses.cs">
          QuinoModelClasses.cs
        </ProjectItem>
        ...
      </Folder>
    </Project>
  </TemplateContent>
</VSTemplate>

Die Namen der generierten Dateinen können ebenfalls durch Platzhalter beeinflusst werden.

Auch innerhalb der einzelnen Codedateien können Platzhalter verwendet werden die dann beim Generieren des Projekts durch die entsprechenden Variablen erstetzt werden:

using Encodo.Quino.Meta;

namespace $quinoapplicationname$.Models
{
  public class $quinoapplicationname$ModelClasses
  {
    public IMetaClass Company { get; set; }

    public IMetaClass Person { get; set; }
  }
}

Wizard

Damit die einzelnen Quino Module an- oder abgewählt werden können sowie um dem Anwender andere Konfigurationsmöglichkeiten - wie etwa das Auswählen des Namespaces - zu geben kann man das Projekt-Template mit einem eigenen Wizard versehen. Dieser wird jedes Mal angezeigt, wenn aus dem Projekt-Template ein neues Projekt erzeugt wird. Der Wizard für das Projekt-Template ist grundsätzlich eine ganz normale .Net Anwendung welche in eine DLL kompiliert und dann im Template registriert wird.

image

Wizard erstellen

Ein eigener Wizard für das Projekt-Template kann implementiert werden indem man vom Interface Microsoft.VisualStudio.TemplateWizard.IWizard ableitet. Da das Quino Projekt-Template momentan zwei Module (Core und Winform) generieren kann werden insgesamt drei Wizards gebraucht:

  • QuinoWizard: Der Hauptwizard welcher den eigentlichen Wizard zur Verfügung stellt in dem die Anwender des Templates die gewünschten Optionen einstellen können.
  • CoreWizard, WinformWizard: Leiten die Einstellungen des QuinoWizards an das jeweilge Modul weiter.

Die beiden Sub-Wizards sind nötig, weil der Hauptwizard keinen Zugriff auf die Replace-Parameter der SubProjekt-Templates hat. Damit der Hauptwizard mit den Subwizards kommunizieren kann ist ein kleiner Trick nötig: Die im Hauptwizard getätigten Einstellungen werden in public static Parametern abgelegt auf diese wiederum die beiden Subwizards zugreifen können. Dies funktioniert, weil alle drei Wizards in der gleichen Runtime Umgebung laufen.

QuinoWizard.cs:

public class QuinoWizard : IWizard
{
  #region Implementation of IWizard

  public void RunStarted(
    object automationObject, 
    Dictionary<string, string> replacementsDictionary,
    WizardRunKind runKind,
    object[] customParams)
  {
    try
    {
      using (var inputForm = new UserInputForm())
      {
        // Der Winforms Dialog wird angezeigt und die Einstellungen des
        // Benutzers werden in public static Parametern abgelegt.
        inputForm.ShowDialog();
        GenerateCore = inputForm.GenerateCore;
        GenerateWinform = inputForm.GenerateWinform;
        QuinoApplicationName = inputForm.DefaultNamespace;
        EncodoSourceRoot = inputForm.EncodoSourceRoot;

        // Die Parameter werden in das replacementsDictionary übernommen.
        Tools.SetReplacementParameters(replacementsDictionary);
      }
    }
    catch (Exception ex)
    {
      MessageBox.Show(ex.ToString());
    }
  }

  public bool ShouldAddProjectItem(string filePath)
  {
    return true;
  }

  // Alle anderen implementierten Methoden haben einen leeren Methodenrumpf.

  #endregion

  public static string QuinoApplicationName { get; private set; }
  public static bool GenerateCore { get; private set; }
  public static bool GenerateWinform { get; private set; }
  public static string EncodoSourceRoot { get; private set; }
}

CoreWizard.cs (stellvertretend für die beiden Subwizards):

{
  #region Implementation of IWizard

  public void RunStarted(
    object automationObject, 
    Dictionary<string, string> replacementsDictionary, 
    WizardRunKind runKind, 
    object[] customParams)
  {
    if (!QuinoWizard.GenerateCore)
    {
      // So wird die Generierung des Subprojekts gegebenenfalls verhindert.
      throw new WizardCancelledException();
    }

    // Die Parameter werden in das replacementsDictionary übernommen.
    Tools.SetReplacementParameters(replacementsDictionary);
  }

  public bool ShouldAddProjectItem(string filePath)
  {
    return true;
  }

  // Alle anderen implementierten Methoden haben einen leeren Methodenrumpf.

  #endregion
}

Der Vollständigkeit halber die Methode Tools.SetReplacementParameters:

public static void SetReplacementParameters(Dictionary<string, string> replacementsDictionary)
{
  replacementsDictionary.Add("$quinoapplicationname$", QuinoWizard.QuinoApplicationName);
  replacementsDictionary.Add("$encodosourceroot$", QuinoWizard.EncodoSourceRoot);
}

Registrieren im GAC

Damit der Wizard vom Projekt-Template aufgerufen werden kann muss er im Global Assembly Cache (GAC) registriert werden. Dazu das Wizward Projekt kompilieren, den Visual Studio Command Prompt im Administratormodus öffnen und das Assembly registrieren:

gacutil -i Wizard.dll

Verlinken mit dem Projekt-Template

In den vorherigen beiden Schritten wurde für jedes Subprojekt sowie für das Hauptprojekt jeweils ein Wizard erstellt. Diese Wizwards müssen jetzt in den einzelnen .vstemplate-Dateien verlinkt werden. Dazu jeweils nach dem Knoten folgenden XML Code einfügen:

QuinoTemplate.vstemplate:

<WizardExtension>
  <Assembly>Wizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=[PublicKey]</Assembly>
  <FullClassName>Wizard.QuinoWizard</FullClassName>
</WizardExtension>

Core.vstemplate:

<WizardExtension>
  <Assembly>Wizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=[PublicKey]</Assembly>
  <FullClassName>Wizard.CoreWizard</FullClassName>
</WizardExtension>

Winform.vstemplate:

<WizardExtension>
  <Assembly>Wizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=[PublicKey]</Assembly>
  <FullClassName>Wizard.WinformWizard</FullClassName>
</WizardExtension>

Der Public Key ist in allen drei Fällen gleich und kann beispielsweise mit Tools wie dotPeek von JetBrains ermittelt werden. Der FullClassName hingegen muss auf den jeweiligen Wizard des Templates verweisen.

Installation

Um das fertige Template zu testen bietet Visual Studio einige praktische Hilfsmittel. So kann kann man einfach F5 drücken und eine neue Instanz von Visual Studio wird hochgezogen in der das neue Template bereits registriert ist. Dies bietet den Vorteil, dass man die Generierung der Projekte debuggen kann und so etwaige Fehler einfach findet.

Wenn das Template fertig ist und funktioniert kann man das Template im Release-Modus builden woraufhin ein Zip-File mit dem Projekt-Template generiert wird. Dieses kann man dann entweder in den Ordner %UserProfile%\Documents\Visual Studio 2012\Templates\ProjectTemplates\Visual C# kopieren wo das Projekt für den aktuellen Benutzer zur Verfügung steht oder in den Ordner %ProgramFiles%\Microsoft Visual Studio 11.0\Common7\IDE\ProjectTemplates\CSharp woraufhin das neue Projekt-Template dann bei allen Benutzern im New Project Dialog erscheint.

Fazit

Das Erstellen eines eigenen Projekt-Templates ist insbesondere für Frameworkentwickler eine gute Möglichkeit, den Umgang mit dem Framework zu erleichtern. Der Anwender kann so mit wenigen Mausklicks eine funktionsfähige Applikation erstellen und sieht auch gleich die grundlegenden Designpatterns. Dies ermöglicht es ihm dann, die Anwendung nach seinen eigenen Bedürfnissen zu erweitern.

Natürlich bedeutet das Erstellen und Unterhalten eines eigenen Projekt-Templates auch einigen Aufwand, da das Template für jede neue Quino Version wieder geprüft und gegebenenfalls angepasst werden muss. Hier bietet es sich an, diesen Prozess zu automatisieren. Das Projekt-Template kann auf einem Buildserver vollautomatisch gebuildet und daraus dann ein Projekt generiert werden. Dieses kann der Buildserver dann wiederum builden und so prüfen, ob das Projekt-Template noch funktionsfähig ist.

Um die Installation des Templates auf verschiedenen Rechnern zu vereinfachen haben wir zusätzlich ein Installationsprogramm erstellt der den Wizard im GAC registriert und das Projekt-Template ins richtige Verzeichnis kopiert.

Insgesamt war das Erstellen des Projekt-Templates für Quino zwar aufwändig, ich würde aber auf jeden Fall sagen, dass der dadurch gewonnene Nutzen den Aufwand mehr als wett macht.

Running Quino applications with a local database

The instructions below explain how to set up a Quino application to use a local database driver in Quino 1.8.5 and higher.

The implementation in this version uses a Mongo database as a backing store, so there a few limitations of which you should be aware:

  • Mongo has no native support for foreign-key constraints. Quino enforces some of these constraints without relying on the database (especially when using the Winform UI components) but there is no guarantee that foreign-key constraints will be enforced locally.
  • Mongo also has no native support for transactions. An application that relies on transactions in its business logic will execute, but will not roll back in case of error.
  • Mongo does not publish a schema, as such, and Quino provides neither schema-checking and nor schema-migration for Mongo databases. If the schema changes, data will continue to load and store, but default values for new properties are not added to existing data, as with databases that support schema-migration.

The Mongo driver sounds quite limited compared to a full-fledged driver like that for PostgreSql or SQL Server. So why would you want to use it? There are situations where non-ACID, schema-less persistence is acceptable. In these cases, the lack of support for the features listed above is not a deal-breaker.1

The following situations lend themselves to using a local database:

  • Quick prototyping: you can give your customer a prototype that works with a model you created without setting up a database server.2 This can be especially useful when a rapid prototyping session with a customer has yielded a result that he or she would like to share with other stakeholders before moving on to the next iteration.
  • Downloadable demos: you can provide access to demos of your applications that only work with local data so that users can very quickly get a feel for how the application works.
  • Development: developers are also very interested in data persistence (so you don't have to constantly re-generate or enter data) but may not want to run a local PostgreSql or SQL Server on their machines.3 The developer is presumably aware that a refresh of the data after large model changes might be required.

If you're convinced, you can try it out in your own application by following the instructions below.

  1. Call configuration.IntegrateLocalDatabase() (an extension method defined in Encodo.Quino.App.MetaConfigurationTools). This simply includes code in the application startup that will start and run a local Mongo database if it detects that the configuration requires it. That's all the .NET code you have to write; the rest is configuration.
  2. Add an entry to your configuration file for the Mongo database driver:
<Mongo>
  <Title>Mongo</Title>
  <typename>Encodo.Quino.Data.Mongo.MongoMetaDatabase, Quino.Data.Mongo</typename>
  <Resource>MyAppDatabaseName</Resource>
</Mongo>

You can change the resource name to something that makes sense for your application. It doesn't really matter because, by default, the database is stored in the user's AppData/Local/... folder and they never see the name anyway4.Again in the configuration file, set the default connection settings to "Mongo":

<?xml version="1.0" encoding="utf-8" ?>
<config>
  <servers>
    <default>Mongo</default>
    <!-- other connection settings -->
    <Mongo>
      <Title>Mongo</Title>
      <typename>Encodo.Quino.Data.Mongo.MongoMetaDatabase, Quino.Data.Mongo</typename>
      <Resource>MyAppDatabaseName</Resource>
    </Mongo>
  </servers>
</config>

Let your application know where to find the Mongo executable. You can either copy the Mongo database daemon to Mongo\mongod.exe next to your application executable or you can specify a location from the configuration file, as shown below.

<mongo>
  <executable>C:\Tools\MongoDB\bin\mongod.exe</executable>
</mongo>

Optional: choose a location to store the local database. By default, the database is stored in a "Data" subfolder of the user's local data for the application. You can specify an alternate location from the configuration file5, as shown below.

<mongo>
  <executable>C:\Tools\MongoDB\bin\mongod.exe</executable>
  <databasepath>U:\Bob\Prototype23\Data</databasepath>
</mongo>

Now you can start your application and store data locally using Mongo.

Happy modeling!



  1. Mongo is also quite fast -- partly due to the fact that it doesn't support transactions and doesn't need to check foreign keys. Just to be clear, we understand that, for many large-data situations, a fast, non-ACID driver is exactly what you want. That's kind of why Quino supports Mongo out-of-the-box.

  2. Since Quino also supports remoting out of the box, you could also run a server either on your own infrastructure or in the cloud (Azure) but that involves a lot more work. It's also not guaranteed because the customer may not have access to the server from their internal network.

  3. This is becoming more and more common as some developers use super-lightweight netbooks without a lot of memory. Naturally, we recommend that developers work with the primary target database as much as possible.

  4. Unless it's shown in the title bar of the main window in debug mode or in the about window

  5. Be aware that, since each running instance of the application has its own Mongo database daemon, it is not possible for multiple users to access data in the same directory. You still need a server for that.

v1.8.5: New model-generation pattern, local databases and more!

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

  • Includes a new model-generation pattern with support in the framework. This pattern helps developers create models and modularize them in a scalable and understandable way. Developers can choose how much of the pattern to implement depending on model size to minimize the amount of effort required for small models. See A scalable pattern for building metadata for more information as well as several examples.
  • The code generator now supports generating one class per file instead of grouping all generated classes into a monolithic file. This policy is opt-in and must be enabled on the module-generation options (e.g. by setting IMetaModule.ClassOptions.OneClassPerFile = true for those modules where it is desired).
  • The Mongo database is now supported as a local database solution for small non-distributed solutions.[^1] This is perfect for demo applications and prototypes. See Running Quino applications with a local database for more information.
  • Modeled methods are now stateless and no longer use the global session. Slight changes have to be made to existing applications (listed below).

Breaking changes

  • The interface IConstantExpression no longer exists. Please update method signatures with IExpression instead.
  • Encodo.Quino.Meta.IMetaModuleGenerator has been moved to Encodo.Quino.Tools.ModelGenerators.IMetaModuleGenerator. Fix compile errors by including the new namespace.
  • The MetaBuilderBase no longer returns MetaGroupContainer; instead it returns IMetaGroupContainer. Code that expects the class rather than the interface will have to be updated to use the interface instead.
  • MetaBuilder.CreateWrapperClass() no longer adds the created class to the model. If this is the desired behavior, use MetaBuilder.AddWrapperClass() instead. In most cases, the extra class in the model was neither necessary nor desired, but we strongly recommend that you verify your calls to CreateWrapperClass() to make sure that it still does what you expect.
  • Modeled methods are now stateless and no longer use the global session. In order to support this, we added a requirement that modeled method classes must now implement the IMetaMethodImplementationContainer interface. There is only one method to implement -- SetSession() -- and you can use the MetaMethodImplementationContainer base class if you don't have another base class that you want to use. Regenerate code to update the generated class for remotable methods to a version that implements the interface.The generated remote methods can no longer be called without specifying the session to use for the remote-method call. For a given method interface -- IBusinessLogic say -- instead of calling ServiceLocator.GetInstance<IBusinessLogic>() to get the instance, use the helper method MetaMethodTools.GetInstance<TIBusinessLogic>(session) instead.

For example, in the Quino demo, the call to DeletePerson used to be:

ServiceLocator.Current.GetInstance<IBusinessLogicMethods>().DeletePerson(person);

Instead, you should call:

MetaMethodTools.GetInstance<IBusinessLogicMethods>(person.Session).DeletePerson(person);
```**XML documentation** files are **no longer included** with the **source-only** release. Instead, you should generate the documentation files locally by calling the `deploydoc` target in the `Quino.build` NAnt file. This target can be called at any time to synchronize the XML documentation files with the current sources.

For example, when you download the Quino sources (and have NAnt in the system `PATH`), you can execute the following to build & deploy Quino as well as source-code documentation.

nant deploydoc deploy

  * As a result of updates made for the model-generation pattern, you will have to re-generate code for your model(s) in order to remove compiler warnings in that code.

------------------------------------------------------------------------


[^1]: Naturally, the Mongo driver can also be used in a distributed solution. It is, as much as possible, a first-class driver for Quino with the following caveats: it supports neither foreign-key constraints nor transactions.
A scalable pattern for building metadata

In the latest version of Quino -- version 1.8.5 -- we took a long, hard look at the patterns we were using to create metadata. The metadata for an application includes all of the usual Quino stuff: classes, properties, paths, relations. With each version, though we're able to use the metadata in more places. That means that the metadata definition code grows and grows. We needed some way to keep a decent overview of that metadata without causing too much pain when defining it.

In order to provide some background, the following are the high-level requirements that we kept in mind while designing the new pattern and supporting framework.

Manage complexity

A simple model should be easy and straightforward to build, with no cumbersome boilerplate; complex models should support multiple layers and provide an overview

Leverage existing knowhow

Our users don't want to learn a new language/IDE in order to create metadata; neither do we want to provide support for our own metadata-definition language

Support modularization

Modules can be used to hide complexity but are also sometimes necessary to define hard boundaries in the application metadata

Support extensibility

Interdependent modules and overlays will need to refer to elements in other modules; there needs to be a standard mechanism for defining and accessing metadata elements that doesn't rely on string constants1

Support refactoring

Rely on convention and name-matching as little as possible to avoid subtle errors

Support introspection

Developers that stick to the pattern should be able to maximize efficiency using common navigation and introspection2 tools like Visual Studio or ReSharper.

Definition Language

Quino metadata has always been defined using a .NET language -- in our case, we always use C# to define the metadata, using the MetaBuilder or InMemoryMetaBuilder to compose the application model. This approach satisfies the need to leverage existing tools, refactoring and introspection.

Since Quino metadata is an in-memory construct, there will always be a .NET API for creating metadata. This is not to say that there will never be a DSL to define Quino metadata but that such an approach is not the subject of this post.

Modularization

Quino applications have always been able to define and integrate metadata modules (e.g. reporting or security) using an IMetaModuleBuilder. Modules solved interdependency issues by splitting the metadata-generation into several phases:

  • Add classes and foreign keys
  • Add paths between classes (depends on foreign keys)
  • Add calculated properties and relations (depends on paths)
  • Add layouts (depends on all properties)

In this way, when a module needed to add a path between a class that it had defined and a class defined in another module, it could be guaranteed that classes and foreign keys for all modules had been defined before any paths were created. Likewise for classes that wanted to define relations based on paths defined in other modules.

The limitation of the previous implementation was that a module generator always created its own module and builder and could not simply re-use those created by another generator. Basically, there was no "lightweight" way of splitting metadata-generation into separate files for purely organizational purposes.

There were also a few issues with the implementation of the main model-generation code as well. The previous pattern depended heavily on local variables, all defined within one mammoth function. Separating code into individual method calls was ad-hoc -- each project did it a little differently -- and involved a lot of migration of local variables to instance variables. With all code in a single method, file-structure navigation tools couldn't help at all. The previous pattern prescribed using file comments or regions that could be located using "find in file". This was clearly sub-optimal.

The new pattern

The new pattern that can be applied for all models, bit or small includes the following parts:

Model generator

As before, there is a class that implements the IMetaModelGenerator interface. This class is used by the application configuration and various tools (e.g. the code generator or UML generator) to create the model.

Model elements

Metadata that is referenced from multiple steps in the metadata-generation process is stored in a separate object (or objects) called the model elements. (E.g. classes are created in the AddClasses() step and referenced in the AddPaths, AddProperties and AddLayouts steps.) The model elements typically has two properties called Classes and Paths.

Metadata generators

Module generators still exist, but there are now also metadata generators that are lightweight, using a metadata builder and elements defined by another generator (typically a module generator or the model generator itself).

This may sound like a lot of overhead for a simple application, but it's really not that much extra code. The benefits are:

  • Models, modules and lightweight parts all use the same pattern, with the same phases and method names. That makes it far easier to know where to look for a definition
  • Since the pattern is the same, it's easy to move functionality from one module to another or to split one module into multiple lightweight parts without doing a lot of refactoring
  • A small model will naturally grow to a medium or large model, all while using the same pattern. There is no moment during development where you have to do a major refactoring in order to get organized: the pattern will naturally support a clean coding style.

Building a model, step by step

But enough chatter; let's take a look at the absolute minimum boilerplate for an empty model.

Step zero: create the boilerplate

public class DemoModelElements
{
  public DemoModelElements()
  {
    Classes = new DemoModelClasses();
    Paths = new DemoModelPaths();
  }

  public DemoModelClasses Classes { get; private set; }
  public DemoModelPaths Paths { get; private set; }
}

public class DemoModelPaths
{
}

public class DemoModelClasses
{
}

public class DemoCoreGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
}

public class DemoModelGenerator : MetaBuilderBasedModelGeneratorBase<DemoModelElements>
{
  protected override void AddMetadata()
  {
    Builder.Include<DemoCoreGenerator>();
  }
}

The code above is functional but doesn't actually create any metadata. So what does it do?

  1. It uses the generic MetaBuilderBasedModelGeneratorBase to indicate the type of Elements that will be exposed by this model generator. The elements class is created automatically and is available as the property Elements (as we'll see in the examples below). Additionally, we're using a ModelGeneratorBase that is based on a MetaBuilder which means that the property Builder is also available and is of type MetaBuilder.
  2. It includes the DemoCoreGenerator which is a dependent generator -- it's lightweight and uses the elements and builder from its owner. The exact types are shown in the class declaration; it can be read as: get elements of type DemoModelElements and a builder of type MetaBuilder from the generator with type DemoModelGenerator. The initial generic argument can be any other metadata generator that implements the IElementsProvider<TElements, TBuilder> interface.
  3. The model generator overrides AddMetadata to include the metadata created by DemoCoreGenerator in the model.

Even though it's not very much code, you can create a snippet or a file template with Visual Studio or a Live Template or file template with ReSharper to quickly create a new model.

Step one: define the model

Now, let's fill the empty model with some metadata. The first step is to define the model that we're going to build. That part goes in the AddMetadata() method.3

public class DemoModelGenerator : MetaBuilderBasedModelGeneratorBase<DemoModelElements>
{
  protected override void AddMetadata()
  {
    Builder.CreateModel<DemoModel>("Demo", /*Guid*/);
    Builder.CreateMainModule("Encodo.Quino");

    Builder.Include<DemoCoreGenerator>();
  }
}

Step two: add a class

A typical next step is to define a class. Let's do that.

public class DemoModelClasses
{
  public IMetaClass Company { get; set; }
}

public class DemoCoreGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
  protected override void AddClasses()
  {
    Elements.Classes.Company = Builder.AddClassWithDefaultPrimaryKey("Company", /*Guid*/, /*Guid*/);
  }
}

As you can see, we added a new class to the elements and created and assigned it in the AddClasses() phase of metadata-generation.

Step three: add another class and a path

An obvious next step is to create another class and define a path between them.

public class DemoModelClasses
{
  public IMetaClass Company { get; set; }
  public IMetaClass Person { get; set; }
}

public class DemoCoreGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
  protected override void AddClasses()
  {
    Elements.Classes.Company = Builder.AddClassWithDefaultPrimaryKey("Company", /*Guid*/, /*Guid*/);
    Elements.Classes.Person = Builder.AddClassWithDefaultPrimaryKey("Person", /*Guid*/, /*Guid*/);
    Builder.AddInvisibleProperty(Elements.Classes.Person, "CompanyId", MetaType.Key, true, /*Guid*/);
  }

  protected override void AddPaths()
  {
    Elements.Paths.CompanyPersonPath = Builder.AddOneToManyPath(
      Elements.Classes.Company, "Id",
      Elements.Classes.Person, "CompanyId",
      /*Guid*/, /*Guid*/
    );    
  }
}

Step four: add relations

Having a path is not enough, though. We can also define how the relations on that path are exposed in the classes.

public class DemoCoreGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
  protected override void AddClasses()
  {
    Elements.Classes.Company = Builder.AddClassWithDefaultPrimaryKey("Company", /*Guid*/, /*Guid*/);
    Elements.Classes.Person = Builder.AddClassWithDefaultPrimaryKey("Person", /*Guid*/, /*Guid*/);
    Builder.AddInvisibleProperty(Elements.Classes.Person, "CompanyId", MetaType.Key, true, /*Guid*/);
  }

  protected override void AddPaths()
  {
    Elements.Paths.CompanyPersonPath = Builder.AddOneToManyPath(
      Elements.Classes.Company, "Id",
      Elements.Classes.Person, "CompanyId",
      /*Guid*/, /*Guid*/
    );    
  }

  protected override void AddProperties()
  {
    Builder.AddRelation(Elements.Classes.Company, "People", "", Elements.Paths.CompanyPersonPath);
    Builder.AddRelation(Elements.Classes.Person, "Company", "", Elements.Paths.CompanyPersonPath);
  }
}

OK, now we have a model with two entities -- companies and people -- that are related to each other so that a company has a list of people and each person belongs to a company.

Step five: add translations

Now we'd like to make the metadata support German as well as English. Quino naturally supports more generalized ways of doing this (e.g. importing from files), but let's just add the metadata manually to see what that would look like (unaffected methods are left off for brevity).

public class DemoModelElements
{
  public DemoModelElements()
  {
    Classes = new DemoModelClasses();
    Paths = new DemoModelPaths();
  }

  public ILanguage English { get; set; }
  public ILanguage German { get; set; }
  public DemoModelClasses Classes { get; private set; }
  public DemoModelPaths Paths { get; private set; }
}

public class DemoCoreGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
  protected override void AddCoreElements()
  {
    Elements.English = Builder.AddDisplayLanguage("en-US", "English");
    Elements.German = Builder.AddDisplayLanguage("de-CH", "Deutsch");
  }

  protected override void AddClasses()
  {
    var company = Elements.Classes.Company = Builder.AddClassWithDefaultPrimaryKey("Company", /*Guid*/, /*Guid*/);
    company.Caption.SetValue(Elements.English, "Company");
    company.Caption.SetValue(Elements.German, "Firma");
    company.PluralCaption.SetValue(Elements.English, "Companies");
    company.PluralCaption.SetValue(Elements.German, "Firmen");

    var person = Elements.Classes.Person = Builder.AddClassWithDefaultPrimaryKey("Person", /*Guid*/, /*Guid*/);
    Builder.AddInvisibleProperty(person, "CompanyId", MetaType.Key, true, /*Guid*/);
    person.Caption.SetValue(Elements.English, "Person");
    person.Caption.SetValue(Elements.German, "Person");
    person.PluralCaption.SetValue(Elements.English, "People");
    person.PluralCaption.SetValue(Elements.German, "Personen");
  }
}

Note that I created a local variable for both company and person. I did this for two reasons:

  • The code is shorter and easier to read
  • There are fewer references to the Elements.Classes.Person and Elements.Classes.Company properties. It's useful to keep the number of references to a minimum in order to make searching for usages with a tool like ReSharper of maximum benefit. Otherwise, there's a lot of noise to signal and you'll get hundreds of references when there are only actually a few dozen "real" references.

Step six: using private methods

You can see that the metadata-generation code is still manageable, but it's growing. Once we've filled out all of the properties, relations, translations, layouts and view aspects for the person and company classes, we'll have a file that's several hundred lines long. A file of that size is still manageable and, since we have methods, it's eminently navigable with a file-structure browser.

If we don't mind keeping -- or we'd rather keep -- everything in one file, we can see more structure by splitting the code into more methods. This is really easy to do because we're using the elements to reference other parts of metadata instead of local variables. For example, let's move the class initialization code for the person and company entities to separate methods (unaffected methods are left off for brevity).

public class DemoCoreGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
  protected override void AddClasses()
  {
    AddCompany();
    AddPerson();
  }

  private void AddCompany()
  {
    var company = Elements.Classes.Company = Builder.AddClassWithDefaultPrimaryKey("Company", /*Guid*/, /*Guid*/);
    company.Caption.SetValue(Elements.English, "Company");
    company.Caption.SetValue(Elements.German, "Firma");
    company.PluralCaption.SetValue(Elements.English, "Companies");
    company.PluralCaption.SetValue(Elements.German, "Firmen");
  }

  private void AddPerson()
  {
    var person = Elements.Classes.Person = Builder.AddClassWithDefaultPrimaryKey("Person", /*Guid*/, /*Guid*/);
    Builder.AddInvisibleProperty(person, "CompanyId", MetaType.Key, true, /*Guid*/);
    person.Caption.SetValue(Elements.English, "Person");
    person.Caption.SetValue(Elements.German, "Person");
    person.PluralCaption.SetValue(Elements.English, "People");
    person.PluralCaption.SetValue(Elements.German, "Personen");
  }
}

Step seven: using multiple generators

While this is a good technique for small models -- with anywhere up to five entities -- most models are larger and include entities with sizable metadata definitions. Another thing to consider is that, when working with larger teams, it's often best to keep a central item like the metadata definition as modular as possible.

To scale the pattern up for larger models, we can move code for larger entity definitions into separate generators. As soon as we move an entity to its own generator, we're faced with the question of where we should create paths for that entity. A path doesn't really belong to one class or another; in which generate should it go?

Well, we thought about that and came to the conclusion that the pattern should be to just create a separate generator for all paths in the model (or multiple path-only generators if you have a larger model). That is, when a model gets a bit larger, it should include the following generators (using the name "Demo" from the examples above):

  • DemoCoreGenerator
  • DemoPathGenerator
  • DemoCompanyGenerator
  • DemoPersonGenerator

The DemoCoreGenerator will create metadata and assign elements like the display languages. It's also recommended to define base types like enumerations and very simple classes4 in the core as well. Obviously, as the model grows, the core generator may also get larger. This isn't a problem: just split the contents logically into multiple generators.

For the purposes of this example, though, we only have a single core and a single path generator and two entity generators. Since these generators will all be dependent on the model's builder and elements, the first step is to define a base class that will be used by the other generators.

internal class DemoDependentGenerator : DependentMetadataGeneratorBase<DemoModelGenerator, DemoModelElements, MetaBuilder>
{
}

public class DemoCoreGenerator : DemoDependentGenerator
{
  protected override void AddCoreElements()
  {
    Elements.English = Builder.AddDisplayLanguage("en-US", "English");
    Elements.German = Builder.AddDisplayLanguage("de-CH", "Deutsch");
  }
}

public class DemoPathGenerator : DemoDependentGenerator
{
  protected override void AddPaths()
  {
    Elements.Paths.CompanyPersonPath = Builder.AddOneToManyPath(
      Elements.Classes.Company, "Id",
      Elements.Classes.Person, "CompanyId",
      /*Guid*/, /*Guid*/
    );
  }
}

public class DemoCompanyGenerator : DemoDependentGenerator
{
  protected override void AddClasses()
  {
    var company = Elements.Classes.Company = Builder.AddClassWithDefaultPrimaryKey("Company", /*Guid*/, /*Guid*/);
    company.Caption.SetValue(Elements.English, "Company");
    company.Caption.SetValue(Elements.German, "Firma");
    company.PluralCaption.SetValue(Elements.English, "Companies");
    company.PluralCaption.SetValue(Elements.German, "Firmen");
  }

  protected override void AddProperties()
  {
    Builder.AddRelation(Elements.Classes.Person, "Company", "", Elements.Paths.CompanyPersonPath);
  }
}

public class DemoPersonGenerator : DemoDependentGenerator
{
  protected override void AddClasses()
  {
    var person = Elements.Classes.Person = Builder.AddClassWithDefaultPrimaryKey("Person", /*Guid*/, /*Guid*/);
    Builder.AddInvisibleProperty(person, "CompanyId", MetaType.Key, true, /*Guid*/);
    person.Caption.SetValue(Elements.English, "Person");
    person.Caption.SetValue(Elements.German, "Person");
    person.PluralCaption.SetValue(Elements.English, "People");
    person.PluralCaption.SetValue(Elements.German, "Personen");
  }

  protected override void AddProperties()
  {
    Builder.AddRelation(Elements.Classes.Company, "People", "", Elements.Paths.CompanyPersonPath);
  }
}

MetaBuilderBasedModelGeneratorBase<DemoModelElements>
{
  protected override void AddMetadata()
  {
    Builder.CreateModel<DemoModel>("Demo", /*Guid*/);
    Builder.CreateMainModule("Encodo.Quino");

    Builder.Include<DemoCoreGenerator>();
    Builder.Include<DemoPathGenerator>();
    Builder.Include<DemoCompanyGenerator>();
    Builder.Include<DemoPersonGenerator>();
  }
}

You'll note that we only moved code around and didn't have to change any implementation or add any new elements or anything that might introduce subtle errors in the metadata. Please note, the classes are all shown in a single code block above, but the pattern dictates that each class should be in its own file.

Step eight: integrating external modules

So far, we've only worked with generators that are dependent on the model generator. How do we access information -- and elements -- generated in other modules? For example, let's include the security module and change a translation for a caption.

public class DemoModelElements
{
  public DemoModelElements()
  {
    Classes = new DemoModelClasses();
    Paths = new DemoModelPaths();
  }

  public ILanguage English { get; set; }
  public ILanguage German { get; set; }
  public SecurityModuleElements Security { get; set; }
  public DemoModelClasses Classes { get; private set; }
  public DemoModelPaths Paths { get; private set; }
}

public class DemoCoreGenerator : DemoDependentGenerator
{
  protected override void AddCoreElements()
  {
    Elements.English = Builder.AddDisplayLanguage("en-US", "English");
    Elements.German = Builder.AddDisplayLanguage("de-CH", "Deutsch");
    Elements.Security = Builder.Include<SecurityModuleGenerator>().Elements;
  }

  protected override void AddProperties()
  {
    Elements.Security.Classes.User.Caption.SetValue(Elements.German, "Benutzer");
  }
}

This approach works well with any module that has adhered to the pattern and exposes its elements in a standardized way.5 In this case, the core module includes the security module and retains a reference to its elements. Any code that uses the core module will now have access not only to the core elements but also to the security elements, as well.

Another major benefit to using this pattern is that the resulting code is quite self-explanatory: it's no mystery to what the Elements.Security.Classes.User.Caption is referring.

One last thing: folder structure

The previous pattern had a single monolithic file. The new pattern increases the number of files -- possibly by quite a lot. It's recommended to put these new files into the following structure:

[-] Models
    [+] Aspects
    [+] Elements
    [+] Generators

The "Aspects" folder isn't new to this pattern, but it's worth mentioning that any model-specific aspects should go into a separate folder.

That's all for now. Happy modeling!



  1. Naturally, the IMetaModel is always available and any part of the generation process can access metadata in the model at any time. However, the API for the model is quite generic and requires knowledge of the unique identifier or index for a piece of metadata.

  2. By introspection, we mean that if metadata is accessed through .NET code structures -- like properties or constants -- we should be able to find all usages of a particular metadata element without resorting to a "find in files" for a particular string.

  3. It doesn't have to go there. The DemoCoreGenerator could also set up the builder (since it's using the same builder object). To do that, you'd override AddCoreElements() and set up the model there. However, it's clearer to keep it in the generator that actually owns the builder that is being configured.

  4. Simple classes generally have few extra properties and no layouts or short description classes.

  5. Through the IElementProvider mentioned above

v1.8.4: spell-checker and rich-edit integration

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

  • Improved spell-checker integration so that it dynamically loads dictionaries in the configuration path and shows loading progress while dictionaries are loading. Dictionaries are no longer included by default and only loaded when spell-checking is actually requested by the user for the first time
  • Several important fixes for the reporting integration with DevExpress 12.1

Breaking changes

The method WinformStatusFeedback.CreateStatusForm() no longer exists and cannot be overridden. If you have code that looks like the following:

public class StartupFormFeedback : WinformDxStatusFeedback
{
  public StartupFormFeedback(ICoreConfiguration configuration = null)
    : base(configuration)
  { }

  protected override IStatusForm CreateStatusForm()
  {
    return new StartupForm(Configuration);
  }
}

you should replace it with the following:

public class StartupFormFeedback : WinformStatusFeedback
{
  public StartupFormFeedback(ICoreConfiguration configuration = null)
    : base(configuration, () => new StartupForm(configuration))
  { }
}

Please note that the base class in the example was changed from WinformStatusDxFeedback to WinformStatusFeedback (the former no longer allows the form to be overridden).

v1.8.3: UI for client-side data-traffic analysis

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

  • Improved registration for remotable methods (now uses a single interface and implementations are integrated with the service locator, as shown below)
  • Many updates to the standard MVC UI
  • Included a binary payload format for the remoting data-handler
  • Improved support for multiple data sources
  • Added support for VS2012 in all projects and build files
  • Improved console migrator with more options for integration into a build server
  • Improved base NAnt support
  • Includes spell-checking support for DevExpress Winform applications
  • Includes DevExpress Winform UI for viewing live database statistics

Breaking changes

  • The DocumentLibrary and IContentRegistry have been moved to the Documents namespace.
  • The ReportsModule.DocumentLibrary no longer exists; instead, access it via the service locator with ServiceLocator.Current.GetInstance<IDocumentLibrary>(). Also the return type is an interface rather than the class itself, but the interface is unchanged.
  • The DocumentBase constructor now takes an IDocumentLibraryProvider instead of a DocumentLibraryProvider
  • The DocumentLibraryProvider is now called DocumentLibraryProviderBase and the constructor no longer requires an IDocumentLibrary.
  • Since the DocumentLibraryProvider no longer has a DocumentLibrary as a constructor parameter, it is no longer auto-registered. Instead, simply get the document library via the service locator (as shown above) and add it to the providers list manually.
  • ViewClassAspect no longer exists; instead, use ViewIconAspect or ViewDynamicIconAspect.
  • The Credentials class is now abstract; instead, use UserCredentials
  • The Encodo.Quino.Data.Persistence.Connections no longer exists; simply remove the using statement as it only included obsolete or internal classes and should have been removed in previous versions anyway.
  • The ViewContextHandler now has generic parameters; instead, most usages will be able to simply switch to using the IViewContextHandler interface.MetaBuilderBase.AddMethods no longer accepts client, base and server classes as parameters. Instead, you should register methods via a single interface and register the appropriate implementation in the startup.

For example, to register IDemoMethods, use:

builder.AddMethods<IBusinessLogicMethods>(...));

To set up a client to use the appropriate implementation, depending on whether a remoting data driver has been selected, use:

configuration.IntegrateRemotableMethods<IDemoMethods, LocalDemoMethods, RemotableDemoMethods>();

To set up the application server to host these methods, simply register the local version with the service locator, as shown below:

configuration.RegisterSingle<IRemoteMethods, ServerRemoteMethods>();
  • MetaMethodTools.Execute() now requires four arguments because the call target must be included (remoting methods are no longer static).
v1.8.1: Remoting/application-server 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

  • The internals of the data driver have been rewritten to include separate analysis, caching, retrieval and local-evaluation handlers
  • The remoting driver is now a data handler instead of a low-level driver based on a database API (that never quite fit)
  • Data-caching improvements
  • (Almost) all singletons and creator functions have been moved to the service locator
  • All configuration has been integrated into the standard application startup
  • Improved schema-check performance over WAN by storing a hash of the model with the Quino metadata
  • Upgraded to DevExpress 12.2
  • Upgraded to MVC 4

Breaking changes

  • Most IQuery, IQueryTable and IQueryCondition (e.g. Where, OrderBy and Join) methods have moved from the interfaces to extension methods. Include the Encodo.Quino.Data.Persistence.Queries namespace to fix compile errors.
  • Namespace, Operation and descendents have been moved from Encodo.Expressions to the Encodo.Expressions.Operations namespace
  • NamespaceTools => OperationTools
  • SerializerTools has been renamed to MetaSerializerTools
  • ExpressionParserFactory has been removed; instead, use ServiceLocator.Current.GetInstance<IExpressionParser>().

| Warning |

This means that you have to regenerate metadata-based classes and code before doing anything else. That is, download Quino, build and deploy it and then regenerate your application's metadata-based code before doing anything else.

  • MetaExpressionTools no longer exists; use ServiceLocator.Current.GetInstance<IExpressionFactory>() to get MetaExpressionTools.ExpressionFactory instead.
  • ContentTypeRegistry.GetSystemIconFromPath() has moved to ShellTools.GetSystemIconFromPath() and the return type is now an Image rather than a Bitmap
  • The API for the IConfigurationActionProvider{TApplication} has changed. Instead of overriding StartupActions and ShutdownActions, override Apply instead. Also, ConfigurationActionProviderTools.AddActions() has been renamed to Integrate().
  • The RemoteClientFactory singleton has been moved to the service locator, so use MetaConfigurationTools.IntegrateRemoting() to include remoting instead of RemoteClientFactory.Instance = new RemoteClientFactoryAuto();
  • ControlAssignmentFactory.Instance no longer exists; instead, retrieve the instance from the service locator: ServiceLocator.Current.GetInstance<IWinformControlAssignmentFactory>()
  • CultureManager.Instance no longer exists; instead, retrieve the instance from the service locator: ServiceLocator.Current.GetInstance<ICultureManager>()
  • EncodoTestingTools and QuinoTestingTools have been moved to the Encodo.Testing.NUnit and Quino.Testing.NUnit assemblies respectively.
  • DisplayLanguageChangedMessage no longer exists; instead all language synchronization has been moved to the CultureManager. Use the CultureManagerTools.RegisterComponent() method to receive notifications when the language changes.
  • The Conf key in the LocationManager has been renamed to Configuration; any configuration files that used the old name should be updated to use the new one instead.
v1.8.0: New data driver architecture

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

New data driver API

The new API includes the following elements for applications:

  • IDataSession
  • IDataProvider
  • IDataCursor

Those are the only new interfaces and applications will just be able to upgrade from the old system to the new one by changing the identifier "Connection" to "Session" wherever the compiler has shown an obsolete warning.

  • An IMetaApplication has an IDataProvider that can be considered roughly equivalent to the IQueryableDatabase property that is now obsolete
  • The IDataSession API is unchanged from that for the IDataConnection (including all extension methods)
  • An IDataSession returns an IDataCursor when objects are retrieved, which must be disposed so that associated resources can be released

More information as to the internals of the data-driver is available in as-yet unpublished white papers. The internal APIs will settle out over the next few point releases (these changes will not affect the external APIs). Once the dust has settled, we'll make more information available. Stay tuned.

Breaking changes

  • InMemoryPersistentQueryAspect overridable members now take an IDataSession rather than an IDataConnection
  • MetaMainFormDxBase.ContextHandler is now of type IViewContextHandler rather than ViewContextHandler
  • SetupImplicitLoginAuthenticator.UpdateSecurityUser now takes an IDataSession rather than an IDataConnection
  • HierarchyClassAspectBase.CreateViewModel now takes an IDataSession rather than an IDataConnection
  • MessageTools.WhereLevel no longer exists; instead, use MessageTools.Any() instead.
  • RuleAspectBase.GetMessageTemplateValue no longer has a parameter
  • Moved Encodo.Quino.Data.Admin.IAdministrableDatabase => Encodo.Data.Admin.IAdministrableDatabase