I\'m trying to do a simple Azure Function to learn about it. There will be 3 functions:
I like the WebApi approach of using [FromBody]
attribute, so using IBinding
I made my own. Now I can just pass in the object.
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)]
[Binding]
public sealed class FromBodyAttribute : Attribute
{
}
public class FromBodyBinding : IBinding
{
private readonly ILogger logger;
public FromBodyBinding(ILogger logger)
{
this.logger = logger;
}
public Task<IValueProvider> BindAsync(BindingContext context)
{
// Get the HTTP request
var request = context.BindingData["req"] as DefaultHttpRequest;
return Task.FromResult<IValueProvider>(new FromBodyValueProvider(request, logger));
}
public bool FromAttribute => true;
public Task<IValueProvider> BindAsync(object value, ValueBindingContext context)
{
return null;
}
public ParameterDescriptor ToParameterDescriptor() => new ParameterDescriptor();
}
public class FromBodyBindingProvider : IBindingProvider
{
private readonly ILogger logger;
public FromBodyBindingProvider(ILogger logger)
{
this.logger = logger;
}
public Task<IBinding> TryCreateAsync(BindingProviderContext context)
{
IBinding binding = new FromBodyBinding(this.logger);
return Task.FromResult(binding);
}
}
public class FromBodyValueProvider : IValueProvider
{
private HttpRequest request;
private ILogger logger;
public FromBodyValueProvider(HttpRequest request, ILogger logger)
{
this.request = request;
this.logger = logger;
}
public async Task<object> GetValueAsync()
{
try
{
string requestBody = await new StreamReader(this.request.Body).ReadToEndAsync();
object result = JsonConvert.DeserializeObject(requestBody);
return result;
}
catch (System.Exception ex)
{
this.logger.LogCritical(ex, "Error deserializing object from body");
throw ex;
}
}
public Type Type => typeof(object);
public string ToInvokeString() => string.Empty;
}
public class BindingExtensionProvider : IExtensionConfigProvider
{
private readonly ILogger logger;
public BindingExtensionProvider(ILogger<Startup> logger)
{
this.logger = logger;
}
public void Initialize(ExtensionConfigContext context)
{
// Creates a rule that links the attribute to the binding
context.AddBindingRule<FromBodyAttribute>().Bind(new FromBodyBindingProvider(this.logger));
}
}
Then inside your Startup.cs file, add the binding.
public class Startup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
JsonConvert.DefaultSettings = () =>
{
return new JsonSerializerSettings()
{
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
},
Formatting = Formatting.Indented
};
};
builder.Services.AddLogging();
builder.AddExtension<BindingExtensionProvider>();
}
}
Now you can just have a regular old class, just like WebApi!
[FunctionName("MyFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
[Binding.FromBody] dynamic data) // or you can change 'dynamic' to some class
{
string username = data?.username;
...
}
If you are using System.Text.Json
, you can read the POST data in one line:
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req,
ILogger log)
{
MyClass myClass = await JsonSerializer.DeserializeAsync<MyClass>(req.Body);
}
If you are using Newtonsoft.Json
, see the answer by Allen Zhang.
The query string (name/value pairs) is by default sent in the HTTP message body of a POST request and not as query string. The GetQueryNameValuePairs method will parse the query string and will by default not work with POST request.
For the POST request you could use something similar to this:
var content = request.Content;
string contentInString = content.ReadAsStringAsync().Result;