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 eintargetClass
AttributeRole 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 | ||
---|---|---|
| ||
<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)