In this guide we will show you how to create a simple policy document, test it and compile it to a policy file.
We will cover the following topics:
- Creation of a new solution for policy documents
- Writing a simple policy document
- Compiling policy document
- Writing complex policy document
- Testing expressions in policy document
-
Check that you have latest .NET SDK 8 sdk version installed.
-
Open terminal and create a new solution by executing
dotnet new sln --output PolicySolution cd PolicySolution
-
Create a new class library project by executing
dotnet new classlib --output Contoso.Apis.Policies dotnet sln add ./Contoso.Apis.Policies
-
❗ Azure API Management Policy toolkit is not yet published to NuGet. Because of that, we need to create a local nuget repository for the packages and put the libraries there.
- Create a local nuget repository for the packages by executing
mkdir packages
- Download the Azure API Management policy toolkit libraries from GitHub release and put them in the
packages
folder. - Create a file named
nuget.config
in the solution folder with the content:<configuration> <packageSources> <!-- local feed for the project --> <add key="local" value="./packages" /> </packageSources> </configuration>
- Create a local nuget repository for the packages by executing
-
Add Azure API Management policy toolkit library by running
cd ./Contoso.Apis.Policies dotnet add package Azure.ApiManagement.PolicyToolkit.Authoring
-
Open the solution in your IDE of choice. We tested Visual Studio , Raider, Visual Studio Code with C# Dev Kit, but any IDE with C# support should work.
Azure API Management policy toolkit defines a new way of writing policy documents: One policy document is one C# class in
one cs
file.
Let's create a new ApiOperationPolicy.cs
file in the project which will be our policy document by executing the following
command.
dotnet new class -n ApiOperationPolicy
The class in the file should inherit from IDocument
interface and have Document
attribute
from Azure.ApiManagement.PolicyToolkit.Authoring
namespace.
using Azure.ApiManagement.PolicyToolkit.Authoring;
namespace Contoso.Apis.Policies;
[Document]
public class ApiOperationPolicy : IDocument
{
}
The IDocument
type contains methods Inbound
, Outbound
, Backend
and OnError
which are used to define policy
sections.
Let's implement Inbound
method. In the method let's add the policy to set header X-Hello
to value World
.
[Document]
public class ApiOperationPolicy : IDocument
{
public void Inbound(IInboundContext context)
{
context.Base();
context.SetHeader("X-Hello", "World");
}
}
Let's unpack the code above:
Inbound
method accepts one parameterIInboundContext context
.IInboundContext
contains methods which are directly mapped to policies.Base
method is mapped tobase
policy.SetHeader
maps toset-header
policy- Parameters
X-Hello
andWorld
inSetHeader
method are mapped to policy'sname
attribute andvalue
element.
Great! We have a simple policy document. Now we would like to check if it really works as expected in Azure API Management instance. To do that we will need to use the Azure API Management policy toolkit document compiler to generate a policy document which is understandable by Azure API Management.
The next step would be to compile this policy to a format which Azure API Management can understand. Currently, Azure API Management supports policy documents in Razor format. The Azure API Management policy toolkit provides a dotnet tool called a compiler which can generate Razor policy documents from C# classes.
To use the compiler, we first need to add it to our solution folder. To do that execute the following command in the solution folder.
cd .. # Go to solution folder if not already there
dotnet tool install Azure.ApiManagement.PolicyToolkit.Compiling
After the installation, the compiler should be available in the project folder. Now lets run the compiler to generate policy document. Execute compiler command in the solution folder.
dotnet azure-apim-policy-compiler --s .\Contoso.Apis.Policies --o . --format true
The compiler is a dotnet tool named azure-apim-policy-compiler
. The --s
parameter is a source folder with policy documents.
The --o
parameter is an output folder for generated policy documents. The --format
parameter is a flag which tells
the compiler to format the generated document.
The compiler will generate ApiOperationPolicy.xml
file in the solution folder. The generated file should have the
following
content:
<policies>
<inbound>
<base/>
<set-header name="X-Hello" exists-action="override">
<value>World</value>
</set-header>
</inbound>
</policies>
The generated file is a Razor policy document which can be imported to Azure API Management instance. You can now import the file to some API operation in your Azure API Management instance and test if the request will contain header. The policy should be imported without any issues and the header should be added to every request.
Great! We have created a simple policy document, compiled it and tested it in Azure API Management instance. Now let's move to more complex policy document.
The Azure API Management policy toolkit allows you to write more complex policy documents in a simple way. Complex policy documents contain a lot of expressions which alter policy behavior based on request context. The Azure API Management policy toolkit allows you to naturally create expressions, test them and compile them to a policy document.
Let's assume that we want to add authorization header to the request.
If a request comes from Company IP addresses 10.0.0.0/24
it should use basic authorization with username
and password
from named values.
If a request comes from other IP addresses it should use Bearer
token received from https://graph.microsoft.com.
For every request we want to add header with the user id.
using Azure.ApiManagement.PolicyToolkit.Authoring;
using Azure.ApiManagement.PolicyToolkit.Authoring.Expressions;
namespace Contoso.Apis.Policies;
[Document]
public class ApiOperationPolicy : IDocument
{
public void Inbound(IInboundContext context)
{
if(IsCompanyIP(context.ExpressionContext))
{
context.AuthenticationBasic("{{username}}", "{{password}}");
}
else
{
context.AuthenticationManagedIdentity(new ManagedIdentityAuthenticationConfig
{
Resource = "https://graph.microsoft.com"
});
}
context.SetHeader("X-User-Id", GetUserId(context.ExpressionContext));
}
public static bool IsCompanyIP(IExpressionContext context)
=> context.Request.IpAddress.StartsWith("10.0.0.");
private static string GetUserId(IExpressionContext context)
=> context.User.Id;
}
Let's unpack the code above it:
if
statement is an equivalent ofchoose
policy withwhen
element.else
statement is an equivalent ofotherwise
element.IsCompanyIP
is a method which checks if request comes from company IP addresses, and it is mapped to a policy expression- Every method, other than section method are treated as expressions. They need to accept one parameter of type
IExpressionContext
with namecontext
. Type is available inAzure.ApiManagement.PolicyToolkit.Authoring.Expressions
namespace. IExpressionContext
type contains the same properties ascontext
object in policy expressions.AuthenticationBasic
method is mapped toauthentication-basic
policy.AuthenticationManagedIdentity
method is mapped toauthentication-managed-identity
policy.SetHeader
method is mapped toset-header
policy with override.
Expressions are methods in the class. They may be static or instance methods, and they can be private or public.
They need to accept one parameter of type IExpressionContext
with name context
.
To use an expression you just need to call that method in the place were you want to use it. In our example, we call
IsCompanyIP
method in the if
statement, and we call GetUserId
method in the SetHeader
method in value parameter.
In other more complex policies, which accept a configuration object, you will invoke method in the field initialization
assignment like in example below.
context.SomePolicy(new Config()
{
Field = Method(context.ExpressionContext)
});
Cool! We have a more complex policy document. Now let's compile it to a policy document.
dotnet azure-apim-policy-compiler --s .\source\ --o . --format true
Content of the generated file should be:
<policies>
<inbound>
<choose>
<when condition="@(context.Request.IpAddress.StartsWith("10.0.0."))">
<authentication-basic username="{{username}}" password="{{password}}"/>
</when>
<otherwise>
<authentication-managed-identity resource="https://graph.microsoft.com"/>
</otherwise>
</choose>
<set-header name="X-User-Id" exists-action="override">
<value>@(context.User.Id)</value>
</set-header>
</inbound>
</policies>
Isn't it great, right? I hope it is. But we can now test the expressions in the policy document.
The Azure API Management policy toolkit provides a way to test expressions in policy documents. To do that we need to create a test project from a solution folder. Then we will need to add a reference to the policy project and the testing library. Last, we need to create a test class and write a test for the expression. All of that you can do by executing the following commands.
dotnet new mstest --output Contoso.Apis.Policies.Tests
dotnet sln add ./Contoso.Apis.Policies.Tests
cd Contoso.Apis.Policies.Tests
dotnet add package Azure.ApiManagement.PolicyToolkit.Testing
dotnet add reference ..\Contoso.Apis.Policies
dotnet new class -n ApiOperationPolicyTest
Perfect! Now we can write a test for IsCompanyIP
method in the class.
using Contoso.Apis.Policies;
using Azure.ApiManagement.PolicyToolkit.Testing.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json.Linq;
namespace Contoso.Apis.Policies.Tests;
[TestClass]
public class ApiOperationPolicyTest
{
[TestMethod]
public void FilterSecrets()
{
var context = new MockExpressionContext();
context.Request.IpAddress = "10.0.0.12";
Assert.IsTrue(ApiOperationPolicy.IsCompanyIP(context));
context.Request.IpAddress = "11.0.0.1";
Assert.IsFalse(ApiOperationPolicy.IsCompanyIP(context));
}
}
Let's unpack the code above:
- Test class is a standard MSTest class with one test method. You can use your favorite testing framework in place of MSTest. Policy framework is not dependent on any testing framework.
MockExpressionContext
is a class which is used to mock request context. It is available inAzure.ApiManagement.PolicyToolkit.Testing.Expressions
namespace. It implementsIExpressionContext
interface and exposes helper properties to set up request context.context.MockRequest.IpAddress = "10.0.0.12"
is setting a IpAddress for request.
To check that the expression works as expected, run the test by executing the following command. Command can be executed in the test project folder or solution folder.
dotnet test
Great! We have tested the expression in the policy document. Now you can write more complex policy documents, test them and compile them to a policy document.