Intermediate

Deploying ASP.NET Core 6 Web API to Azure API Management

Publish an ASP.NET Core Web API to Azure API Management and enhance it with APIM policies.

Technologies: ASP.NET Core 6, Azure API Management (APIM), Azure App Service, Azure CLI, PowerShell

Table of Contents

  1. Course Overview
  2. Module 2 — Azure API Management Overview
  3. Module 3 — Deploying APIs to Azure API Management
  4. Module 4 — Enhancing APIs Using API Management Policies
  5. Architecture Diagrams
  6. Reference Tables
  7. Summary and Best Practices

1. Course Overview

This course teaches how to enhance HTTP API capabilities without modifying application code, using Azure API Management (APIM).

Main Objectives

  • Understand Azure API Management use cases
  • Master APIM concepts: products, subscriptions, versions, revisions, policies
  • Provision an APIM instance via the Azure portal, Azure CLI, and PowerShell
  • Import an ASP.NET Core 6 API into APIM
  • Apply policies to secure, transform, and limit access to APIs

Prerequisites

  • Familiarity with HTTP APIs (REST)
  • Basic Azure knowledge

2. Module 2 — Azure API Management Overview

2.1 Introduction to Web APIs

A Web API is a programming interface exposed over HTTP/HTTPS.

CharacteristicDetail
ProtocolHTTP (port 80) or HTTPS (port 443)
Typical response formatJSON or XML
RoleExpose business functionality to clients

2.2 Developing the ASP.NET Core 6 API

The demo project is the IoT Device Catalogue API for the fictional company Globomantics, which sells industrial IoT devices.

Data Model — IoTDevice.cs

namespace IoTCatalogueAPI
{
    public class IoTDevice
    {
        public int Id { get; set; }
        public string? Model { get; set; }
        public string? DeviceType { get; set; }
        public double Price { get; set; }
        public bool IsEdge { get; set; }  // true = supports edge computing
    }
}

JSON Persistence — JsonDBHelper.cs

using System.Text.Json;

namespace IoTCatalogueAPI
{
    public class JsonDBHelper
    {
        private string _jsonFile = "catalogue.json";

        public JsonDBHelper()
        {
            if (!File.Exists(_jsonFile))
                File.WriteAllText(_jsonFile, JsonSerializer.Serialize(new List<IoTDevice>()));
        }

        public void SaveToDB(List<IoTDevice> catalogue)
        {
            var json = JsonSerializer.Serialize(catalogue);
            File.WriteAllText(_jsonFile, json);
        }

        public List<IoTDevice> LoadFromDB()
        {
            if (!File.Exists(_jsonFile))
                return new List<IoTDevice>();

            return JsonSerializer.Deserialize<List<IoTDevice>>(File.ReadAllText(_jsonFile));
        }
    }
}

Initial Database — catalogue.json

[
  {"Id": 1, "Model": "model-alpha", "DeviceType": "magnetic", "Price": 10, "IsEdge": false},
  {"Id": 3, "Model": "model-beta", "DeviceType": "heat",     "Price": 650, "IsEdge": true}
]

Main Controller — IoTDeviceCatalogueController.cs

using Microsoft.AspNetCore.Mvc;

namespace IoTCatalogueAPI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class IoTDeviceCatalogueController : ControllerBase
    {
        private List<IoTDevice> catalogue = new();
        private JsonDBHelper jsonDBHelper = new();
        private readonly ILogger<IoTDeviceCatalogueController> _logger;

        public IoTDeviceCatalogueController(ILogger<IoTDeviceCatalogueController> logger)
        {
            _logger = logger;
            catalogue = jsonDBHelper.LoadFromDB();
        }

        // GET /IoTDeviceCatalogue
        [HttpGet(Name = "GetCatalogue")]
        public IEnumerable<IoTDevice> GetCatalogue() => catalogue;

        // GET /IoTDeviceCatalogue/{deviceId}
        [HttpGet("{deviceId}", Name = "GetDevice")]
        public IoTDevice GetDevice(int deviceId)
        {
            var device = catalogue.SingleOrDefault(c => c.Id == deviceId);
            jsonDBHelper.SaveToDB(catalogue);
            return device ?? new IoTDevice();
        }

        // POST /IoTDeviceCatalogue
        [HttpPost(Name = "AddDevice")]
        public IoTDevice AddDevice(IoTDevice device)
        {
            if (device == null) return new IoTDevice();
            int newId = catalogue.Any() ? catalogue.Max(c => c.Id) + 1 : 1;
            device.Id = newId;
            catalogue.Add(device);
            jsonDBHelper.SaveToDB(catalogue);
            return device;
        }

        // PUT /IoTDeviceCatalogue/{deviceId}/{newPrice}
        [HttpPut("{deviceId}/{newPrice}", Name = "UpdatePrice")]
        public IoTDevice UpdatePrice(int deviceId, double newPrice)
        {
            var device = catalogue.SingleOrDefault(c => c.Id == deviceId);
            if (device == null) return new IoTDevice();
            device.Price = newPrice;
            jsonDBHelper.SaveToDB(catalogue);
            return device;
        }

        // DELETE /IoTDeviceCatalogue/{deviceId}
        [HttpDelete("{deviceId}", Name = "DeleteDevice")]
        public void DeleteDevice(int deviceId)
        {
            var device = catalogue.SingleOrDefault(c => c.Id == deviceId);
            if (device != null) catalogue.Remove(device);
            jsonDBHelper.SaveToDB(catalogue);
        }
    }
}

API Operations

HTTP MethodRouteOperation NameDescription
GET/IoTDeviceCatalogueGetCatalogueReturns all devices
GET/IoTDeviceCatalogue/{deviceId}GetDeviceReturns a device by ID
POST/IoTDeviceCatalogueAddDeviceAdds a device
PUT/IoTDeviceCatalogue/{deviceId}/{newPrice}UpdatePriceUpdates the price
DELETE/IoTDeviceCatalogue/{deviceId}DeleteDeviceDeletes a device

Entry Point — Program.cs

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();   // exposes /swagger/v1/swagger.json

var app = builder.Build();

// Swagger enabled outside of development (for demos)
app.UseSwagger();
app.UseSwaggerUI();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

Note: The if(true) condition replaces the IsDevelopment() check so that Swagger is accessible from Azure — useful when importing into APIM via the /swagger/v1/swagger.json URL.

2.3 Deploying to Azure App Service

Steps in the Azure portal:

  1. Go to App ServicesCreate
  2. Choose the existing Resource Group
  3. Name the service (e.g. appservicedemo-iotcatalogue) — must be globally unique
  4. Publish: Code
  5. Runtime stack: .NET 6
  6. OS: Linux (.NET Core runtime works on Linux and Windows)
  7. Region: Canada Central (or the closest region)
  8. App Service Plan: Select or create a plan

Deploying from Visual Studio:

  • Right-click on the project → Publish → select the created App Service → Publish

Verification: Navigate to https://<app-service-url>/swagger to access the Swagger UI.

2.4 New Business Requirements

After the initial deployment, the team receives requirements unrelated to IoT domain logic:

RequirementDescriptionAPIM Solution
Discovery for external developersExpose the API to third-party developersDeveloper Portal
Usage quotasMax 1,000 calls/hour per clientrate-limit-by-key policy
Defense in depth (IP filtering)Restrict by IP addressip-filter policy
Format transformationReturn XML instead of JSONjson-to-xml policy
Azure AD authenticationJWT validationvalidate-jwt policy
Header validationVerify the presence of specific headerscheck-header policy
CachingImprove performancecache-lookup / cache-store policy

All these requirements share one thing in common: they have nothing to do with IoT business logic and should not pollute the API code.

2.5 Introduction to Azure API Management

Microsoft definition: Azure API Management is an API management platform that allows you to import and enhance existing APIs without modifying their code.

Key capabilities:

  • API Discovery & Consumption: built-in developer portal
  • Internal and external support: internal consumers (intranet) and external (internet)
  • No-code / low-code enhancement: XML policies
  • Native Azure integrations: App Services, Function Apps, Logic Apps, AKS, Service Fabric
  • Advanced security: Application Gateway + Virtual Networks integration (Premium tier)

2.6 Azure APIM Global Architecture

The main components of an APIM instance:

ComponentRole
API GatewaySingle entry point; relays requests to backends
Management Plane (Azure Portal)Configuration of APIs, policies, products, subscriptions
Developer PortalSelf-service portal for API consumers
Git RepositoryBackup and restore of APIM configuration

3. Module 3 — Deploying APIs to Azure API Management

3.1 Pricing Tiers

TierUsageCharacteristics
ConsumptionServerless / Pay-per-useUsage-based billing, no dedicated resources
DeveloperEvaluation & TestingNear-Premium features, not for production
BasicEntry-level productionBasic capabilities
StandardMid-volume productionIntermediate capabilities
PremiumEnterprise / High availabilityVNet integration, multi-region, scaling
Isolated (preview)Isolated environmentsCurrently in preview

Tip: Use the Developer tier to evaluate Premium features before making a financial commitment.

3.2 Provisioning — Azure Portal

Steps:

  1. API Management servicesCreate
  2. Resource Group: select the existing group
  3. Region: Canada Central
  4. Name: e.g. apim-demo-01 (globally unique name)
  5. Organization: Globomantics
  6. Administrator email: your address
  7. Pricing tier: choose according to needs (e.g. Premium)
  8. Monitoring: optional — Azure Application Insights integration
  9. Scale: number of processing units (more = more expensive)
  10. Managed Identity: optional — for AAD authentication
  11. Virtual Network: optional — None, External, or Internal
  12. Review + Create

⚠️ Provisioning takes 30 to 45 minutes.

3.3 Provisioning — Azure CLI

# Log in to Azure
az login

# Create an APIM instance
az apim create \
  --name myapim-demo-cli01 \
  -g demo-rg \
  -l canadacentral \
  --publisher-email admin@globomantics.com \
  --publisher-name Globomantics

# Display the name and SKU of an existing instance
az apim show \
  --name myapim-demo-cli \
  --resource-group demo-rg \
  --query "{Name:name, Sku:sku.name}"

# Delete an instance
az apim delete -n apimdemo02 -g demo-rg

Required parameters for az apim create:

ParameterDescription
--nameAPIM instance name (globally unique)
-g / --resource-groupParent Resource Group
-l / --locationAzure region
--publisher-emailAdministrator email
--publisher-nameOrganization name

3.4 Provisioning — Azure PowerShell

# Create an APIM instance via PowerShell
New-AzApiManagement `
  -ResourceGroupName "demo-rg" `
  -Name "apim-powershell-demo" `
  -Location "Canada Central" `
  -Organization "Globomantics" `
  -AdminEmail "admin@globomantics.com" `
  -Sku Developer

Can be run locally or via the Cloud Shell in the Azure portal.

3.5 Key Concepts: Products, Subscriptions, Versions, Revisions

Backend API vs Frontend API

Client
  │
  ▼
┌─────────────────────────┐
│  Azure API Management   │
│  ┌─────────────────┐    │
│  │  Frontend API   │    │  ← what clients see
│  └────────┬────────┘    │
│           │ relay       │
│  ┌────────▼────────┐    │
│  │  API Gateway    │    │
└──┴────────┬────────┴────┘
            │
            ▼
    ┌───────────────┐
    │  Backend API  │  ← Azure App Service / Function App / etc.
    └───────────────┘

Products

A Product groups multiple Frontend APIs to apply shared permissions to them.

  • Each Product can have different access levels
  • By default, APIM creates two products: Starter and Unlimited
  • APIs are associated with one or more Products
  • Products can be published (visible in the Developer Portal) or unpublished

Subscriptions

A Subscription grants access to a Product via subscription keys:

  • Primary Key and Secondary Key (allow key rotation without interruption)
  • The key is sent in the Ocp-Apim-Subscription-Key header
  • Subscriptions can be scoped to: Global, Product, or individual API

Versions

  • Allow maintaining multiple versions in production simultaneously
  • Three versioning methods:
    • Path: /v1/..., /v2/...
    • Header: Api-Version: v2
    • Query string: ?api-version=v2
  • Each version has its own independent policies

Revisions

  • Allow testing changes before going to production
  • Only one revision is Current (active) at a time
  • Revisions are non-disruptive for consumers
  • Key difference from Versions: Revisions are not meant to be in production simultaneously

3.6 Importing the API into APIM

Steps in the Azure portal:

  1. API ManagementAPIsAdd API
  2. Select OpenAPI (import via swagger.json)
  3. Enter the swagger URL: https://<app-service-url>/swagger/v1/swagger.json
  4. Configure the Display name and URL suffix
  5. Associate with a Product (optional)
  6. Create

Backend URL Configuration:

  • After import: go to API Settings
  • Update Web service URL with the App Service URL
  • Save

3.7 Preventing APIM Bypass

Problem: Clients can bypass APIM by calling the App Service URL directly.

Solution: Security Header in API code

// In the controller — SecurityToken header check
[HttpGet(Name = "GetCatalogue")]
public IActionResult GetCatalogue()
{
    // Validate the security header
    if (!Request.Headers.TryGetValue("SecurityToken", out var token) || token != "gateway-secret-789")
        return Unauthorized();

    return Ok(catalogue);
}

⚠️ In production, never hardcode the token. Use Azure Key Vault or App Settings.

APIM Policy to automatically inject the header:

<!-- File: IoTCatalogueAPI__1[Current].xml -->
<policies>
    <inbound>
        <base />
        <!-- APIM injects the SecurityToken before relaying to the backend -->
        <set-header name="SecurityToken" exists-action="override">
            <value>gateway-secret-789</value>
        </set-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Secured flow:

Client ──────────────────────────────────────► App Service (Direct)
                                                    → 401 Unauthorized ✗

Client ──► APIM (set-header SecurityToken) ──► App Service
                                                    → 200 OK ✓

3.8 Products and Subscriptions (demo)

Create a custom Product:

  1. APIsProductsAdd
  2. Name: Catalog-Product
  3. Add the IoTCatalogueAPI API to the Product
  4. Configure: subscriptionRequired: true, approvalRequired: true

Product JSON configuration (excerpt from Git repository):

{
  "id": "/products/catalog-product",
  "name": "Catalog-Product",
  "subscriptionRequired": true,
  "approvalRequired": true,
  "state": "published",
  "$refs-groups": [
    "api-management/groups/Administrators/configuration.json",
    "api-management/groups/Developers/configuration.json"
  ]
}

API call with Subscription key (Postman):

GET https://<apim-name>.azure-api.net/IoTDeviceCatalogue
Ocp-Apim-Subscription-Key: <primary-or-secondary-key>

3.9 Developer Portal

The Developer Portal is a self-service portal for API consumers.

Two versions:

  • New Developer Portal: customizable (look & feel)
    • URL: https://<apim-name>.developer.azure-api.net
  • Deprecated Developer Portal (classic): functional but being phased out
    • URL: https://<apim-name>.portal.azure-api.net

Developer registration process:

  1. Developer accesses the Developer Portal
  2. Signs up and creates an account
  3. Browses the available Products
  4. Subscribes to a Product (Request subscription)
  5. Administrator approves the request (if approvalRequired: true)
  6. Developer receives their subscription keys
  7. Can test APIs directly from the portal

Subscription management:

  • Admins can grant keys directly from the Azure portal
  • Developers can self-serve via the Developer Portal

3.10 API Versions

Create a new version:

  1. APIs → right-click on the API → Add version
  2. Choose the method: Path (/v2), Header, or Query string
  3. Full version name: e.g. iotcatalogueapi-v2
  4. Associate with the same Product or a different one

Structure after creation:

IoTCatalogueAPI
├── Original (v1)   ← URL: /IoTDeviceCatalogue
└── v2              ← URL: /IoTDeviceCatalogue/v2

Each version has its own independent policies.

3.11 API Revisions

Create a revision:

  1. APIs → right-click on a version → Add revision
  2. The new revision is created as a copy of the current version
  3. Make changes and test on the new revision
  4. When ready: Make current to push to production

Versions vs Revisions:

VersionsRevisions
PurposeMultiple versions simultaneously in productionTest changes before going to production
Simultaneously in productionYesNo (only one revision is Current)
Visible to consumersYesNo (except Current)

3.12 Saving Configuration to a Git Repository

Each APIM instance has a built-in Git repository.

Save configuration:

  1. Deployment + InfrastructureRepository
  2. Save to repository
  3. Choose the branch (e.g. master)
  4. Name the commit (e.g. adding security policy)
  5. Save (approximately 1 minute)

Restore from repository:

  1. RepositoryDeploy to API Management
  2. Choose the branch
  3. Deploy (approximately 2 minutes)

Clone locally:

git clone https://<apim-name>.scm.azure-api.net/

APIM Git repository structure:

api-management/
├── apis/
│   └── IoTCatalogueAPI__1[Current]/
│       └── configuration.json
├── backends/
│   └── _WebApp_iotdevicecatalogue/
│       └── configuration.json
├── groups/
│   ├── Administrators/
│   ├── Developers/
│   └── Guests/
├── policies/
│   ├── global.xml          ← Global scope
│   ├── apis/
│   │   └── IoTCatalogueAPI__1[Current].xml   ← API scope
│   └── products/
│       └── Starter.xml     ← Product scope
├── products/
│   ├── Catalog-Product/
│   │   └── configuration.json
│   ├── Starter/
│   └── Unlimited/
└── configuration.json

4. Module 4 — Enhancing APIs Using API Management Policies

4.1 Policy Concepts

Microsoft definition: Azure API Management policies allow API publishers to modify API behavior through XML configuration statements.

Key points:

  • No backend code modification required
  • Declarative XML configuration
  • Applied in real time on each request/response
  • Inheritable according to the scope hierarchy

4.2 XML Policy Structure

<policies>
    <!-- Executed upon receiving the inbound request -->
    <inbound>
        <base />  <!-- inherits policies from the parent scope -->
        <!-- your inbound policies here -->
    </inbound>

    <!-- Executed just before sending the request to the backend -->
    <backend>
        <base />
        <!-- your backend policies here -->
    </backend>

    <!-- Executed on the backend response before sending it back to the client -->
    <outbound>
        <base />
        <!-- your outbound policies here -->
    </outbound>

    <!-- Executed on error -->
    <on-error>
        <base />
        <!-- your error policies here -->
    </on-error>
</policies>

Mnemonic guide for policy placement:

SectionWhen to use it
<inbound>Validate/transform the incoming request (auth, IP filter, headers)
<backend>Modify the request before sending to backend (set-backend-service)
<outbound>Transform the backend response (json-to-xml, caching)
<on-error>Handle errors (logging, custom messages)

4.3 Policy Scopes

┌─────────────────────────────────────────────────────┐
│  GLOBAL SCOPE                                       │
│  (applies to ALL APIs)                              │
│  ┌───────────────────────────────────────────────┐  │
│  │  PRODUCT SCOPE                                │  │
│  │  (applies to APIs in the Product)             │  │
│  │  ┌─────────────────────────────────────────┐  │  │
│  │  │  API SCOPE                              │  │  │
│  │  │  (applies to all operations)            │  │  │
│  │  │  ┌───────────────────────────────────┐  │  │  │
│  │  │  │  OPERATION SCOPE                  │  │  │  │
│  │  │  │  (individual operation)           │  │  │  │
│  │  │  └───────────────────────────────────┘  │  │  │
│  │  └─────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────┘
ScopeWhere to configureCoverage
GlobalAll APIs → PolicyAll APIs in the instance
ProductProducts → [Product] → PolicyAll APIs in the Product
APIAPIs → [API] → PolicyAll operations of the API
OperationAPIs → [API] → [Operation] → PolicyA single operation

<base /> inherits policies from the parent scope. Removing it prevents inheritance.

4.4 Policy Categories

CategoryExample policies
Access restrictionrate-limit, rate-limit-by-key, quota, ip-filter, check-header
Authenticationauthentication-basic, authentication-certificate, validate-jwt
Cachingcache-lookup, cache-store, cache-lookup-value, cache-store-value
Cross-domaincors, jsonp
Transformationjson-to-xml, xml-to-json, set-header, set-body, rewrite-uri
Routingset-backend-service, forward-request
Logging & Monitoringlog-to-eventhub, trace
Mockmock-response

4.5 Demo: Rate Limiting / Quotas

Objective: Limit API calls to 5 calls every 30 seconds per IP address.

XML Policy — Scope: Operation (GetCatalogue)

<policies>
    <inbound>
        <base />
        <!-- Limit: 5 calls / 30 seconds, key = client IP address -->
        <rate-limit-by-key calls="5"
                           renewal-period="30"
                           counter-key="@(context.Request.IpAddress)"
                           increment-condition="@(context.Response.StatusCode == 200)" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Starter Product Policy — Weekly Quota:

<policies>
    <inbound>
        <!-- Limit: 5 calls/min and 100 calls/week (604800 seconds) -->
        <rate-limit calls="5" renewal-period="60" />
        <quota calls="100" renewal-period="604800" />
        <base />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
</policies>

HTTP response when limit is exceeded:

  • Status Code: 429 Too Many Requests
  • Header: Retry-After: <seconds>

Benefit: Also protects against Denial of Service (DoS) attacks.

4.6 Demo: IP Filtering (ip-filter)

Objective: Only allow certain IP addresses (defense in depth).

XML Policy — Scope: API

<policies>
    <inbound>
        <base />
        <!-- Allow only specific IPs -->
        <ip-filter action="allow">
            <address>203.0.113.42</address>             <!-- Single IP -->
            <address-range from="192.168.1.1"
                           to="192.168.1.254" />        <!-- IP range -->
        </ip-filter>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Result if IP is not allowed:

{
    "statusCode": 403,
    "message": "Forbidden"
}

Variant: Use action="forbid" to block specific IPs (blocklist).

4.7 Demo: JSON → XML Conversion

Objective: Return XML instead of JSON, without changing the backend API code.

XML Policy — Scope: Operation (GetCatalogue)

<policies>
    <inbound>
        <base />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
        <!-- Converts the JSON response to XML, ignores the client's Accept header -->
        <json-to-xml apply="always" consider-accept-header="false" />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Transformation example:

Backend response (JSON):

[
  {"Id": 1, "Model": "model-alpha", "DeviceType": "magnetic", "Price": 10, "IsEdge": false}
]

APIM response after policy (XML):

<ArrayOfIoTDevice>
    <IoTDevice>
        <Id>1</Id>
        <Model>model-alpha</Model>
        <DeviceType>magnetic</DeviceType>
        <Price>10</Price>
        <IsEdge>false</IsEdge>
    </IoTDevice>
</ArrayOfIoTDevice>

4.8 Demo: HTTP Header Validation

Objective: Require the presence of a specific header with an expected value — applied at the Global scope.

XML Policy — Scope: Global (All APIs)

<policies>
    <inbound>
        <!-- Default CORS policy -->
        <cors allow-credentials="true">
            <allowed-origins>
                <origin>https://myapim-demo-cli.developer.azure-api.net</origin>
            </allowed-origins>
            <allowed-methods preflight-result-max-age="300">
                <method>*</method>
            </allowed-methods>
            <allowed-headers>
                <header>*</header>
            </allowed-headers>
            <expose-headers>
                <header>*</header>
            </expose-headers>
        </cors>

        <!-- Custom header validation -->
        <check-header name="x-custom-api-key"
                      failed-check-httpcode="401"
                      failed-check-error-message="Not authorized"
                      ignore-case="false">
            <value>api-key-5678</value>
        </check-header>
    </inbound>
    <backend>
        <forward-request />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Test with Postman:

GET https://<apim-name>.azure-api.net/IoTDeviceCatalogue
Ocp-Apim-Subscription-Key: <subscription-key>
x-custom-api-key: api-key-5678

4.9 Demo: Mock API Response

Objective: Return a fake response when the backend is not ready.

Context: The GetDevice operation on the backend returns a 401 (intentionally broken code). Frontend developers need a response to continue their work.

Steps in the portal:

  1. Select the GetDevice operation
  2. Inbound processingAdd policyMock responses
  3. Configure the mocked response: 200 OK with a JSON body
  4. Save

Generated XML policy:

<policies>
    <inbound>
        <base />
        <!-- Returns a mocked response instead of calling the backend -->
        <mock-response status-code="200" content-type="application/json" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

Use cases: Parallel front/back development, integration testing, demos.


5. Architecture Diagrams

5.1 Azure APIM Global Architecture

graph TB
    subgraph Internet
        ExtDev[External Developer]
        ExtClient[External Client]
    end

    subgraph AzureAPIM["Azure API Management"]
        GW[API Gateway]
        MP[Management Plane\nAzure Portal]
        DP[Developer Portal]
        GIT[Git Repository\nconfiguration]
    end

    subgraph AzureBackend["Azure Backend Services"]
        AS[Azure App Service\nIoT Catalogue API]
        FA[Function App]
        LA[Logic App]
        AKS[AKS / Service Fabric]
    end

    subgraph Security["Security"]
        AAD[Azure Active Directory]
        KV[Key Vault]
        AG[Application Gateway]
        VNet[Virtual Network\nPremium tier]
    end

    ExtDev -->|"Ocp-Apim-Subscription-Key"| GW
    ExtClient -->|HTTPS| GW
    GW -->|relay + SecurityToken| AS
    GW -->|relay| FA
    GW -->|relay| LA
    GW -->|relay| AKS
    MP -->|configure| GW
    DP -->|self-service subscription| ExtDev
    GW <-->|validate-jwt| AAD
    GW <-->|secrets| KV
    AG --> GW
    VNet --> GW
    MP -->|save/restore| GIT

5.2 Deployment Flow

flowchart LR
    A[Visual Studio 2022\nASP.NET Core 6] -->|Publish - Zip Deploy| B[Azure App Service\nIoT Catalogue API]
    B -->|swagger.json URL| C[Azure APIM\nImport OpenAPI]
    C -->|Configure Backend URL| D[APIM Frontend API\nIoTCatalogueAPI]
    D -->|Add to Product| E[Catalog-Product\nSubscription Required]
    E -->|Publish| F[Developer Portal\nVisible to developers]
    F -->|Subscribe| G[Developer\nSubscription Key]
    G -->|Ocp-Apim-Subscription-Key| D

5.3 Conceptual CI/CD Pipeline

flowchart TD
    A[Code Push\ngit push] --> B[Build\ndotnet build]
    B --> C[Tests\ndotnet test]
    C --> D{Tests OK?}
    D -->|No| E[Notification\nfailure]
    D -->|Yes| F[Publish\ndotnet publish]
    F --> G[Deploy App Service\naz webapp deploy]
    G --> H[Update APIM\naz apim api import]
    H --> I[Apply Policies\naz apim policy set]
    I --> J[Save Config\nAPIM Git Repository]
    J --> K[Smoke Tests\nPostman / Newman]
    K --> L{Smoke OK?}
    L -->|No| M[Rollback\nDeploy previous revision]
    L -->|Yes| N[Production Live ✓]

5.4 Policy Processing Flow

sequenceDiagram
    participant C as Client
    participant GW as APIM Gateway
    participant BE as Backend API

    C->>GW: HTTP Request + Subscription Key
    
    rect rgb(230, 245, 255)
        Note over GW: INBOUND PROCESSING
        GW->>GW: Validate Subscription Key
        GW->>GW: check-header (x-custom-api-key)
        GW->>GW: ip-filter (IP check)
        GW->>GW: rate-limit-by-key (quota)
        GW->>GW: set-header SecurityToken
    end

    rect rgb(255, 245, 220)
        Note over GW: BACKEND PROCESSING
        GW->>BE: Forward request + SecurityToken
        BE->>BE: Business logic processing
        BE->>GW: JSON Response
    end

    rect rgb(220, 255, 230)
        Note over GW: OUTBOUND PROCESSING
        GW->>GW: json-to-xml (if configured)
        GW->>GW: cache-store
        GW->>C: Response (XML or JSON)
    end

    Note over C,GW: On error → on-error section

6. Reference Tables

Comparison of Provisioning Methods

MethodInterfaceIdeal forAutomatable
Azure PortalGUIDemo, learningNo
Azure CLI (az apim)TerminalScripts, CI/CDYes
Azure PowerShell (New-AzApiManagement)PowerShellWindows environmentsYes
ARM TemplateJSONInfrastructure as CodeYes
BicepDSLModern Infrastructure as CodeYes
VS Code ExtensionIDELocal developmentNo

Most Common Policies

PolicySectionPurpose
set-headerinbound / outboundAdd/modify an HTTP header
check-headerinboundValidate the value of a header
ip-filterinboundFilter by IP address
rate-limitinboundRate limiting (global quota)
rate-limit-by-keyinboundLimit by key (IP, subscription)
quotainboundQuota over a long period
validate-jwtinboundValidate a JWT (Azure AD)
json-to-xmloutboundConvert JSON → XML
xml-to-jsonoutboundConvert XML → JSON
cache-lookupinboundLook up in cache
cache-storeoutboundStore in cache
mock-responseinboundReturn fake response
corsinboundConfigure CORS
forward-requestbackendRelay the request

APIM Endpoints

TypeURL Pattern
API Gatewayhttps://<apim-name>.azure-api.net
Developer Portal (new)https://<apim-name>.developer.azure-api.net
Developer Portal (classic)https://<apim-name>.portal.azure-api.net
Management APIhttps://<apim-name>.management.azure-api.net
Git Repository (SCM)https://<apim-name>.scm.azure-api.net

HTTP Responses from Restriction Policies

SituationStatus CodeDescription
Missing/invalid subscription key401 UnauthorizedSubscription key required
IP not allowed (ip-filter)403 ForbiddenIP blocked
Missing header (check-header)401 UnauthorizedRequired header absent
Quota exceeded (rate-limit)429 Too Many RequestsToo many calls

7. Summary and Best Practices

Security Best Practices

  1. Never hardcode tokens/passwords in code → use Azure Key Vault + Named Values in APIM
  2. Always force traffic through APIM via a security header injected by APIM and verified by the backend
  3. Combine ip-filter + subscription keys for defense in depth
  4. Use validate-jwt for Azure Active Directory integration
  5. Enable HTTPS only on the backend App Service

APIM Best Practices

  1. Regularly save configuration to the APIM Git repository
  2. Use Revisions for policy changes in production (zero-downtime)
  3. Apply policies at the right scope: global for cross-cutting rules, operation for specific rules
  4. Monitor with Azure Application Insights (native APIM integration)
  5. Delete unused APIM instances (high cost, especially Premium/Standard tiers)
  6. Test on Developer tier before upgrading to Premium

Key Takeaways

  • Azure APIM is not an API development tool — it is a management and enhancement tool
  • The backend API must be deployed and accessible before configuring it in APIM
  • XML policies enable implementing: security, transformation, throttling, caching without touching the code
  • The Developer Portal simplifies onboarding of API consumers (internal and external)
  • Products and Subscriptions are the central access control mechanism in APIM
  • The distinction between Frontend API / Backend API is fundamental: clients never see the backend directly

Training notes — Deploying ASP.NET Core 6 Web API to Azure API Management


Search Terms

asp.net · deploying · core · web · api · azure · management · apis · c# · .net · development · apim · policy · provisioning · architecture · policies · products · revisions · subscriptions · versions · concepts · flow · global · http

Interested in this course?

Contact us to book it or get a custom training plan for your team.