Builder API Architecture and Usage

Note: This text is currently being split into several other topics.

Overview

This document covers the basics of the current Quino Builder API.

Architecture

The following assemblies are included in the Quino.Builders assembly.

  • Encodo.Core - most basic library for simple tasks.

  • Encodo.Expressions - basic expression handling.

  • Quino.Meta - standard meta definitions can be found here. Whenever you want to retrieve metadata from a model, a module, class or property you probably will found a matching extension in here. For details read the documentation.

In the Quino.Builders assembly you'll find the following things:

  • Builders which are finalizing the model. They can help you with basics tasks like generating default layouts for every class. We're going to take a look at them later.

  • Extensions to create your model more quickly. Those methods are not doing any magic - they're there so you don't have to reinvent everything from scratch - which would be technically possible. We're going to look into the most frequently used once later on. The public API is documented and if you can't find an answer here you probably want to move on and search the extensions by yourself.

  • Application extensions which can be used by your application to setup your model at startup. We're going to take a look at those too later on.

All of the things mentioned above are supposed to help you setting up your model in a quick and clean way. Let's take a look at the basic definition of a model in the way Quino sees it. Every model has the following components:

  • The model itself is defined as an IMetaModel

  • The model is divided into IMetaModules which are a logical way to split your model into different topics.

  • Each module contains classes - this is where things are starting to get interesting.

  • A class consists of properties, relations and actions. For an in-deep overview of the Quino-Metdata concept consider reading the Quino.Meta documentation.

The general setup of a model is a straight forward process. We're using builders to create a model. Each builder provides access to the different stages of model creation. The following steps are currently available:

  • AddCoreElements is used to add low-level and independent components to the metadata (for example languages).

  • AddClasses is used to create the classes and foreign keys needed later on in AddPaths.

  • AddPaths is used to establish paths between the classes. Foreign keys are created here too.

  • AddProperties is used to create any properties not needed for paths and to create relations (which are depending on paths).

  • AddLayouts gets called when all properties and relations are available. In this step we define the visual representation of our metadata.

  • PostFinalize can be used to finalize the model. Some of the internal builders of Quino use this step to create fallbacks in captions for example.

You can find some examples in the Sandbox.Core assembly. In the next chapter we're going to setup a builder from scratch and define our model.

Basic usages

Create a simple model

In this chapter we're going to setup a simple builder and add a class and property to it.

Create a builder which derives from MetaBuilderBasedModelBuilderBase<TElements>. The generic parameter TElements can be used to pass data around with your builder. Fore example you probably want to make a class which contains all of your meta classes and the main module. This allows you to access them in a type-safe way without using the string identifier. For our sample builder we're going to create the following class and pass it as the generic parameter.

public class Elements { public IMetaModule Module { get; set; } public IMetaClass Company { get; set; } public IMetaClass Person { get; set; } }

Be aware that we need to set this properties in our builder. They're not set automatically for you. Like mentioned before - there's no magic.

Now lets create a builder which derives from MetaBuilderBasedModelBuilderBase<Elements>. In our builder we're going to override the CreateModel function to setup our custom model. The final builder should look something similar to this:

/// <inheritdoc/> public class SampleModelBuilder : MetaBuilderBasedModelBuilderBase<Elements> { protected override IMetaModel CreateModel() { Elements.Module = this.CreateModel("SampleModel").GetMainModule(); return Elements.Module.Model; } }

Don't create a new model in every builder. The model should only be created once. Otherwise you'll get an exception that the model has already been created. Use the RegisterDependencies method to include other builders. We're going to see an example on how to do that later.

Now let's add some content to our empty model. As a general pattern we recommend creating a separate builder for every class or object you're going to create. Now let's stick to this convention and create a CompanyBuilder which derives from MetadataBuilderBase<TModelBuilder, TElements>. We're going to pass in our MainModelBuilder and the Elements. In the next step we're going to let our MainModelBuilder now about the new builder. To achieve that override the RegisterDependencies in our MainModelBuilder method and add the following code:

protected override void RegisterDependencies(IMetadataDependencies dependencies) { base.RegisterDependencies(dependencies); dependencies.Include<CompanyBuilder>(); }

In our CompanyBuilder we're going to hook into the AddClasses method to add our custom class. This should similar to this:

We're assigning our class to the property defined in our Elements. The AddId() call creates a default primary key on our class. Our graph would now look something similar to this:

Now we're going to add some properties to the Company class. Override the AddProperties method and add the following code:

We now want to create a third builder and create a Person class with it. We can create the builder and set up the class similar to the one we already did. Keep in mind that you need to link them together in the MainModelBuilder.

In this chapter we've created a simple model which consists of two classes. In the next chapter we're going to look into the task of creating paths and relations.

It's all about relations

In the previous chapter, we set up two builders with two classes. We're now going to create a relation between them.

Let's override the AddPaths method in our `CompanyBuilder``` and add the following code:

We're setting up a path for the database. It's a 1:n relation between Company and Person. Next we're going to setup some navigation properties for easier access. You can use the AddProperties() method to do so. Let's add the company to our PersonBuilder:

Now our relation is set up and we can navigate from the Person to the assigned Company. All other relations can be set up similarly.

Virtual relations

You need to generate code/metadata for your model in order for this sample to compile.

You can define virtual relations that are not based on a path and that don't even need to be mapped to the database. Since we don't have a path, we have to tell Quino how to load data for the relation.

The first step is to implement IQueryAspect. The sample below shows several new things:

  • Apply calls to the query to restrict, sort, etc.

  • Use the session to get data from other sources (e.g. through other relations)

  • Use the generated SampleModel metadata to reference fields and tables in a type-safe manner

After that, add the virtual relation and include you aspect on it.

This can be handy if you have to access the same sort of data multiple times in your application. This way you can actually define a property in your class which contains exactly the related list you need, even if the query to get that data is more complex.

Delegate Properties

You need to generate code/metadata for your model in order for this sample to compile.

For example we now want to create a property which returns the name of the company currently assigned to the person. For that we extend the AddProperties() method on the PersonBuilder. It should look something similar to this:

We aware that this sort of expression can be dangerous.

Multi-language Properties

// TODO

Layouts

Layouts define how your classes are displayed to the end user. The basic concept is simple:

  • Every IMetaClass has a collection of Layouts. All layouts are of type IMetaGroup and can contain other meta elements.

  • There are four general types of layouts

    • The detail layout defines how your class is shown to the user and which fields are visible.

    • The list layout defines how your class looks if showed in list or data grid.

    • The title layout defines how your class is displayed as a string.

    • The search layout defines which properties are contributing to the search.

For most cases the default layout setup is enough to fulfill your needs. We're going to look at an advanced use case later on.

Let's create a simple Layout for a class.

The Layouts are used with the "using" clause which allows automatic stacking. Let's create a nested layout for the detail.

Our detail layout now contains a tab control with two tab items. The caption is provided in the AddTab method. This way we can nest our layouts and add different containers on the same page.

If you want to add a menu structure to your model you can attach it with the following pattern:

This will create a menu group which consists of a single item. The item references to the company meta class and will therefore open the default list layout for the company if clicked. If you want to stack your menu structure further you're allowed to do so. The following code shows an example of a stacked menu group:

This way our menu contains the following structure:

If you want to refer to a custom layout you need to set the right identifier which is matching with your custom layouts identifier. The CreateMenuEntry has an optional parameter which can be used for this. The custom layouts must all be named the same. The framework is handling the difference between list, detail, title and search.

Fluent API Overview

The fluent builder API is builded up on five files. Whenever you search for some function those files are probably a good starting point.

  • ModelExtensions contains all the extension which target the module itself. For example creating a new module.

  • ModuleExtensions contains all the functions to create classes and add them to the specific module.

  • ClassExtensions contains all the functions to manipulate classes and adding properties and relations.

  • PropertyExtensions are used to manipulate properties.

  • LayoutExtensions can be used for layouts. They contain all the crucial methods for generating and manipulating layouts.

The extension methods do not contain any magic. They're basically designed to help you write code faster. And they don't force you to repeat yourself all the time. Some of the most important shall be listed here:

  • CreateModule(this IMetaModel model, string identifier) create a new module and attach it to the model.

  • AddClass(this IMetaModule module, string identifier) create a new class for the specified module.

  • AddId(this IMetaClass metaClass) can be used to create and assign a default Id-property to your class.

  • AddProperty(this IMetaClass metaClass, string identifier, MetaType type) add a new property to your class with the defined type.

  • AddCalculatedProperty(this IMetaClass metaClass, string identifier, MetaType type, IExpressionParser expressionParser, string expression) add a new calculated property which evaluates to the given expression. For an overview about expressions see the Encodo.Expressions assembly documentation.