extensions/Worker.Extensions.Http.AspNetCore/src/AspNetMiddleware/FunctionsEndpointDataSource.cs (110 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
namespace Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.AspNetMiddleware
{
internal class FunctionsEndpointDataSource : EndpointDataSource
{
private const string FunctionsApplicationDirectoryKey = "FUNCTIONS_APPLICATION_DIRECTORY";
private const string HostJsonFileName = "host.json";
private const string DefaultRoutePrefix = "api";
private readonly IFunctionMetadataProvider _functionMetadataProvider;
private readonly object _lock = new();
private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
AllowTrailingCommas = true,
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
};
private List<Endpoint>? _endpoints;
public FunctionsEndpointDataSource(IFunctionMetadataProvider functionMetadataProvider)
{
_functionMetadataProvider = functionMetadataProvider ?? throw new ArgumentNullException(nameof(functionMetadataProvider));
}
public override IReadOnlyList<Endpoint> Endpoints
{
get
{
if (_endpoints is null)
{
lock (_lock)
{
_endpoints ??= BuildEndpoints();
}
}
return _endpoints;
}
}
private List<Endpoint> BuildEndpoints()
{
List<Endpoint> endpoints = new List<Endpoint>();
string scriptRoot = Environment.GetEnvironmentVariable(FunctionsApplicationDirectoryKey) ??
throw new InvalidOperationException("Cannot determine script root directory.");
var metadata = _functionMetadataProvider.GetFunctionMetadataAsync(scriptRoot).GetAwaiter().GetResult();
string routePrefix = GetRoutePrefixFromHostJson(scriptRoot) ?? DefaultRoutePrefix;
foreach (var functionMetadata in metadata)
{
var endpoint = MapHttpFunction(functionMetadata, routePrefix);
if (endpoint is not null)
{
endpoints.Add(endpoint);
}
}
return endpoints;
}
public override IChangeToken GetChangeToken() => NullChangeToken.Singleton;
internal static Endpoint? MapHttpFunction(IFunctionMetadata functionMetadata, string routePrefix)
{
if (functionMetadata.RawBindings is null)
{
return null;
}
var functionName = functionMetadata.Name ?? string.Empty;
int order = 0;
foreach (var binding in functionMetadata.RawBindings)
{
var functionBinding = JsonSerializer.Deserialize<FunctionHttpBinding>(binding, _jsonSerializerOptions);
if (functionBinding is null)
{
continue;
}
if (functionBinding.Type.Equals("httpTrigger", StringComparison.OrdinalIgnoreCase))
{
string routeSuffix = functionBinding.Route ?? functionName;
string route = $"{routePrefix}/{routeSuffix}";
var pattern = RoutePatternFactory.Parse(route);
var endpointBuilder = new RouteEndpointBuilder(FunctionsHttpContextExtensions.InvokeFunctionAsync, pattern, order++)
{
DisplayName = functionName
};
var methods = functionBinding.Methods ?? [];
endpointBuilder.Metadata.Add(new HttpMethodMetadata(methods));
// no need to look at other bindings for this function
return endpointBuilder.Build();
}
}
return null;
}
private static string? GetRoutePrefixFromHostJson(string scriptRoot)
{
string hostJsonPath = Path.Combine(scriptRoot, HostJsonFileName);
if (!File.Exists(hostJsonPath))
{
return null;
}
string hostJsonString = File.ReadAllText(hostJsonPath);
return GetRoutePrefix(hostJsonString);
}
internal static string? GetRoutePrefix(string hostJsonString)
{
var hostJson = JsonSerializer.Deserialize<HostJsonModel>(hostJsonString, _jsonSerializerOptions);
return hostJson?.Extensions?.Http?.RoutePrefix;
}
}
}