Azure DevOps REST API in a Azure Function - c#

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;
}
}
}

Related

C# Call RestAPI with basic Authentication

I am new to RestAPI and am not sure how to call one and pass it credentials. I have a Windows Forms with a "POST" button. This project is built using .Net framework 4.5. When user clicks on this button it executes code with the class below. In this calls there is a Post method that calls a RestAPI. This RestApi requires basic authentication. Any help would be great.
public static class CallRestAPI
{
private static readonly string baseURL = "https://servername/fscmRestApi/resources/11.13.18.05/erpintegrations";
public static async Task<string> Post(string name, string job)
{
var inputData = new Dictionary<string, string>
{
{"name", name },
{"job", job }
};
var input = new FormUrlEncodedContent(inputData);
using (HttpClient client = new HttpClient())
{
using (HttpResponseMessage res = await client.PostAsync(baseURL + "users", input))
{
using (HttpContent content = res.Content)
{
string data = await content.ReadAsStringAsync();
if (data != null)
{
return data;
}
}
}
}
return string.Empty;
}
}
You have to add the authorization header:
var byteArray = Encoding.ASCII.GetBytes($"{UserName}:{Password}");
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

Creating Methods in Azure function

I have a Azure DevOps Function which creates a new team in devOps. I'm extending the code to create Repositories and Files and want to have the different parts divided into methods to have it more streamlined.
I would really appreciate it if someone can guide me how I can create methods inside an Azure function and call them as I'm quiet new to C#.
I am using Visual Studio with .NET Core 3.0.
Here is the code so far:
[FunctionName("Function2")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req)
{
var PAT = "";
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}", "", PAT))));
// Create Team
var teambody = new
{
name = "myteamname",
};
//Connecting to the DevOps REST API
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"https://dev.azure.com/{}/_apis/projects/{}/teams?api-version=6.0");
requestMessage.Content = new StringContent(JsonConvert.SerializeObject(teambody), 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!");
}
// Create Repository
// Create Files
}
}
You can add the methods under your function class(which on the same level with public static async Task<HttpResponseMessage> Run.....).
For example:
namespace FunctionApp2
{
public static class Function2
{
[FunctionName("Function2")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req)
{
//use your method
string name = Method(name);
.............
}
//add your method here
public static string Method(string name)
{
return name+"abc";
}
}
}

Parameterized query with c # HttpClient

I am sending an httpclient request as below. I want to send a parameter with a get request. How can I do that ? Or how can I use it properly?
For example; http://localhost:3000/users?business_code=123
using System.Net.Http;
using System;
using System.Threading.Tasks;
using MyApplication.Models;
namespace MyApplication.Api
{
public class ModelsRepository
{
public HttpClient _client;
public HttpResponseMessage _response;
public HttpRequestMessage _request;
public ModelsRepository()
{
_client = new HttpClient();
_client.BaseAddress = new Uri("http://localhost:3000/");
_client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImZ0aG1seW16QGhvdG1haWwuY29tIiwidXNlcklkIjoxLCJpYXQiOjE2MTM5NzI1NjcsImV4cCI6MTYxNDE0NTM2N30.-EVUg2ZmyInOLBx3YGzLcWILeYzNV-svm8xJiN8AIQI");
_client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<UsersModel> GetList()
{
_response = await _client.GetAsync($"users");
var json = await _response.Content.ReadAsStringAsync();
var listCS = UsersModel.FromJson(json);
return listCS;
}
}
}
What exactly do you mean by "you have to send the variable as a parameter"? Does it mean you want to add parameters dynamically by number and name?
In this case you could have e.g. a Dictionary as input and convert it into a query string:
var queryParameters = new Dictionary<string, string>
{
{ "business_code", "123" },
{ "someOtherParam", "456"}
};
var dictFormUrlEncoded = new FormUrlEncodedContent(queryParameters);
var queryString = await dictFormUrlEncoded.ReadAsStringAsync();
var response = await _client.GetAsync($"users?{queryString}")

Best way to setup MSTest for REST service with cookie-authentication?

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);
}
}
}
}

Get body text from an HttpWebRequest object?

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());
}
}
}

Categories