Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ToDoItem sample Web API and .NET client #487

Merged
merged 16 commits into from
Feb 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,13 @@ updates:
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: nuget
directory: "/sample-api/api"
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: nuget
directory: "/sample-api/clients/dotnet"
schedule:
interval: daily
open-pull-requests-limit: 10
31 changes: 31 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Build and test ToDoItem sample API

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
paths:
- 'sample-api/api/**'

jobs:
build:

defaults:
run:
working-directory: sample-api/api

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build --verbosity normal
6 changes: 6 additions & 0 deletions sample-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Kiota ToDoItem Sample API and clients

This folder contains multiple projects that work together.

- An [ASP.NET Core Web API](./api) that you can use to generate Kiota SDKs and test against.
- A [.NET client](./clients/dotnet/) that calls the sample API via a Kiota-generated SDK.
2 changes: 2 additions & 0 deletions sample-api/api/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
3 changes: 3 additions & 0 deletions sample-api/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bin/
obj/
appsettings.Development.json*
30 changes: 30 additions & 0 deletions sample-api/api/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"version": "0.2.0",
"configurations": [
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/src/bin/Debug/net6.0/ToDoApi.dll",
"args": [],
"cwd": "${workspaceFolder}/src",
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",
"type": "coreclr",
"request": "attach"
}
]
}
7 changes: 7 additions & 0 deletions sample-api/api/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"cSpell.words": [
"Usings",
"xunit"
],
"dotnet-test-explorer.testProjectPath": "**/*.sln"
}
42 changes: 42 additions & 0 deletions sample-api/api/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/src/ToDoApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/src/ToDoApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/src/ToDoApi.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}
92 changes: 92 additions & 0 deletions sample-api/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Kiota ToDoItem Sample API

[![Build](https://github.com/microsoft/kiota-samples/actions/workflows/dotnet.yml/badge.svg)](https://github.com/microsoft/kiota-samples/actions/workflows/dotnet.yml)

This sample implements a basic OData Web API. The OpenAPI description generated by the sample can be used with [Kiota](https://github.com/microsoft/kiota) to generate client-side SDKs.

## Prerequisites

- [.NET SDK](https://dotnet.microsoft.com/download) version 6+
- An Azure account with an active subscription. Create an [Azure account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F) or [join the Microsoft 365 Developer Program](https://developer.microsoft.com/office/dev-program) to get a free instant sandbox for testing.
- The Azure account must have permission to manage applications in Azure Active Directory (Azure AD). Any of the following Azure AD roles include the required permissions:
- Application administrator
- Application developer
- Cloud application administrator
- Global administrator

## Configure the sample

The sample uses [Azure AD to protect the Web API](https://docs.microsoft.com/azure/active-directory/develop/scenario-protected-web-api-overview). You'll need to [register an application in Azure AD](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) and add the app registration to the sample. You can install the [Microsoft Graph PowerShell SDK](https://github.com/microsoftgraph/msgraph-sdk-powershell) and use the included [PowerShell script](RegisterAPI.ps1) to automate the process, or register the app manually.

### Use PowerShell

**Note:** The PowerShell script requires an account with the Application administrator, Cloud application administrator, or Global administrator role. If your account has the Application developer role, you can [register manually](#register-manually).

Open PowerShell in the root of this project and run the following command.

```powershell
.\RegisterAPI.ps1
```

This will open a browser window to the Microsoft identity platform's sign-in page. Sign in with your Azure account, review the requested permissions, and consent to the application. This creates an app registration named `Kiota ToDoItem Sample API` and save the needed configuration in `./src/appsettings.Development.json`. It also outputs the API scope client application will need to access the API.

The script also accepts optional parameters to change how it behaves.

- `-AppName <string>`: Set the name of the app registration
- `-TenantId <string>`: Specify the tenant ID of the Azure subscription to create the app registration in.
- `-StayConnected`: The script will leave the Microsoft Graph PowerShell SDK's session open rather than disconnect when it is finished.

### Register manually

1. Open a browser and navigate to the [Azure Active Directory admin center](https://aad.portal.azure.com). Login with your Azure account.
1. Select **Azure Active Directory** in the left-hand navigation, then select **App registrations** under **Manage**.
1. Select **New registration**. On the **Register an application** page, set the values as follows.

- Set **Name** to `Kiota ToDoItem Sample API`.
- Set **Supported account types** to **Accounts in this organizational directory only**.
- Leave **Redirect URI** blank.

1. Select **Register**. On the **Kiota ToDoItem Sample API** page, copy the values of the **Application (client) ID** and **Directory (tenant) ID and save them, you will need to add them to the sample once you are done configuring the app registration.

1. Select **Expose an API** under **Manage**, then choose **Add a scope**.
1. Accept the default **Application ID URI** and choose **Save and continue**.
1. Fill in the **Add a scope** form as follows:

- **Scope name:** `ToDoItem.ReadWrite`
- **Who can consent?:** Admins and users
- **Admin consent display name:** `Full access to users' ToDoItems`
- **Admin consent description:** `Allows the app to read and write users' ToDoItems`
- **User consent display name:** `Full access to your ToDoItems`
- **User consent description:** `Allows the app to read and write your ToDoItems`
- **State:** Enabled

1. Select **Add scope**.
1. Copy the new scope, you'll need it to configure client applications.
1. Create a new file in the [./src](/src) directory named **appsettings.Development.json** and add the following contents. Replace `YOUR_CLIENT_ID` with the client ID from your app registration, and replace `YOUR_TENANT_ID` with your tenant ID.

```json
{
"AzureAd": {
"ClientId": "YOUR_CLIENT_ID",
"TenantId": "YOUR_TENANT_ID"
}
}
```

## Run the sample

Start the sample with the `dotnet run` command in the ./src directory. Alternatively, you can open this project folder with [Visual Studio Code](https://code.visualstudio.com/Download) or open **kiota-sample-api.sln** with [Visual Studio](https://visualstudio.microsoft.com/downloads/) and debug the code by pressing **F5**.

Once the application has started, open a browser and navigate to `https://localhost:7206/openapi/openapi.yaml` to view the OpenAPI description for the sample API.

## Calling the API

To call this API, you'll need a client capable of [getting access tokens from the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/active-directory-v2-protocols) and making HTTP requests. You can get started writing your own client by generating an SDK for the sample API with [Kiota](https://github.com/microsoft/kiota).

With the sample running, you can use `https://localhost:7206/openapi/openapi.yaml` as the value for the `--openapi` parameter to generate an SDK.

```powershell
kiota --openapi https://localhost:7206/openapi/openapi.yaml ...
```

See [Using the Kiota tool](https://microsoft.github.io/kiota/using) for a full listing of available parameters.
99 changes: 99 additions & 0 deletions sample-api/api/RegisterAPI.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
param(
[Parameter(Mandatory=$false,
HelpMessage="The friendly name of the app registration")]
[String]
$AppName = "Kiota ToDoItem Sample API",

[Parameter(Mandatory=$false,
HelpMessage="Your Azure Active Directory tenant ID")]
[String]
$TenantId,

[Parameter(Mandatory=$false)]
[Switch]
$StayConnected = $false
)

# API Scope descriptions
$AdminConsentDisplayName = "Full access to users' ToDoItems"
$AdminConsentDescription = "Allows the app to read and write users' ToDoItems"
$UserConsentDisplayName = "Full access to your ToDoItems"
$UserConsentDescription = "Allows the app to read and write your ToDoItems"
$ScopeName = "ToDoItem.ReadWrite"
$AppSettingsFile = "appsettings.Development.json"
$AppSettingsPath = "./src/" + $AppSettingsFile

# Requires an admin
if ($TenantId)
{
Connect-MgGraph -Scopes "Application.ReadWrite.All User.Read" -TenantId $TenantId -ErrorAction Stop
}
else
{
Connect-MgGraph -Scopes "Application.ReadWrite.All User.Read" -ErrorAction Stop
}

# Get context for access to tenant ID
$context = Get-MgContext -ErrorAction Stop

$scopeId = New-Guid
# Create app registration
$appRegistration = New-MgApplication -DisplayName $AppName -SignInAudience "AzureADMyOrg" -ErrorAction Stop
Write-Host -ForegroundColor Cyan "App registration created with app ID" $appRegistration.AppId

# Update the app registration to expose an API
Update-MgApplication -ApplicationId $appRegistration.Id -IdentifierUris @("api://" + $appRegistration.AppId) `
-Api @{ Oauth2PermissionScopes=@(@{ Id=$scopeId; AdminConsentDescription=$AdminConsentDescription; `
AdminConsentDisplayName=$AdminConsentDisplayName; UserConsentDescription=$UserConsentDescription; `
UserConsentDisplayName=$UserConsentDisplayName; Value=$ScopeName; IsEnabled=$true; Type="User" })} `
-ErrorAction SilentlyContinue -ErrorVariable UpdateError

if ($UpdateError)
{
WriteHost -ForegroundColor Red "The app registration could not be configured."
WriteHost -ForegroundColor Red $UpdateError
Exit
}
Write-Host -ForegroundColor Cyan "App registration udpated with API details" $appRegistration.AppId

# Create corresponding service principal
New-MgServicePrincipal -AppId $appRegistration.AppId | Out-Null -ErrorAction SilentlyContinue -ErrorVariable SPError
if ($SPError)
{
WriteHost -ForegroundColor Red "A service principal for the app could not be created."
WriteHost -ForegroundColor Red $SPError
Exit
}

Write-Host -ForegroundColor Cyan "Service principal created"
Write-Host
Write-Host -ForegroundColor Green "Success"
Write-Host

# Generate appsettings.Development.json
# If file already exists, back it up
if (Test-Path $AppSettingsPath)
{
$timestamp = Get-Date -UFormat %s
$newFileName = $AppSettingsFile + ".backup." + $timestamp
Rename-Item -Path $AppSettingsPath -NewName $newFileName
Write-Host -ForegroundColor Yellow "Renamed existing appsettings.Development.json to " + $newFileName
}

$appSettings = @{ AzureAd=@{ ClientId=$appRegistration.AppId; TenantId=$context.TenantId } }
$appSettings | ConvertTo-Json | Add-Content -Path $AppSettingsPath
$apiScope = "api://" + $appRegistration.AppId + "/" + $ScopeName
Write-Host -ForegroundColor Cyan "App registration details saved in " + $AppSettingsPath
Write-Host -ForegroundColor Cyan "Use the following API scope to configure your client application:"
Write-Host -ForegroundColor Yellow $apiScope

if ($StayConnected -eq $false)
{
Disconnect-MgGraph
Write-Host "Disconnected from Microsoft Graph"
}
else
{
Write-Host
Write-Host -ForegroundColor Yellow "The connection to Microsoft Graph is still active. To disconnect, use Disconnect-MgGraph"
}
28 changes: 28 additions & 0 deletions sample-api/api/kiota-sample-api.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToDoApi", "src\ToDoApi.csproj", "{1036C5C5-8BC1-4FB0-ABDF-FF80BF618317}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ToDoApi.Tests", "test\ToDoApi.Tests.csproj", "{AB77E947-8ED8-41B4-95C4-F2C0ECF4E2E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{1036C5C5-8BC1-4FB0-ABDF-FF80BF618317}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1036C5C5-8BC1-4FB0-ABDF-FF80BF618317}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1036C5C5-8BC1-4FB0-ABDF-FF80BF618317}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1036C5C5-8BC1-4FB0-ABDF-FF80BF618317}.Release|Any CPU.Build.0 = Release|Any CPU
{AB77E947-8ED8-41B4-95C4-F2C0ECF4E2E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB77E947-8ED8-41B4-95C4-F2C0ECF4E2E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB77E947-8ED8-41B4-95C4-F2C0ECF4E2E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB77E947-8ED8-41B4-95C4-F2C0ECF4E2E1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
35 changes: 35 additions & 0 deletions sample-api/api/src/Controllers/OpenApiController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Mvc;
using Microsoft.OData.ModelBuilder;
using Microsoft.OpenApi;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.OData;
using ToDoApi.Models;

namespace ToDoApi.Controllers
{
[Route("[controller]")]
public class OpenApiController : ControllerBase
{
[HttpGet("openapi.yaml")]
public ActionResult GetOpenApiDoc()
{
var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EnableLowerCamelCase();
odataBuilder.EntitySet<ToDoItem>("ToDoItems");
var model = odataBuilder.GetEdmModel();

var openApi = model.ConvertToOpenApi(new OpenApiConvertSettings
{
ServiceRoot = new Uri($"{Request.Scheme}://{Request.Host}/"),
EnableKeyAsSegment = true,
PrefixEntityTypeNameBeforeKey = true
});

var yaml = openApi.SerializeAsYaml(OpenApiSpecVersion.OpenApi3_0);
return File(System.Text.Encoding.UTF8.GetBytes(yaml), "text/yaml");
}
}
}
Loading