From 532e937c38aac475bd631af8a5cd231385f3f512 Mon Sep 17 00:00:00 2001 From: KubaZ2 Date: Wed, 17 Jun 2026 16:40:43 +0200 Subject: [PATCH 1/4] Add AWS Lambda guide --- Directory.Packages.props | 1 + .../http-events/AWSLambda/AWSLambda.csproj | 27 +++++++ .../AWSLambda/AWSLambda.csproj.diff | 9 +++ .../guides/http-events/AWSLambda/Program.cs | 43 ++++++++++++ .../guides/http-events/AWSLambda/Readme.md | 51 ++++++++++++++ .../AWSLambda/appsettings.Development.json | 8 +++ .../http-events/AWSLambda/appsettings.json | 9 +++ .../AWSLambda/aws-lambda-tools-defaults.json | 14 ++++ .../http-events/AWSLambda/serverless.template | 47 +++++++++++++ .../AWSLambda/serverless.template.diff | 32 +++++++++ .../Introduction}/HttpInteractionHandler.cs | 0 .../Introduction/Introduction.csproj} | 0 .../Introduction}/Program.cs | 0 .../Properties/launchSettings.json | 0 .../Introduction}/appsettings.json | 0 .../guides/http-events/aws-lambda.md | 70 +++++++++++++++++++ .../introduction.md} | 6 +- Documentation/guides/toc.yml | 8 ++- NetCord.slnx | 5 +- 19 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 Documentation/guides/http-events/AWSLambda/AWSLambda.csproj create mode 100644 Documentation/guides/http-events/AWSLambda/AWSLambda.csproj.diff create mode 100644 Documentation/guides/http-events/AWSLambda/Program.cs create mode 100644 Documentation/guides/http-events/AWSLambda/Readme.md create mode 100644 Documentation/guides/http-events/AWSLambda/appsettings.Development.json create mode 100644 Documentation/guides/http-events/AWSLambda/appsettings.json create mode 100644 Documentation/guides/http-events/AWSLambda/aws-lambda-tools-defaults.json create mode 100644 Documentation/guides/http-events/AWSLambda/serverless.template create mode 100644 Documentation/guides/http-events/AWSLambda/serverless.template.diff rename Documentation/guides/{basic-concepts/HttpInteractions => http-events/Introduction}/HttpInteractionHandler.cs (100%) rename Documentation/guides/{basic-concepts/HttpInteractions/HttpInteractions.csproj => http-events/Introduction/Introduction.csproj} (100%) rename Documentation/guides/{basic-concepts/HttpInteractions => http-events/Introduction}/Program.cs (100%) rename Documentation/guides/{basic-concepts/HttpInteractions => http-events/Introduction}/Properties/launchSettings.json (100%) rename Documentation/guides/{basic-concepts/HttpInteractions => http-events/Introduction}/appsettings.json (100%) create mode 100644 Documentation/guides/http-events/aws-lambda.md rename Documentation/guides/{basic-concepts/http-interactions.md => http-events/introduction.md} (94%) diff --git a/Directory.Packages.props b/Directory.Packages.props index 794b74bd..3da36691 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,6 +23,7 @@ + diff --git a/Documentation/guides/http-events/AWSLambda/AWSLambda.csproj b/Documentation/guides/http-events/AWSLambda/AWSLambda.csproj new file mode 100644 index 00000000..f1a9b9e9 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/AWSLambda.csproj @@ -0,0 +1,27 @@ + + + net10.0 + enable + enable + true + Lambda + + + true + + + + + true + + + + + + + + + + + + diff --git a/Documentation/guides/http-events/AWSLambda/AWSLambda.csproj.diff b/Documentation/guides/http-events/AWSLambda/AWSLambda.csproj.diff new file mode 100644 index 00000000..8318b367 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/AWSLambda.csproj.diff @@ -0,0 +1,9 @@ + + + ... ++ true +- +- true + + ... + diff --git a/Documentation/guides/http-events/AWSLambda/Program.cs b/Documentation/guides/http-events/AWSLambda/Program.cs new file mode 100644 index 00000000..2a329b28 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/Program.cs @@ -0,0 +1,43 @@ +using NetCord.Hosting.Rest; +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Services.ApplicationCommands; + +using Amazon.Lambda.Serialization.SystemTextJson; +using Amazon.Lambda.APIGatewayEvents; + +using System.Text.Json.Serialization; + +var registerCommands = args.Contains("--register-commands"); + +var builder = WebApplication.CreateSlimBuilder(args); + +var services = builder.Services; + +services + .AddDiscordRest() + .AddHttpApplicationCommands(o => o.AutoRegisterCommands = registerCommands) + .AddAWSLambdaHosting(LambdaEventSource.HttpApi, + // That is only required when using Native AOT, + // otherwise that parameter can be omitted + new SourceGeneratorLambdaJsonSerializer()); + +var app = builder.Build(); + +app.AddSlashCommand("ping", "Ping AWS Lambda", () => "Pong from AWS Lambda!"); + +if (registerCommands) +{ + await app.StartAsync(); + await app.StopAsync(); + return; +} + +app.UseHttpInteractions("/"); + +await app.RunAsync(); + +// That is passed to AddAWSLambdaHosting for Native AOT, +// otherwise that class can be omitted +[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] +[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] +public partial class APIGatewaySerializerContext : JsonSerializerContext; diff --git a/Documentation/guides/http-events/AWSLambda/Readme.md b/Documentation/guides/http-events/AWSLambda/Readme.md new file mode 100644 index 00000000..7d69335b --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/Readme.md @@ -0,0 +1,51 @@ +# ASP.NET Core Minimal API Serverless Application + +This project shows how to run an ASP.NET Core Web API project as an AWS Lambda exposed through Amazon API Gateway. The NuGet package [Amazon.Lambda.AspNetCoreServer](https://www.nuget.org/packages/Amazon.Lambda.AspNetCoreServer) contains a Lambda function that is used to translate requests from API Gateway into the ASP.NET Core framework and then the responses from ASP.NET Core back to API Gateway. + + +For more information about how the Amazon.Lambda.AspNetCoreServer package works and how to extend its behavior view its [README](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.AspNetCoreServer/README.md) file in GitHub. + +## Executable Assembly ## + +.NET Lambda projects that use C# top level statements like this project must be deployed as an executable assembly instead of a class library. To indicate to Lambda that the .NET function is an executable assembly the +Lambda function handler value is set to the .NET Assembly name. This is different then deploying as a class library where the function handler string includes the assembly, type and method name. + +To deploy as an executable assembly the Lambda runtime client must be started to listen for incoming events to process. For an ASP.NET Core application the Lambda runtime client is started by included the +`Amazon.Lambda.AspNetCoreServer.Hosting` NuGet package and calling `AddAWSLambdaHosting(LambdaEventSource.HttpApi)` passing in the event source while configuring the services of the application. The +event source can be API Gateway REST API and HTTP API or Application Load Balancer. + +### Project Files ### + +* serverless.template - an AWS CloudFormation Serverless Application Model template file for declaring your Serverless functions and other AWS resources +* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS +* Program.cs - entry point to the application that contains all of the top level statements initializing the ASP.NET Core application. +The call to `AddAWSLambdaHosting` configures the application to work in Lambda when it detects Lambda is the executing environment. +* Controllers\CalculatorController - example Web API controller + +You may also have a test project depending on the options selected. + +## Here are some steps to follow from Visual Studio: + +To deploy your Serverless application, right click the project in Solution Explorer and select *Publish to AWS Lambda*. + +To view your deployed application open the Stack View window by double-clicking the stack name shown beneath the AWS CloudFormation node in the AWS Explorer tree. The Stack View also displays the root URL to your published application. + +## Here are some steps to follow to get started from the command line: + +Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. + +Install Amazon.Lambda.Tools Global Tools if not already installed. +``` + dotnet tool install -g Amazon.Lambda.Tools +``` + +If already installed check if new version is available. +``` + dotnet tool update -g Amazon.Lambda.Tools +``` + +Deploy application +``` + cd "AWSLambda/src/AWSLambda" + dotnet lambda deploy-serverless +``` diff --git a/Documentation/guides/http-events/AWSLambda/appsettings.Development.json b/Documentation/guides/http-events/AWSLambda/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda/appsettings.json b/Documentation/guides/http-events/AWSLambda/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Documentation/guides/http-events/AWSLambda/aws-lambda-tools-defaults.json b/Documentation/guides/http-events/AWSLambda/aws-lambda-tools-defaults.json new file mode 100644 index 00000000..38996c34 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/aws-lambda-tools-defaults.json @@ -0,0 +1,14 @@ +{ + "Information": [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + "dotnet lambda help", + "All the command line options for the Lambda command can be specified in this file." + ], + "profile": "", + "region": "", + "configuration": "Release", + "s3-prefix": "AWSLambda/", + "template": "serverless.template", + "template-parameters": "" +} diff --git a/Documentation/guides/http-events/AWSLambda/serverless.template b/Documentation/guides/http-events/AWSLambda/serverless.template new file mode 100644 index 00000000..ebf03734 --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/serverless.template @@ -0,0 +1,47 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.", + "Parameters": {}, + "Conditions": {}, + "Resources": { + "AspNetCoreFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "MyBot", + "Runtime": "dotnet10", + "CodeUri": "", + "MemorySize": 512, + "Timeout": 30, + "Role": null, + "Policies": [ + "AWSLambda_FullAccess" + ], + "Events": { + "ProxyResource": { + "Type": "HttpApi", + "Properties": { + "Path": "/{proxy+}", + "Method": "ANY" + } + }, + "RootResource": { + "Type": "HttpApi", + "Properties": { + "Path": "/", + "Method": "ANY" + } + } + } + } + } + }, + "Outputs": { + "ApiURL": { + "Description": "API endpoint URL for Prod environment", + "Value": { + "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" + } + } + } +} diff --git a/Documentation/guides/http-events/AWSLambda/serverless.template.diff b/Documentation/guides/http-events/AWSLambda/serverless.template.diff new file mode 100644 index 00000000..af9a37ed --- /dev/null +++ b/Documentation/guides/http-events/AWSLambda/serverless.template.diff @@ -0,0 +1,32 @@ +{ + ... + "Resources": { + "AspNetCoreFunction": { + ... + "Properties": { + ... + "Events": { + "ProxyResource": { ++ "Type": "HttpApi", +- "Type": "Api", + ... + }, + "RootResource": { ++ "Type": "HttpApi", +- "Type": "Api", + ... + } + } + } + } + }, + "Outputs": { + "ApiURL": { + ... + "Value": { ++ "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/" +- "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" + } + } + } +} diff --git a/Documentation/guides/basic-concepts/HttpInteractions/HttpInteractionHandler.cs b/Documentation/guides/http-events/Introduction/HttpInteractionHandler.cs similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/HttpInteractionHandler.cs rename to Documentation/guides/http-events/Introduction/HttpInteractionHandler.cs diff --git a/Documentation/guides/basic-concepts/HttpInteractions/HttpInteractions.csproj b/Documentation/guides/http-events/Introduction/Introduction.csproj similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/HttpInteractions.csproj rename to Documentation/guides/http-events/Introduction/Introduction.csproj diff --git a/Documentation/guides/basic-concepts/HttpInteractions/Program.cs b/Documentation/guides/http-events/Introduction/Program.cs similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/Program.cs rename to Documentation/guides/http-events/Introduction/Program.cs diff --git a/Documentation/guides/basic-concepts/HttpInteractions/Properties/launchSettings.json b/Documentation/guides/http-events/Introduction/Properties/launchSettings.json similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/Properties/launchSettings.json rename to Documentation/guides/http-events/Introduction/Properties/launchSettings.json diff --git a/Documentation/guides/basic-concepts/HttpInteractions/appsettings.json b/Documentation/guides/http-events/Introduction/appsettings.json similarity index 100% rename from Documentation/guides/basic-concepts/HttpInteractions/appsettings.json rename to Documentation/guides/http-events/Introduction/appsettings.json diff --git a/Documentation/guides/http-events/aws-lambda.md b/Documentation/guides/http-events/aws-lambda.md new file mode 100644 index 00000000..e31cb42c --- /dev/null +++ b/Documentation/guides/http-events/aws-lambda.md @@ -0,0 +1,70 @@ +# Running Serverless C# Discord Bots on AWS Lambda + +Deploying HTTP-based C# Discord bots on AWS Lambda is an excellent way to leverage a serverless architecture. AWS Lambda allows you to run your code without provisioning or managing underlying servers, making it a highly scalable and cost-effective hosting choice for your bot. + +This guide will walk you through the steps to deploy your C# Discord bot to AWS Lambda. It will also cover how to enable Native AOT to drastically reduce your bot's cold start times. + +> [!NOTE] +> This guide assumes you have a basic understanding of AWS Lambda. It specifically uses the AWS Lambda ASP.NET Core integration for seamless setup. See [Deploy ASP.NET applications](https://docs.aws.amazon.com/lambda/latest/dg/csharp-package-asp.html) for more information. + +## 1. Project Setup and Optimization + +To get started, create a new project using the `serverless.AspNetCoreMinimalAPI` template. If you aren't familiar with this template, refer to the AWS documentation linked in the note above. + +Once generated, we need to clean up and optimize the configuration. + +### Upgrading to the HTTP API +By default, the template configures a REST API. We can optimize costs and performance by switching to the newer HTTP API. Make the following changes to your `serverless.template` file: + +[!code-diff[serverless.template](AWSLambda/serverless.template.diff)] + +### Removing Unnecessary Files +Remove the `Controllers` directory with its contents as they are not needed. + +### Enabling Native AOT +To reduce your bot's cold start times in a serverless environment, we highly recommend enabling Native AOT compilation. Update your project file as follows: + +[!code-diff[AWSLambda.csproj](AWSLambda/AWSLambda.csproj.diff)] + +### Adding Required Dependencies +Next, add the necessary NuGet packages to power the bot and handle cryptographic operations: +* [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) +* [libsodium](https://www.nuget.org/packages/libsodium) + +## 2. Writing the Bot Application + +Now it's time to write the code. Update your `Program.cs` file to match the implementation below. This sets up a simple HTTP interaction bot featuring a basic `/ping` command. + +[!code-cs[Program.cs](AWSLambda/Program.cs)] + +Notice the inclusion of the `--register-commands` flag. In a serverless environment like AWS Lambda, your application starts and stops frequently. Registering commands on every boot wastes resources and slows down startup times. + +When deploying, run your bot locally to register the commands (e.g., `dotnet run -- --register-commands`). Note that registering commands requires the bot token, see [Configuring Secrets](#4-configuring-secrets). + +You can also use the `registerCommands` variable to load certain services specifically when the bot is running in AWS Lambda. + +## 3. Deploying to AWS Lambda + +With your code ready, you can deploy the bot. Use the following .NET CLI command to package your application and publish it directly to AWS Lambda: + +```bash +dotnet lambda deploy-serverless +``` + +Once the deployment completes, the AWS endpoint URL will be printed in your console. + +## 4. Configuring Secrets + +Before your bot can receive and verify interactions from Discord at your new endpoint, you must add your bot's Public Key to the Lambda function. We will use environment variables for this purpose. + +1. In the AWS Management Console, navigate to your Lambda function. +2. Click on **Configuration**, then select **Environment variables**. +3. Add a new variable named `Discord__PublicKey` (note the double underscore). +4. Paste your bot's Public Key, which can be found in the Discord Developer Portal. + +If you are building a more complex bot that requires authenticated @NetCord.Rest.RestClient usage, you will also need to provide your bot token. To do so, add an environment variable named `Discord__Token` in the same way. + +The bot token is also required to register commands locally using the `--register-commands` flag, so ensure it gets provided. + +> [!NOTE] +> If you want maximum security for your sensitive credentials, consider using AWS Secrets Manager instead. See [AWS .NET Configuration Extension for Systems Manager](https://github.com/aws/aws-dotnet-extensions-configuration) for more information. diff --git a/Documentation/guides/basic-concepts/http-interactions.md b/Documentation/guides/http-events/introduction.md similarity index 94% rename from Documentation/guides/basic-concepts/http-interactions.md rename to Documentation/guides/http-events/introduction.md index f20752f6..32bf2da9 100644 --- a/Documentation/guides/basic-concepts/http-interactions.md +++ b/Documentation/guides/http-events/introduction.md @@ -15,13 +15,13 @@ Before you get started, ensure that you've installed the necessary native depend ## Setting Up HTTP Interactions in C# To handle HTTP interactions from Discord in your bot, you need to use @NetCord.Hosting.Rest.RestClientServiceCollectionExtensions.AddDiscordRest* to add the @NetCord.Rest.RestClient and then call @NetCord.Hosting.AspNetCore.EndpointRouteBuilderExtensions.UseHttpInteractions* to map the HTTP interactions route. You can also use @NetCord.Hosting.Services.ApplicationCommands.ApplicationCommandServiceServiceCollectionExtensions.AddHttpApplicationCommands* to add the application command service with preconfigured HTTP contexts to your host builder. -[!code-cs[Program.cs](HttpInteractions/Program.cs?highlight=8,16)] +[!code-cs[Program.cs](Introduction/Program.cs?highlight=8,16)] ### Receiving HTTP Interactions via HTTP Interaction Handler You can also create your own @NetCord.Hosting.IHttpInteractionHandler to handle HTTP interactions manually. This allows you to have full control over the behavior of your bot when receiving HTTP interactions. You register them using @"NetCord.Hosting.HttpInteractionHandlerServiceCollectionExtensions.AddHttpInteractionHandler``1(Microsoft.Extensions.DependencyInjection.IServiceCollection)?text=AddHttpInteractionHandler". -[!code-cs[HttpInteractionHandler.cs](HttpInteractions/HttpInteractionHandler.cs#L6-L14)] +[!code-cs[HttpInteractionHandler.cs](Introduction/HttpInteractionHandler.cs#L6-L14)] ## Configuring Your Discord Bot for HTTP Interactions @@ -32,7 +32,7 @@ To make your bot receive HTTP interactions from Discord, you need to store the p ### Specifying the Public Key in the Configuration You can for example use `appsettings.json` file for configuration. It should look like this: -[!code-json[appsettings.json](HttpInteractions/appsettings.json?highlight=4)] +[!code-json[appsettings.json](Introduction/appsettings.json?highlight=4)] ### Specifying the Interactions Endpoint URL diff --git a/Documentation/guides/toc.yml b/Documentation/guides/toc.yml index 696fba77..3a82827c 100644 --- a/Documentation/guides/toc.yml +++ b/Documentation/guides/toc.yml @@ -16,12 +16,16 @@ href: basic-concepts/responding-to-interactions.md - name: Sending Messages href: basic-concepts/sending-messages.md - - name: HTTP Interactions - href: basic-concepts/http-interactions.md - name: Sharding href: basic-concepts/sharding.md - name: Voice href: basic-concepts/voice.md +- name: HTTP Events + items: + - name: Introduction + href: http-events/introduction.md + - name: AWS Lambda + href: http-events/aws-lambda.md - name: Services items: - name: Introduction diff --git a/NetCord.slnx b/NetCord.slnx index 23417afd..8eb0dbe4 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -6,13 +6,16 @@ - + + + + From 5024fbb363278164be35ba4a810250d183cbe776 Mon Sep 17 00:00:00 2001 From: KubaZ2 Date: Mon, 22 Jun 2026 13:09:18 +0200 Subject: [PATCH 2/4] Add Azure Functions guide --- Directory.Packages.props | 12 +- .../http-events/AzureFunction/.gitignore | 264 ++++++++++++++++++ .../AzureFunction/.vscode/extensions.json | 5 + .../AzureFunction/AzureFunction.csproj | 24 ++ .../http-events/AzureFunction/Program.cs | 38 +++ .../Properties/launchSettings.json | 9 + .../http-events/AzureFunction/host.json | 4 + .../guides/http-events/aws-lambda.md | 3 +- .../guides/http-events/azure-function.md | 62 ++++ Documentation/guides/toc.yml | 2 + NetCord.slnx | 1 + 11 files changed, 418 insertions(+), 6 deletions(-) create mode 100644 Documentation/guides/http-events/AzureFunction/.gitignore create mode 100644 Documentation/guides/http-events/AzureFunction/.vscode/extensions.json create mode 100644 Documentation/guides/http-events/AzureFunction/AzureFunction.csproj create mode 100644 Documentation/guides/http-events/AzureFunction/Program.cs create mode 100644 Documentation/guides/http-events/AzureFunction/Properties/launchSettings.json create mode 100644 Documentation/guides/http-events/AzureFunction/host.json create mode 100644 Documentation/guides/http-events/azure-function.md diff --git a/Directory.Packages.props b/Directory.Packages.props index 29d17193..4397210b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,13 +1,15 @@ - true true - + + + + @@ -24,6 +26,8 @@ + + + - - + \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction/.gitignore b/Documentation/guides/http-events/AzureFunction/.gitignore new file mode 100644 index 00000000..ff5b00c5 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/.gitignore @@ -0,0 +1,264 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# Azure Functions localsettings file +local.settings.json + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction/.vscode/extensions.json b/Documentation/guides/http-events/AzureFunction/.vscode/extensions.json new file mode 100644 index 00000000..dde673dc --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions" + ] +} \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction/AzureFunction.csproj b/Documentation/guides/http-events/AzureFunction/AzureFunction.csproj new file mode 100644 index 00000000..8c8c48da --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/AzureFunction.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + + diff --git a/Documentation/guides/http-events/AzureFunction/Program.cs b/Documentation/guides/http-events/AzureFunction/Program.cs new file mode 100644 index 00000000..0632198c --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/Program.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Builder; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using NetCord.Hosting.AspNetCore; +using NetCord.Hosting.Rest; +using NetCord.Hosting.Services.ApplicationCommands; + +var registerCommands = Environment.GetEnvironmentVariable("REGISTER_COMMANDS") is "1"; + +var builder = FunctionsApplication.CreateBuilder(args); + +builder.Logging.AddConsole(); + +builder.ConfigureFunctionsWebApplication(); + +var services = builder.Services; + +services.AddDiscordRest() + .AddHttpApplicationCommands(o => o.AutoRegisterCommands = registerCommands) + .AddHttpInteractionProcessor(); + +var host = builder.Build(); + +host.AddSlashCommand("ping", "Ping!", () => "Pong from Azure Function!"); + +await host.RunAsync(); + +public class Interaction(IHttpInteractionProcessor processor) +{ + [Function("interaction")] + public Task RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest request) + { + return processor.ProcessAsync(request.HttpContext); + } +} diff --git a/Documentation/guides/http-events/AzureFunction/Properties/launchSettings.json b/Documentation/guides/http-events/AzureFunction/Properties/launchSettings.json new file mode 100644 index 00000000..5823e7ef --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "AzureFunction": { + "commandName": "Project", + "commandLineArgs": "--port 7078", + "launchBrowser": false + } + } +} \ No newline at end of file diff --git a/Documentation/guides/http-events/AzureFunction/host.json b/Documentation/guides/http-events/AzureFunction/host.json new file mode 100644 index 00000000..bb804e04 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/host.json @@ -0,0 +1,4 @@ +{ + "version": "2.0", + "telemetryMode": "OpenTelemetry" +} diff --git a/Documentation/guides/http-events/aws-lambda.md b/Documentation/guides/http-events/aws-lambda.md index e31cb42c..8c5a53ff 100644 --- a/Documentation/guides/http-events/aws-lambda.md +++ b/Documentation/guides/http-events/aws-lambda.md @@ -59,8 +59,7 @@ Before your bot can receive and verify interactions from Discord at your new end 1. In the AWS Management Console, navigate to your Lambda function. 2. Click on **Configuration**, then select **Environment variables**. -3. Add a new variable named `Discord__PublicKey` (note the double underscore). -4. Paste your bot's Public Key, which can be found in the Discord Developer Portal. +3. Add a new environment variable with the key `Discord__PublicKey` and set its value to your bot's Public Key from the Discord Developer Portal. If you are building a more complex bot that requires authenticated @NetCord.Rest.RestClient usage, you will also need to provide your bot token. To do so, add an environment variable named `Discord__Token` in the same way. diff --git a/Documentation/guides/http-events/azure-function.md b/Documentation/guides/http-events/azure-function.md new file mode 100644 index 00000000..61832fc2 --- /dev/null +++ b/Documentation/guides/http-events/azure-function.md @@ -0,0 +1,62 @@ +# Running Serverless C# Discord Bots on Azure Functions + +Deploying HTTP-based C# Discord bots on Azure Functions is an excellent way to leverage a serverless architecture. Azure Functions allows you to run your code without provisioning or managing underlying servers, making it a highly scalable and cost-effective hosting choice for your bot. + +This guide will walk you through the steps to deploy your C# Discord bot to Azure Functions. + +> [!NOTE] +> This guide assumes you have a basic understanding of Azure Functions. It specifically uses the Azure Functions in the isolated worker model with ASP.NET Core integration. See [Guide for running C# Azure Functions in the isolated worker model](https://learn.microsoft.com/azure/azure-functions/dotnet-isolated-process-guide) for more information. + +## 1. Project Setup and Optimization + +To get started, create a new project using the command below. This will set up an Azure Functions project using the isolated worker model with .NET 10.0 as the target framework. + +```bash +func init --worker-runtime dotnet-isolated --target-framework net10.0 +``` + +### Adding Required Dependencies +Next, add the necessary NuGet packages to power the bot and handle cryptographic operations: +* [NetCord.Hosting.AspNetCore](https://www.nuget.org/packages/NetCord.Hosting.AspNetCore) +* [libsodium](https://www.nuget.org/packages/libsodium) + +## 2. Writing the Bot Application + +Now it's time to write the code. Update your `Program.cs` file to match the implementation below. This sets up a simple HTTP interaction bot featuring a basic `/ping` command. + +[!code-cs[Program.cs](AzureFunction/Program.cs)] + +Notice the inclusion of the `REGISTER_COMMANDS` environment variable. In a serverless environment like Azure Functions, your application starts and stops frequently. Registering commands on every boot wastes resources and slows down startup times. + +When deploying, run your bot locally (e.g., `dotnet run`) with the `REGISTER_COMMADNS` environment variable set to `1` to register the commands. You can pass that environment variable via `local.settings.json`: + +[!code-json[local.settings.json](AzureFunction/local.settings.json)] + +Note that registering commands requires the bot token, see [Configuring Secrets](#4-configuring-secrets). + +You can also use the `registerCommands` variable to load certain services specifically when the bot is running in Azure Functions. + +## 3. Deploying to Azure Functions + +With your code ready, you can deploy the bot. Use the following Azure CLI command to package your application and publish it directly to Azure Functions: + +```bash +func azure functionapp publish +``` + +Once the deployment completes, the Azure endpoint URL will be printed in your console. + +## 4. Configuring Secrets + +Before your bot can receive and verify interactions from Discord at your new endpoint, you must add your bot's Public Key to the Azure Function. We will use environment variables for this purpose. + +1. In the Azure Portal, navigate to your Function App. +2. Expand the "Settings" section and click on "Environment variables". +3. Add a new environment variable with the name `Discord__PublicKey` and set its value to your bot's Public Key from the Discord Developer Portal. + +If you are building a more complex bot that requires authenticated @NetCord.Rest.RestClient usage, you will also need to provide your bot token. To do so, add an environment variable named `Discord__Token` in the same way. + +The bot token is also required to register commands locally using the `REGISTER_COMMANDS` environment variable, so ensure it gets provided. + +> [!NOTE] +> If you want maximum security for your sensitive credentials, consider using Azure Key Vault instead. See [Use Azure Key Vault configuration provider in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-10.0) for more information. diff --git a/Documentation/guides/toc.yml b/Documentation/guides/toc.yml index 3a82827c..9a2b9d6d 100644 --- a/Documentation/guides/toc.yml +++ b/Documentation/guides/toc.yml @@ -26,6 +26,8 @@ href: http-events/introduction.md - name: AWS Lambda href: http-events/aws-lambda.md + - name: Azure Function + href: http-events/azure-function.md - name: Services items: - name: Introduction diff --git a/NetCord.slnx b/NetCord.slnx index 8eb0dbe4..67e61a3a 100644 --- a/NetCord.slnx +++ b/NetCord.slnx @@ -15,6 +15,7 @@ + From d1a72aade6cdcce96dd2324fcdd8cdfeaec88897 Mon Sep 17 00:00:00 2001 From: KubaZ2 Date: Tue, 23 Jun 2026 13:10:29 +0200 Subject: [PATCH 3/4] Add local.settings.json --- Documentation/guides/http-events/AzureFunction/.gitignore | 5 +++-- .../guides/http-events/AzureFunction/local.settings.json | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Documentation/guides/http-events/AzureFunction/local.settings.json diff --git a/Documentation/guides/http-events/AzureFunction/.gitignore b/Documentation/guides/http-events/AzureFunction/.gitignore index ff5b00c5..33579ca6 100644 --- a/Documentation/guides/http-events/AzureFunction/.gitignore +++ b/Documentation/guides/http-events/AzureFunction/.gitignore @@ -1,8 +1,9 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. +# Commented out because it is used by a guide to show a sample local.settings.json file # Azure Functions localsettings file -local.settings.json +# local.settings.json # User-specific files *.suo @@ -261,4 +262,4 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc diff --git a/Documentation/guides/http-events/AzureFunction/local.settings.json b/Documentation/guides/http-events/AzureFunction/local.settings.json new file mode 100644 index 00000000..c3912a12 --- /dev/null +++ b/Documentation/guides/http-events/AzureFunction/local.settings.json @@ -0,0 +1,8 @@ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "REGISTER_COMMANDS": "true" + } +} From 2d0e42c2bd44a306c9ae63b399c1b467b86cc686 Mon Sep 17 00:00:00 2001 From: KubaZ2 Date: Tue, 23 Jun 2026 13:12:24 +0200 Subject: [PATCH 4/4] Fix REGISTER_COMMANDS value --- .../guides/http-events/AzureFunction/local.settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/guides/http-events/AzureFunction/local.settings.json b/Documentation/guides/http-events/AzureFunction/local.settings.json index c3912a12..cc7908ba 100644 --- a/Documentation/guides/http-events/AzureFunction/local.settings.json +++ b/Documentation/guides/http-events/AzureFunction/local.settings.json @@ -3,6 +3,6 @@ "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", - "REGISTER_COMMANDS": "true" + "REGISTER_COMMANDS": "1" } }