From bfe3a67667fa9a6109b045cb1d7118b1d46acd75 Mon Sep 17 00:00:00 2001 From: Shantanu Negi Date: Tue, 10 Mar 2026 15:10:42 +1100 Subject: [PATCH 1/3] azure functions gateway related work. --- ApiGateway.sln | 14 + .../.gitignore | 264 ++++++++++++++ .../ApiGatewayConfigService.cs | 61 ++++ .../ApiGatewayLog.cs | 6 + .../ApiGatewayOptions.cs | 13 + .../ApiGatewayRequestProcessor.cs | 119 +++++++ .../ApiGatewayResponseCache.cs | 56 +++ .../ApiOrchestrator.cs | 135 +++++++ ...spNetCore.ApiGateway.AzureFunctions.csproj | 39 ++ .../CHANGELOG.md | 3 + .../Extensions.cs | 182 ++++++++++ .../GatewayConstants.cs | 7 + .../GatewayFunctions.cs | 188 ++++++++++ .../HttpService.cs | 17 + .../IApiOrchestrator.cs | 12 + .../IMediator.cs | 22 ++ .../Mediator.cs | 191 ++++++++++ .../MediatorHelper.cs | 7 + .../Orchestration.cs | 58 +++ .../Program.cs | 9 + .../Properties/serviceDependencies.json | 7 + .../Properties/serviceDependencies.local.json | 7 + .../README.md | 41 +++ .../host.json | 12 + .../AspNetCore.ApiGateway.Tests.csproj | 2 + .../AzureFunctionsGatewayTests.cs | 334 ++++++++++++++++++ Sample.ApiGateway.AzureFunctions/.gitignore | 264 ++++++++++++++ .../ApiOrchestration.cs | 43 +++ Sample.ApiGateway.AzureFunctions/Function1.cs | 24 ++ .../IWeatherService.cs | 12 + .../Models/AddWeatherTypeRequest.cs | 10 + .../Models/StockQuote.cs | 13 + .../Models/UpdateWeatherTypeRequest.cs | 13 + .../Models/WeatherForecast.cs | 19 + .../Models/WeatherTypeResponse.cs | 10 + Sample.ApiGateway.AzureFunctions/Program.cs | 35 ++ .../Properties/serviceDependencies.json | 7 + .../Properties/serviceDependencies.local.json | 7 + .../Sample.ApiGateway.AzureFunctions.csproj | 37 ++ Sample.ApiGateway.AzureFunctions/Startup.cs | 47 +++ .../WeatherService.cs | 47 +++ Sample.ApiGateway.AzureFunctions/host.json | 21 ++ 42 files changed, 2415 insertions(+) create mode 100644 AspNetCore.ApiGateway.AzureFunctions/.gitignore create mode 100644 AspNetCore.ApiGateway.AzureFunctions/ApiGatewayConfigService.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/ApiGatewayLog.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/ApiGatewayOptions.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/ApiGatewayRequestProcessor.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/ApiGatewayResponseCache.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/ApiOrchestrator.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/AspNetCore.ApiGateway.AzureFunctions.csproj create mode 100644 AspNetCore.ApiGateway.AzureFunctions/CHANGELOG.md create mode 100644 AspNetCore.ApiGateway.AzureFunctions/Extensions.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/GatewayConstants.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/HttpService.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/IApiOrchestrator.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/IMediator.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/Mediator.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/MediatorHelper.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/Orchestration.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/Program.cs create mode 100644 AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.json create mode 100644 AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json create mode 100644 AspNetCore.ApiGateway.AzureFunctions/README.md create mode 100644 AspNetCore.ApiGateway.AzureFunctions/host.json create mode 100644 AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs create mode 100644 Sample.ApiGateway.AzureFunctions/.gitignore create mode 100644 Sample.ApiGateway.AzureFunctions/ApiOrchestration.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Function1.cs create mode 100644 Sample.ApiGateway.AzureFunctions/IWeatherService.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Models/AddWeatherTypeRequest.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Models/StockQuote.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Models/UpdateWeatherTypeRequest.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Models/WeatherForecast.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Models/WeatherTypeResponse.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Program.cs create mode 100644 Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.json create mode 100644 Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json create mode 100644 Sample.ApiGateway.AzureFunctions/Sample.ApiGateway.AzureFunctions.csproj create mode 100644 Sample.ApiGateway.AzureFunctions/Startup.cs create mode 100644 Sample.ApiGateway.AzureFunctions/WeatherService.cs create mode 100644 Sample.ApiGateway.AzureFunctions/host.json diff --git a/ApiGateway.sln b/ApiGateway.sln index db76296..aea08b6 100644 --- a/ApiGateway.sln +++ b/ApiGateway.sln @@ -72,6 +72,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.ApiGateway.Minim EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiGateway.API.Minimal", "ApiGateway.API.Minimal\ApiGateway.API.Minimal.csproj", "{7238A13E-4DBD-4CAD-949C-FF4831F219FA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCore.ApiGateway.AzureFunctions", "AspNetCore.ApiGateway.AzureFunctions\AspNetCore.ApiGateway.AzureFunctions.csproj", "{48C710DC-EA94-4E65-A4D6-0484900A867A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.ApiGateway.AzureFunctions", "Sample.ApiGateway.AzureFunctions\Sample.ApiGateway.AzureFunctions.csproj", "{05D846A9-C9BC-4EE1-84EA-30FD52F82F04}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -118,6 +122,14 @@ Global {7238A13E-4DBD-4CAD-949C-FF4831F219FA}.Debug|Any CPU.Build.0 = Debug|Any CPU {7238A13E-4DBD-4CAD-949C-FF4831F219FA}.Release|Any CPU.ActiveCfg = Release|Any CPU {7238A13E-4DBD-4CAD-949C-FF4831F219FA}.Release|Any CPU.Build.0 = Release|Any CPU + {48C710DC-EA94-4E65-A4D6-0484900A867A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48C710DC-EA94-4E65-A4D6-0484900A867A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48C710DC-EA94-4E65-A4D6-0484900A867A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48C710DC-EA94-4E65-A4D6-0484900A867A}.Release|Any CPU.Build.0 = Release|Any CPU + {05D846A9-C9BC-4EE1-84EA-30FD52F82F04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05D846A9-C9BC-4EE1-84EA-30FD52F82F04}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05D846A9-C9BC-4EE1-84EA-30FD52F82F04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05D846A9-C9BC-4EE1-84EA-30FD52F82F04}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -133,6 +145,8 @@ Global {4EEA8028-DAB4-4A8E-B4CF-BA95A52E3028} = {09FF380F-5D8D-417E-B16A-ABB9109FE882} {275933B1-2754-4584-8D93-64A9FBBACFD3} = {09FF380F-5D8D-417E-B16A-ABB9109FE882} {7238A13E-4DBD-4CAD-949C-FF4831F219FA} = {042DBA12-AE6F-43F3-8BC4-1204678F90D2} + {48C710DC-EA94-4E65-A4D6-0484900A867A} = {09FF380F-5D8D-417E-B16A-ABB9109FE882} + {05D846A9-C9BC-4EE1-84EA-30FD52F82F04} = {042DBA12-AE6F-43F3-8BC4-1204678F90D2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D41EFF44-6827-41B7-9F6F-62057780D65C} diff --git a/AspNetCore.ApiGateway.AzureFunctions/.gitignore b/AspNetCore.ApiGateway.AzureFunctions/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/.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/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayConfigService.cs b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayConfigService.cs new file mode 100644 index 0000000..0523f20 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayConfigService.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Configuration; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class ApiSetting + { + public string Identifier { get; set; } + public string ApiKey { get; set; } + public string[] BackendAPIBaseUrls { get; set; } + public RouteSetting[] Routes { get; set; } + public RouteSetting this[string routeIdentifier] + { + get + { + return Routes.Single(r => r.Identifier == routeIdentifier); + } + } + } + + public class RouteSetting + { + public string Identifier { get; set; } + public string RouteKey { get; set; } + public GatewayVerb Verb { get; set; } + public string BackendAPIRoutePath { get; set; } + public int ResponseCachingDurationInSeconds { get; set; } = -1; + } + + public interface IApiGatewayConfigService + { + ApiSetting this[string identifier] { get; } + } + + public class ApiGatewayConfigService : IApiGatewayConfigService + { + private IEnumerable Settings { get; set; } + + public ApiGatewayConfigService(IConfiguration configuration) + { + var settings = configuration.GetSection("Settings") + .Get>(); + + this.Settings = settings; + + ApiGatewayConfigProvider.MySettings = this; + } + + public ApiSetting this[string identifier] + { + get + { + return Settings.Single(s => s.Identifier == identifier); + } + } + } + + public static class ApiGatewayConfigProvider + { + public static IApiGatewayConfigService MySettings { get; set; } + } +} \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayLog.cs b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayLog.cs new file mode 100644 index 0000000..c8cab42 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayLog.cs @@ -0,0 +1,6 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class ApiGatewayLog + { + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayOptions.cs b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayOptions.cs new file mode 100644 index 0000000..f8c9f45 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Net.Http; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class ApiGatewayOptions + { + public bool UseResponseCaching { get; set; } + public ApiGatewayResponseCacheSettings ResponseCacheSettings { get; set; } + public Action DefaultHttpClientConfigure { get; set; } + public Func DefaultMyHttpClientHandler { get; set; } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayRequestProcessor.cs b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayRequestProcessor.cs new file mode 100644 index 0000000..258bae1 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayRequestProcessor.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Web; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public interface IApiGatewayRequestProcessor + { + IApiOrchestrator ApiOrchestrator { get; } + Task ProcessAsync(string apiKey, string routeKey, + HttpRequest httpRequest, + Func> backEndCall, + Func getContent = null, + object request = null, + string parameters = null); + } + + public class ApiGatewayRequestProcessor : IApiGatewayRequestProcessor + { + readonly IApiOrchestrator _apiOrchestrator; + readonly ILogger _logger; + readonly IHttpService _httpService; + + public IApiOrchestrator ApiOrchestrator => _apiOrchestrator; + + public ApiGatewayRequestProcessor(IApiOrchestrator apiOrchestrator, ILogger logger, IHttpService httpService) + { + _apiOrchestrator = apiOrchestrator; + _logger = logger; + _httpService = httpService; + } + + public async Task ProcessAsync( + string apiKey, + string routeKey, + HttpRequest httpRequest, + Func> backEndCall, + Func getContent = null, + object request = null, + string parameters = null) + { + if (parameters != null) + parameters = HttpUtility.UrlDecode(parameters); + else + parameters = string.Empty; + + _logger.LogApiInfo(apiKey, routeKey, parameters, request); + + var apiInfo = _apiOrchestrator.GetApi(apiKey, true); + + var gwRouteInfo = apiInfo.Mediator.GetRoute(routeKey); + + var routeInfo = gwRouteInfo.Route; + + if (routeInfo.Exec != null) + { + return await routeInfo.Exec(apiInfo, httpRequest); + } + else + { + using (var client = routeInfo.HttpClientConfig?.HttpClient()) + { + HttpContent content = null; + + if (request != null) + { + if (routeInfo.HttpClientConfig?.HttpContent != null) + { + content = routeInfo.HttpClientConfig.HttpContent(); + } + else + { + if (getContent != null) + { + content = getContent(request); + } + else + { + content = new StringContent(request.ToString(), Encoding.UTF8, "application/json"); + + content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + } + } + } + + httpRequest.Headers?.AddRequestHeaders((client ?? _httpService.Client).DefaultRequestHeaders); + + if (client == null) + { + routeInfo.HttpClientConfig?.CustomizeDefaultHttpClient?.Invoke(_httpService.Client, httpRequest); + } + + _logger.LogApiInfo($"{apiInfo.BaseUrl}{routeInfo.Path}{parameters}"); + + var response = await backEndCall((client ?? _httpService.Client), apiInfo, routeInfo, content); + + _logger.LogApiInfo($"{apiInfo.BaseUrl}{routeInfo.Path}{parameters}", false); + + response.EnsureSuccessStatusCode(); + + var returnedContent = await response.Content.ReadAsStringAsync(); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + return await Task.FromResult(routeInfo.ResponseType != null + ? !string.IsNullOrEmpty(returnedContent) ? JsonSerializer.Deserialize(returnedContent, routeInfo.ResponseType, options) : string.Empty + : returnedContent); + } + } + } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayResponseCache.cs b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayResponseCache.cs new file mode 100644 index 0000000..04597ef --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/ApiGatewayResponseCache.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class ApiGatewayResponseCacheSettings + { + // Gets or sets the value of the cache profile name. + public string CacheProfileName { get; set; } + // Gets or sets the duration in seconds for which the response is cached. This sets + // "max-age" in "Cache-control" header. + public int Duration { get; set; } = -1; + // Gets or sets the location where the data from a particular URL must be cached. + public ResponseCacheLocation Location { get; set; } + // Gets or sets the value which determines whether the data should be stored or + // not. When set to true, it sets "Cache-control" header to "no-store". Ignores + // the "Location" parameter for values other than "None". Ignores the "duration" + // parameter. + public bool NoStore { get; set; } + // Gets or sets the value for the Vary response header. + public string VaryByHeader { get; set; } + // Gets or sets the query keys to vary by. + public string[] VaryByQueryKeys { get; set; } + } + + internal class ResponseCacheTillAttribute : ResponseCacheAttribute + { + public ResponseCacheTillAttribute(IHttpContextAccessor httpContextAccessor, IApiOrchestrator apiOrchestrator, + ApiGatewayOptions apiGatewayOptions) + { + if (!apiGatewayOptions.UseResponseCaching) + { + base.NoStore = true; + return; + } + + var apiKey = httpContextAccessor.HttpContext.Request.RouteValues["apiKey"].ToString(); + var routeKey = httpContextAccessor.HttpContext.Request.RouteValues["routeKey"].ToString(); + + var api = apiOrchestrator.GetApi(apiKey); + var route = api.Mediator.GetRoute(routeKey); + + int duration = route.Route.ResponseCachingDurationInSeconds; + + if (duration >= 0) + { + base.Duration = duration; + } + else + { + base.Duration = apiGatewayOptions.ResponseCacheSettings.Duration; + } + } + } + +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/ApiOrchestrator.cs b/AspNetCore.ApiGateway.AzureFunctions/ApiOrchestrator.cs new file mode 100644 index 0000000..16d58a1 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/ApiOrchestrator.cs @@ -0,0 +1,135 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class ApiInfo + { + public string BaseUrl { get; set; } + + public IMediator Mediator { get; set; } + } + + + internal class LoadBalancing + { + public LoadBalancingType Type { get; set; } = LoadBalancingType.Random; + public string[] BaseUrls { get; set; } + public int LastBaseUrlIndex { get; set; } = -1; + } + + public enum LoadBalancingType + { + Random, + RoundRobin + } + + public class ApiOrchestrator : IApiOrchestrator + { + Dictionary apis = new Dictionary(); + + Dictionary apiLoadBalancing = new Dictionary(); + + private static Random _random = new Random(); + private static readonly object _syncLock = new object(); + + private readonly IMediator _mediator; + + public ApiOrchestrator(IMediator mediator) + { + _mediator = mediator; + _mediator.ApiOrchestrator = this; + } + + public IMediator AddApi(string apiKey, params string[] baseUrls) + { + if (string.IsNullOrWhiteSpace(apiKey)) + { + throw new ArgumentException("Api key must be specified."); + } + if (baseUrls == null || baseUrls.Length == 0) + { + throw new ArgumentException("At least one base url must be specified."); + } + + var mediator = _mediator; + + apis.Add(apiKey.Trim().ToLower(), new ApiInfo() { BaseUrl = baseUrls.First(), Mediator = mediator }); + + apiLoadBalancing.Add(apiKey.ToLower(), new LoadBalancing { BaseUrls = baseUrls }); + + MediatorHelper.CurrentApiKey = apiKey; + + return mediator; + } + + public IMediator AddApi(string apiKey, LoadBalancingType loadBalancingType, params string[] baseUrls) + { + if (string.IsNullOrWhiteSpace(apiKey)) + { + throw new ArgumentException("Api key must be specified."); + } + if (baseUrls == null || baseUrls.Length == 0) + { + throw new ArgumentException("At least one base url must be specified."); + } + + var mediator = _mediator; + + apis.Add(apiKey.Trim().ToLower(), new ApiInfo() { BaseUrl = baseUrls.First(), Mediator = mediator }); + + apiLoadBalancing.Add(apiKey.ToLower(), new LoadBalancing { Type = loadBalancingType, BaseUrls = baseUrls }); + + MediatorHelper.CurrentApiKey = apiKey; + + return mediator; + } + + public ApiInfo GetApi(string apiKey, bool withLoadBalancing = false) + { + var apiInfo = apis[apiKey.ToLower()]; + + if (withLoadBalancing) + { + //if more than 1 base url is specified + //get the load balancing base url based on load balancing algorithm specified. + + var loadBalancing = apiLoadBalancing[apiKey.ToLower()]; + + if (loadBalancing.BaseUrls.Count() > 1) + { + apiInfo.BaseUrl = this.GetLoadBalancedUrl(loadBalancing); + } + } + + return apiInfo; + } + + public IEnumerable Orchestration => apis? + .GroupBy(x => x.Key).Select(x => new ApiOrchestration + { + ApiKey = x.Key, + ApiRoutes = x.First().Value.Mediator.Routes.Where(y => y.ApiKey == x.Key).ToList(), + Routes = x.First().Value.Mediator.Routes.Where(y => y.ApiKey == x.Key).ToList(), + }); + + private string GetLoadBalancedUrl(LoadBalancing loadBalancing) + { + if (loadBalancing.Type == LoadBalancingType.RoundRobin) + { + loadBalancing.LastBaseUrlIndex++; + + if (loadBalancing.LastBaseUrlIndex >= loadBalancing.BaseUrls.Length) + { + loadBalancing.LastBaseUrlIndex = 0; + } + return loadBalancing.BaseUrls[loadBalancing.LastBaseUrlIndex]; + } + else + { + lock(_syncLock) + { + var selected = _random.Next(0, loadBalancing.BaseUrls.Count()); + return loadBalancing.BaseUrls[selected]; + } + } + } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/AspNetCore.ApiGateway.AzureFunctions.csproj b/AspNetCore.ApiGateway.AzureFunctions/AspNetCore.ApiGateway.AzureFunctions.csproj new file mode 100644 index 0000000..da0e859 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/AspNetCore.ApiGateway.AzureFunctions.csproj @@ -0,0 +1,39 @@ + + + net8.0 + v4 + Library + enable + disable + + + + \ + PreserveNewest + True + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/CHANGELOG.md b/AspNetCore.ApiGateway.AzureFunctions/CHANGELOG.md new file mode 100644 index 0000000..effcb2b --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog (Release Notes) + +- Added support for reading from appsettings.json. \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/Extensions.cs b/AspNetCore.ApiGateway.AzureFunctions/Extensions.cs new file mode 100644 index 0000000..a6362ec --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/Extensions.cs @@ -0,0 +1,182 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using System.Net.Http.Headers; +using System.Text.Json; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public static class Extensions + { + static ApiGatewayOptions Options { get; set; } + + private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter() + { + var builder = new ServiceCollection() + .AddLogging() + .AddMvc() + .AddNewtonsoftJson() + .Services.BuildServiceProvider(); + + return builder + .GetRequiredService>() + .Value + .InputFormatters + .OfType() + .First(); + } + + public static void AddApiGateway(this IServiceCollection services, Action options = null) + { + Options = new ApiGatewayOptions(); + + options?.Invoke(Options); + + services.AddSingleton(); + + services.AddSingleton(sp => new ApiOrchestrator( + sp.GetRequiredService())); + + services.AddScoped(); + + if (Options.DefaultMyHttpClientHandler != null) + { + services + .AddHttpClient() + .ConfigurePrimaryHttpMessageHandler(Options.DefaultMyHttpClientHandler); + } + else if (Options.DefaultHttpClientConfigure != null) + { + services.AddHttpClient(Options.DefaultHttpClientConfigure); + } + else + { + services.AddHttpClient(); + } + + //services.AddApiGatewayResponseCaching(); + + //services.AddControllers() + // .AddNewtonsoftJson(); // Enables support for JsonPatchDocument + + //services.AddControllers(options => + //{ + // options.InputFormatters.Insert(0, GetJsonPatchInputFormatter()); + //}); + } + + public static void UseApiGateway(this IHost app, Action setApis) + { + var serviceProvider = app.Services; + var apiOrchestrator = serviceProvider.GetService(); + setApis(apiOrchestrator); + //if (Options != null) + //{ + // if (Options.UseResponseCaching) + // { + // app.UseResponseCaching(); + // } + //} + //app.UseMiddleware(); + + //var gatewayMiddleware = serviceProvider.GetServiceOrNull(); + + //if (gatewayMiddleware != null) + //{ + // app.UseMiddleware(); + //} + } + + internal static T GetServiceOrNull(this IServiceProvider serviceProvider) + where T: class + { + try + { + return serviceProvider.GetService(); + } + catch(Exception) + { + return null; + } + } + + internal static void AddApiGatewayResponseCaching(this IServiceCollection services) + { + if (Options != null) + { + if (Options.UseResponseCaching && Options.ResponseCacheSettings != null) + { + services.AddResponseCaching(); + + services.AddScoped(sp => new ResponseCacheTillAttribute(sp.GetRequiredService(), + sp.GetRequiredService(), + Options) + { + NoStore = Options.ResponseCacheSettings.NoStore, + Location = Options.ResponseCacheSettings.Location, + VaryByHeader = Options.ResponseCacheSettings.VaryByHeader, + VaryByQueryKeys = Options.ResponseCacheSettings.VaryByQueryKeys, + CacheProfileName = Options.ResponseCacheSettings.CacheProfileName + }); + } + else + { + services.AddScoped(sp => new ResponseCacheTillAttribute(sp.GetRequiredService(), + sp.GetRequiredService(), + Options) + { + NoStore = true + }); + } + } + } + + internal static void AddRequestHeaders (this IHeaderDictionary requestHeaders, HttpRequestHeaders headers) + { + foreach (var item in requestHeaders) + { + try + { + if (!headers.Contains(item.Key)) + headers.Add(item.Key, item.Value.ToString()); + } + catch(Exception) + { } + } + } + + internal static ApiOrchestration FilterRoutes(this ApiOrchestration orchestration, string key) + { + orchestration.ApiRoutes = orchestration.ApiRoutes.Where(y => y.RouteKey.Contains(key.Trim())).ToList(); + orchestration.Routes = orchestration.ApiRoutes; + return orchestration; + } + + internal static string ToUtcLongDateTime(this DateTime dateTime) + { + return dateTime.ToString("MM/dd/yyyy hh:mm:ss.fff tt"); + } + + internal static void LogApiInfo(this ILogger logger, string api, string key, string parameters, object request = null) + { + if (request != null) + logger.LogInformation($"ApiGateway: Incoming POST request. api: {api}, key: {key}, object: {JsonSerializer.Serialize(request)}, parameters: {parameters}, UtcTime: { DateTime.UtcNow.ToUtcLongDateTime() }"); + else + logger.LogInformation($"ApiGateway: Incoming POST request. api: {api}, key: {key}, UtcTime: { DateTime.UtcNow.ToUtcLongDateTime() }"); + } + + internal static void LogApiInfo(this ILogger logger, string url, bool beforeBackendCall = true) + { + if (beforeBackendCall) + logger.LogInformation($"ApiGateway: Calling back end. Url: {url}, UtcTime: { DateTime.UtcNow.ToUtcLongDateTime() }"); + else + logger.LogInformation($"ApiGateway: Finished calling back end. Url: {url}, UtcTime: { DateTime.UtcNow.ToUtcLongDateTime() }"); + } + + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/GatewayConstants.cs b/AspNetCore.ApiGateway.AzureFunctions/GatewayConstants.cs new file mode 100644 index 0000000..c47ad53 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/GatewayConstants.cs @@ -0,0 +1,7 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + internal static class GatewayConstants + { + public static string GATEWAY_PATH_REGEX = "^/?api/Gateway(/(?!orchestration)(hub/)?(?.*?)/(?.*?)(/.*?)?)?$"; + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs b/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs new file mode 100644 index 0000000..4d1257f --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs @@ -0,0 +1,188 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using System.Text; +using System.Text.Json; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class GatewayFunctions + { + private readonly ILogger _logger; + private readonly IApiGatewayRequestProcessor _requestProcessor; + + public GatewayFunctions(IApiGatewayRequestProcessor requestProcessor, ILogger logger = null) + { + _logger = logger; + _requestProcessor = requestProcessor; + } + + [Function("ApiGateway-Head")] + public async Task HeadAsync([HttpTrigger(AuthorizationLevel.Function, "head", Route = "api/Gateway/{apiKey}/{routeKey}")] + HttpRequest request, string apiKey, string routeKey, string parameters = null) + { + _logger?.LogInformation("C# HTTP trigger function processed a request."); + + return Results.Ok(await _requestProcessor.ProcessAsync( + apiKey, + routeKey, + request, + (client, apiInfo, routeInfo, content) => client.GetAsync($"{apiInfo.BaseUrl}{(routeInfo.IsParameterizedRoute ? routeInfo.GetPath(request) : routeInfo.Path + parameters)}"), + null, + null, + parameters + )); + } + + [Function("ApiGateway-Get")] + public async Task GetAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "api/Gateway/{apiKey}/{routeKey}")] + HttpRequest request, string apiKey, string routeKey, string parameters = null) + { + _logger?.LogInformation("C# HTTP trigger function processed a request."); + + return Results.Ok(await _requestProcessor.ProcessAsync( + apiKey, + routeKey, + request, + (client, apiInfo, routeInfo, content) => client.GetAsync($"{apiInfo.BaseUrl}{(routeInfo.IsParameterizedRoute ? routeInfo.GetPath(request) : routeInfo.Path + parameters)}"), + null, + null, + parameters + )); + } + + [Function("ApiGateway-Post")] + public async Task PostAsync([HttpTrigger(AuthorizationLevel.Function, "post", Route = "api/Gateway/{apiKey}/{routeKey}")] + HttpRequest request, string apiKey, string routeKey, string parameters = null) + { + _logger?.LogInformation("C# HTTP trigger function processed a request."); + + string requestBody = await new StreamReader(request.Body).ReadToEndAsync(); + object requestObj = JsonConvert.DeserializeObject(requestBody); + + return Results.Ok(await _requestProcessor.ProcessAsync( + apiKey, + routeKey, + request, + (client, apiInfo, routeInfo, content) => client.PostAsync($"{apiInfo.BaseUrl}{(routeInfo.IsParameterizedRoute ? routeInfo.GetPath(request) : routeInfo.Path + parameters)}", content), + null, + requestObj, + parameters + )); + } + + [Function("ApiGateway-Put")] + public async Task PutAsync([HttpTrigger(AuthorizationLevel.Function, "put", Route = "api/Gateway/{apiKey}/{routeKey}")] + HttpRequest request, string apiKey, string routeKey, string parameters = null) + { + _logger?.LogInformation("C# HTTP trigger function processed a request."); + + string requestBody = await new StreamReader(request.Body).ReadToEndAsync(); + object requestObj = JsonConvert.DeserializeObject(requestBody); + + return Results.Ok(await _requestProcessor.ProcessAsync( + apiKey, + routeKey, + request, + (client, apiInfo, routeInfo, content) => client.PutAsync($"{apiInfo.BaseUrl}{(routeInfo.IsParameterizedRoute ? routeInfo.GetPath(request) : routeInfo.Path + parameters)}", content), + null, + requestObj, + parameters + )); + } + + [Function("ApiGateway-Patch")] + public async Task PatchAsync([HttpTrigger(AuthorizationLevel.Function, "patch", Route = "api/Gateway/{apiKey}/{routeKey}")] + HttpRequest request, string apiKey, string routeKey, string parameters = null) + { + _logger?.LogInformation("C# HTTP trigger function processed a request."); + + using var reader = new StreamReader(request.Body); + + string body = await reader.ReadToEndAsync(); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + var patch = System.Text.Json.JsonSerializer.Deserialize(body, options); + + return Results.Ok(await _requestProcessor.ProcessAsync( + apiKey, + routeKey, + request, + (client, apiInfo, routeInfo, content) => client.PatchAsync($"{apiInfo.BaseUrl}{(routeInfo.IsParameterizedRoute ? routeInfo.GetPath(request) : routeInfo.Path + parameters)}", content), + request => + { + var p = System.Text.Json.JsonSerializer.Serialize(request); + + return new StringContent(p, Encoding.UTF8, "application/json-patch+json"); + }, + patch.operations, + parameters + )); + } + + [Function("ApiGateway-Delete")] + public async Task DeleteAsync([HttpTrigger(AuthorizationLevel.Function, "delete", Route = "api/Gateway/{apiKey}/{routeKey}")] + HttpRequest request, string apiKey, string routeKey, string parameters = null) + { + _logger?.LogInformation("C# HTTP trigger function processed a request."); + + return Results.Ok(await _requestProcessor.ProcessAsync( + apiKey, + routeKey, + request, + (client, apiInfo, routeInfo, content) => client.DeleteAsync($"{apiInfo.BaseUrl}{(routeInfo.IsParameterizedRoute ? routeInfo.GetPath(request) : routeInfo.Path + parameters)}"), + null, + null, + parameters + )); + } + + //[Function("ApiGateway-GetOrchestration")] + //public async Task GetOrchestrationAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "/api/Gateway/orchestration")] + // HttpRequest request, string apiKey = null, string routeKey = null) + //{ + // _logger?.LogInformation("C# HTTP trigger function processed a request."); + + // var apiOrchestrator = _requestProcessor.ApiOrchestrator; + + // apiKey = apiKey?.ToLower(); + // routeKey = routeKey?.ToLower(); + + // var orchestrations = await Task.FromResult(string.IsNullOrEmpty(apiKey) && string.IsNullOrEmpty(routeKey) + // ? apiOrchestrator.Orchestration + // : (!string.IsNullOrEmpty(apiKey) && string.IsNullOrEmpty(routeKey) + // ? apiOrchestrator.Orchestration?.Where(x => x.ApiKey.Contains(apiKey.Trim())) + // : (string.IsNullOrEmpty(apiKey) && !string.IsNullOrEmpty(routeKey) + // ? apiOrchestrator.Orchestration?.Where(x => x.ApiRoutes.Any(y => y.RouteKey.Contains(routeKey.Trim()))) + // .Select(x => x.FilterRoutes(routeKey)) + // : apiOrchestrator.Orchestration?.Where(x => x.ApiKey.Contains(apiKey.Trim())) + // .Select(x => x.FilterRoutes(routeKey))))); + + // return Results.Ok(orchestrations); + //} + } + + public class ContractResolver + { + } + + public class Operation + { + public int value { get; set; } + public int operationType { get; set; } + public string path { get; set; } + public string op { get; set; } + public object from { get; set; } + } + + public class PatchObj + { + public List operations { get; set; } + public ContractResolver contractResolver { get; set; } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/HttpService.cs b/AspNetCore.ApiGateway.AzureFunctions/HttpService.cs new file mode 100644 index 0000000..1a5c834 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/HttpService.cs @@ -0,0 +1,17 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public interface IHttpService + { + HttpClient Client { get; } + } + + public class HttpService : IHttpService + { + public HttpService(HttpClient client) + { + this.Client = client; + } + + public HttpClient Client { get; } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/IApiOrchestrator.cs b/AspNetCore.ApiGateway.AzureFunctions/IApiOrchestrator.cs new file mode 100644 index 0000000..8457d52 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/IApiOrchestrator.cs @@ -0,0 +1,12 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public interface IApiOrchestrator + { + IMediator AddApi(string apiKey, params string[] baseUrl); + IMediator AddApi(string apiKey, LoadBalancingType loadBalancingType, params string[] baseUrls); + + ApiInfo GetApi(string apiKey, bool withLoadBalancing = false); + + IEnumerable Orchestration { get; } + } +} \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/IMediator.cs b/AspNetCore.ApiGateway.AzureFunctions/IMediator.cs new file mode 100644 index 0000000..5cd6004 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/IMediator.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Http; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public interface IMediatorBase + { + IApiOrchestrator ApiOrchestrator { get; set; } + } + + public interface IMediator: IMediatorBase + { + IMediator AddRoute(string routeKey, GatewayVerb verb, RouteInfo routeInfo); + + IMediator AddRoute(string routeKey, GatewayVerb verb, Func> exec); + + GatewayRouteInfo GetRoute(string routeKey); + + IMediator AddApi(string apiKey, params string[] baseUrls); + + IEnumerable Routes { get; } + } +} \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/Mediator.cs b/AspNetCore.ApiGateway.AzureFunctions/Mediator.cs new file mode 100644 index 0000000..99b2194 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/Mediator.cs @@ -0,0 +1,191 @@ +using Microsoft.AspNetCore.Http; +using System.Text.RegularExpressions; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public enum GatewayVerb + { + GET, + HEAD, + POST, + PUT, + PATCH, + DELETE + } + + public class HttpClientConfig + { + public Func HttpClient { get; set; } + + public Func HttpContent { get; set; } + + public Action CustomizeDefaultHttpClient { get; set; } + } + + public abstract class GatewayRouteInfoBase + { + public string ApiKey { get; set; } + } + + public class GatewayRouteInfo : GatewayRouteInfoBase + { + public GatewayVerb Verb { get; set; } + + public RouteInfo Route { get; set; } + } + + public class RouteInfo + { + private IEnumerable _routeParams = new List(); + private bool _isPathTypeDetermined = false; + private bool _isParameterizedRoute = false; + private string _path = string.Empty; + + public string Path + { + get => _path; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(Path)); + } + _path = value.Trim(); + } + } + internal bool IsParameterizedRoute + { + get + { + if (!_isPathTypeDetermined) + { + _isParameterizedRoute = Regex.Match(this.Path, @"\{.+?\}", RegexOptions.IgnoreCase | RegexOptions.Compiled).Success; + _isPathTypeDetermined=true; + } + return _isParameterizedRoute; + } + } + public Type ResponseType { get; set; } + public Type RequestType { get; set; } + public Func> Exec { get; set; } + public HttpClientConfig HttpClientConfig { get; set; } + public int ResponseCachingDurationInSeconds { get; set; } = -1; + private IEnumerable Params + { + get + { + if (_routeParams.Any()) + { + return _routeParams; + } + + var m = Regex.Match(this.Path, @"^.*?(\{(?.+?)\}.*?)*$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + if (m.Success) + { + _routeParams = m.Groups["param"].Captures.Select(x => x.Value).ToList(); + } + + return _routeParams; + } + } + internal string GetPath(HttpRequest request) + { + var p = request.Query["parameters"][0]; + + var allParams = p.Split('&'); + + var paramDictionary = allParams.Select(x => x.Split('=')); + + var paramValues = paramDictionary.Join(this.Params, x => x[0], y => y, (x, y) => new { Key = x[0], Value = x[1] }).ToList(); + + var path = new string(this.Path.ToCharArray()); + + paramValues.ForEach(x => + { + path = Regex.Replace(path, $"{{{x.Key}}}", x.Value); + }); + + return path; + } + } + + public class Mediator : IMediator + { + IApiOrchestrator _apiOrchestrator; + Dictionary paths = new Dictionary(); + + public IApiOrchestrator ApiOrchestrator + { + get => _apiOrchestrator; + set => _apiOrchestrator = value; + } + + public IMediator AddRoute(string routeKey, GatewayVerb verb, RouteInfo routeInfo) + { + if (string.IsNullOrWhiteSpace(routeKey)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(routeKey)); + } + + var gatewayRouteInfo = new GatewayRouteInfo + { + Verb = verb, + ApiKey = MediatorHelper.CurrentApiKey, + Route = routeInfo + }; + + paths.Add(routeKey.Trim().ToLower(), gatewayRouteInfo); + + return this; + } + + public IMediator AddRoute(string routeKey, GatewayVerb verb, Func> exec) + { + if (string.IsNullOrWhiteSpace(routeKey)) + { + throw new ArgumentException("Value cannot be null or whitespace.", nameof(routeKey)); + } + + if (exec == null) + { + throw new ArgumentNullException(nameof(exec)); + } + + var gatewayRouteInfo = new GatewayRouteInfo + { + Verb = verb, + ApiKey = MediatorHelper.CurrentApiKey, + Route = new RouteInfo + { + Exec = exec + } + }; + + paths.Add(routeKey.Trim().ToLower(), gatewayRouteInfo); + + return this; + } + + public IMediator AddApi(string apiKey, params string[] baseUrls) + { + _apiOrchestrator.AddApi(apiKey, baseUrls); + + return _apiOrchestrator.GetApi(apiKey).Mediator; + } + + public GatewayRouteInfo GetRoute(string key) + { + return paths[key.ToLower()]; + } + + public IEnumerable Routes => paths.Select(x => new Route + { + RouteKey = x.Key, + ApiKey = x.Value?.ApiKey, + Verb = x.Value?.Verb.ToString(), + DownstreamPath = x.Value?.Route?.Path?.ToString(), + }); + } + +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/MediatorHelper.cs b/AspNetCore.ApiGateway.AzureFunctions/MediatorHelper.cs new file mode 100644 index 0000000..4957545 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/MediatorHelper.cs @@ -0,0 +1,7 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + internal static class MediatorHelper + { + public static string CurrentApiKey { get; set; } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/Orchestration.cs b/AspNetCore.ApiGateway.AzureFunctions/Orchestration.cs new file mode 100644 index 0000000..f8a2963 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/Orchestration.cs @@ -0,0 +1,58 @@ +using System.Text.Json.Serialization; + +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class Orchestration + { + [JsonPropertyOrder(1)] + [JsonPropertyName("apiKey")] + public string ApiKey { get; set; } + + [JsonIgnore()] + public List ApiRoutes { get; set; } + } + + public class ApiOrchestration : Orchestration + { + public ApiOrchestration() + { + } + + [JsonPropertyOrder(2)] + [JsonPropertyName("routes")] + public IEnumerable Routes { get; set; } + } + + public class RouteBase + { + [JsonPropertyOrder(1)] + [JsonPropertyName("routeKey")] + + public string RouteKey { get; set; } + + [JsonPropertyOrder(1)] + [JsonPropertyName("apiKey")] + [JsonIgnore()] + + public string ApiKey { get; set; } + } + + public class Route : RouteBase + { + [JsonPropertyOrder(3)] + [JsonPropertyName("verb")] + public string Verb { get; set; } + + [JsonPropertyOrder(4)] + [JsonPropertyName("downstreamPath")] + public string DownstreamPath { get; set; } + + //[JsonPropertyOrder(5)] + //[JsonPropertyName("requestJsonSchema")] + //public JsonSchema RequestJsonSchema { get; set; } + + //[JsonPropertyOrder(6)] + //[JsonPropertyName("responseJsonSchema")] + //public JsonSchema ResponseJsonSchema { get; set; } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/Program.cs b/AspNetCore.ApiGateway.AzureFunctions/Program.cs new file mode 100644 index 0000000..7bbaabd --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/Program.cs @@ -0,0 +1,9 @@ +namespace AspNetCore.ApiGateway.AzureFunctions +{ + public class Program + { + public static void Main(string[] args) + { + } + } +} diff --git a/AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.json b/AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.json new file mode 100644 index 0000000..c264e8c --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + } + } +} \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json b/AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..5a956e8 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + } + } +} \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/README.md b/AspNetCore.ApiGateway.AzureFunctions/README.md new file mode 100644 index 0000000..74bb693 --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/README.md @@ -0,0 +1,41 @@ +# API Gateway as a RESTful Minimal API Facade + +## Supports .NET 8/9/10. + +|Packages|Version|Downloads| +|---------------------------|:---:|:---:| +|*AspNetCore.ApiGateway.Client*|[![Nuget Version](https://img.shields.io/nuget/v/AspNetCore.ApiGateway.Client)](https://www.nuget.org/packages/AspNetCore.ApiGateway.Client)|[![Downloads count](https://img.shields.io/nuget/dt/AspNetCore.ApiGateway.Client)](https://www.nuget.org/packages/AspNetCore.ApiGateway.Client)| +|*ts-aspnetcore-apigateway-client*|[![NPM Version](https://img.shields.io/npm/v/ts-aspnetcore-apigateway-client)](https://www.npmjs.com/package/ts-aspnetcore-apigateway-client)|[![Downloads count](https://img.shields.io/npm/dy/ts-aspnetcore-apigateway-client)](https://www.npmjs.com/package/ts-aspnetcore-apigateway-client)| + +This project demonstrates how to implement an API Gateway using ASP.NET Core Minimal APIs. + +The API Gateway serves as a single entry point for clients, routing requests to various backend services while providing features such as authentication, logging, and response aggregation. + +### Framework to help build your own Api Gateway. Out of the box, simple to use facade. + +## Seamlessly transition your Minimal API skills to the API Gateway. + +# This project has been on-boarded by the .NET Foundation and endorsed as revolutionary. + +### Read more on the Project website + +## Features + +* Swagger +* Authorization +* Load balancing +* Response caching +* Request aggregation +* Middleware service +* Logging +* Clients available in + * .NET + * Typescript + +## Social Media + +Read more [**here**](https://www.linkedin.com/feed/update/urn:li:activity:7168255226624372736/) + +## Documentation + +Read more [**here**](https://github.com/VeritasSoftware/AspNetCore.ApiGateway#gateway-as-a-restful-minimal-api-facade) \ No newline at end of file diff --git a/AspNetCore.ApiGateway.AzureFunctions/host.json b/AspNetCore.ApiGateway.AzureFunctions/host.json new file mode 100644 index 0000000..ee5cf5f --- /dev/null +++ b/AspNetCore.ApiGateway.AzureFunctions/host.json @@ -0,0 +1,12 @@ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file diff --git a/AspNetCore.ApiGateway.Tests/AspNetCore.ApiGateway.Tests.csproj b/AspNetCore.ApiGateway.Tests/AspNetCore.ApiGateway.Tests.csproj index f85e554..9ed9e18 100644 --- a/AspNetCore.ApiGateway.Tests/AspNetCore.ApiGateway.Tests.csproj +++ b/AspNetCore.ApiGateway.Tests/AspNetCore.ApiGateway.Tests.csproj @@ -10,6 +10,7 @@ + @@ -26,6 +27,7 @@ + diff --git a/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs b/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs new file mode 100644 index 0000000..939ec07 --- /dev/null +++ b/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs @@ -0,0 +1,334 @@ +using AspNetCore.ApiGateway.Tests; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Learn.AzureFunctionsTesting; +using System; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; +using StockAPI = Stock.API; +using WeatherAPI = Weather.API; + +[assembly: TestFramework("Microsoft.Learn.AzureFunctionsTesting.TestFramework", "Microsoft.Learn.AzureFunctionsTesting")] +[assembly: AssemblyFixture(typeof(FunctionFixture))] +namespace AspNetCore.ApiGateway.Tests +{ + public class FunctionStartup : IFunctionTestStartup + { + public void Configure(FunctionTestConfigurationBuilder builder) + { + var path = Path.Combine(Environment.CurrentDirectory, @"..\..\..\..\Sample.ApiGateway.AzureFunctions\bin\Debug\net8.0"); + builder.SetFunctionAppPath(path); + builder.SetFunctionAppPort(7055); + } + } + + // Define a collection for the FunctionFixture + [CollectionDefinition("Function collection")] + public class FunctionCollection : ICollectionFixture> + { + // This class has no code, and is never created. Its purpose is just to be the place to apply [CollectionDefinition] and all the ICollectionFixture<> interfaces. + } + + public class AzureFunctionsAPIInitialize + { + public TestServer GatewayAPI { get; set; } + + public AzureFunctionsAPIInitialize() + { + //Start Weather API + IWebHostBuilder weatherAPI = new WebHostBuilder() + .UseStartup() + .UseKestrel(options => options.Listen(IPAddress.Any, 5003, listenOptions => listenOptions.UseHttps(o => o.AllowAnyClientCertificate()))); + + weatherAPI.Start(); + + //Start Stock API + IWebHostBuilder stockAPI = new WebHostBuilder() + .UseStartup() + .UseKestrel(options => options.Listen(IPAddress.Any, 5005, listenOptions => listenOptions.UseHttps(o => o.AllowAnyClientCertificate()))); + + stockAPI.Start(); + } + } + + [Collection("Function collection")] + public class AzureFunctionsGatewayTests : IClassFixture + { + //readonly AzureFunctionsAPIInitialize _apiInit; + private readonly HttpClient _httpClient; + + public AzureFunctionsGatewayTests(AzureFunctionsAPIInitialize apiInit, FunctionFixture fixture) + { + //_apiInit = apiInit; + _httpClient = fixture.Client; + //_httpClient.BaseAddress = new Uri("https://localhost:7055"); + } + + [Fact] + public async Task Test_Get_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key and Route key + var gatewayUrl = "api/Gateway/weatherservice/forecast"; + + var response = await client.GetAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + var forecasts = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + Assert.True(forecasts.Length > 0); + } + + [Fact] + public async Task Test_Multiple_Get_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key and Route key + //Weather API call + var gatewayUrl = "api/Gateway/weatherservice/forecast"; + + var response = await client.GetAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + var forecasts = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + Assert.True(forecasts.Length > 0); + + client = _httpClient; + + //Stock API call + gatewayUrl = "api/Gateway/stockservice/stocks"; + + response = await client.GetAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + var stockQuotes = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + Assert.True(stockQuotes.Length > 0); + } + + [Fact] + public async Task Test_Get_WithParam_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key, Route key and Param + var gatewayUrl = "api/Gateway/weatherservice/type?parameters=3"; + + var response = await client.GetAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + var strResponse = await response.Content.ReadAsStringAsync(); + + var weatherType = JsonSerializer.Deserialize(strResponse); + + Assert.NotNull(weatherType); + Assert.True(!string.IsNullOrEmpty(weatherType.Type)); + + //client = _httpClient; + + //gatewayUrl = "api/Gateway/weatherservice/typewithparams?parameters=index=3"; + + //response = await client.GetAsync(gatewayUrl); + + //response.EnsureSuccessStatusCode(); + + //strResponse = await response.Content.ReadAsStringAsync(); + + //weatherType = JsonSerializer.Deserialize(strResponse); + + //Assert.NotNull(weatherType); + //Assert.True(!string.IsNullOrEmpty(weatherType.Type)); + } + + [Fact] + public async Task Test_Post_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key and Route key + var gatewayUrl = $"api/Gateway/weatherservice/add"; + + AddWeatherTypeRequest request = new AddWeatherTypeRequest + { + WeatherType = "Windy" + }; + + // POST JSON + HttpResponseMessage response = await client.PostAsJsonAsync(gatewayUrl, request); + + //var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); + //content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + //var httprequest = new HttpRequestMessage + //{ + // //RequestUri = new Uri($"{client.BaseAddress}{gatewayUrl}"), + // RequestUri = new Uri(gatewayUrl), + // Content = content, + // Method = HttpMethod.Post + //}; + + //var response = await client.SendAsync(httprequest); + + response.EnsureSuccessStatusCode(); + + var weatherTypes = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + Assert.True(weatherTypes.Last() == "Windy"); + } + + [Fact] + public async Task Test_Put_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key and Route key + var gatewayUrl = "api/Gateway/weatherservice/update"; + + UpdateWeatherTypeRequest request = new UpdateWeatherTypeRequest + { + WeatherType = "Coooooooool", + Index = 3 + }; + + // POST JSON + HttpResponseMessage response = await client.PutAsJsonAsync(gatewayUrl, request); + + //var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); + //content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); + + //var httprequest = new HttpRequestMessage + //{ + // RequestUri = new Uri(gatewayUrl), + // Content = content, + // Method = HttpMethod.Put + //}; + + //var response = await client.SendAsync(httprequest); + + response.EnsureSuccessStatusCode(); + + //Gateway API url with Api key and Route key + gatewayUrl = "api/Gateway/weatherservice/types"; + + response = await client.GetAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + var weatherTypes = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + Assert.True(weatherTypes[3] == "Coooooooool"); + } + + [Fact] + public async Task Test_Patch_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key and Route key + var gatewayUrl = "api/Gateway/weatherservice/patch"; + + JsonPatchDocument jsonPatch = new JsonPatchDocument(); + jsonPatch.Add(x => x.TemperatureC, 35); + + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + var response = await client.PatchAsJsonAsync(gatewayUrl, jsonPatch, options); + + response.EnsureSuccessStatusCode(); + + var strResponse = await response.Content.ReadAsStringAsync(); + + var weatherForecast = JsonSerializer.Deserialize(strResponse); + + Assert.True(weatherForecast.TemperatureC == 35); + } + + [Fact] + public async Task Test_Delete_Pass() + { + var client = _httpClient; + + //Gateway API url with Api key, Route key and Param + var gatewayUrl = "api/Gateway/weatherservice/remove?parameters=0"; + + var response = await client.DeleteAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + //Gateway API url with Api key and Route key + gatewayUrl = "api/Gateway/weatherservice/types"; + + response = await client.GetAsync(gatewayUrl); + + response.EnsureSuccessStatusCode(); + + var weatherTypes = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + + Assert.DoesNotContain(weatherTypes, x => x == "Freezing"); + } + + //[Fact] + //public async Task Test_Get_Invalid_ApiKey_Fail() + //{ + // var client = _httpClient; + + // //Gateway API url with invalid Api key and Route key + // var gatewayUrl = "api/Gateway/xyzservice/forecast"; + + // var response = await client.GetAsync(gatewayUrl); + + // Assert.True(response.StatusCode == HttpStatusCode.NotFound); + //} + + //[Fact] + //public async Task Test_Get_Invalid_RouteKey_Fail() + //{ + // var client = _httpClient; + + // //Gateway API url with Api key and invalid Route key + // var gatewayUrl = "api/Gateway/weatherservice/xyz"; + + // var response = await client.GetAsync(gatewayUrl); + + // Assert.True(response.StatusCode == HttpStatusCode.NotFound); + //} + + //[Fact] + //public async Task Test_GetOrchestration_Pass() + //{ + // var client = _httpClient; + + // //Gateway API Orchestration url + // var gatewayUrl = "api/Gateway/orchestration"; + + // var response = await client.GetAsync(gatewayUrl); + + // response.EnsureSuccessStatusCode(); + + // var strResponse = await response.Content.ReadAsStringAsync(); + + // var orchestration = JsonSerializer.Deserialize(strResponse); + + // Assert.True(orchestration.Length > 0); + //} + } +} diff --git a/Sample.ApiGateway.AzureFunctions/.gitignore b/Sample.ApiGateway.AzureFunctions/.gitignore new file mode 100644 index 0000000..ff5b00c --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/.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/Sample.ApiGateway.AzureFunctions/ApiOrchestration.cs b/Sample.ApiGateway.AzureFunctions/ApiOrchestration.cs new file mode 100644 index 0000000..534953b --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/ApiOrchestration.cs @@ -0,0 +1,43 @@ +using AspNetCore.ApiGateway.AzureFunctions; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Sample.ApiGateway.AzureFunctions +{ + public static class ApiOrchestration + { + public static void Create(IApiOrchestrator orchestrator, IHost app) + { + var serviceProvider = app.Services; + + var weatherService = serviceProvider.GetRequiredService(); + + var weatherApiClientConfig = weatherService.GetClientConfig(); + + orchestrator.AddApi("weatherservice", "https://localhost:5003/") + //Get + .AddRoute("forecast", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/forecast", ResponseType = typeof(IEnumerable) }) + //Head + .AddRoute("forecasthead", GatewayVerb.HEAD, new RouteInfo { Path = "weatherforecast/forecast" }) + //Get with params + .AddRoute("typewithparams", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/{index}" }) + //Get using custom HttpClient + .AddRoute("types", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types", ResponseType = typeof(string[]), HttpClientConfig = weatherApiClientConfig }) + //Get with param using custom HttpClient + .AddRoute("type", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/", ResponseType = typeof(WeatherTypeResponse), HttpClientConfig = weatherApiClientConfig }) + //Get using custom implementation + .AddRoute("forecast-custom", GatewayVerb.GET, weatherService.GetForecast) + //Post + .AddRoute("add", GatewayVerb.POST, new RouteInfo { Path = "weatherforecast/types/add", RequestType = typeof(AddWeatherTypeRequest), ResponseType = typeof(string[]) }) + //Put + .AddRoute("update", GatewayVerb.PUT, new RouteInfo { Path = "weatherforecast/types/update", RequestType = typeof(UpdateWeatherTypeRequest), ResponseType = typeof(string[]) }) + //Patch + .AddRoute("patch", GatewayVerb.PATCH, new RouteInfo { Path = "weatherforecast/forecast/patch", ResponseType = typeof(WeatherForecast) }) + //Delete + .AddRoute("remove", GatewayVerb.DELETE, new RouteInfo { Path = "weatherforecast/types/remove/", ResponseType = typeof(string[]) }) + .AddApi("stockservice", "https://localhost:5005/") + .AddRoute("stocks", GatewayVerb.GET, new RouteInfo { Path = "stock", ResponseType = typeof(IEnumerable) }) + .AddRoute("stock", GatewayVerb.GET, new RouteInfo { Path = "stock/", ResponseType = typeof(StockQuote) }); + } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/Function1.cs b/Sample.ApiGateway.AzureFunctions/Function1.cs new file mode 100644 index 0000000..87a0f91 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Function1.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class Function1 + { + private readonly ILogger _logger; + + public Function1(ILogger logger) + { + _logger = logger; + } + + [Function("Function1")] + public IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req) + { + _logger.LogInformation("C# HTTP trigger function processed a request."); + return new OkObjectResult("Welcome to Azure Functions!"); + } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/IWeatherService.cs b/Sample.ApiGateway.AzureFunctions/IWeatherService.cs new file mode 100644 index 0000000..7a97dfe --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/IWeatherService.cs @@ -0,0 +1,12 @@ +using AspNetCore.ApiGateway; +using AspNetCore.ApiGateway.AzureFunctions; +using Microsoft.AspNetCore.Http; + +namespace Sample.ApiGateway.AzureFunctions +{ + public interface IWeatherService + { + HttpClientConfig GetClientConfig(); + Task GetForecast(ApiInfo apiInfo, HttpRequest request); + } +} \ No newline at end of file diff --git a/Sample.ApiGateway.AzureFunctions/Models/AddWeatherTypeRequest.cs b/Sample.ApiGateway.AzureFunctions/Models/AddWeatherTypeRequest.cs new file mode 100644 index 0000000..56c275b --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Models/AddWeatherTypeRequest.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class AddWeatherTypeRequest + { + [JsonPropertyName("weatherType")] + public string WeatherType { get; set; } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/Models/StockQuote.cs b/Sample.ApiGateway.AzureFunctions/Models/StockQuote.cs new file mode 100644 index 0000000..407c1c7 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Models/StockQuote.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class StockQuote + { + [JsonPropertyName("companyName")] + public string CompanyName { get; set; } + + [JsonPropertyName("costPerShare")] + public string CostPerShare { get; set; } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/Models/UpdateWeatherTypeRequest.cs b/Sample.ApiGateway.AzureFunctions/Models/UpdateWeatherTypeRequest.cs new file mode 100644 index 0000000..b6b3132 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Models/UpdateWeatherTypeRequest.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class UpdateWeatherTypeRequest + { + [JsonPropertyName("weatherType")] + public string WeatherType { get; set; } + + [JsonPropertyName("index")] + public int Index { get; set; } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/Models/WeatherForecast.cs b/Sample.ApiGateway.AzureFunctions/Models/WeatherForecast.cs new file mode 100644 index 0000000..04ed6c1 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Models/WeatherForecast.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class WeatherForecast + { + [JsonPropertyName("date")] + public DateTime Date { get; set; } + + [JsonPropertyName("temperatureC")] + public int TemperatureC { get; set; } + + [JsonPropertyName("temperatureF")] + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + [JsonPropertyName("summary")] + public string Summary { get; set; } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/Models/WeatherTypeResponse.cs b/Sample.ApiGateway.AzureFunctions/Models/WeatherTypeResponse.cs new file mode 100644 index 0000000..3f60c4d --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Models/WeatherTypeResponse.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class WeatherTypeResponse + { + [JsonPropertyName("type")] + public string Type { get; set; } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/Program.cs b/Sample.ApiGateway.AzureFunctions/Program.cs new file mode 100644 index 0000000..926cbb5 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Program.cs @@ -0,0 +1,35 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Azure.Functions.Worker.Builder; +using Microsoft.Extensions.Hosting; +using Sample.ApiGateway.AzureFunctions; + +//var builder = FunctionsApplication.CreateBuilder(args); + +//builder.ConfigureFunctionsWebApplication(); + +//// Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4. +//// builder.Services +//// .AddApplicationInsightsTelemetryWorkerService() +//// .ConfigureFunctionsApplicationInsights(); + +//builder.Build().Run(); + +var builder = FunctionsApplication.CreateBuilder(args); + +builder.ConfigureFunctionsWebApplication(); + +//builder.WebHost.ConfigureAppConfiguration(configBuilder => +//{ +// configBuilder.SetBasePath(Environment.CurrentDirectory) +// .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) +// .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", optional: true)//To specify environment +// .AddEnvironmentVariables();//You can add if you need to read environment variables. +//}); + +var startup = new Startup(builder.Configuration); +startup.ConfigureServices(builder.Services); + +var app = builder.Build(); +startup.Configure(app); + +app.Run(); diff --git a/Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.json b/Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.json new file mode 100644 index 0000000..c264e8c --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights" + } + } +} \ No newline at end of file diff --git a/Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json b/Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json new file mode 100644 index 0000000..5a956e8 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Properties/serviceDependencies.local.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "appInsights1": { + "type": "appInsights.sdk" + } + } +} \ No newline at end of file diff --git a/Sample.ApiGateway.AzureFunctions/Sample.ApiGateway.AzureFunctions.csproj b/Sample.ApiGateway.AzureFunctions/Sample.ApiGateway.AzureFunctions.csproj new file mode 100644 index 0000000..6631e63 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Sample.ApiGateway.AzureFunctions.csproj @@ -0,0 +1,37 @@ + + + net8.0 + v4 + Exe + enable + enable + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + Never + + + + + + \ No newline at end of file diff --git a/Sample.ApiGateway.AzureFunctions/Startup.cs b/Sample.ApiGateway.AzureFunctions/Startup.cs new file mode 100644 index 0000000..c6af900 --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/Startup.cs @@ -0,0 +1,47 @@ +using AspNetCore.ApiGateway; +using AspNetCore.ApiGateway.AzureFunctions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddTransient(); + + //services.AddSingleton(); + + //Api gateway + services.AddApiGateway(options => + { + options.UseResponseCaching = false; + options.ResponseCacheSettings = new ApiGatewayResponseCacheSettings + { + Duration = 60, //default for all routes + Location = ResponseCacheLocation.Any, + //Use VaryByQueryKeys to vary the response for each apiKey & routeKey + VaryByQueryKeys = new[] { "apiKey", "routeKey" } + }; + }); + } + + //public void Configure(IApplicationBuilder app, WebApplication webApplication) + public void Configure(IHost app) + { + //Api gateway + app.UseApiGateway(orchestrator => ApiOrchestration.Create(orchestrator, app)); + } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/WeatherService.cs b/Sample.ApiGateway.AzureFunctions/WeatherService.cs new file mode 100644 index 0000000..a85ea2c --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/WeatherService.cs @@ -0,0 +1,47 @@ +using AspNetCore.ApiGateway; +using AspNetCore.ApiGateway.AzureFunctions; +using Microsoft.AspNetCore.Http; +using System.Text.Json; + +namespace Sample.ApiGateway.AzureFunctions +{ + public class WeatherService : IWeatherService + { + /// + /// If you want to customize the default HttpClient or + /// use your own custom HttpClient or HttpContent + /// to hit the backend Api call, you can do this. + /// + /// + public HttpClientConfig GetClientConfig() + { + return new HttpClientConfig() + { + //customize the default HttpClient. eg. add a header. + CustomizeDefaultHttpClient = (httpClient, request) => httpClient.DefaultRequestHeaders.Add("My header", "My header value"), + //OR + //your own custom HttpClient + HttpClient = () => new HttpClient() + }; + } + + /// + /// If you want to completely customize your backend Api call, you can do this + /// + /// The api info + /// The gateway's incoming request + /// + public async Task GetForecast(ApiInfo apiInfo, HttpRequest request) + { + //Create your own implementation to hit the backend. + using (var client = new HttpClient()) + { + var response = await client.GetAsync($"{apiInfo.BaseUrl}weatherforecast/forecast"); + + response.EnsureSuccessStatusCode(); + + return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); + } + } + } +} diff --git a/Sample.ApiGateway.AzureFunctions/host.json b/Sample.ApiGateway.AzureFunctions/host.json new file mode 100644 index 0000000..391770d --- /dev/null +++ b/Sample.ApiGateway.AzureFunctions/host.json @@ -0,0 +1,21 @@ +{ + "version": "2.0", + "functionTimeout": "00:10:00", + "extensions": { + "http": { + "routePrefix": "" + }, + "https": { + "routePrefix": "" + } + }, + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + } +} \ No newline at end of file From 9a582f4395e70459047cb45baffb3fe94b2a7b5c Mon Sep 17 00:00:00 2001 From: Shantanu Negi Date: Tue, 10 Mar 2026 15:46:57 +1100 Subject: [PATCH 2/3] azure functions gateway related work. --- ApiGateway.sln | 1 + .../GatewayFunctions.cs | 25 +- .../AzureFunctionsGatewayClientTests.cs | 255 ++++++++++++++++++ .../AzureFunctionsGatewayTests.cs | 50 +--- Docs/APIGatewayAzureFunctions.png | Bin 0 -> 140615 bytes Docs/ApiGatewayAzureFunctionCall.png | Bin 0 -> 67092 bytes Docs/README_AzureFunctions.md | 193 +++++++++++++ Docs/README_MinimalAPI.md | 195 +++++++------- README.md | 11 + Sample.ApiGateway.AzureFunctions/Startup.cs | 1 - 10 files changed, 580 insertions(+), 151 deletions(-) create mode 100644 AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayClientTests.cs create mode 100644 Docs/APIGatewayAzureFunctions.png create mode 100644 Docs/ApiGatewayAzureFunctionCall.png create mode 100644 Docs/README_AzureFunctions.md diff --git a/ApiGateway.sln b/ApiGateway.sln index aea08b6..6a5d94e 100644 --- a/ApiGateway.sln +++ b/ApiGateway.sln @@ -38,6 +38,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md Docs\README_ActionFilters.md = Docs\README_ActionFilters.md Docs\README_Authorization.md = Docs\README_Authorization.md + Docs\README_AzureFunctions.md = Docs\README_AzureFunctions.md Docs\README_ConfigSettings.md = Docs\README_ConfigSettings.md Docs\README_Customizations.md = Docs\README_Customizations.md Docs\README_EventSourcing.md = Docs\README_EventSourcing.md diff --git a/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs b/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs index 4d1257f..5f3d86d 100644 --- a/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs +++ b/AspNetCore.ApiGateway.AzureFunctions/GatewayFunctions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -19,12 +20,12 @@ public GatewayFunctions(IApiGatewayRequestProcessor requestProcessor, ILogger HeadAsync([HttpTrigger(AuthorizationLevel.Function, "head", Route = "api/Gateway/{apiKey}/{routeKey}")] + public async Task HeadAsync([HttpTrigger(AuthorizationLevel.Function, "head", Route = "api/Gateway/{apiKey}/{routeKey}")] HttpRequest request, string apiKey, string routeKey, string parameters = null) { _logger?.LogInformation("C# HTTP trigger function processed a request."); - return Results.Ok(await _requestProcessor.ProcessAsync( + return new OkObjectResult(await _requestProcessor.ProcessAsync( apiKey, routeKey, request, @@ -36,12 +37,12 @@ public async Task HeadAsync([HttpTrigger(AuthorizationLevel.Function, " } [Function("ApiGateway-Get")] - public async Task GetAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "api/Gateway/{apiKey}/{routeKey}")] + public async Task GetAsync([HttpTrigger(AuthorizationLevel.Function, "get", Route = "api/Gateway/{apiKey}/{routeKey}")] HttpRequest request, string apiKey, string routeKey, string parameters = null) { _logger?.LogInformation("C# HTTP trigger function processed a request."); - return Results.Ok(await _requestProcessor.ProcessAsync( + return new OkObjectResult(await _requestProcessor.ProcessAsync( apiKey, routeKey, request, @@ -53,7 +54,7 @@ public async Task GetAsync([HttpTrigger(AuthorizationLevel.Function, "g } [Function("ApiGateway-Post")] - public async Task PostAsync([HttpTrigger(AuthorizationLevel.Function, "post", Route = "api/Gateway/{apiKey}/{routeKey}")] + public async Task PostAsync([HttpTrigger(AuthorizationLevel.Function, "post", Route = "api/Gateway/{apiKey}/{routeKey}")] HttpRequest request, string apiKey, string routeKey, string parameters = null) { _logger?.LogInformation("C# HTTP trigger function processed a request."); @@ -61,7 +62,7 @@ public async Task PostAsync([HttpTrigger(AuthorizationLevel.Function, " string requestBody = await new StreamReader(request.Body).ReadToEndAsync(); object requestObj = JsonConvert.DeserializeObject(requestBody); - return Results.Ok(await _requestProcessor.ProcessAsync( + return new OkObjectResult(await _requestProcessor.ProcessAsync( apiKey, routeKey, request, @@ -73,7 +74,7 @@ public async Task PostAsync([HttpTrigger(AuthorizationLevel.Function, " } [Function("ApiGateway-Put")] - public async Task PutAsync([HttpTrigger(AuthorizationLevel.Function, "put", Route = "api/Gateway/{apiKey}/{routeKey}")] + public async Task PutAsync([HttpTrigger(AuthorizationLevel.Function, "put", Route = "api/Gateway/{apiKey}/{routeKey}")] HttpRequest request, string apiKey, string routeKey, string parameters = null) { _logger?.LogInformation("C# HTTP trigger function processed a request."); @@ -81,7 +82,7 @@ public async Task PutAsync([HttpTrigger(AuthorizationLevel.Function, "p string requestBody = await new StreamReader(request.Body).ReadToEndAsync(); object requestObj = JsonConvert.DeserializeObject(requestBody); - return Results.Ok(await _requestProcessor.ProcessAsync( + return new OkObjectResult(await _requestProcessor.ProcessAsync( apiKey, routeKey, request, @@ -93,7 +94,7 @@ public async Task PutAsync([HttpTrigger(AuthorizationLevel.Function, "p } [Function("ApiGateway-Patch")] - public async Task PatchAsync([HttpTrigger(AuthorizationLevel.Function, "patch", Route = "api/Gateway/{apiKey}/{routeKey}")] + public async Task PatchAsync([HttpTrigger(AuthorizationLevel.Function, "patch", Route = "api/Gateway/{apiKey}/{routeKey}")] HttpRequest request, string apiKey, string routeKey, string parameters = null) { _logger?.LogInformation("C# HTTP trigger function processed a request."); @@ -109,7 +110,7 @@ public async Task PatchAsync([HttpTrigger(AuthorizationLevel.Function, var patch = System.Text.Json.JsonSerializer.Deserialize(body, options); - return Results.Ok(await _requestProcessor.ProcessAsync( + return new OkObjectResult(await _requestProcessor.ProcessAsync( apiKey, routeKey, request, @@ -126,12 +127,12 @@ public async Task PatchAsync([HttpTrigger(AuthorizationLevel.Function, } [Function("ApiGateway-Delete")] - public async Task DeleteAsync([HttpTrigger(AuthorizationLevel.Function, "delete", Route = "api/Gateway/{apiKey}/{routeKey}")] + public async Task DeleteAsync([HttpTrigger(AuthorizationLevel.Function, "delete", Route = "api/Gateway/{apiKey}/{routeKey}")] HttpRequest request, string apiKey, string routeKey, string parameters = null) { _logger?.LogInformation("C# HTTP trigger function processed a request."); - return Results.Ok(await _requestProcessor.ProcessAsync( + return new OkObjectResult(await _requestProcessor.ProcessAsync( apiKey, routeKey, request, diff --git a/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayClientTests.cs b/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayClientTests.cs new file mode 100644 index 0000000..0ad7767 --- /dev/null +++ b/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayClientTests.cs @@ -0,0 +1,255 @@ +using AspNetCore.ApiGateway.Client; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Learn.AzureFunctionsTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace AspNetCore.ApiGateway.Tests +{ + [Collection("Azure Functions Gateway")] + public class AzureFunctionsGatewayClientTests : IClassFixture + { + private readonly IServiceProvider _serviceProvider; + + public AzureFunctionsGatewayClientTests(AzureFunctionsAPIInitialize apiInit, FunctionFixture fixture) + { + IServiceCollection services = new ServiceCollection(); + + //Wire up the Client for dependency injection using extension + services.AddApiGatewayClient(settings => settings.ApiGatewayBaseUrl = "http://localhost:7055"); + + _serviceProvider = services.BuildServiceProvider(); + } + + [Fact] + public async Task Test_Get_Pass() + { + //Arrange + + //Get Client from dependency injection container + var client = _serviceProvider.GetRequiredService(); + + var parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "forecast", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + //Act + var forecasts = await client.GetAsync(parameters); + + //Assert + Assert.True(forecasts.Length > 0); + } + + [Fact] + public async Task Test_Get_WithParam_Pass() + { + //Arrange + + //Get Client from dependency injection container + var client = _serviceProvider.GetRequiredService(); + + var parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "type", + Parameters = "3", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + //Act + var weatherType = await client.GetAsync(parameters); + + //Assert + Assert.NotNull(weatherType); + Assert.True(!string.IsNullOrEmpty(weatherType.Type)); + + ////Arrange + //parameters = new ApiGatewayParameters + //{ + // ApiKey = "weatherservice", + // RouteKey = "typewithparams", + // Parameters = "index=3", + // Headers = new Dictionary + // { + // { "Authorization", "bearer wq298cjwosos==" } + // } + //}; + + ////Act + //weatherType = await client.GetAsync(parameters); + + ////Assert + //Assert.NotNull(weatherType); + //Assert.True(!string.IsNullOrEmpty(weatherType.Type)); + } + + [Fact] + public async Task Test_Post_Pass() + { + //Arrange + + //Get Client from dependency injection container + var client = _serviceProvider.GetRequiredService(); + + AddWeatherTypeRequest payload = new AddWeatherTypeRequest + { + WeatherType = "Windy" + }; + + var parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "add", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + //Act + var weatherTypes = await client.PostAsync(parameters, payload); + + //Assert + Assert.True(weatherTypes.Last() == "Windy"); + } + + [Fact] + public async Task Test_Put_Pass() + { + //Arrange + + //Get Client from dependency injection container + var client = _serviceProvider.GetRequiredService(); + + UpdateWeatherTypeRequest payload = new UpdateWeatherTypeRequest + { + WeatherType = "Coooooooool", + Index = 3 + }; + + var parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "update", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + //Act + await client.PutAsync(parameters, payload); + + parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "types", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + var weatherTypes = await client.GetAsync(parameters); + + //Assert + Assert.True(weatherTypes[3] == "Coooooooool"); + } + + [Fact] + public async Task Test_Patch_Pass() + { + //Arrange + + //Get Client from dependency injection container + var client = _serviceProvider.GetRequiredService(); + + JsonPatchDocument jsonPatch = new JsonPatchDocument(); + jsonPatch.Add(x => x.TemperatureC, 35); + + var parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "patch", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + //Act + var weatherForecast = await client.PatchAsync(parameters, jsonPatch, true); + + //Assert + Assert.True(weatherForecast.TemperatureC == 35); + } + + [Fact] + public async Task Test_Delete_Pass() + { + //Arrange + + //Get Client from dependency injection container + var client = _serviceProvider.GetRequiredService(); + + var parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "remove", + Parameters = "0", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + //Act + await client.DeleteAsync(parameters); + + parameters = new ApiGatewayParameters + { + ApiKey = "weatherservice", + RouteKey = "types", + Headers = new Dictionary + { + { "Authorization", "bearer wq298cjwosos==" } + } + }; + + var weatherTypes = await client.GetAsync(parameters); + + //Assert + Assert.DoesNotContain(weatherTypes, x => x == "Freezing"); + } + + //[Fact] + //public async Task Test_GetOrchestration_Pass() + //{ + // //Arrange + + // //Get Client from dependency injection container + // var client = _serviceProvider.GetRequiredService(); + + // var parameters = new ApiGatewayParameters(); + + // //Act + // var orchestrations = await client.GetOrchestrationAsync(parameters, true); + + // //Assert + // Assert.Equal(2, orchestrations.Count()); + //} + } +} diff --git a/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs b/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs index 939ec07..4d30bb6 100644 --- a/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs +++ b/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs @@ -8,9 +8,7 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Net.Http.Json; -using System.Text; using System.Text.Json; using System.Threading.Tasks; using Xunit; @@ -32,7 +30,7 @@ public void Configure(FunctionTestConfigurationBuilder builder) } // Define a collection for the FunctionFixture - [CollectionDefinition("Function collection")] + [CollectionDefinition("Azure Functions Gateway")] public class FunctionCollection : ICollectionFixture> { // This class has no code, and is never created. Its purpose is just to be the place to apply [CollectionDefinition] and all the ICollectionFixture<> interfaces. @@ -60,17 +58,14 @@ public AzureFunctionsAPIInitialize() } } - [Collection("Function collection")] + [Collection("Azure Functions Gateway")] public class AzureFunctionsGatewayTests : IClassFixture { - //readonly AzureFunctionsAPIInitialize _apiInit; private readonly HttpClient _httpClient; public AzureFunctionsGatewayTests(AzureFunctionsAPIInitialize apiInit, FunctionFixture fixture) { - //_apiInit = apiInit; _httpClient = fixture.Client; - //_httpClient.BaseAddress = new Uri("https://localhost:7055"); } [Fact] @@ -140,20 +135,20 @@ public async Task Test_Get_WithParam_Pass() Assert.NotNull(weatherType); Assert.True(!string.IsNullOrEmpty(weatherType.Type)); - //client = _httpClient; + client = _httpClient; - //gatewayUrl = "api/Gateway/weatherservice/typewithparams?parameters=index=3"; + gatewayUrl = "api/Gateway/weatherservice/typewithparams?parameters=index=3"; - //response = await client.GetAsync(gatewayUrl); + response = await client.GetAsync(gatewayUrl); - //response.EnsureSuccessStatusCode(); + response.EnsureSuccessStatusCode(); - //strResponse = await response.Content.ReadAsStringAsync(); + strResponse = await response.Content.ReadAsStringAsync(); - //weatherType = JsonSerializer.Deserialize(strResponse); + weatherType = JsonSerializer.Deserialize(strResponse); - //Assert.NotNull(weatherType); - //Assert.True(!string.IsNullOrEmpty(weatherType.Type)); + Assert.NotNull(weatherType); + Assert.True(!string.IsNullOrEmpty(weatherType.Type)); } [Fact] @@ -172,19 +167,6 @@ public async Task Test_Post_Pass() // POST JSON HttpResponseMessage response = await client.PostAsJsonAsync(gatewayUrl, request); - //var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); - //content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - //var httprequest = new HttpRequestMessage - //{ - // //RequestUri = new Uri($"{client.BaseAddress}{gatewayUrl}"), - // RequestUri = new Uri(gatewayUrl), - // Content = content, - // Method = HttpMethod.Post - //}; - - //var response = await client.SendAsync(httprequest); - response.EnsureSuccessStatusCode(); var weatherTypes = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync()); @@ -209,18 +191,6 @@ public async Task Test_Put_Pass() // POST JSON HttpResponseMessage response = await client.PutAsJsonAsync(gatewayUrl, request); - //var content = new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"); - //content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - //var httprequest = new HttpRequestMessage - //{ - // RequestUri = new Uri(gatewayUrl), - // Content = content, - // Method = HttpMethod.Put - //}; - - //var response = await client.SendAsync(httprequest); - response.EnsureSuccessStatusCode(); //Gateway API url with Api key and Route key diff --git a/Docs/APIGatewayAzureFunctions.png b/Docs/APIGatewayAzureFunctions.png new file mode 100644 index 0000000000000000000000000000000000000000..e8d62db87cd5f9b0db7fe053f49a824cdc4883af GIT binary patch literal 140615 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 literal 0 HcmV?d00001 diff --git a/Docs/ApiGatewayAzureFunctionCall.png b/Docs/ApiGatewayAzureFunctionCall.png new file mode 100644 index 0000000000000000000000000000000000000000..d0635760c4e88d758164ca858455e2e465db99aa GIT binary patch literal 67092 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 z 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

YE0HHb??){w#(j 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 + +## Implementation + +In the solution, there are 2 **back end APIs** : **Weather API** and **Stock API**. + +For eg. To make a GET call to the backend API, you would set up an Api and a GET Route in your Gateway API's **Api Orchestrator**. + +Then, the client app would make a GET call to the Gateway API which would make a GET call to the backend API using HttpClient. + +## In your Backend API + +Let us say you have a GET endpoint like this. + +* **HTTP GET - /weatherforecast/forecast** + +## In your Gateway API + +You add a Route for the backend GET call in the **Api Orchrestrator**. + +You create a backend API with ApiKey called **weatherservice** for eg. +And, a Route with RouteKey called **forecast** for eg. + +So, the call to the Gateway would become: + +* **HTTP GET - /weatherservice/forecast** + +**Add a reference to the package and...** + +* Create an **Api Orchestration**. + + You create an Api (weatherservice) and add a Route (forecast). + +```C# +public static class ApiOrchestration +{ + public static void Create(IApiOrchestrator orchestrator, IHost app) + { + var serviceProvider = app.Services; + + var weatherService = serviceProvider.GetRequiredService(); + + var weatherApiClientConfig = weatherService.GetClientConfig(); + + orchestrator.AddApi("weatherservice", "https://localhost:5003/") + //Get + .AddRoute("forecast", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/forecast", ResponseType = typeof(IEnumerable) }) + //Head + .AddRoute("forecasthead", GatewayVerb.HEAD, new RouteInfo { Path = "weatherforecast/forecast" }) + //Get with params + .AddRoute("typewithparams", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/{index}" }) + //Get using custom HttpClient + .AddRoute("types", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types", ResponseType = typeof(string[]), HttpClientConfig = weatherApiClientConfig }) + //Get with param using custom HttpClient + .AddRoute("type", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/", ResponseType = typeof(WeatherTypeResponse), HttpClientConfig = weatherApiClientConfig }) + //Get using custom implementation + .AddRoute("forecast-custom", GatewayVerb.GET, weatherService.GetForecast) + //Post + .AddRoute("add", GatewayVerb.POST, new RouteInfo { Path = "weatherforecast/types/add", RequestType = typeof(AddWeatherTypeRequest), ResponseType = typeof(string[]) }) + //Put + .AddRoute("update", GatewayVerb.PUT, new RouteInfo { Path = "weatherforecast/types/update", RequestType = typeof(UpdateWeatherTypeRequest), ResponseType = typeof(string[]) }) + //Patch + .AddRoute("patch", GatewayVerb.PATCH, new RouteInfo { Path = "weatherforecast/forecast/patch", ResponseType = typeof(WeatherForecast) }) + //Delete + .AddRoute("remove", GatewayVerb.DELETE, new RouteInfo { Path = "weatherforecast/types/remove/", ResponseType = typeof(string[]) }) + .AddApi("stockservice", "https://localhost:5005/") + .AddRoute("stocks", GatewayVerb.GET, new RouteInfo { Path = "stock", ResponseType = typeof(IEnumerable) }) + .AddRoute("stock", GatewayVerb.GET, new RouteInfo { Path = "stock/", ResponseType = typeof(StockQuote) }); + } +} +``` + +* Hook up in Startup.cs + +```C# +public void ConfigureServices(IServiceCollection services) +{ + services.AddTransient(); + + //services.AddSingleton(); + + //Api gateway + services.AddApiGateway(options => + { + options.UseResponseCaching = false; + options.ResponseCacheSettings = new ApiGatewayResponseCacheSettings + { + Duration = 60, //default for all routes + Location = ResponseCacheLocation.Any, + //Use VaryByQueryKeys to vary the response for each apiKey & routeKey + VaryByQueryKeys = new[] { "apiKey", "routeKey" } + }; + }); +} + +public void Configure(IHost app) +{ + //Api gateway + app.UseApiGateway(orchestrator => ApiOrchestration.Create(orchestrator, app)); +} +``` + +The Functions start as shown below: + +![API Gateway Azure Functions](/Docs/APIGatewayAzureFunctions.png) + +To call the **forecast** Route on the **weather service** Api, + +you can enter the **Api key** and **Route key** into Swagger as below: + +![API Gateway Minimal Swagger](/Docs/ApiGatewayAzureFunctionCall.png) + +This will hit the **weatherforecast/forecast** endpoint on the backend Weather API. + +### Using appsettings.json + +If you want, you can keep the **ApiKey, RouteKey, backend API base urls and Route path**, + +in the **appsettings.json**, read it using a Config Service, + +and pass it to the Api Orchestrator in the Create method. + +Read [**more**](/Docs/README_ConfigSettings.md). + +### Verbs usage & Routing + +You can check out how the Api Gateway supported Verbs are used & Routing below. + +### [Verbs Usage & Routing](Docs/README_VERBS.md) + +### Customizations + +* Create a new or customize the default **HttpClient** used by all the routes, to hit the backend Api. +* Create a new or customize the default **HttpClient** which each route uses to hit the backend Api. +* Use your own custom implementation to hit the backend Api. + +For **Request aggregation**, see this section. + +### [Customizations](Docs/README_Customizations.md) + +### Load Balancing + +### [Load Balancing](Docs/README_LoadBalancing.md) + +## Clients + +The Api Gateway supports a fixed set of endpoints. + +All routes go through these endpoints. + +The Client application has to talk to these endpoints of the Api Gateway. + +A Client library is provided for: + +* [**.Net**](Docs/README_Net_Client.md) + +* [**Typescript**](Docs/README_Typescript_Client.md) + +## Making requests to Azure Functions Gateway + +These [**Tests**](/AspNetCore.ApiGateway.Tests/AzureFunctionsGatewayTests.cs) show how to make calls to the Azure Functions Gateway. \ No newline at end of file diff --git a/Docs/README_MinimalAPI.md b/Docs/README_MinimalAPI.md index cbfa97f..da8ef80 100644 --- a/Docs/README_MinimalAPI.md +++ b/Docs/README_MinimalAPI.md @@ -66,127 +66,126 @@ So, the call to the Gateway would become: You create an Api (weatherservice) and add a Route (forecast). ```C# - public static class ApiOrchestration +public static class ApiOrchestration +{ + public static void Create(IApiOrchestrator orchestrator, IApplicationBuilder app) { - public static void Create(IApiOrchestrator orchestrator, IApplicationBuilder app) - { - var serviceProvider = app.ApplicationServices; - - var weatherService = serviceProvider.GetService(); - - var weatherApiClientConfig = weatherService.GetClientConfig(); - - orchestrator.AddApi("weatherservice", "https://localhost:5003/") - //Get - .AddRoute("forecast", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/forecast", ResponseType = typeof(IEnumerable) }) - //Head - .AddRoute("forecasthead", GatewayVerb.HEAD, new RouteInfo { Path = "weatherforecast/forecast" }) - //Get with params - .AddRoute("typewithparams", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/{index}" }) - //Get using custom HttpClient - .AddRoute("types", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types", ResponseType = typeof(string[]), HttpClientConfig = weatherApiClientConfig }) - //Get with param using custom HttpClient - .AddRoute("type", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/", ResponseType = typeof(WeatherTypeResponse), HttpClientConfig = weatherApiClientConfig }) - //Get using custom implementation - .AddRoute("forecast-custom", GatewayVerb.GET, weatherService.GetForecast) - //Post - .AddRoute("add", GatewayVerb.POST, new RouteInfo { Path = "weatherforecast/types/add", RequestType = typeof(AddWeatherTypeRequest), ResponseType = typeof(string[]) }) - //Put - .AddRoute("update", GatewayVerb.PUT, new RouteInfo { Path = "weatherforecast/types/update", RequestType = typeof(UpdateWeatherTypeRequest), ResponseType = typeof(string[]) }) - //Patch - .AddRoute("patch", GatewayVerb.PATCH, new RouteInfo { Path = "weatherforecast/forecast/patch", ResponseType = typeof(WeatherForecast) }) - //Delete - .AddRoute("remove", GatewayVerb.DELETE, new RouteInfo { Path = "weatherforecast/types/remove/", ResponseType = typeof(string[]) }) - .AddApi("stockservice", "https://localhost:5005/") - .AddRoute("stocks", GatewayVerb.GET, new RouteInfo { Path = "stock", ResponseType = typeof(IEnumerable) }) - .AddRoute("stock", GatewayVerb.GET, new RouteInfo { Path = "stock/", ResponseType = typeof(StockQuote) }); - } + var serviceProvider = app.ApplicationServices; + + var weatherService = serviceProvider.GetService(); + + var weatherApiClientConfig = weatherService.GetClientConfig(); + + orchestrator.AddApi("weatherservice", "https://localhost:5003/") + //Get + .AddRoute("forecast", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/forecast", ResponseType = typeof(IEnumerable) }) + //Head + .AddRoute("forecasthead", GatewayVerb.HEAD, new RouteInfo { Path = "weatherforecast/forecast" }) + //Get with params + .AddRoute("typewithparams", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/{index}" }) + //Get using custom HttpClient + .AddRoute("types", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types", ResponseType = typeof(string[]), HttpClientConfig = weatherApiClientConfig }) + //Get with param using custom HttpClient + .AddRoute("type", GatewayVerb.GET, new RouteInfo { Path = "weatherforecast/types/", ResponseType = typeof(WeatherTypeResponse), HttpClientConfig = weatherApiClientConfig }) + //Get using custom implementation + .AddRoute("forecast-custom", GatewayVerb.GET, weatherService.GetForecast) + //Post + .AddRoute("add", GatewayVerb.POST, new RouteInfo { Path = "weatherforecast/types/add", RequestType = typeof(AddWeatherTypeRequest), ResponseType = typeof(string[]) }) + //Put + .AddRoute("update", GatewayVerb.PUT, new RouteInfo { Path = "weatherforecast/types/update", RequestType = typeof(UpdateWeatherTypeRequest), ResponseType = typeof(string[]) }) + //Patch + .AddRoute("patch", GatewayVerb.PATCH, new RouteInfo { Path = "weatherforecast/forecast/patch", ResponseType = typeof(WeatherForecast) }) + //Delete + .AddRoute("remove", GatewayVerb.DELETE, new RouteInfo { Path = "weatherforecast/types/remove/", ResponseType = typeof(string[]) }) + .AddApi("stockservice", "https://localhost:5005/") + .AddRoute("stocks", GatewayVerb.GET, new RouteInfo { Path = "stock", ResponseType = typeof(IEnumerable) }) + .AddRoute("stock", GatewayVerb.GET, new RouteInfo { Path = "stock/", ResponseType = typeof(StockQuote) }); } +} ``` * Hook up in Startup.cs ```C# - public void ConfigureServices(IServiceCollection services) +public void ConfigureServices(IServiceCollection services) +{ + //Hook up GatewayHub using SignalR + services.AddCors(options => options.AddPolicy("CorsPolicy", builder => { - //Hook up GatewayHub using SignalR - services.AddCors(options => options.AddPolicy("CorsPolicy", builder => - { - builder - .AllowAnyMethod() - .AllowAnyHeader() - .AllowAnyOrigin(); - })); + builder + .AllowAnyMethod() + .AllowAnyHeader() + .AllowAnyOrigin(); + })); - services.AddTransient(); + services.AddTransient(); - //services.AddSingleton(); + //services.AddSingleton(); - //Api gateway - services.AddApiGateway(options => + //Api gateway + services.AddApiGateway(options => + { + options.UseResponseCaching = false; + options.ResponseCacheSettings = new ApiGatewayResponseCacheSettings { - options.UseResponseCaching = false; - options.ResponseCacheSettings = new ApiGatewayResponseCacheSettings - { - Duration = 60, //default for all routes - Location = ResponseCacheLocation.Any, - //Use VaryByQueryKeys to vary the response for each apiKey & routeKey - VaryByQueryKeys = new[] { "apiKey", "routeKey" } - }; - }); - - services.AddEndpointsApiExplorer(); // Required for Minimal APIs - services.AddSwaggerGen(c => + Duration = 60, //default for all routes + Location = ResponseCacheLocation.Any, + //Use VaryByQueryKeys to vary the response for each apiKey & routeKey + VaryByQueryKeys = new[] { "apiKey", "routeKey" } + }; + }); + + services.AddEndpointsApiExplorer(); // Required for Minimal APIs + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { - c.SwaggerDoc("v1", new OpenApiInfo - { - Title = "My Minimal API Gateway", - Description = "A simple example of ASP.NET Core Minimal API with Swagger", - Version = "v1" - }); + Title = "My Minimal API Gateway", + Description = "A simple example of ASP.NET Core Minimal API with Swagger", + Version = "v1" }); + }); - services.AddMvc(); - } + services.AddMvc(); +} - //public void Configure(IApplicationBuilder app, WebApplication webApplication) - public void Configure(IApplicationBuilder app) +public void Configure(IApplicationBuilder app) +{ + app.UseSwagger(); + app.UseSwaggerUI(c => { - app.UseSwagger(); - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Minimal API Gateway V1"); - // Optional: set the UI to load at the app root URL - // c.RoutePrefix = string.Empty; - }); + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My Minimal API Gateway V1"); + // Optional: set the UI to load at the app root URL + // c.RoutePrefix = string.Empty; + }); - //webApplication. - app.UseCors("CorsPolicy"); + //webApplication. + app.UseCors("CorsPolicy"); - //Api gateway - app.UseApiGateway(orchestrator => ApiOrchestration.Create(orchestrator, app)); + //Api gateway + app.UseApiGateway(orchestrator => ApiOrchestration.Create(orchestrator, app)); - app.UseHttpsRedirection(); + app.UseHttpsRedirection(); - app.UseRouting(); + app.UseRouting(); - app.UseAuthorization(); + app.UseAuthorization(); - app.UseEndpoints(endpoints => - { - // Api Gateway Minimal API - endpoints.MapApiGatewayHead(); - endpoints.MapApiGatewayGet(); - endpoints.MapApiGatewayPost(); - endpoints.MapApiGatewayPut(); - endpoints.MapApiGatewayDelete(); - endpoints.MapApiGatewayPatch(); - endpoints.MapApiGatewayGetOrchestration(); - - // Maps all the endpoints - //endpoints.MapAllApiGatewayEndpoints(); - }); - } + app.UseEndpoints(endpoints => + { + // Api Gateway Minimal API + endpoints.MapApiGatewayHead(); + endpoints.MapApiGatewayGet(); + endpoints.MapApiGatewayPost(); + endpoints.MapApiGatewayPut(); + endpoints.MapApiGatewayDelete(); + endpoints.MapApiGatewayPatch(); + endpoints.MapApiGatewayGetOrchestration(); + + // Maps all the endpoints + //endpoints.MapAllApiGatewayEndpoints(); + }); +} ``` The Gateway Swagger appears as shown below: diff --git a/README.md b/README.md index 9a61880..644782e 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ The framework is available as a * **RESTful Microservice Facade.** * **RESTful Minimal API Facade.** +* **Azure Functions Facade.** ## Gateway as a RESTful Minimal API Facade @@ -50,6 +51,16 @@ The API Gateway has be implemented as a Minimal API, which acts as a facade over Read [**more**](/Docs/README_MinimalAPI.md) +## Gateway as a Azure Functions Facade + +The API Gateway has be implemented as Azure Functions, which acts as a facade over your backend microservices. + +Real easy to integrate the library into a Functions project. + +Seamlessly transition you Azure Functions knowledge to the Api Gateway. + +Read [**more**](/Docs/README_AzureFunctions.md) + ## Gateway as a RESTful Microservice Facade ## Features diff --git a/Sample.ApiGateway.AzureFunctions/Startup.cs b/Sample.ApiGateway.AzureFunctions/Startup.cs index c6af900..9db375b 100644 --- a/Sample.ApiGateway.AzureFunctions/Startup.cs +++ b/Sample.ApiGateway.AzureFunctions/Startup.cs @@ -37,7 +37,6 @@ public void ConfigureServices(IServiceCollection services) }); } - //public void Configure(IApplicationBuilder app, WebApplication webApplication) public void Configure(IHost app) { //Api gateway From 20430fec0f4186fd113b2d920a5502a57bcf0612 Mon Sep 17 00:00:00 2001 From: Shantanu Negi Date: Tue, 10 Mar 2026 15:50:46 +1100 Subject: [PATCH 3/3] updated readme. --- Docs/README_AzureFunctions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Docs/README_AzureFunctions.md b/Docs/README_AzureFunctions.md index b4b8348..443dcba 100644 --- a/Docs/README_AzureFunctions.md +++ b/Docs/README_AzureFunctions.md @@ -6,7 +6,7 @@ You can hook up **Authorization** etc. just like any Azure Function. |Packages|Version|Downloads| |---------------------------|:---:|:---:| -|*Veritas.AspNetCore.ApiGateway.Minimal*|[![Nuget Version](https://img.shields.io/nuget/v/Veritas.AspNetCore.ApiGateway.Minimal)](https://www.nuget.org/packages/Veritas.AspNetCore.ApiGateway.Minimal)|[![Downloads count](https://img.shields.io/nuget/dt/Veritas.AspNetCore.ApiGateway.Minimal)](https://www.nuget.org/packages/Veritas.AspNetCore.ApiGateway.Minimal)| +|*Veritas.AspNetCore.ApiGateway.AzureFunctions*|[![Nuget Version](https://img.shields.io/nuget/v/Veritas.AspNetCore.ApiGateway.AzureFunctions)](https://www.nuget.org/packages/Veritas.AspNetCore.ApiGateway.AzureFunctions)|[![Downloads count](https://img.shields.io/nuget/dt/Veritas.AspNetCore.ApiGateway.AzureFunctions)](https://www.nuget.org/packages/Veritas.AspNetCore.ApiGateway.AzureFunctions)| |*AspNetCore.ApiGateway.Client*|[![Nuget Version](https://img.shields.io/nuget/v/AspNetCore.ApiGateway.Client)](https://www.nuget.org/packages/AspNetCore.ApiGateway.Client)|[![Downloads count](https://img.shields.io/nuget/dt/AspNetCore.ApiGateway.Client)](https://www.nuget.org/packages/AspNetCore.ApiGateway.Client)| |*ts-aspnetcore-apigateway-client*|[![NPM Version](https://img.shields.io/npm/v/ts-aspnetcore-apigateway-client)](https://www.npmjs.com/package/ts-aspnetcore-apigateway-client)|[![Downloads count](https://img.shields.io/npm/dy/ts-aspnetcore-apigateway-client)](https://www.npmjs.com/package/ts-aspnetcore-apigateway-client)| @@ -21,7 +21,7 @@ You can hook up **Authorization** etc. just like any Azure Function. * .NET * Typescript -### Your **Gateway API** is a minimal API host which exposes endpoints that are a **facade** over your backend API endpoints. +### Your **Gateway API** are Azure Functions which exposes endpoints that are a **facade** over your backend API endpoints. * HEAD * GET @@ -140,7 +140,7 @@ To call the **forecast** Route on the **weather service** Api, you can enter the **Api key** and **Route key** into Swagger as below: -![API Gateway Minimal Swagger](/Docs/ApiGatewayAzureFunctionCall.png) +![API Gateway Postman call](/Docs/ApiGatewayAzureFunctionCall.png) This will hit the **weatherforecast/forecast** endpoint on the backend Weather API.