Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Parsing and evaluating expressions both use IExpressionContext objects that indicate the environment available to the expression. An expression Salary + Bonus references two identifiers, which must be available in the context or expression-parsing in strict mode fails.

In most cases, the context will be an IMetaClass while parsing and an IDataObject during evaluation. The properties of the class or object are those against which the parser and evaluator will validate.

Other objects

A product will generally encounter contexts when working with Delegate Expression. There are two types of objects that the product can extract from the context:

  • Transient objects that are specific to the call.

  • Singleton services provided by Quino and the product.

Transient objects

The context contains zero or more transient objects, usually an IDataSession and IDataObject but also sometimes an ILanguage that indicates the preferred language, if different from the user's selected language (e.g. in some reporting functions).

Most products will use builder methods that pass type-safe business objects to product code as parameters. See Using generated code for more information.

However, a product can get the session with context.TryGetSession() and the underlying object with context.TryGetInstance<IDataObject>(). See Accessing the object in the context for more information.

Singleton Services

An expression function can always directly inject and use singleton services. This is the "proper" way to access services (see the chapter on the IOC for more information).

The following example shows how to inject a service into a metadata builder and then use it from an expression.

The overload of CreateDelegate used here doesn't even provide the context to the product. Instead, it extracts the Person from the context and calls the product code only if it's actually present.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  public GeneratedPersonBuilder([NotNull] IProductService productService)
  {
    _productService = productService;
  }

  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        p => _productService.GetFormattedAddress(p)
      )
    );
  }

  private readonly IProductService _productService;
}

The example above is clean and straightforward, but a bit more verbose, especially when scaled to multiple services and properties.

Another way of getting at the service is to extract it from the context instead of injecting it. As noted above, this may make it more difficult to determine dependencies, but it's up to the product to decide how to use its services.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        (c, p) => c.GetSingle<IProductService>().GetFormattedAddress(p)
      )
    );
  }
}

The example above calls GetSingle(), which throws an exception if the IOC is not available to the context.

During initialization, Quino tests some expressions (e.g. for default values) with an empty context in order to determine whether they are "static" (e.g. to determine whether they can be mapped to the database as part of schema-migration).

Therefore, products must be careful not to assume that the context will contain anything.

In this case, it's fine to call GetSingle() because the product can correctly assume that if the context contains a Person, then it also has access to the IOC.

However, if the expression were not dependent on anything else, or if the product needed to be more careful, then it can use TryGetSingle instead.

In either case, if there is an IOC in the context, but the requested object is not registered, then the call crashes, just as any other failed call to GetInstance() would crash. This is by design, as a product using an unregistered service is a program error, not a situation to be handled.

Because the following implementation depends on the Person as well, the product can rely on Quino to handle empty-context situations correctly.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        (c, p) => 
        {
          if (c.TryGetSingle<IProductService>(out var s))
          {
            return s.GetFormattedAddress(p);
          }

          return string.Empty;
        }
      )
    );
  }
}

In some cases, though, the product has to be more careful about the result. If it always returns a value, then Quino will assume that it always has the same value (i.e. is "static") and may make incorrect assumptions about the code.

In these cases, the product can return not just the result, but also a Boolean that indicates whether the value could be calculated.

Quino provides an overload that supports a tuple result, where the first element is the result and the second element is a Boolean value indicating whether the value could be calculated.

The following example shows how a product can use this overload to return a complete answer.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        (c, p) => {
          if (c.TryGetSingle<IProductService>(out var s))
          {
            return (s.GetFormattedAddress(p), true);
          }

          return (null, false);
        }
      )
    );
  }
}

In the example above, the result that includes true indicates that Quino can use the value. The false indicates that it cannot, so we just use the null value because it will not be used.Der Kontext bestimmt auf welche Daten in der Expression zugegriffen werden kann. Je nachdem wo die Expression verwendet wird, gibt es einen unterschiedlichen Kontext.

TargetClass

In den meisten Fällen definiert eine spezifische Meta-Klasse den Kontext.

  • Ansicht: Das layout Element hat ein targetClass Attribute

  • Role Permissions: Beim Vergeben der Restrictions wir ebenfalls Bezug auf die TargetClass genommen. Siehe Benutzer und Rollen.

Alle Properties und Relationen der Meta-Klasse sind damit in der Expression verfügbar. Die Daten kommen vom jeweiligen Objekt.

Beispiel KategorieId der Klasse Adresse:

Code Block
languagexml
<layout type="Detail" targetClass="Adresse" ...>
  <propertyLink link="Firma">
    <visible>KategorieId != 3</visible>
  </propertyLink>
  ...
</layout>

Standard Funktionen

Auf andere Elemente wie z.B. dem User kann mit den Expression Standard Funktionen zugegriffen werden.

Zum Beispiel der User und seine Berechtigungen können mit den Funktionen aus https://encodo.atlassian.net/wiki/spaces/EB/pages/155385876/Expression+Standard+Funktionen#User eingebunden werden: User.IsInRole('admin')

Weitere

Je Nach Anwendung der Expression sind auch auch andere Kontext Informationen verfügbar. Im spezifischen Fall werden diese dann separat dokumentiert.

Technischer Hintergrund

Um zu verstehen welche Entitäten in der jeweiligen Expression angesprochen werden können, sehen Sie die Technischen Hintergründe in:

DevOps -> Quino 10 -> Developer Documentation -> Expressions (Nur für Entwickler zugänglich)