I have the following function:
public class WidgetRequest {
[FunctionName("CreateWidget")]
public static async Task<IActionResult> CreateWidget(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "/widget")] HttpRequest req,
[Queue("widgets"), StorageAccount("StorageForWidgets")] ICollector<string> messageQueue,
ILogger log)
{
ProvisionedWidgetsRepository storage = new ProvisionedWidgetsRepository();
WidgetResponse response = new WidgetResponse ();
var content = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation($"Received following payload: {content}");
var widgetRequest = JsonConvert.DeserializeObject<Widget>(content);
if (widgetRequest .name != null){
//add the request to queue for processing
messageQueue.Add(JsonConvert.SerializeObject(widgetRequest));
//also add to provisioned storage table
response = await storage.ProvisioningRequest(widgetRequest , req.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString(), "enqueued");
}
else {
response.status = "Error: Invalid Request";
response.requestId=null;
}
return new OkObjectResult(JsonConvert.SerializeObject(response));
}
I have the following test:
[Fact]
public async void widget_creation_requests_should_be_stored_in_queue(){
var messageQueue = TestFactory.CreateAzureStorageQueue();
var storageTable = TestFactory.CreateAzureStorageTable();
var request = TestFactory.CreateWidgetRequest();
var response = (OkObjectResult)await WidgetRequest.CreateWidget(request, messageQueue, logger);
Assert.NotNull(response);
Assert.True(((AzureStorageQueueTestClient<string>)messageQueue).Messages.Count > 0);
}
The test fails because I don't know how to pass in the mock storage table that I'm spinning up in the test ("var storageTable"). As you can see in the actual function, I manually instantiate a new storage object like this:
ProvisionedWidgetsRepository storage = new ProvisionedWidgetsRepository();
I don't pass it in.
Any tips would be appreciated.
Thanks
EDIT 1
So I've refactored to move out the storage logic into a separate class with an interface to support dependency injection.
The code with the main logic now has a constructor, and uses the new interface. It looks like this:
public class WidgetRequest {
//Using Interface for storage so we can dependency inject for testing.
private IWidgetRepositoryController storage;
public WidgetRequest(IWidgetRepositoryController storage)
{
this.storage = storage;
}
[FunctionName("CreateWidget")]
public static async Task<IActionResult> CreateWidget(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "/widget")] HttpRequest req,
[Queue("widgets"), StorageAccount("StorageForWidgets")] ICollector<string> messageQueue,
ILogger log)
{
//ProvisionedWidgetsRepository storage = new ProvisionedWidgetsRepository();
WidgetResponse response = new WidgetResponse ();
var content = await new StreamReader(req.Body).ReadToEndAsync();
log.LogInformation($"Received following payload: {content}");
var widgetRequest = JsonConvert.DeserializeObject<Widget>(content);
if (widgetRequest .name != null){
//add the request to queue for processing
messageQueue.Add(JsonConvert.SerializeObject(widgetRequest));
//also add to provisioned storage table
response = await storage.AddToTable(widgetRequest , req.HttpContext.Items["MS_AzureFunctionsRequestID"].ToString(), "enqueued");
}
else {
response.status = "Error: Invalid Request";
response.requestId=null;
}
return new OkObjectResult(JsonConvert.SerializeObject(response));
}
This is what the new Storage Controller class looks like, along with the interface:
public interface IWidgetRepositoryController
{
Task<WidgetResponse> AddToTable(Widget widgetRequest, string requestID, string partitionName);
}
public class WidgetRepositoryController
{
public async Task<WidgetResponse> AddToTable(Widget widgetRequest, string requestID, string partitionName)
{
ProvisionedWidgetRepository storage = new ProvisionedWidgetRepository ();
WidgetResponse response = await storage.ProvisioningRequest(widgetRequest, requestID, partitionName);
return response;
}
}
So far all the logic above seems to be ok - as in no errors. But I am now trying to create the Startup.cs file. I'm getting an error CS0311.
Here's the code:
[assembly: FunctionsStartup(typeof(Az.Fn.Widgets.Startup))]
namespace Az.Fn.Widget
{
public class Startup: FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddTransient<IWidgetRepositoryController, WidgetRepositoryController>();
}
}
The full error:
The type 'Az.Fn.Widgets.WidgetRepositoryController' cannot be used as
type parameter 'TImplementation' in the generic type or method
'ServiceCollectionServiceExtensions.AddTransient<TService,
TImplementation>(IServiceCollection)'. There is no implicit reference
conversion from 'Az.Fn.Widgets.WidgetRepositoryController' to
'Az.Fn.Widgets.IWidgetRepositoryController'.
I had to refactor to support DI as per the comment from ThrowingSpoon, and then I had a bug. I had to change the WidgetRespositoryController class from this:
public class WorkspaceRepositoryController
to this:
public class WorkspaceRepositoryController : IWorkspaceRepositoryController
And it seems to be happy.
Related
tl;dr: I'm having trouble mocking restease**
Also, I realize I may be totally on the wrong track, so any suggestions / nudges in the right direction would be of great help. I am quite new to this.
I'm making a small HTTP Client library, built around RestEase. RestEase is nice and easy to use, but I'm having trouble mocking the calls for the purpose of unit testing.
I want to use moq and NUnit, but I can't properly mock the RestClient. Example (shortened for brevity):
IBrandFolderApi - interface needed by restease to send calls
public interface IBrandFolderApi
{
[Post("services/apilogin")]
Task<LoginResponse> Login([Query] string username, [Query] string password);
}
BrandfolderClient.cs - the main class
public class BrandfolderClient : IBrandfolderClient
{
private IBrandFolderApi _brandFolderApi { get; set; }
public BrandfolderClient(string url)
{
_brandFolderApi = RestClient.For<IBrandFolderApi >(url);
}
public async Task<string> Login(string username, string password)
{
LoginResponse loginResponse = await _brandFolderApi .Login(username, password);
if (loginResponse.LoginSuccess)
{
....
}
....
return loginResponse.LoginSuccess.ToString();
}
}
The unit tests
public class BrandFolderTests
{
BrandfolderClient _brandfolderClient
Mock<IBrandFolderApi> _mockBrandFolderApii;
[SetUp]
public void Setup()
{
//The test will fail here, as I'm passing a real URL and it will try and contact it.
//If I try and send any string, I receive an Invalid URL Format exception.
string url = "https://brandfolder.companyname.io";
_brandfolderClient = new BrandfolderClient (url);
_mockBrandFolderApii= new Mock<IBrandFolderApi>();
}
....
}
So, I don't know how to properly mock the Restclient so it doesn't send an actual request to an actual URL.
The test is failing at the constructor - if I send a valid URL string, then it will send a call to the actual URL. If I send any other string, I get an invalid URL format exception.
I believe I haven't properly implemented something around the rest client, but I'm not sure where. I'm very stuck on this, I've been googling and reading like crazy, but I'm missing something and I don't know what.
So, I don't know how to properly mock the Restclient so it doesn't send an actual request to an actual URL.
You actually should not have any need to mock RestClient.
Refactor your code to depend explicitly on the abstraction you control
public class BrandfolderClient : IBrandfolderClient {
private readonly IBrandFolderApi brandFolderApi;
public BrandfolderClient(IBrandFolderApi brandFolderApi) {
this.brandFolderApi = brandFolderApi; //RestClient.For<IBrandFolderApi >(url);
}
public async Task<string> Login(string username, string password) {
LoginResponse loginResponse = await brandFolderApi.Login(username, password);
if (loginResponse.LoginSuccess) {
//....
}
//....
return loginResponse.LoginSuccess.ToString();
}
}
removing the tight coupling to static 3rd party implementation concerns will allow your subject to be more explicit about what it actually needs to perform its function.
This will also make it easier for the subject to be tested in isolation.
For example:
public class BrandFolderTests {
BrandfolderClient subject;
Mock<IBrandFolderApi> mockBrandFolderApi;
[SetUp]
public void Setup() {
mockBrandFolderApi = new Mock<IBrandFolderApi>();
subject = new BrandfolderClient(mockBrandFolderApi.Object);
}
//....
[Test]
public async Task LoginTest() {
//Arrange
LoginResponse loginResponse = new LoginResponse() {
//...
};
mockBrandFolderApi
.Setup(x => x.Login(It.IsAny<string>(), It.IsAny<string>()))
.ReturnsAsync(loginResponse);
//Act
string response = await subject.Login("username", "password");
//Assert
mockBrandFolderApi.Verify(x => x.Login(It.IsAny<string>(), It.IsAny<string>()), Times.Once);
}
}
In production code, register and configure the IBrandFolderApi abstraction with the container, applying what ever 3rd party dependencies are required
Startup.ConfigureServices
//...
ApiOptions apiOptions = Configuration.GetSection("ApiSettings").Get<ApiOptions>();
services.AddSingleton(apiOptions);
services.AddScoped<IBrandFolderApi>(sp => {
ApiOptions options = sp.GetService<ApiOptions>();
string url = options.Url;
return RestClient.For<IBrandFolderApi>(url);
});
Where ApiOptions is used to store settings
public class ApiOptions {
public string Url {get; set;}
//... any other API specific settings
}
that can be defined in appsetting.json
{
....
"ApiSettings": {
"Url": "https://brandfolder.companyname.io"
}
}
so that they are not hard coded all over you code.
The HttpClient comes from System.Net.Http, which is not easy to mock.
You can, however, create a test HttpClient by passing a fake HttpMessageHandler. Here is an example:
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly bool _isSuccessResponse;
public FakeHttpMessageHandler(bool isSuccessResponse = true)
{
_isSuccessResponse = isSuccessResponse;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(
new HttpResponseMessage(_isSuccessResponse ? HttpStatusCode.OK : HttpStatusCode.InternalServerError));
}
}
You can create create a test instance of HttpClient as shown below:
var httpClient = new HttpClient(new FakeHttpMessageHandler(true))
{ BaseAddress = new Uri("baseUrl") };
Not sure how you are using verify on _httpClient, its not a mock. but what you are looking for is https://github.com/canton7/RestEase#custom-httpclient. Most people pass in factory for this
//constructor
public httpClientConstructor(string url, IHttpHandlerFactory httpHandler)
{
var httpClient = new HttpClient(httpHandler.GetHandler())
{
BaseAddress = new Uri(url),
};
_exampleApi = RestClient.For<IExampleApi>(url);
}
public interface IHttpHandlerFactory<T>
{
T GetHandler() where T: HttpMessageHandler
}
Thanks Ankit Vijay https://stackoverflow.com/a/68240316/5963888
public class FakeHttpMessageHandler : HttpMessageHandler
{
private readonly bool _isSuccessResponse;
public FakeHttpMessageHandler(bool isSuccessResponse = true)
{
_isSuccessResponse = isSuccessResponse;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(
new HttpResponseMessage(_isSuccessResponse ? HttpStatusCode.OK : HttpStatusCode.InternalServerError));
}
}
[SetUp]
public void Setup()
{
var fakeHandler = new Mock<IHttpHandlerFactory>();
fakeHandler.Setup(e => e.GetHandler() ).Returns( new FakeHttpHandler() );
_httpClient = new HttpClient(fakeHandler.Object);
_exampleApi = new Mock<IExampleApi>();
}
I am dealing with a piece of code that looks like this:
public class Uploader : IUploader
{
public Uploader()
{
// assign member variables to dependency injected interface implementations
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
var client = new HttpClient(handler);
result = await client.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
I am trying to unit test the Upload function. In particular, I need to mock the HttpClient. After reading the other answers on here and these two articles, I know that one of the better ways to solve this is to mock the HttpMessageHandler instead and pass that to HttpClient and have it return whatever I want.
So, I started along that path by first passing in HttpClient in the constructor as a dependency:
public class Uploader : IUploader
{
private readonly HttpClient m_httpClient; // made this a member variable
public Uploader(HttpClient httpClient) // dependency inject this
{
m_httpClient = httpClient;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
var handler = new HttpClientHandler();
result = await m_httpClient.PostAsync(url, new FormUrlEncodedContent(data));
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
and adding: services.AddSingleton<HttpClient>(); to the ConfigureServices method of Startup.cs.
But now I face a slight issue where the original code specifically creates a HttpClientHandler to pass in. How then do I refactor that to take in a mockable handler?
I find the simplest way is to continue using HttpClient, but pass in a mocking HttpClientHandler such as https://github.com/richardszalay/mockhttp
Code sample from the link above:
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://localhost/api/user/*")
.Respond("application/json", "{'name' : 'Test McGee'}");
// Inject the handler or client into your application code
var client = mockHttp.ToHttpClient();
var response = await client.GetAsync("http://localhost/api/user/1234");
var json = await response.Content.ReadAsStringAsync();
Console.Write(json); // {'name' : 'Test McGee'}
The Dependency Injection framework built into .NET Core ignores internal constructors, so it will call the parameter-less constructor in this scenario.
public sealed class Uploader : IUploader
{
private readonly HttpClient m_httpClient;
public Uploader() : this(new HttpClientHandler())
{
}
internal Uploader(HttpClientHandler handler)
{
m_httpClient = new HttpClient(handler);
}
// regular methods
}
In your unit tests, you can use the constructor accepting the HttpClientHandler:
[Fact]
public async Task ShouldDoSomethingAsync()
{
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("http://myserver.com/upload")
.Respond("application/json", "{'status' : 'Success'}");
var uploader = new Uploader(mockHttp);
var result = await uploader.UploadAsync();
Assert.Equal("Success", result.Status);
}
Normally I'm not a big fan of having an internal constructor to facilitate testing, however, I find this more obvious and self-contained than registering a shared HttpClient.
HttpClientFactory might be another good option, but I haven't played around with that too much, so I'll just give info on what I've found useful myself.
One way would be to abstract your HTTP functionality into a service i.e. HttpService which implements an interface of IHttpService:
IHttpService
public interface IHttpService
{
Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null);
}
HttpService
public class HttpService : IHttpService
{
private static HttpClient _httpClient;
private const string MimeTypeApplicationJson = "application/json";
public HttpService()
{
_httpClient = new HttpClient();
}
private static async Task<HttpResponseMessage> HttpSendAsync(HttpMethod method, Uri url, string payload,
Dictionary<string, string> headers = null)
{
var request = new HttpRequestMessage(method, url);
request.Headers.Add("Accept", MimeTypeApplicationJson);
if (headers != null)
{
foreach (var header in headers)
{
request.Headers.Add(header.Key, header.Value);
}
}
if (!string.IsNullOrWhiteSpace(payload))
request.Content = new StringContent(payload, Encoding.UTF8, MimeTypeApplicationJson);
return await _httpClient.SendAsync(request);
}
public async Task<HttpResponseMessage> Post(Uri url, string payload, Dictionary<string, string> headers = null)
{
return await HttpSendAsync(HttpMethod.Post, url, payload, headers);
}
}
Add to your services:
services.AddSingleton<IHttpService, HttpService>();
In your class you would then inject IHttpService as a dependency:
public class Uploader : IUploader
{
private readonly IHttpService _httpService; // made this a member variable
public Uploader(IHttpService httpService) // dependency inject this
{
_httpService = httpService;
}
public async Task<string> Upload(string url, string data)
{
HttpResponseMessage result;
try
{
result = await _httpService.PostAsync(new Uri(url), data);
if (result.StatusCode != HttpStatusCode.OK)
{
return "Some Error Message";
}
else
{
return null; // Success!
}
}
catch (Exception ex)
{
// do some fancy stuff here
}
}
}
You could then use Moq to mock HttpService in your unit test:
[TestClass]
public class UploaderTests
{
private Mock<IHttpService> _mockHttpService = new Mock<IHttpService>();
[TestMethod]
public async Task WhenStatusCodeIsNot200Ok_ThenErrorMessageReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(url, data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));
// act
var result = await uploader.Upload(new Uri(url), data);
// assert
Assert.AreEqual("Some Error Message", result);
}
[TestMethod]
public async Task WhenStatusCodeIs200Ok_ThenNullReturned()
{
// arrange
var uploader = new Uploader(_mockHttpService.Object);
var url = "someurl.co.uk";
var data = "data";
// need to setup your mock to return the response you want to test
_mockHttpService
.Setup(s => s.PostAsync(new Uri(url), data))
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK));
// act
var result = await uploader.Upload(url, data);
// assert
Assert.AreEqual(null, result);
}
}
I'm trying to do a simple Azure Function to learn about it. There will be 3 functions:
1 function to insert a row into a table of a database. This table will contain the current date and a string parameters typed by the user and passed by GET.
1 function similar to the previous one, but passing the parameter by POST.
1 function to read the table and show its content.
I've been able to do the first and the third ones. But I can't pass the parameter by POST. I've looked for examples but I couldn't run them with success. The client app is a Windows Forms one.
Could anyone show me an example anout how to pass parameters by POST to the function and how to read them?
Thank's in advance
EDIT:
Here's the code to pass the parameters by GET (this is working fine):
private void button2_Click(object sender, EventArgs e)
{
string cadena = lsql1.Text + "?notas=" + tNotas.Text;
try
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(cadena);
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
if (res.StatusCode == HttpStatusCode.OK)
{
MessageBox.Show("Grabado");
}
else
{
MessageBox.Show(res.StatusDescription);
}
}catch (WebException ex)
{
using (Stream s = ex.Response.GetResponseStream())
{
StreamReader sr = new StreamReader(s);
string text = sr.ReadToEnd();
text = text.Substring(1, text.Length - 2);
sr.Close();
text = text.Replace("\\", "");
text = "{" + text + "}";
Error mensajeError = JsonConvert.DeserializeObject<Error>(text);
MessageBox.Show(mensajeError.ExceptionMessage);
}
}
}
And here's the code to receive it and do the insert (this is working too):
[FunctionName("sql1")]
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
try
{
log.Info("C# HTTP trigger function processed a request.");
var cnnString = "Server=SERVIDOR;Database=base_prueba;User ID =azure;Password=0000;Trusted_Connection=False;Encrypt=False;";
using (SqlConnection connection = new SqlConnection(cnnString))
{
connection.Open();
SqlCommand cmd = connection.CreateCommand();
DateTime fecha = DateTime.Today;
string notas = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "notas", true) == 0)
.Value;
// insert a log to the database
cmd.CommandText = "INSERT INTO Prueba_Azure (fecha, notas) VALUES ('" + fecha.ToString() + "', '" + notas + "')";
cmd.ExecuteNonQuery();
}
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
return name == req.CreateResponse(HttpStatusCode.OK, "Done");
}
catch (Exception ex)
{
HttpResponseMessage res = req.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
return res;
}
}
What I'm looking for is to to this by POST
In case google took you here, this is how it's done in March 2019 (Azure Functions v3):
public static async void Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req,
ILogger log)
{
var content = await new StreamReader(req.Body).ReadToEndAsync();
MyClass myClass = JsonConvert.DeserializeObject<MyClass>(content);
}
To get the request content from the request body(post request), you could use req.Content.ReadAsAsync method. Here is the code sample.
Sample request body.
{
"name": "Azure"
}
Define a class to deserialize the post data.
public class PostData
{
public string name { get;set; }
}
Get the post data and display it.
PostData data = await req.Content.ReadAsAsync<PostData>();
log.Info("name:" + data.name);
Client side code to send the post request.
HttpWebRequest req = (HttpWebRequest)WebRequest.Create("function-url");
req.Method = "POST";
req.ContentType = "application/json";
Stream stream = req.GetRequestStream();
string json = "{\"name\": \"Azure\" }";
byte[] buffer = Encoding.UTF8.GetBytes(json);
stream.Write(buffer,0, buffer.Length);
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
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.
For passing parameters as POST request, you need to do following things:
Make Json model of the parameters that u need to pass,ex:
{"UserProfile":{ "UserId":"xyz1","FirstName":"Tom","LastName":"Hank" }}
Post your data model using client like POSTMAN
Now you will get the posted content in HttpRequestMessage body, sample code is as follows:
[FunctionName("TestPost")]
public static HttpResponseMessage POST([HttpTrigger(AuthorizationLevel.Function, "put", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
try
{
//create redis connection and database
var RedisConnection = RedisConnectionFactory.GetConnection();
var serializer = new NewtonsoftSerializer();
var cacheClient = new StackExchangeRedisCacheClient(RedisConnection, serializer);
//read json object from request body
var content = req.Content;
string JsonContent = content.ReadAsStringAsync().Result;
var expirytime = DateTime.Now.AddHours(Convert.ToInt16(ConfigurationSettings.AppSettings["ExpiresAt"]));
SessionModel ObjModel = JsonConvert.DeserializeObject<SessionModel>(JsonContent);
bool added = cacheClient.Add("RedisKey", ObjModel, expirytime); //store to cache
return req.CreateResponse(HttpStatusCode.OK, "RedisKey");
}
catch (Exception ex)
{
return req.CreateErrorResponse(HttpStatusCode.InternalServerError, "an error has occured");
}
}
You can just supply your custom data class as a parameter to the HttpTrigger argument. This way you don't have to mess with the json deserialization yourself:
public async Task<IActionResult> UpdateAccount(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "api/v1/accounts/{id:guid}")]
SomeData someData, // <----- Post body ends up here automatically
HttpRequest req,
Guid id,
ILogger log)
{
log.LogInformation ("Got POST with " + someData.Foo);
}
public class SomeData
{
public string Foo { get; set; } = null!;
}
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;
You need to attach data to the body of the post request and process it properly:
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log) {
// This reads your post request body into variable "data"
string data = await req.Content.ReadAsStringAsync();
// Here you can process json into an object
dynamic parsed = JsonConvert.DeserializeObject(data);
return exitstring == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Something went wrong, sorry")
: req.CreateResponse(HttpStatusCode.OK);
}
You can find a slightly different example here and the exact example here.
It can be done in following way with custom class
Azure Function
[FunctionName("PostParameterFunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
try
{
// Convert all request perameter into Json object
var content = req.Content;
string jsonContent = content.ReadAsStringAsync().Result;
dynamic requestPram = JsonConvert.DeserializeObject<RequestModel>(jsonContent);
// Validate the required param
if (string.IsNullOrEmpty(requestPram.FirstName))
{
return req.CreateResponse(HttpStatusCode.OK, "Please enter First Name!");
}
if (string.IsNullOrEmpty(requestPram.LastName))
{
return req.CreateResponse(HttpStatusCode.OK, "Please enter Last Name!");
}
//Create object for partner Model to bind the response on it
RequestModel objRequestModel = new RequestModel();
objRequestModel.FirstName = requestPram.FirstName;
objRequestModel.LastName = requestPram.LastName;
//Return Request Model
return req.CreateResponse(HttpStatusCode.OK, objRequestModel);
}
catch (Exception ex)
{
return req.CreateResponse(HttpStatusCode.OK, "Cannot Create Request! Reason: {0}", string.Format(ex.Message));
}
}
Request Class:
public class RequestModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Request Input:
{
"FirstName": "Kiron",
"LastName":"Test"
}
PostMan Output Example:
I have done a very simple example to get data using POST request in Azure Function App. Please find the following example.
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace MyFunctions
{
public static class MyFunctionsOperations
{
[FunctionName("MyFunctionsOperations")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
var headers = req.Headers;
string collection = headers.GetValues("collection").First(); //getting parameter from header
CosmosdbOperation obj = new CosmosdbOperation();
dynamic data = await req.Content.ReadAsAsync<object>(); //getting body content
Boolean response = await obj.MyFunctionExecution(data.ToString(), collection);
return (response)
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a proper argument in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Operation successfully executed..");
}
}
}
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;
...
}
Here is the point --> https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=in-process%2Cfunctionsv2&pivots=programming-language-csharp
[FunctionName("LuckyNumber")]
public static async Task<IActionResult> Run(
[HttpTrigger(
AuthorizationLevel.Function,
"get", "post",
Route = "max/{max:int?}/min/{min:int?}")] HttpRequest req,
int? max, <-- Parameter max
int? min, <-- Parameter min
ILogger log)
{
int? maxInternal = max;
int? minInternal = min;
}
PS: I´m using .NET 6
We can do it by just one line code using System.Text.Json.
public static async void Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]
HttpRequest req,
ILogger log)
{
MyClass myClass = await JsonSerializer.DeserializeAsync<MyClass>(req.Body);
}
I have a web api (written in c# on ASP.NET Web API 2). One of the methods takes a HTTP POST and adds it to an Azure Queue.
I'd like to know how I can get hold of a http status code (or whatever is available) from the Azure Queue so that I can return an appropriate http status code from my method.
The code so far is similar to this:
[Route("api/v1/somewhere/")]
[ResponseType(typeof(Thingy))]
public async Task<IHttpActionResult> Post(Thingy thingy)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["AzureWebJobsStorage"].ToString());
var queueClient = storageAccount.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("thingys");
var msg = new CloudQueueMessage(JsonConvert.SerializeObject(thingy));
await queue.AddMessageAsync(msg);
return Json(thingy);
}
I'd like to swap out the return Json(thingy); line and return a http status code 202 if all goes well with the await queue.AddMessageAsync(msg); and another appropriate code if something goes wrong.
Any ideas?
You should make use of LastResult property of OperationContext object. It has a HttpStatusCode property which will return the status code from Storage Service. Here's an example of the same:
static void QueueTest1()
{
var account = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var queueClient = account.CreateCloudQueueClient();
var queue = queueClient.GetQueueReference("my-queue");
queue.CreateIfNotExists();
var messageContents = "This is a test message";
var opContext = new OperationContext();
var msg = new CloudQueueMessage(messageContents);
queue.AddMessage(msg, null, null, null, opContext);
var statusCode = opContext.LastResult.HttpStatusCode;
Console.WriteLine("Status Code = " + statusCode);//Prints 201 as status code
queue.DeleteIfExists();
}
Check out this page:
http://bitoftech.net/2014/03/05/new-asp-net-web-api-2-ihttpactionresult-cors-tutorial/
I think you should wrapp the response you want in your own class, something like that:
public class MyResponse : IHttpActionResult
{
public string Message { get; private set; }
public HttpRequestMessage Request { get; private set; }
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ExecuteResult());
}
public HttpResponseMessage ExecuteResult()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Accepted);
response.Content = new StringContent(Message);
response.RequestMessage = Request;
return response;
}
}
This is a follow-up on an earlier question regarding using HttpClient with Web API performing authentication using a custom Message Handler.
I can request data from the server using the provided solution, but now I am having trouble posting JSON data to the server. Whenever I try posting data to the Web API I am returned an Internal Server Error response code.
Here is the code on the client side:
using (var httpClient = new HttpClient())
{
var request = new HttpRequestMessage();
request.Headers.Add("X-Token", UserSession.GlobalInstance.SecurityToken);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Post;
request.RequestUri = new Uri(_apiBaseAddress + "api/User");
request.Content = new ObjectContent<UserDTO>(userDTO, new JsonMediaTypeFormatter());
var response = httpClient.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
// handle result code
}
throw new Exception(String.Format("Server generated error response: {0}", response.StatusCode));
}
The declaration for the controller method:
public class UserController : ApiController
{
public long Post(UserDTO userDTO)
{
// create user and return custom result
// code (e.g. success, duplicate email, etc...)
}
}
(I've also added [FromBody] to the method parameter, but end up with the same result).
A snapshot of the code for my message handler and routing configuration can be found here.
Your code works as expected...
The server side.
Create a console application and run NuGet
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Program.cs
internal class Program
{
private static IDisposable _server;
private static void Main(string[] args)
{
_server = WebApp.Start<Startup>("http://localhost:12345");
Console.ReadLine();
_server.Dispose();
}
}
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
}
}
WebApiConfig.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var userTokenInspector = new UserTokenInspector {InnerHandler = new HttpControllerDispatcher(config)};
config.Routes.MapHttpRoute(
"UserAuthenticationApi",
"api/{controller}/Authenticate",
new {controller = "User", action = "Authenticate"},
null
);
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional},
null,
userTokenInspector
);
}
}
UserTokenInspector.cs
public class UserTokenInspector : DelegatingHandler {
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) {
const string TOKEN_NAME = "X-Token";
if (!request.Headers.Contains(TOKEN_NAME)) {
return Task.FromResult(request.CreateErrorResponse(HttpStatusCode.Unauthorized,
"Request is missing authorization token."));
}
try {
//var token = UserToken.Decrypt(request.Headers.GetValues(TOKEN_NAME).First());
// validate token
// ...
// ...
Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("alex"), new string[] { });
}
catch {
return Task.FromResult(request.CreateErrorResponse(HttpStatusCode.Unauthorized, "Invalid token."));
}
return base.SendAsync(request, cancellationToken);
}
}
UserController.cs
public class UserController : ApiController
{
public long Post(UserDTO userDTO)
{
// create user and return custom result
// code (e.g. success, duplicate email, etc...)
return 1;
}
}
UserDto.cs
public class UserDTO
{
public string Username { get; set; }
}
ValuesController.cs
public class ValuesController : ApiController
{
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, "yay");
}
}
The Client... create a Console application and run NuGet:
Install-Package Microsoft.AspNet.WebApi.Client
Program.cs
internal class Program
{
private static void Main(string[] args)
{
var request = new HttpRequestMessage();
request.Headers.Add("X-Token", "token");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Post;
var baseAddress = "http://localhost:12345/";
request.RequestUri = new Uri(baseAddress + "api/User");
var userDto = new UserDTO() {Username = "Alex"};
request.Content = new ObjectContent<UserDTO>(userDto, new JsonMediaTypeFormatter());
var httpClient = new HttpClient();
var response = httpClient.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
// handle result code
Console.WriteLine(response.StatusCode);
Console.ReadLine();
}
}
}