Create Azure DevOps attachment - c#

I want to create a Azure DevOps Attachment. The attachment is an Outlook message.
From the documentation seen here.
The body must be "[BINARY FILE CONTENT]". How can I read a .msg-File into C# and add it to a JsonBody Request?

Had to do something like this recently, the code looks like the stuff below.
The byte[] is the data i read directly from the filestream, like so
byte[] bytes;
using (var img = File.OpenRead("message.msg"))
{
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
}
then posting it to Azure DevOps
internal async Task<Attachement> UploadAttachment(string filename,byte[] content)
{
ByteArrayContent data = new ByteArrayContent(content);
HttpClient client = GetApiClient();
using (HttpResponseMessage response = await client.PostAsync($"https://dev.azure.com/{_org}/_apis/wit/attachments?fileName={filename}&api-version=5.1", data))
{
// Parse response body en evaluate result
var responseBody = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Attachement>(responseBody);
}
}
This seems to work quit well for us :)
don't forget to also actually add it to the workitem using: https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/update?view=azure-devops-rest-5.1#add-an-attachment

Related

.NET Azure SDK - get raw response for async pageable result

I'm using the lastest Azure.ResourceManager SDK for .NET to list all resources in a subscription, this works fine:
var armClient = new ArmClient(new DefaultAzureCredential());
var subscription = await armClient.GetDefaultSubscriptionAsync();
await foreach(var resource in subscription.GetGenericResourcesAsync())
{
_logger.LogInformation("Got resource {id}", resource.Id);
}
However, I would like to get access to the raw HTTP response. This article explains how to do this for other, non-pageable responses. So I'm looking for a way to do this in my case, too. Something like GetRawResponse() - but I don't have a response object.
Please try something like the following:
var credential = new DefaultAzureCredential();
var armClient = new ArmClient(credential);
var subscription = await armClient.GetDefaultSubscriptionAsync();
var allResources = subscription.GetGenericResourcesAsync();
await foreach(Page<GenericResource> page in allResources.AsPages())
{
Response http = page.GetRawResponse();
Stream contentStream = http.ContentStream;
// Rewind the stream
contentStream.Position = 0;
using (StreamReader reader = new StreamReader(contentStream))
{
Console.WriteLine(reader.ReadToEnd());
}
}
For more details, please see this link: https://learn.microsoft.com/en-us/dotnet/azure/sdk/pagination

Fix issue with custom response ".NET Core API Gateway Ocelot - Middleware"

Good day,
I have an issue with a custom response in API Gateway Ocelot with Middleware.
inside FormatResponse(context.Response) I change response for specific endpoint and I see the new response on debug but I receive the original Response in final result on postman.
ex :
original Response
{
"name":"mo"
}
after a change will be
{
"name":"mo123"
}
my code
// .NET Core 3.1
public async Task InvokeAsync(HttpContext context)
{
context.Request.EnableBuffering();
var builder = new StringBuilder();
var request = await FormatRequest(context.Request);
builder.Append("Request: ").AppendLine(request);
builder.AppendLine("Request headers:");
foreach (var header in context.Request.Headers)
{
builder.Append(header.Key).Append(':').AppendLine(header.Value);
}
//Copy a pointer to the original response body stream
var originalBodyStream = context.Response.Body;
//Create a new memory stream...
using var responseBody = new MemoryStream();
//...and use that for the temporary response body
context.Response.Body = responseBody;
//Continue down the Middleware pipeline, eventually returning to this class
await _next(context);
//Format the response from the server
var response = await FormatResponse(context.Response); // here ,i see new respose
builder.Append("Response: ").AppendLine(response);
builder.AppendLine("Response headers: ");
foreach (var header in context.Response.Headers)
{
builder.Append(header.Key).Append(':').AppendLine(header.Value);
}
//Save log to chosen datastore
_logger.LogInformation(builder.ToString());
//Copy the contents of the new memory stream (which contains the response) to the original
// stream, which is then returned to the client.
await responseBody.CopyToAsync(originalBodyStream);
}
private async Task<string> FormatResponse(HttpResponse response)
{
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
text = CustomRes(text); //************************ here, I change response
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
return $"{response.StatusCode}: {text}";
}
Reference: https://www.abhith.net/blog/dotnet-core-api-gateway-ocelot-logging-http-requests-response-including-headers-body/
The best answer from Richard Deeming:
https://www.codeproject.com/Questions/5294847/Fix-issue-with-custom-response-NET-core-API-gatewa

Forwarding a stream in c# api

In my service we need to get a zip file created by another service and return it.
This is my code (code has been simplified for the question):
[HttpGet("mediafiles/{id}")]
public async Task<IActionResult> DownloadMediaFiles(int id)
{
var fileIds = _myProvider.GetFileIdsForEntityId(id); // result be like "1,2,3,4"
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"http://file-service/bulk/{fileIds}");
var stream = await response.Content.ReadAsStreamAsync();
return File(stream, "application/octet-stream", "media_files.zip");
}
With the id I can gather the info I need to create the fileIds string and call the other service.
Here's the api on the other service (code has been simplified for the question):
[HttpGet("bulk/{idList}")]
public async Task<IActionResult> DownloadBulk(string idList)
{
var ids = string.IsNullOrEmpty(idList) ? new List<int>() : idList.Split(',').Select(x => Convert.ToInt32(x));
using var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var index = archive.CreateEntry("hello.txt");
using (var entryStream = index.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write("hello");
}
}
var byteArray = memoryStream.ToArray();
return File(byteArray, "application/octet-stream", "media_files.zip");
}
but when the client tries to open the zip we get
Exception has occurred. ArchiveException (FormatException: Could not
find End of Central Directory Record)
I'm absolutely not confident about these two lines of the /mediafiles/{id}
var stream = await response.Content.ReadAsStreamAsync();
return File(stream, "application/octet-stream", "media_files.zip");
And probably the issue might be there.
I just need to forward back the file-service response, but I don't know why
I believe the problem you're experiencing is that in DownloadMediaFiles(int id) you are using an HttpClient that gets disposed when leaving the function scope. The stream you created from the response therefore is closed and disposed of as well, before the response payload has finished writing its contents to the client. The client therefore receives an incomplete zip-file that you can't open. See here for reference.
In this answer there's a simple solution you could use, which is simply to read the response stream (the response stream from $"http://file-service/bulk/{fileIds}") into a byte array and then pass it to the response to the client:
using var httpClient = new HttpClient();
var response = await httpClient.GetAsync($"http://file-service/bulk/{fileIds}");
var byteArr = await response.Content.ReadAsByteArrayAsync();
return File(byteArr, "application/octet-stream", "media_files.zip");
You might realize that this means loading the whole file into memory, which can quickly become an issue if you plan on working with large files or even with medium sized files if the API is supposed to be used by a lot of clients simultaneously. Your web application would most likely run out of memory at some point.
Instead, I came upon this article which shows how you can return the contents of the stream from a request using an HttpClient. You should be able to stick with the first section of that article (all the ZIP-file and callback-based response stuff is unrelated).
To recap on that article all you need is something like this:
// Your ControllerClass.cs
private static HttpClient Client { get; } = new HttpClient();
[HttpGet("mediafiles/{id}")]
public async Task<IActionResult> DownloadMediaFiles(int id)
{
var fileIds = _myProvider.GetFileIdsForEntityId(id); // result be like "1,2,3,4"
var stream = await Client.GetStreamAsync($"http://file-service/bulk/{fileIds}");
return File(stream, "application/octet-stream", "media_files.zip");
}
You'll notice, that the stream object is not disposed of here but ASP.Net Core does this for you as part of writing the response payload to the client. The Client which is stored in a static global variable is not disposed of either, which means you can reuse it between requests (it's usually recommended not to instantiate a new HttpClient everytime you need it). ASP.Net Core 2.1 and up has special support for dependency injecting the client for you through the IHttpClientFactory interface. I would suggest you do that instead of a static variable. Read here for the most basic usage of injecting the client factory.
Now you should be able to enjoy streaming the file contents directly from your "other service" without loading it into memory in your API web application.

How to send File Object to Web api using POST call in Xamarin forms.?

I need to make a POST call from my Xamarin forms app where I need to upload a file object as it is to API using POST Call. Is there any way to make it possible?
if you send file object using Base64 or Byte[] then it will allowed only limited may be upto 2-4 Mb but if you have a larger image than that it will not support.
So, Solution is Post Stream Content Like,
var file = await CrossMedia.Current.PickPhotoAsync(new PickMediaOptions
{
PhotoSize = PhotoSize.Full,
CompressionQuality = 100
});
Create Object of MediaFile like, public MediaFile AttachedImage; and Store file into it so Memory stream will not lost. Like,AttachedImage = file
Post Code on API,
HttpClient httpClient = new HttpClient();
MultipartFormDataContent mt = new MultipartFormDataContent();
AttachedImage.GetStream().Position = 0;
StreamContent imagePart = new StreamContent(AttachedImage.GetStream());
imagePart.Headers.Add("Content-Type", ImageType);
mt.Add(imagePart, String.Format("file"), String.Format("bk.jpeg"));
requestMessage.Content = mt;
var response = await httpClient.PostAsync("Your URL", mt);
if (response.IsSuccessStatusCode)
{
var responseString = await response.Content.ReadAsStringAsync();
var objRootObjectuploadImage = JsonConvert.DeserializeObject<RootObjectuploadImage>(responseString);
if (objRootObjectuploadImage != null)
{
}
else
{
}
}
else
{
Loading(ActIndicator, false);
await DisplayAlert(res.LAlert, "webserver not responding.", res.LOk);
}
NO, it is not possible to send file object. You can send as a json by converting the file in the Base64 string. This is the advised proven solution. This link has code for converting back and forth from Base64.

Microsoft Cognitive Services Vision API: Sending multipart data

I am trying to call the Microsoft Cognitive API by passing multiple images as per documentation and using the multipart/form-data, but I am getting an error that says "Unsupported Media Type". I have tried to use both ByteArray and StreamContent.
Api documentation.
private static byte[] GetImageAsByteArray(Stream fileStream)
{
using (var binaryReader = new BinaryReader(fileStream))
{
return binaryReader.ReadBytes((int)fileStream.Length);
}
}
static void Main(string[] args)
{
var uriBase = "https://westus.api.cognitive.microsoft.com/vision/v1.0/recognizeText";
var subscriptionKey = "<subscriptionKey>";
var client = new HttpClient();
var uri = string.Concat(uriBase, "?", "language=en&detectOrientation=true");
var images = new List<Stream>();
var img = Image.FromStream(File.Open("<imageName>", FileMode.Open));
var stream = new MemoryStream();
img.Save(stream, ImageFormat.Bmp);
stream.Position = 0;
images.Add(stream);
using (var content = new MultipartFormDataContent())
{
foreach (var image in images)
{
//content.Add(new StreamContent(stream));
content.Add(new ByteArrayContent(GetImageAsByteArray(image)));
}
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
content.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data");
var response = client.PostAsync(uri, content).Result;
}
}
I am trying to call the Microsoft Cognitive API by passing multiple images as per documentation and using the multipart/form-data, but I am getting an error that says "Unsupported Media Type".
It is not possible to send multiple images, regardless of header.
Please refer to the documentation Step 2, it mentions:
The basic way to perform the Computer Vision API call is by uploading an image directly. This is done by sending a "POST" request with application/octet-stream content type together with the data read from the image.
Example code can be found here
Test environment here.
Notice regardless of header, it is still sending 1 image.
The limits also mention a single image.

Categories