Plugin note
This functionality is contained within an expression evaluator plugin. As such, it is available only when the corresponding plugin is installed.
Plugin assembly
CSF.Zpt.ExpressionEvaluators.CSharpExpressions.dll
Plugin classes
CSF.Zpt.ExpressionEvaluators.CSharpExpressions.CSharpNamespaceExpressionEvaluator
,
CSF.Zpt.ExpressionEvaluators.CSharpExpressions.CSharpAssemblyExpressionEvaluator
&
CSF.Zpt.ExpressionEvaluators.CSharpExpressions.CSharpTypeExpressionEvaluator
Expression prefixes
csnamespace:
,
csassembly:
&
cstype:
Advanced 'csharp' expressions
In order to deal with the difficulties of using extension methods in CSharp expressions, there are three additional expression types supported by the CSharp expression plugin. Between them, they allow the author of a ZPT document to specify the precise type of a TALES variable, thus permitting the expression evaluator to apply extension methods to that variable's members. These extra expressions are:
-
csassembly:
- which specifies an assembly name which should be referenced by CSharp expressions which have this variable in-scope. -
csnamespace:
- which acts like ausing
directive for importing .NET namespaces into CSharp expressions. -
cstype:
- which specifies the type definition for another named variable.
Assembly references
The csassembly:
expression is put to best use in tal:define
attributes in order to define a special assembly-reference TALES variable.
The content of the assembly-reference expression is the full name of a .NET assembly.
That full name is the result which would be obtained if Assembly.FullName
were interrogated for that assembly.
This named assembly must be either in the same directory as the application's main executable, in the
bin
directory for a MVC web application or available in the computer's Global Assembly Cache.
For assemblies which are in the application's directory, it might be possible to use just the shortened name instead, which is typically the same as the assembly filename, omitting the .dll suffix.
Whilst this variable has no particular direct use in any expressions (it is a throwaway definition),
csharp:
expressions which are evaluated where this assembly-reference variable is in-scope will
reference that assembly.
Example
<div tal:define="myTypes csassembly:My.Types">
<p>
This expression will be compiled with a reference to <code>My.Types.dll</code>:
<span tal:replace="csharp:DateTime.Now.ToString("yyyy-MM-dd")">2015-01-01</span>.
</p>
<p>
The following expression will be as well:
<span tal:replace="csharp:DateTime.Now.ToString("yyyy-MM-dd")">2015-01-01</span>.
</p>
</div>
<p>
But the following expression will not, because it is outside the scope of the <code>myTypes</code> variable
definition:
<span tal:replace="csharp:DateTime.Now.ToString("yyyy-MM-dd")">2015-01-01</span>.
</p>
Namespace imports
The csnamespace:
expression is used to import .NET namespaces into compiled expressions via
using
directives. Much like the assembly-reference above, it should be used in
tal:define
attributes to define the imported namespace. Then - that namespace will be imported
for any expressions which are evaluated where that defined variable is in-scope.
You never actually make direct use of the defined variable in any expressions.
The expression body itself is simply the namespace you wish to import.
tal:define="scg csnamespace:System.Collections.Generic" would mean that all CSharp expressions
which are declared in the same scope as the scg variable would include
using System.Collections.Generic;
.
Optionally, you may specify a namespace alias for a
using with alias directive
(the third form listed on that linked page).
Where desired, the alias is specified in the expression as a name followed by a single space character,
preceding the namespace name.
In the expression this would look like using alias = Name.Space.Name;
.
Example
<div tal:define="ns1 csnamespace:System.Collections.Generic;
ns2 csnamespace:glob System.Globalization">
<p>
The following expression defines a variable containing a
<code>System.Collections.Generic.List<System.Globalization.CultureInfo<</code>, making use of the
namespace imports declared on the container:
<span tal:define="cultureList csharp:new List<glob.CultureInfo>()"
tal:content="csharp:cultureList.Count">2</span>
</p>
</div>
Specifying variable types
The last advanced expression, cstype:
is used to tell the CSharp expression compiler the .NET
type for a given TALES variable. This changes the way the compiler works such that it uses the specified
type for that variable instead of treating it as dynamic
.
As with the above expressions, a type-declaration expression is only useful in tal:define
attributes and the variable it creates is not used directly in any expressions. Instead, whilst in-scope,
this variable tells the compiler to treat the named variable as the named type.
The syntax for such an expression is to specify a variable name (the TALES variable for which we are
declaring the type), followed by a single space character, followed by the type name.
The definition tal:define="typ1 cstype:myCollection IEnumerable<MyItem>" would declare that
the variable myCollection is an IEnumerable<MyItem>
.
Of course, for this to work, you will require sppropriate assembly references and assembly imports such that
both IEnumerable<T>
and MyItem
types are available.
Putting it all together
Putting these three expression types together, it becomes possible to use extension methods (such as Linq) within CSharp expressions in a ZPT document.
For this example, let us assume that the model has a property named Items;
this property is an IEnumerable<MyItem>
.
The MyItem
type is defined in the assembly MyTypes.dll
, in the namespace
MyTypes.SomeNamespace
and has a boolean property named IsActive
.
<div tal:define="items here/Items;
itemAssembly csassembly:MyTypes;
ns1 csnamespace:System.Collections.Generic;
ns2 csnamespace:MyTypes.SomeNamespace;
itemType cstype:items IEnumerable<MyItem>">
<p tal:define="activeCount csharp:items.Count(x => x.IsActive)">
There are <span tal:replace="activeCount">2</span> active items.
</p>
</div>
As you can see, this is an awful lot of markup-level code when all you wanted was the count of active items. As stated on the CSharp expressions page, it is not advised to use this mechanism unless you really need to. In most scenarios it is better to create a property or method on your MVC model class which exposes what you need.
Configuration settings which affect this
As well as using the csassembly:
and csnamespace:
expressions, it is possible to
globally add assembly references and/or namespace imports for every ZPT document,
into your application configuration file.
Using the configuration file to specify your baseline assemblies & namespaces can take some of the pain
out of otherwise large blocks of boilerplate definitions (such as shown above).