I have a (legacy) method in my codebase which generates and returns a ready-to-send HTTP POST message as a System.Net.HttpWebRequest object:
public HttpWebRequest GetHttpWebRequest(string body, string url, string contentType)
{
HttpWebRequest request = HttpWebRequest.CreateHttp(url);
request.Method = "POST";
// (More setup stuff here...)
using (var writer = new StreamWriter(request.GetRequestStream()))
{
writer.Write(body);
}
return request;
}
I'd like to write a unit test which verifies that the HttpWebRequest instance returned by this method actually does have the message body text that was passed in to the method in the body parameter.
Question: How can I get the body text of an HttpWebRequest object (without ever actually sending the HTTP request)?
Stuff I've tried so far:
new StreamReader(myHttpWebRequest.GetRequestStream()).ReadToEnd() - Fails at runtime with ArgumentException: Stream was not readable.
The HttpWebRequest class doesn't seem to have any property that would allow getting/reading the HTTP message body such as Body, Message, Text, etc.
I would write a http listener and make real http requests.
Here is a sample server using WCF + the client code. Just call await TestClient.Test(); (You can also test the server with a browser like http://localhost:8088/TestServer/Dummy)
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Text;
using System.Threading.Tasks;
namespace SO
{
[ServiceContract]
public class TestServer
{
static WebServiceHost _host = null;
public static Task Start()
{
var tcs = new TaskCompletionSource<object>();
try
{
_host = new WebServiceHost(typeof(TestServer), new Uri("http://0.0.0.0:8088/TestServer"));
_host.Opened += (s, e) => { tcs.TrySetResult(null); };
_host.Open();
}
catch(Exception ex)
{
tcs.TrySetException(ex);
}
return tcs.Task;
}
//A method that accepts anything :)
[OperationContract, WebInvoke(Method = "*", UriTemplate ="*")]
public Message TestMethod(Stream stream )
{
var ctx = WebOperationContext.Current;
var request = ctx.IncomingRequest.UriTemplateMatch.RequestUri.ToString();
var body = new StreamReader(stream).ReadToEnd();
Console.WriteLine($"{ctx.IncomingRequest.Method} {request}{Environment.NewLine}{ctx.IncomingRequest.Headers.ToString()}BODY:{Environment.NewLine}{body}");
return ctx.CreateTextResponse( JsonConvert.SerializeObject( new { status = "OK", data= "anything" }), "application/json", Encoding.UTF8);
}
}
public class TestClient
{
public static async Task Test()
{
await TestServer.Start();
var client = new HttpClient();
var objToSend = new { name = "L", surname = "B" };
var content = new StringContent( JsonConvert.SerializeObject(objToSend) );
var response = await client.PostAsync("http://localhost:8088/TestServer/TestMethod?aaa=1&bbb=2", content);
Console.WriteLine(response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
}
Related
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.IO;
namespace FunctionRestApi
{
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
var httpWebRequest = (HttpWebRequest)WebRequest.Create("URL_with_client_id_authorization_token");
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "GET";
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
log.LogInformation(result);
}
return new OkObjectResult(value: httpWebRequest);
}
}
}
I am new to azure function. This code works when I am just using 'GET' method. But if I want to use 'POST' method with request body data i.e. date range (start_date and end_date) and some other sub_user_id, then how can I do that?
Here is a simple example of a POST request using HttpClient with some comments:
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace AzureFunctionsSandbox.Functions
{
public static class Function1
{
private static readonly HttpClient _httpClient = new HttpClient();
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest request,
ILogger log)
{
// create request as an object to easily set values
var myRequest = new MyRequest
{
StartDate = DateTime.Now,
EndDate = DateTime.Now.AddDays(1),
SubUserId = "ABC123"
};
// serialize to JSON string for POST request body
var myRequestJsonBody = JsonConvert.SerializeObject(myRequest);
// .PostAsync() requires a HttpContent object - StringContent is a sub class
var requestContent = new StringContent(myRequestJsonBody, Encoding.UTF8, "application/json");
// make the POST request
var response = await _httpClient.PostAsync("URL_with_client_id_authorization_token", requestContent);
// use response body for further work if needed...
var responseBody = response.Content.ReadAsStringAsync();
return new OkResult();
}
}
public class MyRequest
{
[JsonProperty(PropertyName = "start_date")]
public DateTime StartDate { get; set; }
[JsonProperty(PropertyName = "end_date")]
public DateTime EndDate { get; set; }
[JsonProperty(PropertyName = "sub_user_id")]
public string SubUserId { get; set; }
}
}
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0
See one of the answers in this SO question, it shows how to make a POST call using the HttpClient class, however it is creating new instance of it, it is not the right way. As a best practice use only a static object of HttpClient in your function app.
first check with POSTMAN how you can connect to external API with what authentication configuration BASIC/ BEARER then you can write code using same configuration
I'm trying to create a POST Request for Azure DevOps Teams and Repositories and want to create a new team and a new repository through the API method. My Team is created fine but I don't know how to extend the code to create a repository in the same HttpRequest and also how do I have the 'body' to include both the name of the team and the name of the repository as they both have the same 'name' parameter.
I'm quiet new to C# and Azure functions and don't know how to properly implement this in my own project. I would really appreciate it if someone could guide me into the right direction.
I am using Visual Studio with .NET Core 3.0.
Here is the code so far:
using System.IO;
using System;
using System.Net;
using Microsoft.Azure.WebJobs;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs.Extensions.Http;
namespace TeamsAdd
{
public static class TeamsAdd
{
[FunctionName("Function1")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]
HttpRequestMessage req)
{
var personalaccesstoken = "";
var body = new
{
name = "myteamname",
project = new
{
id = "xxxxxxx",
projectname = "myprojectname",
}
};
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", "", personalaccesstoken))));
//Connecting to the DevOps REST API
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"https://dev.azure.com/{organization}/_apis/projects/{projectId}/teams?api-version=6.0");
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
//Reading Server Response
using (HttpResponseMessage response = await client.SendAsync(requestMessage))
{
if (!response.IsSuccessStatusCode)
{
response.EnsureSuccessStatusCode();
}
return req.CreateResponse(HttpStatusCode.Created, "Teams created successfully!");
}
}
}
}
}
The API used to create a team and repo are different. For more details, please refer to here and here. So we cannot create the two resources in one request. we need to send two requests to create the two different resources.
For example
public static class Function2
{
public static HttpClient Client = new HttpClient();
public static string PAT = "";
[FunctionName("Function2")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
Object teamBody = new
{
name = "mytest7878orf",
};
Object repoBody = new
{
name = "mytest85698",
project= new {
id = "8d5fa9a0-1061-4891-977b-f91189a0dcbe",
}
};
var teamRes= sendRequest(HttpMethod.Post, "https://dev.azure.com/{organization}/_apis/projects/{projectId}/teams?api-version=6.0", teamBody);
var repoRes= sendRequest(HttpMethod.Post, "https://dev.azure.com/{organization}/{projectId}/_apis/git/repositories?api-version=6.0", repoBody);
var res = new
{
teamRes = JsonConvert.DeserializeObject(teamRes),
repoRes = JsonConvert.DeserializeObject(repoRes)
};
return req.CreateResponse(HttpStatusCode.OK, JsonConvert.SerializeObject(res), "application/json");
}
public static string sendRequest(HttpMethod method, string url, Object body = null) {
Client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(
ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}", "", PAT))));
//Connecting to the DevOps REST API
var requestMessage = new HttpRequestMessage(method, url);
if (body != null) {
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
}
using (HttpResponseMessage response = Client.SendAsync(requestMessage).Result)
{
if (!response.IsSuccessStatusCode)
{
try
{
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex) {
return ex.Message;
}
}
var content = response.Content.ReadAsStringAsync().Result;
return content;
}
}
}
Background: I am using ASP.NET Core 3.1, and integration testing a REST service that requires cookie authentication.
Candidate solution below.
Note:
The reason I use a vanilla Host instead of TestServer is because of the cookie requirement. When using TestServer, it provides an HttpClient for you, but the client does not pass cookies back to the server.
I also attempted to use a custom HttpClient with TestServer. That consistently generated a System.Net.Sockets.SocketException (No connection could be made because the target machine actively refused it.)
using Microsoft.Extensions.Hosting;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi; // Contains my Startup.cs
namespace WebApiTest
{
[TestClass]
public class UserTest
{
static IHost HttpHost;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
HttpHost = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.Build();
await HttpHost.StartAsync();
}
[ClassCleanup]
public static async Task ClassCleanup()
{
await HttpHost.StopAsync();
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
public static HttpClient GetCookieHttpClient()
{
SocketsHttpHandler handler = new SocketsHttpHandler
{
AllowAutoRedirect = false,
CookieContainer = new CookieContainer(),
UseCookies = true
};
return new HttpClient(handler);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = GetCookieHttpClient())
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
HttpClient is a thin-client; it doesn't do anything unless you explicitly tell it to. In other words, it will never send the cookie for you; you must add a Cookie header to the request with the cookie value for each request. The test server "client" is just an HttpClient instance set up to proxy requests to the test server. You should use the test server, as prescribed, along with its client, and then add the Cookie header the requests you make with that.
Solutions based on Chris Pratt's suggestions
After some further digging, Microsoft provides a solution for this (WebApplicationFactory):
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
[TestClass]
public class Class2
{
static WebApplicationFactory<Startup> Factory;
static WebApplicationFactoryClientOptions ClientOptions;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
Factory = new WebApplicationFactory<Startup>();
ClientOptions = new WebApplicationFactoryClientOptions();
ClientOptions.AllowAutoRedirect = false;
ClientOptions.HandleCookies = true;
ClientOptions.BaseAddress = new Uri("http://localhost:5000");
}
public static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = null;
if (content != null)
{
httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
}
return httpContent;
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (HttpClient client = Factory.CreateClient(ClientOptions))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.PostAsync("http://localhost:5000/api/auth/login", GetHttpContent(credentials));
response = await client.GetAsync(String.Format("http://localhost:5000/api/users/{0}", credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
In case you want to stick with TestServer, here is a manual Cookie-passing implementation:
using Microsoft.AspNetCore.TestHost;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using WebApi;
namespace WebApiTest
{
public class CookieHttpClient : IDisposable
{
private static HttpContent GetHttpContent(object content)
{
HttpContent httpContent = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(content, content.GetType()));
httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return httpContent;
}
private static IEnumerable<string> GetCookieStrings(CookieCollection collection)
{
List<string> output = new List<string>(collection.Count);
foreach (Cookie cookie in collection)
{
output.Add(cookie.Name + "=" + cookie.Value);
}
return output;
}
private HttpClient client;
private CookieContainer container;
public CookieHttpClient(HttpClient client)
{
this.client = client;
this.container = new CookieContainer();
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri)
{
return await this.SendAsync(method, uri, null);
}
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri uri, object data)
{
HttpRequestMessage request = new HttpRequestMessage(method, uri);
// Add data
if (data != null)
{
request.Content = GetHttpContent(data);
}
// Add cookies
CookieCollection collection = this.container.GetCookies(uri);
if (collection.Count > 0)
{
request.Headers.Add("Cookie", GetCookieStrings(collection));
}
HttpResponseMessage response = await this.client.SendAsync(request);
// Remember cookies before returning
if (response.Headers.Contains("Set-Cookie"))
{
foreach (string s in response.Headers.GetValues("Set-Cookie"))
{
this.container.SetCookies(uri, s);
}
}
return response;
}
public void Dispose()
{
this.client.Dispose();
}
}
[TestClass]
public class Class1
{
static TestServer TestServer;
[ClassInitialize]
public static async Task ClassStartup(TestContext context)
{
IWebHostBuilder builder = new WebHostBuilder()
.UseStartup<Startup>();
TestServer = new TestServer(builder);
}
[TestMethod]
public async Task GetUserData_ReturnsSuccess()
{
using (CookieHttpClient client = new CookieHttpClient(TestServer.CreateClient()))
{
var credentials = new
{
Email = "test#test.com",
Password = "password123",
};
HttpResponseMessage response = await client.SendAsync(HttpMethod.Post, new Uri("http://localhost:5000/api/auth/login"), credentials);
response = await client.SendAsync(HttpMethod.Get, new Uri("http://localhost:5000/api/users/" + credentials.Email));
Assert.IsTrue(response.StatusCode == HttpStatusCode.OK);
}
}
}
}
i want to do a REST POST call, using HttpClient.I keep having error - 'RestCall.PostRestCall(string, string, string, string)': not all code paths return a value'
Below is my Code. Its meant to receive baseUrl, contentType, requestBody, httpMethod; as input parameter and return the response status code, status description and response content.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace RestPostCall
{
public class RestCall
{
public static string PostRestCall(string baseURL, string httpMethod, string contentType, string requestBody)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseURL);
// Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue(contentType));
// List data response.
HttpResponseMessage response = client.GetAsync(requestBody).Result;
if (response.IsSuccessStatusCode)
{
// Parse the response body.
var dataObjects = response.Content.ReadAsAsync<IEnumerable<IDataObject>>().Result;
//Make sure to add a reference to System.Net.Http.Formatting.dll
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
//Make any other calls using HttpClient here.
//Dispose once all HttpClient calls are complete.
client.Dispose();
}
}
}
You method does not return any value, so change signature to return void type:
public static void PostRestCall(string baseURL, string httpMethod, string contentType,
string requestBody)
Change your method return type to void or if you want to return any value use return statement.
Have to consume a web API. The Controller looks like this:
using System;
using System.Collections.Generic;
using System.Web.Http;
using ERPService.Api.Models;
using ERPService.Core.Services;
namespace ERPService.Api.Controllers
{
[RoutePrefix("api/local")]
public class LocalProductController : BaseApiController
{
[Route("product/{productId}/export")]
public ApiResponse<IList<ServiceResponse<object>>> ExportProduct(int productId)
{
return Response(this.ServiceFactory.ProductService.ExportProduct(productId));
}
}
}
Using HttpClient how can I call that ExportProduct function?
I created a console application to consume this:
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:49319/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("api/local/product/{productId}/export").Result;
The result is an error as following:
Error Code - MethodNotAllowed
Message - Method Not Allowed
string json;
WebRequest req = HttpWebRequest.Create(url);
req.Method = "GET";
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
using (StreamReader responseReader = new StreamReader(responseStream))
{
json = responseReader.ReadToEnd();
}
}
}
Found the solution for the problem: had to add the [HttpGet] attribute so it should look like this:
[Route("product/{productId}/export")]
[HttpGet]
public ApiResponse<IList<ServiceResponse<object>>> ExportProduct(int productId)
{
return Response(this.ServiceFactory.ProductService.ExportProduct(productId));
}