Azure Function HTTP Triggers / Method Routing / Open API Implementation - azure

I am currently making an API that will be hosted via Azure Functions. I'm running .net core 3.1. The way I have the project routed right now is defining the accepted methods as a parameter for the HttpTrigger then I have an if statement for determining how the endpoint was called. I am attempting to use the OpenAPI package to create API definitions, but when I assign Methods to the function, the Swagger document only picks up the first Method listed (PUT). I am unsure of the intended structure / usage of endpoints that have multiple possible request methods.
See code below. (OpenAPI tags are placeholder descriptions)
namespace Dyn.Sync.Func.PractifiSync
{
public class Prospect
{
[FunctionName("Prospect")]
[OpenApiOperation(operationId: "Run", tags: new[] { "name" })]
[OpenApiSecurity("function_key", SecuritySchemeType.ApiKey, Name = "code", In = OpenApiSecurityLocationType.Query)]
[OpenApiParameter(name: "name", In = ParameterLocation.Query, Required = true, Type = typeof(string), Description = "The **Name** parameter")]
[OpenApiResponseWithBody(statusCode: HttpStatusCode.OK, contentType: "text/plain", bodyType: typeof(string), Description = "The OK response")]
public async Task<IActionResult> Create([HttpTrigger(AuthorizationLevel.Anonymous, "post", "put", Route = null)] HttpRequest req, ILogger log)
{
string primarySecretsContainerName = "Main";
DynUser user = await DynAuthManager.CreateDynUserAsync(req);
DynProspect prospect = JsonSerializer.Deserialize<DynProspect>(req.Body);
PFIConnection pfiConnector = PFIConnectionsCache.GetConnection(user, DynSecretsCache.GetSecretsContainer(primarySecretsContainerName));
try
{
if (!pfiConnector.IsConnected) { await pfiConnector.Connect(); }
if (req.Method == "POST") { return await pfiConnector.CreateProspect(prospect); }
if (req.Method == "PUT") { return await pfiConnector.UpdateProspect(prospect); }
else { return new ObjectResult("Invalid method.") { StatusCode = 400 }; }
}
catch (Exception ex)
{
DynError dynError = new DynError(ex);
log.LogError(ex, "Exception " + dynError.RequestID.ToString() + " occured.");
return (IActionResult)new ExceptionResult(ex, true);
}
}
}
}
My question is this: When the swagger document is created, it only lists whatever method I defined first (in other words, it ignores the "put" method). What is the intended way to structure an API when creating it in Azure functions? I tried creating a separate method in the same class for each HTTP method that it would accept, but then I couldn't even hit the endpoint when making requests. Does microsoft want us to create a new function class for each endpoint? So then instead of:
PUT http://myapi.azure.com/api/prospect
POST http://myapi.azure.com/api/prospect
it would be:
PUT http://myapi.azure.com/api/updateprospect
POST http://myapi.azure.com/api/prospect
I should note that this will eventually live under and Azure API Management instance, which makes me even more worried to implement it in a "one function per method" fashion as when loading azure functions the way I have done it, it correctly assigns the methods in APIM and I'd prefer not to have to manually configure it.
I have been searching for documentation on this specific issue with no luck. Anyone have any ideas how Microsoft intended this to be used?
Thanks.

Related

Send JSON to restful API via Logic App and Azure Function

I have JSON file which is composed in Logic Apps, Now i need to send this JSON file to a Rest API using the Post method through Azure Function.
Does anyone know how to achieve this using PowerShell in Function if not any other languages like c# or python?
I would recommend you to upload the file to a blob storage and generate a SAS url. Then you can use azure function to call the http and send the url.
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
List<FileAndUrl> filenamesAndUrls;
if (req.Method == HttpMethod.Get)
{
filenamesAndUrls = req.RequestUri.Query
.Remove(0, 1) // remove '?' at the start of the query
.Split('&') // all comands are seperated by '&'
.Select(s =>
{
var keyValue = s.Split('=');
return new FileAndUrl() { FileName = keyValue[0], Url = keyValue[1] };
}).ToList();
}
else
{
filenamesAndUrls = await req.Content.ReadAsAsync<List<FileAndUrl>>();
}
return CreateResult(filenamesAndUrls);
}
EXAMPLE
You can use the Azure functions connector in your workflow (logic apps). Then, you just need to set the body of the request with your payload, also specify the header 'Content-Type' as 'application/json'
https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-azure-functions

ServiceStack MQ: how to populate data in RequestContext

I'm developing a JWT-based multi-tenancy system using ServiceStack. The JWT token contains shard information, and I use JwtAuthProvider to translate the JWT token to session object following instructions at http://docs.servicestack.net/jwt-authprovider.
Now, I want to use ServiceStack MQ for asynchronous processing. The MQ request needs to be aware of the shard information, so I populate the request context before executing it as follow
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
var sessionKey = SessionFeature.GetSessionKey(m.GetBody().SessionId);
var session = HostContext.TryResolve<ICacheClient>().Get<Context>(sessionKey);
req.Items[Keywords.Session] = session;
var response = ExecuteMessage(m, req);
return response;
});
Here, Context is my custom session class. This technique is stemmed from the instruction at http://docs.servicestack.net/messaging#authenticated-requests-via-mq. Since I execute the message within the context of req, I reckon that I should then be able to resolve Context as follow
container.AddScoped<Context>(c =>
{
var webRequest = HostContext.TryGetCurrentRequest();
if (webRequest != null)
{
return webRequest.SessionAs<Context>();
} else
{
return HostContext.RequestContext.Items[Keywords.Session] as Context;
}
});
However, HostContext.RequestContext.Items is always empty. So the question is, how to populate HostContext.RequestContext.Items from within message handler registration code?
I've tried to dig a little bit into ServiceStack code and found that the ExecuteMessage(IMessage dto, IRequest req) in ServiceController doesn't seem to populate data in RequestContext. For my case, it is a bit too late to get session inside service instance, as a service instance depends on some DB connections whose shard info is kept in session.
The same Request Context instance can't be resolved from the IOC. The Request Context instance is created in the MQ's RegisterHandler<T>() where you can add custom data in the IRequest.Items property, e.g:
mqServer.RegisterHandler<EmployeeAssignedToProject>(m =>
{
var req = new BasicRequest { Verb = HttpMethods.Post };
req.Items[MyKey] = MyValue; //Inject custom per-request data
//...
var response = ExecuteMessage(m, req);
return response;
});
This IRequest instance is available throughout the Request pipeline and from base.Request in your Services. It's not available from your IOC registrations so you will need to pass it in as an argument when calling your dependency, e.g:
public class MyServices : Service
{
public IDependency MyDep { get; set; }
public object Any(MyRequest request) => MyDep.Method(base.Request, request.Id);
}

Azure Durable Function (external functions)

I developed a couple of microservice using Azure functions, every service has independent use case and different programming language.
Now I have a use case to use all service in below order, So I developed one more Azure function to use all service in given order. below code running well.
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
string returnValue = string.Empty;
dynamic data = await req.Content.ReadAsStringAsync();
if (data == null)
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a value in the request body");
}
else
{
string body = data.ToString();
var transformResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.TransformServiceEndPoint, body, HttpMethod.POST);
var validationResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.ValidationServiceEndPoint, transformResult.Result.ToString(), HttpMethod.POST);
if (validationResult.Result != null && Convert.ToBoolean(validationResult.Result))
{
var encryptionResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.EncryptionServiceEndPoint, transformResult.Result.ToString(), HttpMethod.POST);
var storageResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.StorageServiceEndPoint, encryptionResult.Result.ToString(), HttpMethod.POST);
returnValue = storageResult.Result.ToString();
}
else
{
returnValue = "Validation Failed";
}
return req.CreateResponse(HttpStatusCode.OK, returnValue, "text/plain");
}
}
Question
If every microservice takes 1 min to execution, I have to wait ~4min in my Super Service and billed for 4+ min. (We don't need to pay for waiting time :) https://www.youtube.com/watch?v=lVwWlZ-4Nfs)
I want to use Azure Durable functions here but didn't get any method to call external url.
Please help me or suggest a better solution.
Thanks In Advance
Durable Orchestration Functions don't work with arbitrary HTTP endpoints. Instead, you need to create individual functions as Activity-triggered.
Orchestration will use message queues behind the scenes rather than HTTP. HTTP is request-response in nature, so you have to keep the connection and thus pay for it.
Queue-based orchestrator can also give you some extra resilience in face of intermittent failures.

Filters for Azure Functions

Is it possible to have Filters (Auth or Exception) for Azure functions? I just want to not duplicate code to validate bearer token in every function. I see that there is a filter concept in webjobs sdk. https://github.com/Azure/azure-webjobs-sdk/wiki/Function-Filters
I want to only validate the bearer token before executing any function. So if filters are not the best option then is there any other better way to handle this situation ?
Depending on how feature rich you want your responses you could use function filtered but they are very limited at the moment until this issue has been completed - https://github.com/Azure/azure-webjobs-sdk/issues/1314
Alternatively, you could set up a pipeline in each of your functions so you could apply the same cross-cutting concern logic inside your function app. obviously this will be a lot more work but comes with a lot more flexibility.
Example - https://github.com/kevbite/AzureFunctions.GreenPipes
Instead of bringing in another package, you can just pass your function code as an argument in to a wrapper method.
//business logic
[FunctionName("PostWidget")]
public async Task<IActionResult> PostWidget(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "widgets")] Widget item, HttpRequest req, ILogger log)
{
return await _functionWrapper.Execute(req, item, async () =>
{
log.LogInformation($"posting widget: ${item.Id}");
var newItem = await dbContext.Widgets.AddAsync(item);
await dbContext.SaveChangesAsync();
return new ResponseEnvelopeResult<Widget>(HttpStatusCode.Created, newItem.Entity);
});
}
//functionWrapper class
public async Task<IActionResult> Execute(T model, HttpRequest req, Func<Task<IActionResult>> azureFunction)
{
var results = await _validator.ValidateAsync(model, ruleSet: $"default,audit,{req.Method}");
if (!results.IsValid)
{
var errors = results.Errors.Select(x => x.ErrorMessage).ToList();
_log.LogWarning($"Model validation failed for type '{typeof(T).Name}'. Validation errors: [{errors.Join()}] ");
return new ResponseEnvelopeResult<T>(HttpStatusCode.BadRequest, null, errors);
}
try
{
return await azureFunction();
}
catch (Exception e)
{
_log.LogError(e, "Unhandled exception occured in FunctionWrapper");
return new ResponseEnvelopeResult<T>(HttpStatusCode.InternalServerError, null, new[] { e.Message });
}
}
Then your wrapper can be setup to do validation, retrieve user info, etc. If you need items passed back to your function layer, you can easily do so without obscuring your function intent. I've got a large example of this implementation on my blog.
https://blog.bruceleeharrison.com/2019/09/04/azure-v2-functions-with-fluentvalidation/

Swagger authentication in Azure App Service

In my Azure Mobile .NET backend I want to use Azure Mobile .NET Server Swagger . I'm looking for fast way to hide swagger UI from public access ? Is there any way to provide access only for selected users ?
First a disclaimer: Even if you protect your Swagger UI from public consumption, you are not protecting your APIs from public consumption. You have to assume that everyone knows all of your routes and have the appropriate security in place to protect any requests that may come in.
That being said, there's still not a simple way to do this. Swashbuckle (the piece that adds Swagger to Web API) adds a custom HttpMessageHandler to the /swagger/ui route (as seen here). If you look at the Web API pipeline, you can see that if you specify a custom handler, you can bypass all of the Controller selection, Auth filters, etc. This is what happens here.
Some solutions:
Use an app setting to conditionally call ConfigureSwagger(config) in debug modes only. This would prevent all /swagger routes from making it into production. Or you could use a staging slot and only add it there.
You can wrap the SwaggerUiHandler with something like this Basic Auth MessageHandler. This would prompt the user for basic creds if they went to the /swagger/ui route. See below for my modified version of this code.
Maybe with a little more thought we can come up with a better solution -- I see a couple of issues (here and here) in the Swashbuckle repo that indicate you're not the first one to hit this.
Modified BasicAuthHandler (from here):
Warning: minimally tested (and be sure to change how you verify user/pass)
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public BasicAuthMessageHandler(HttpMessageHandler innerHandler)
{
this.InnerHandler = innerHandler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
HttpResponseMessage unauthorizedResponse = request.CreateUnauthorizedResponse();
if (authValue != null && !string.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
// TODO: Check that the user/pass are valid
if (parsedCredentials.Username == "user" &&
parsedCredentials.Password == "pass")
{
// If match, pass along to the inner handler
return base.SendAsync(request, cancellationToken);
}
}
}
else
{
// Prompt for creds
unauthorizedResponse.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
return Task.FromResult(unauthorizedResponse);
}
private Credentials ParseAuthorizationHeader(string authHeader)
{
string[] credentials = Encoding.ASCII.GetString(Convert
.FromBase64String(authHeader))
.Split(
new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0])
|| string.IsNullOrEmpty(credentials[1])) return null;
return new Credentials()
{
Username = credentials[0],
Password = credentials[1],
};
}
}
Registering with Swagger route
// Do this after calling ConfigureSwagger
ConfigureSwagger(config);
// Remove the swagger_ui route and re-add it with the wrapped handler.
var route = config.Routes["swagger_ui"];
config.Routes.Remove("swagger_ui");
config.Routes.MapHttpRoute("swagger_ui", route.RouteTemplate, route.Defaults, route.Constraints, new BasicAuthMessageHandler(route.Handler));

Resources