Policy Authoring Patterns
This skill describes how to create authoring types (config records and context interface methods) for policies in the Azure API Management policy toolkit.
Config Record — src/Authoring/Configs/{PolicyName}Config.cs
File Template
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace Microsoft.Azure.ApiManagement.PolicyToolkit.Authoring;
/// <summary>
/// Configuration for the {xml-element-name} policy.<br/>
/// {Brief description of what the policy does.}
/// </summary>
public record {PolicyName}Config
{
/// <summary>
/// {Property description}. Policy expressions are allowed.
/// </summary>
[ExpressionAllowed]
public required {Type} {PropertyName} { get; init; }
/// <summary>
/// Optional. {Property description}.
/// </summary>
public {Type}? {OptionalPropertyName} { get; init; }
}
Related Attributes
[Document](src/Authoring/Attributes/DocumentAttribute.cs) — Marks a class as a policy document. Optionalnameparameter;ScopeandTypeproperties control document scope and type.[Expression](src/Authoring/Attributes/ExpressionAttribute.cs) — Marks a method as a policy expression (captures source file path via[CallerFilePath]). Used by the compiler to identify expression helper methods.[ExpressionAllowed](src/Authoring/Attributes/ExpressionAllowedAttribute.cs) — Marks config properties or parameters that accept policy expressions.
Property Rules
- Required properties: use the
requiredkeyword +initsetter. - Optional properties: use a nullable type (
?) +initsetter. Do not userequired. - Expression-enabled properties: decorate with
[ExpressionAllowed]attribute. Add "Policy expressions are allowed." to the XML doc. - Constrained properties (enums, allowed values): use either:
- A C#
enumtype (preferred for fixed, type-safe values) - A
stringproperty with XML doc listing allowed values (for values documented outside code) - Validation is applied at compilation time by the compiler class, not at authoring time. The config record accepts any value; the compiler reports diagnostics if invalid.
- A C#
- Sub-configs (child element arrays): define as separate
public recordtypes in the same file. - Sub-config naming convention: Use descriptive nouns that reflect their purpose (e.g.,
ApiRateLimit,AddressRange,MatchCondition), not generic names likeItemorElement. - Every public type and member must have
/// <summary>XML documentation covering:- Purpose
- Required vs optional status
- Default value (if any)
- Expression support (yes/no)
- Allowed values / enums (if applicable)
Real Examples
Simple attributes with expression support — RateLimitByKeyConfig.cs:
public record RateLimitByKeyConfig
{
[ExpressionAllowed]
public required int Calls { get; init; }
[ExpressionAllowed]
public required int RenewalPeriod { get; init; }
[ExpressionAllowed]
public required string CounterKey { get; init; }
[ExpressionAllowed]
public bool? IncrementCondition { get; init; }
public string? RetryAfterHeaderName { get; init; }
}
Nested sub-records — RateLimitConfig.cs:
public record RateLimitConfig
{
public required int Calls { get; init; }
public required int RenewalPeriod { get; init; }
public string? RetryAfterHeaderName { get; init; }
public ApiRateLimit[]? Apis { get; init; }
}
public record ApiRateLimit : EntityLimitConfig
{
public OperationRateLimit[]? Operations { get; init; }
}
public abstract record EntityLimitConfig
{
public string? Name { get; init; }
public string? Id { get; init; }
public required int Calls { get; init; }
public required int RenewalPeriod { get; init; }
}
Typed child arrays — IpFilterConfig.cs:
public record IpFilterConfig
{
/// <summary>
/// The action to apply: 'allow' or 'deny'. Policy expressions are allowed.
/// </summary>
[ExpressionAllowed]
public required string Action { get; init; }
/// <summary>
/// Optional. IP addresses. Policy expressions are allowed.
/// </summary>
[ExpressionAllowed]
public string[]? Addresses { get; init; }
/// <summary>
/// Optional. IP address ranges.
/// </summary>
public AddressRange[]? AddressRanges { get; init; }
}
/// <summary>
/// An IP address range with 'from' and 'to' octets.
/// </summary>
public record AddressRange
{
/// <summary>
/// The starting IP address. Policy expressions are allowed.
/// </summary>
[ExpressionAllowed]
public required string From { get; init; }
/// <summary>
/// The ending IP address. Policy expressions are allowed.
/// </summary>
[ExpressionAllowed]
public required string To { get; init; }
}
Constrained values with enum — AdvancedPolicyConfig.cs (example pattern):
/// <summary>
/// The caching behavior for the policy action.
/// </summary>
public enum CachingBehavior
{
/// <summary>Cache the response.</summary>
Store,
/// <summary>Reuse a cached response if available.</summary>
Validate,
/// <summary>Bypass cache entirely.</summary>
Bypass
}
public record AdvancedPolicyConfig
{
/// <summary>
/// Required. The caching behavior: 'Store', 'Validate', or 'Bypass'.
/// </summary>
public required CachingBehavior Behavior { get; init; }
}
Context Interface Methods
Add method signatures to the appropriate section interfaces. Policies may appear in one or more sections:
src/Authoring/IInboundContext.cssrc/Authoring/IOutboundContext.cssrc/Authoring/IBackendContext.cssrc/Authoring/IOnErrorContext.cssrc/Authoring/IFragmentContext.cs
Method Signature Template
/// <summary>
/// {Description of the policy behavior}.<br/>
/// Compiled to <a href="{documentation-url}">{xml-element-name}</a> policy.
/// </summary>
/// <param name="config">
/// Configuration for the {xml-element-name} policy.
/// </param>
void {MethodName}({PolicyName}Config config);
Rules
- The method name is PascalCase and matches the config name minus the
Configsuffix (e.g.,RateLimitforRateLimitConfig). - All section interfaces extend
IHaveExpressionContext, giving access toExpressionContext. - For parameters that are expressions (not config objects), use
[ExpressionAllowed]on the parameter itself. - Some policies use direct parameters instead of a config object (e.g.,
void SetMethod(string method)). Use this pattern only for policies with 1–2 simple parameters and no optional fields.
IFragmentContext Duplication
IFragmentContext currently duplicates method signatures from the section-specific interfaces (see the //TODO comment at the top of the file). Every time you add a policy method to any section interface, you must also add the same signature to IFragmentContext.cs. Copy the exact same method signature verbatim. Do not attempt to refactor this pattern.