Following the documentation for aws lambda net6:
https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html
or small tutorial https://aws.amazon.com/blogs/compute/introducing-the-net-6-runtime-for-aws-lambda/
The simple Top-level lambda code below ignores my simple POST request. The console displays >;0. Why ? Did I forget a package?
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
var handler = async (LambdaInput input, ILambdaContext context) =>
{
Console.Write($">{input.Property1};{input.Property2}");
}
await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer())
.Build()
.RunAsync();
public class LambdaInput
{
public string Property1 { get; set; } = "";
public int Property2 { get; set; }
}
The test.http
POST https://my.api.com/request
Content-Type: application/json
{
"Property1": "Simple text",
"Property2": 8612
}
Question and answer here:
https://github.com/aws/aws-lambda-dotnet/issues/1227
by #normj
You are writing a Lambda function that listens to API Gateway's HTTP events. The event type for those Lambda functions is Amazon.Lambda.APIGatewayEvents.Amazon.Lambda.APIGatewayEvents and should be your first parameter in your function. That event object represents all of the parts of an HTTP request including headers, resource and request body. In your example you can take Body property and serialize into LambdaInput.
You might be interested in checking this new library we are working on that removes some of this complexity into a pattern you might be more use to.
https://aws.amazon.com/blogs/developer/introducing-net-annotations-lambda-framework-preview/
Related
I'm executing the URL
https://localhost:44310/api/Licensee/{"name":"stan"}
in address field of my browser, getting the error
"title": "Unsupported Media Type", "status": 415
which is described as
... the origin server is refusing to service the request because the payload
is in a format not supported by this method on the target resource.
The suggested troubleshot is
... due to the request's indicated Content-Type or Content-Encoding, or as a result of inspecting the data ...
I can't really control what header the browser provides. Due to intended usage, I can't rely on Postman or a web application. It needs to be exected from the URL line. The parameter will differ in structure, depending what search criteria that are applied.
The controller looks like this.
[HttpGet("{parameters}")]
public async Task<ActionResult> GetLicensee(LicenseeParameters parameters)
{
return Ok(await Licensee.GetLicenseeByParameters(parameters));
}
I considered decorating the controller with [Consumes("application/json")] but found something dicouraging it. I tried to add JSON converter as suggested here and here but couldn't really work out what option to set, fumbling according to this, not sure if I'm barking up the right tree to begin with.
services.AddControllers()
.AddJsonOptions(_ =>
{
_.JsonSerializerOptions.AllowTrailingCommas = true;
_.JsonSerializerOptions.PropertyNamingPolicy = null;
_.JsonSerializerOptions.DictionaryKeyPolicy = null;
_.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
My backup option is to use query string specifying the desired options for a particular search. However, I'd prefer to use the object with parameters for now.
How can I resolve this (or at least troubleshoot further)?
The reason is that there might be a loooot of parameters and I don't want to refactor the controller's signature each time
Actually, you don't have to change the controller's signature each time. ASP.NET Core Model binder is able to bind an object from query string automatically. For example, assume you have a simple controller:
[HttpGet("/api/licensee")]
public IActionResult GetLicensee([FromQuery]LicenseeParameters parameters)
{
return Json(parameters);
}
The first time the DTO is:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
}
What you need is to send a HTTP Request as below:
GET /api/licensee?name=stan¬e=it+works
And later you decide to change the LicenseeParameters:
public class LicenseeParameters
{
public string Name {get;set;}
public string Note {get;set;}
public List<SubNode> Children{get;set;} // a complex array
}
You don't have to change the controller signature. Just send a payload in this way:
GET /api/licensee?name=stan¬e=it+works&children[0].nodeName=it&children[1].nodeName=minus
The conversion is : . represents property and [] represents collection or dictionary.
In case you do want to send a json string within URL, what you need is to create a custom model binder.
internal class LicenseeParametersModelBinder : IModelBinder
{
private readonly JsonSerializerOptions _jsonOpts;
public LicenseeParametersModelBinder(IOptions<JsonSerializerOptions> jsonOpts)
{
this._jsonOpts = jsonOpts.Value;
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var name= bindingContext.FieldName;
var type = bindingContext.ModelType;
try{
var json= bindingContext.ValueProvider.GetValue(name).FirstValue;
var obj = JsonSerializer.Deserialize(json,type, _jsonOpts);
bindingContext.Result = ModelBindingResult.Success(obj);
}
catch (JsonException ex){
bindingContext.ModelState.AddModelError(name,$"{ex.Message}");
}
return Task.CompletedTask;
}
}
and register the model binder as below:
[HttpGet("/api/licensee/{parameters}")]
public IActionResult GetLicensee2([ModelBinder(typeof(LicenseeParametersModelBinder))]LicenseeParameters parameters)
{
return Json(parameters);
}
Finally, you can send a json within URL(suppose the property name is case insensive):
GET /api/licensee/{"name":"stan","note":"it works","children":[{"nodeName":"it"},{"nodeName":"minus"}]}
The above two approaches both works for me. But personally I would suggest you use the the first one as it is a built-in feature.
I have an Azure Function 2.x that reside on a static class that looks like this
[FunctionName("Register")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")]HttpRequest req, ILogger log)
{
MyTypeClass defReturn = new MyTypeClass();
HttpStatusCode defCode = HttpStatusCode.BadRequest;
/*
* Logics that might or might not changes
* defReturn and defCode value
*/
return StatusCode((int) defCode, JsonConvert.SerializeObject(defReturn))
}
How can i achieve the return StatusCode((int) defCode, JsonConvert.SerializeObject(defReturn)) part ? is there any such method or equivalent in Azure Functions 2.x ?
in Azure Functions 1.x i can do the equivalent with req.CreateResponse(defCode, defReturn) where req is HttpRequestMessage , but i'm trying to stick with 2.x template/standard
Additional explanation : The said Code should return HTTP 400 Bad Request with the defReturn as it's response body to the client. But when i change the defCode to HttpStatusCode.Accepted, it should return HTTP 202 Accepted with the same response body. How can i achieve this ?
Additional explanation#2 : (If i remember correctly) in ASP.NET Core 1.x i can exactly do like that, returning IActionResult by calling a static method StatusCode not StatusCodes (which is a static class that contains HTTP codes constants
Thank you
Quite late reply, but I was stumbling into the same problem today, so maybe this is helpful for other searchers
Option 1: Default Codes
This is stated in detail on the blog Here
Some codes like 200 and 400 are predefined and can be used by
return new OkObjectResult("Your message"); // 200
return new BadRequestObjectResult("Your error message"); // 400
These functions are not available for every known Status Codes but some of the most frequent.
Option 2: Manual setting Code
If you need specific codes, that are not provided by default, you can use the base classes and create them yourself.
To achieve the Teapot Response for example, you can just use
using Microsoft.AspNetCore.Http;
var result = new ObjectResult("Your message");
result.StatusCode = StatusCodes.Status418ImATeapot;
return result;
In this example, the Statuscode is used from the StatusCodes class, but you can use enter other codes as well (usually, just stick to these codes)
Also, the ObjectResult class offers additional formatting options, if needed.
You can create a model class in which you can define two properties, i.e. one form your status code and one for you Json object and later on you can return the complete model. Code sample would be like below:
public static class QueueTriggerTableOutput
{
[FunctionName("QueueTriggerTableOutput")]
[return: Table("outTable", Connection = "MY_TABLE_STORAGE_ACCT_APP_SETTING")]
public static Person Run(
[QueueTrigger("myqueue-items", Connection = "MY_STORAGE_ACCT_APP_SETTING")]JObject order,
ILogger log)
{
return new Person() {
PartitionKey = "Orders",
RowKey = Guid.NewGuid().ToString(),
Name = order["Name"].ToString(),
MobileNumber = order["MobileNumber"].ToString() };
}
}
public class Person
{
public string PartitionKey { get; set; }
public string RowKey { get; set; }
public string Name { get; set; }
public string MobileNumber { get; set; }
}
on the receiving front, you can catch both the property.
P.S.- you have to change the return type of your function.
Hope it helps.
I have a web api with some actions that should receive both data and files. To do so, I am accepting multipart/form-data instead of JSON and binding to the model using [FromForm]:
[Authorize(Policy = "MyCustomPolicy")]
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
// Do some stuff here
}
public class MyCustomDto
{
public int Id { get; set; }
public string Title { get; set; }
public IEnumerable<IFormFile> Attachments { get; set; }
}
This works fine and is correctly bound to the DTO.
The Policy is checked and enforced using a AuthorizationHandler, which also works fine.
In said AuthorizationHandler, I need to access some stuff passed to the controller, such as IDs in the route or DTOs.
Accessing route data is quite easy with authContext.RouteData.Values["nameOfIdField"].
For the body, however, I have created a helper extension method that reads the body stream and deserializes it:
public static async Task<T> DeserializeBody<T>(this AuthorizationFilterContext context, string name)
{
var content = string.Empty;
using(var reader = new StreamReader(context.HttpContext.Request.Body, System.Text.Encoding.UTF8, true, 1024, true))
{
content = await reader.ReadToEndAsync();
reader.BaseStream.Seek(0, SeekOrigin.Begin);
}
if (string.IsNullOrWhiteSpace(content))
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(content);
}
Again, this also works quite fine. However, now I am having trouble with DTOs that are not passed as JSON but - as said in the beginning - as form data.
The body's content is not a JSON that can be serialized easily:
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="id"
232
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="title"
test
-----------------------------7e2b13b820d4a
Content-Disposition: form-data; name="attachments"; filename="C:\Temp\Test.jpg"
Content-Type: image/jpeg
Is there any way to bind this to my DTO easily, without having to manually parse it?
You're better off handling this situation in your action method:
public class SomeController : Controller
{
private readonly IAuthorizationService _authorizationService;
public SomeController(IAuthorizationService authorizationService)
{
_authorizationService = authorizationService;
}
public async Task<IActionResult> Create([FromForm]MyCustomDto myDto)
{
var authorizationResult = await _authorizationService.AuthorizeAsync(User, myDto, "MyCustomPolicy");
if (!authorizationResult.Succeeded)
return User.Identity.IsAuthenticated ? Forbid() : (IActionResult)Challenge();
// Do some stuff here
}
And you define your authorization handler like this:
public class MyCustomDtoAuthorizationHandler : AuthorizationHandler<MyCustomDtoRequirement, MyCustomDto>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyCustomDtoRequirement requirement, MyCustomDto resource)
{
// your authorization logic based on the resource argument...
}
}
The big problem with choosing the AuthorizeFilter way is that authorization filters are executed before model binding takes place. (Just take a look at the source of the ResourceInvoker class.) You would need to bind your model manually to access the required information for authorization in it. Then the framework would do its job resulting in model binding being done two times and thus, in performance degradation. And this should and could be avoided as it was described earlier.
Update
I've just noticed I accidentally left an important piece of code out of the action method. Corrected.
In said AuthorizationHandler, I need to access some stuff passed to the controller, such as IDs in the route or DTOs
Please don't do that. You are basically replicating the logic already in place on how to parse your parameters from the request.
The basic handlers are for basic cases: For example, only authenticated members of the "BookClub" role should be able to access the BooksController methods. That's great.
As soon as you find yourself needing information from the message itself, please don't go and do all of that parsing by hand. Let ASP do it's thing and parse the message as per your given constraints and then when the message is complete, invoke your authorization logic on the objects you got.
Microsoft Example
Hello we are currently using c# MVC Core 2 and Angular 5 and we're struggling to understand why our endpoints are not having there information bound inside the action handler.
Here is our interceptor which sets all of our necessary information about the request..
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (this._cookieService.check('Abp.AuthToken')) {
// We have our auth token..
let bearerToken = this._cookieService.get('Abp.AuthToken');
// Clone the request to add the new header.
const authReq = req.clone({headers: req.headers.set('Authorization', 'bearer ' + bearerToken).set('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')});
// Pass on the cloned request instead of the original request.
return next.handle(authReq);
} else {
// #todo This should sign the user out of the application...
console.log('here');
}
}
Please note we've also tried 'application/json' as well to no success.
Below is our request made using the angular HttpClient:
this.http.post('http://localhost:22742/api/services/app/UserGridSetting/SaveUserGridSettings', {'jsonData': JSON.stringify(gridSettingsObject)}).subscribe((d: any) => {
console.log(d);
});
Here is our C# corresponding endpoint:
public async Task<int> SaveUserGridSettings(string jsonData)
{
return await _repository.InsertOrUpdateAndGetIdAsync(setting);
}
The Endpoint is being correctly called and run but our jsonData is never bound into the method, we have tried a plethora of different things with the Angular client but the information is never bound in.
I would try doing 3 things:
Use content type "application/json" not "JSON/Application"
Post the string directly
this.http.post('http://localhost:22742/api/services/app/UserGridSetting/SaveUserGridSettings', JSON.stringify(gridSettingsObject)) //...
Have the c# method like this:
public async Task<int> SaveUserGridSettings([FromBody]string jsonData)
We found the answer after several hours, the expected type of string was not able to bind our jsonData identifier from the HttpClient as it was checking for the data property on the primitive type string.
Simply adding a class and changing the signature to:
public async Task<int> SaveUserGridSettings(ColumnState jsonData)
With ColumnState being:
public class ColumnState
{
public string data { get; set; }
}
and using angular like:
this.http.post('http://localhost:22742/api/services/app/UserGridSetting/SaveUserGridSettings', {data: JSON.stringify(gridSettingsObject)}).subscribe((d: any) => {
console.log(d);
});
I have a simple C# Aws Lambda function which succeeds to a test from the Lambda console test but fails with a 502 (Bad Gateway) if called from the API Gateway (which i generated from the Lambda trigger option) and also if I use postman.(this initial function has open access (no security))
// request header
Content-Type: application/json
// request body
{
"userid":22,
"files":["File1","File2","File3","File4"]
}
The error I get in the logs is:
Wed Feb 08 14:14:54 UTC 2017 : Endpoint response body before transformations: {
"errorType": "NullReferenceException",
"errorMessage": "Object reference not set to an instance of an object.",
"stackTrace": [
"at blahblahmynamespace.Function.FunctionHandler(ZipRequest input, ILambdaContext context)",
"at lambda_method(Closure , Stream , Stream , ContextInfo )"
]
}
It seems like the posted object is not being passed to the lambda input argument.
Code below
// Lambda function
public LambdaResponse FunctionHandler(ZipRequest input, ILambdaContext context)
{
try
{
var logger = context.Logger;
var headers = new Dictionary<string, string>();
if (input == null || input.files.Count == 0)
{
logger.LogLine($"input was null");
headers.Add("testheader", "ohdear");
return new LambdaResponse { body = "fail", headers = headers, statusCode = HttpStatusCode.BadRequest };
}
else
{
logger.LogLine($"recieved request from user{input?.userid}");
logger.LogLine($"recieved {input?.files?.Count} items to zip");
headers.Add("testheader", "yeah");
return new LambdaResponse { body = "hurrah", headers = headers, statusCode = HttpStatusCode.OK };
}
}
catch (Exception ex)
{
throw ex;
}
}
//Lambda response/ZipRequest class
public class LambdaResponse
{
public HttpStatusCode statusCode { get; set; }
public Dictionary<string, string> headers { get; set; }
public string body { get; set; }
}
public class ZipRequest
{
public int userid { get; set; }
public IList<string> files { get; set; }
}
This might not have been available when the OP asked the question, but when invoking a Lambda function using the API Gateway, specific response objects are provided.
As previously noted in the documentation Api Gateway Simple Proxy for Lambda Input Format, the API Gateway wraps the input arguments in a fairly verbose wrapper. It also expects a similarly verbose response object.
However, it is not necessary to create custom request and response objects. The AWS team provides the Amazon.Lambda.APIGatewayEvents library, which is also available on NuGet. This library includes APIGatewayProxyRequest and APIGatewayProxyResponse objects ready-made.
It is still necessary to manually deserialize the Body of the request, as it is a string, not a JSON object. I assume this was done for flexibility?
An example function could look like this. It's a modification of the default function provided by the AWS tools:
public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest request, ILambdaContext context)
{
var bodyString = request?.Body;
if (!string.IsNullOrEmpty(bodyString))
{
dynamic body = JsonConvert.DeserializeObject(bodyString);
if (body.input != null)
{
body.input = body.input?.ToString().ToUpper();
return new APIGatewayProxyResponse
{
StatusCode = 200,
Body = JsonConvert.SerializeObject(body)
};
}
}
return new APIGatewayProxyResponse
{
StatusCode = 200
};
}
When using Lambda Proxy Integration in API Gateway, the first parameter to your FunctionHandler is not the body of your POST, but is another API Gateway-created object, which let's call LambdaRequest. Try these changes to your sample code. Add:
public class LambdaRequest
{
public string body { get; set; }
}
Change your handler prototype to:
public LambdaResponse FunctionHandler(LambdaRequest req, ILambdaContext context)
And inside FunctionHandler add:
ZipRequest input = JsonConvert.DeserializeObject<ZipRequest>(req.Body);
The full LambdaRequest object is documented under Input Format of a Lambda Function for Proxy Integration in the AWS docs, and contains HTTP headers, the HTTP method, the query string, the body, and a few other things.
I also lost a lot of time trying to get a "Path Parameter" passed in on a Get method. For example, if you had a path such as
/appsetting/123
... then you'd have something configured like
By specifying the resource "appid" as {appid} it tells the API Gateway to capture this as a path variable.
One key discovery I found was that by posting in the body of a POST type action, my Lambda would work. Reading around on some other threads, I then discovered I can transform my path variable into a body of the GET action by:
Selecting the GET value (as pictured)
Clicking Integration Request
Creating a mapping template as shown below
Now when I test, I'm able to plug in my appid value in only and get the proper result. Hope this helps someone.