C# HttpTriggered Azure funtion get content as interface from request - c#

I am writing a simple http trigger azure function in c# as shown below.
[FunctionName("CommandReceiver")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]
HttpRequestMessage req,
[ServiceBus("cqrs-commands", AccessRights.Listen, Connection = "", EntityType = EntityType.Topic)]
IAsyncCollector<BrokeredMessage> messageTopic,
TraceWriter log)
{
var request = await req.Content.ReadAsAsync<IPayload>();
var brokeredMessage = new BrokeredMessage(request );
await messageTopic.AddAsync(brokeredMessage);
return req.CreateResponse(HttpStatusCode.OK, JsonConvert.SerializeObject(delRequest));
}
From the client i am using the below code:
async Task<bool> IBus.RaiseCommand(Payload message)
{
var httpClient = HttpClientHelper.GetHttpClient(_azureSettings.Value.BaseUrl);
var jsonInString = JsonConvert.SerializeObject(message);
HttpResponseMessage response = await httpClient.PostAsync("api/CommandReceiver",
new StringContent(jsonInString, Encoding.UTF8, "application/json"));
return response.IsSuccessStatusCode;
}
public interface IPayload{
}
public class Payload : IPayload {
}
Using the above code i am not able to make it work. the azure function is throwing error at the line req.Content.ReadAsAsync<IPayload>;
Can anyone please help me the right way to achieve this?
Error i am getting is:
C# HTTP trigger function processed a request.
Exception while executing function: CommandReceiver
Microsoft.Azure.WebJobs.Host.FunctionInvocationException : Exception while executing function: CommandReceiver ---> Newtonsoft.Json.JsonSerializationException : Could not create an instance of type Commands.Model.IPayload. Type is an interface or abstract class and cannot be instantiated. Path 'Action', line 2, position 10.
Thanks

As #Thomas mentioned in the comment, serializer does not support Deserialize (and thus ReadAsAsync) calls with abstract types as generic parameter. Basically, just looking at JSON there's no way for serializer to know which concrete class it should instantiate to satisfy the desired interface requirement.
The obvious fix to this problem is to use Payload class directly:
await req.Content.ReadAsAsync<Payload>();
If you want to reuse the same Azure Function for multiple content types, you would need to either deserilize to JObject or dynamic and then act upon some properties, or add some sort of if-else logic based on HTTP request headers, route, etc.

Related

How do I fix my error when deserializing json from HttpRequest in Azure Function App?

I've recently started using azure function apps. I am passing some json data from Postman into my app when it's running so that I can test it and I'm getting an error of an unexpected character '{'.
{
"testId": "001",
"shopItems": {
"itemName": "something"
}
}
Here is what is happening.
Firstly, I have my very basic function, which can be called via it's route in postman. I pass the above request in the body as JSON to this function which then calls a service to work with it.
[FunctionName("UpdateCart")]
public async Task<IActionResult> UpdateCart(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "v1/cart/update")] HttpRequest req,
ILogger log)
{
await _myService.UpdateCartAsync(req.Body);
//Return ok
return new OkResult();
}
The body property of 'HttpRequest' is a 'Stream' and therefore my service and it's interface expect a stream to be passed in.
Interface
namespace MyProject.Services.Interface
{
public interface IMyService
{
Task UpdateCartAsync(Stream contents);
}
}
Service
Since we know it's a stream, I use "ReadToEndAsync" which then produces a string from the result. I pass that string to the "JsonConvert.Deserializer" to map that to my model and that's where it fails with the unexpected character '{' which is the bracer before "itemName".
public async Task UpdateCartAsync(Stream content)
{
var result = await new StreamReader(content).ReadToEndAsync();
Basket cart = JsonConvert.DeserializeObject<Cart>(result); //fails here
var db = _connectionMultiplexer.GetDatabase();
await db.StringSetAsync(cart.CartId, cart.CartContents);
}
Model
public class Cart
{
public string CartId { get; set; }
public string CartContents { get; set; }
}
I'm guessing here that the Steam string isn't actually accurate or that I perhaps need to serialize first, I'm not sure. Is there another way to pass JSON from the body to a functions app?

Enforce the compiler to pass a method into another method in c# by showing error

I am having a service class which contains a method GetSalespersonsAsync(). It will create an object for Data Service and calls a method dataService.GetSalesPersonsAsync(). Here I need all the Data Service method calls to pass to another method in the service InitiateApiCallAsync().
public async Task<List<Salesperson>> GetSalespersonsAsync()
{
using (var dataService = DataServiceFactory.CreateDataService())
{
var response = await InitiateApiCallAsync(dataService.GetSalesPersonsAsync());
return response?.code == 0 ? response.data : null;
}
}
public async Task<TResponse> InitiateApiCallAsync<TResponse>(Task<TResponse> dataService) where TResponse : BaseAPIResponse
{
TResponse response = await dataService;
await BaseProcess(response); // async process of some internal actions with response from the method call
return response;
}
If any of the data service methods failed to pass into the InitiateApiCallAsync() method, then the compiler need to show compile time error, so that it won't be missed to pass into it in future.
Kindly provide the possible solution to handle this to avoid missing this behavior in future.

AzureFunctions.Autofac threadsafe dependency injection issue

I am using AzureFunctions.Autofac to inject into my Azure Functions web api. An example of the config:
public class DIConfig
{
public DIConfig()
{
DependencyInjection.Initialize(builder =>
{
// DAL
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerLifetimeScope();
builder.RegisterType<SecretCompanyContext>().InstancePerLifetimeScope();
builder.RegisterType<SecretCompanyContext>().As<ICartContext>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();
// Services
builder.RegisterType<InventoryServices>().As<IInventoryServices>().InstancePerLifetimeScope();
// Controllers ported from ASP.NET MVC Web API
builder.RegisterType<InventoryController>().InstancePerLifetimeScope();
});
}
Then my Azure functions, I have one class that defines all methods in the API
[DependencyInjectionConfig(typeof(DIConfig))]
public class InventoryFunctions : FunctionsApi
{
[FunctionName("GetProductsByCategory")]
// /inventory/categories/{id}/products
public static async Task<HttpResponseMessage> GetProductsByCategory(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory/categories/{id}/products")]
HttpRequestMessage req,
TraceWriter log,
int id,
[Inject] InventoryController controller)
{
// do stuff
var result = await controller.GetProductsByCategory(id);
return JsonResponse(result, HttpStatusCode.OK);
}
[FunctionName("GetInventoryBySku")]
// /inventory/skus?sku=ASDF&sku=ASDG&sku=ASDH
public static async Task<HttpResponseMessage> GetInventoryBySku(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory")]
HttpRequestMessage req,
TraceWriter log,
[Inject] InventoryController controller)
{
// do stuff
var result = await controller.QueryInventoryBySkuList(skuList);
return JsonResponse(result, HttpStatusCode.OK);
}
[FunctionName("UpdateProductsQuantity")]
// /inventory
// Post
public static async Task<HttpResponseMessage> UpdateProductsQuantity(
[HttpTrigger(AuthorizationLevel.Function, "put", Route = "inventory")]
HttpRequestMessage req,
TraceWriter log,
[Inject] InventoryController controller)
{
// do stuff
var inventoryProducts = await req.Content.ReadAsAsync<List<InvProductOperation>>();
var result = await controller.UpdateAvailableProductsQuantity(inventoryProducts);
return JsonResponse(result, HttpStatusCode.OK);
}
But I keep getting this error:
A second operation started on this context before a previous
asynchronous operation completed. Use 'await' to ensure that
any asynchronous operations have completed before calling
another method on this context. Any instance members are not
guaranteed to be thread safe.
I have verified that async and await are used properly, so following the error message's recommendation isn't fixing it. What appears to be the issue is that IDbContext is not honoring the InstancePerLifetimeScope as expected. Is this happening because I have more than one method in my InventoryFunctions class? Or is AzureFunctions.Autofac not threadsafe?
Change the registration of the DbContext to this:
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();
You can find a deeper explanation of mine for why this is happening here.
I was going by this SO answer: Autofac - InstancePerHttpRequest vs InstancePerLifetimeScope which said that InstancePerLifetimeScope was the non-ASP.NET equivalent of InstancePerRequest.
I spoke to the developers and they said the truth is that getting one DbContext per HttpRequest was the default behavior when you simply register using builder.RegisterType<SecretCompanyContext>.As<IDbContext>() so there's some misinformation out there.
So the solution is, instead of using
builder.Register<IDbContext>(c => new SecretCompanyContext()).InstancePerDependency();
or
builder.RegisterType<SecretCompanyContext>().As<IDbContext>().InstancePerLifetimeScope();
one should just use
builder.RegisterType<SecretCompanyContext>().As<IDbContext>();
if the goal is one instance per HTTP request.

Web api return values for async methods

I'm a bit confused with HttpResponseMessage and Task<HttpResponseMessage>.
If I'm using the HttpClient method PostAsync() to post data I need to give the Web Service method the Task<HttpResponseMessage> instead of HttpResponseMessage as return value as far as I understood things.
If I use Request.CreateResponse(HttpStatusCode.Forbidden, myError.ToString());
then I'm only getting the Response message object but not the Task object.
So my question here is how do I have to create the Fitting return for async calls to web api methods?
(thus are my understandings there correct and if so how to best transform the message object int a Task<HttpResponseMessage> object)
The original code:
public HttpResponseMessage DeviceLogin(MyDevice device)
{
EnummyError myError = EnummyError.None;
// Authenticate Device.
myError = this.Authenticate(device);
if (myError != EnummyError.None)
{
return Request.CreateResponse(HttpStatusCode.Forbidden, myError.ToString());
}
}
The updated Method header:
public Task<HttpResponseMessage> DeviceLogin(MyDevice device)
Web Api 2 has these abstraction classes which are now recommended to use. You can still use HttpResponseMessage (easier to follow for beginners, in my opinion), but Web Api 2 recommends using IHttpActionResult.
As for the return type, just did what you did before. Task<T> works automagically that way.
You also might want to check if this.Authenticate() has an async variant.
public async Task<IHttpActionResult> DeviceLogin(MyDevice device)
{
EnummyError myError = EnummyError.None;
// Authenticate Device.
myError = this.Authenticate(device);
// Perhaps Authenticate has an async method like this.
// myError = await this.AuthenticateAsync(device);
if (myError != EnummyError.None)
{
return ResponseMessage(Request.CreateResponse(Request.CreateResponse(HttpStatusCode.Forbidden, myError.ToString()));
}
}
The ResponseMessage() method creates a ResponseMessageResult under water. This class derives from IHttpActionResult and accepts a HttpResponseMessage as a parameter in the constructor (which is made by Request.CreateResponse()).

Deserializing a generic object from POST body

I have a WebAPI endpoint that to takes in a generic object.
[HttpPost]
[ApiRoute("endpoint/{type}")]
public IHttpActionResult MyPostEndpoint(TypeEnum type, [FromBody] object myObject){}
We work on the object generically but then eventually convert it to our object type, but when we do we have to turn it into a JObject first, so grabbing the object looks like this:
var myfoo = ((JObject) object).ToObject<Foo>();
If I supply Foo directly as my POST parameter (e.g. [FromBody] Foo myObject) then it deserializes the incoming JSON to a Foo, but it won't deserialize to a generic C# object. Is there a way I can get it to deserialize to a generic C# object instead of leaving it a JObject so I can get myfoo like this instead?
var myfoo = (Foo) object;
As generic post method with data returned, I use. Than You can pass any class, so the request is more gerneric
public class Requests
{
//...
public async Task<ResultType> Create<ResultType>(string uri)
{
//TODO implementation of httpclient POST logic go here
var data = await results.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<ResultType>(data);
return result;
}
Call method
List<foo> foos = new List<foo>();
Request requestToServer = new request();
Task.WaitAll(Task.Run(async =>(){
foos = await requestToServer.Create<Foo>("/foo");
}));
Now You can pass any predefined class
I think you can do like following to have a loosely typed method
public static class HttpRequestHelper
{
public static async Task<T> GetDataModelFromRequestBodyAsync<T>(HttpRequestMessage req)
{
dynamic requestBody = await req.Content.ReadAsStringAsync();
object blobModelObject = JsonConvert.DeserializeObject<object>(requestBody);
var blobModel = ((JObject)blobModelObject).ToObject<T>();
return blobModel;
}
}
and usage is like following:
var blobModel = await HttpRequestHelper.GetDataModelFromRequestBodyAsync<RequestBlobModel>(req);
Hope This Helps
.NET CLI
dotnet new web --name "GenericEndpointExample"
cd GenericEndpointExample
dotnet add package SingleApi
Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// map generic endpoint
app.MapSingleApi("sapi",
// add your generic request handler
// for example, return the received data (already typed object)
x => Task.FromResult(x.Data),
// add assemblies for resolving received data types
typeof(MyClassName).Assembly, typeof(List<>).Assembly, typeof(int).Assembly);
app.Run();
Example request for type: MyClassName
POST /sapi/MyClassName
{"Name":"Example"}
Example request for generic: Dictionary<string,int?[]>
POST /sapi/Dictionary(String-Array(Nullable(Int32)))
{"key1":[555,null,777]}
GitHub repository with examples

Categories