How to return multipart content with html and xml - asp.net-core

I have to re-write a web service in .NET Core 2.2, and the service must return a multipart response with XML and base-64 encoded HTML. The current service is a really old java program with no documentation.
The request comes from an old service that can't change and must receive HTML 4.01 as the response. An actual sample is shown below.
What kind of response object do I return?
How do I combine the XML and encoded HTML together into one response object?
I don't expect anyone to write my code for me but I would appreciate some help getting started.
Sample response
--75df5969-8400-11d5-c000-0021ffffff97
Content-type: text/xml; charset="UTF-8"
Content-id: XMLROOT
<?xml version="1.0" encoding="UTF-8"?><XMLROOT><Service><StatusCd>0</StatusCd></Service></XMLROOT>
--75df5969-8400-11d5-c000-0021ffffff97
Content-type: application/html
Content-id: Inquiry.html
Content-description: Response to View Inquiry
PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEgVHJhbnNpdGlvbmFsLy9FTiI+Cgo8SFRNTD4KPEhFQUQ+CjxNRVRBIGh0dHAtZXF1aXY9IkNvbnRlbnQtVHlwZSIgY29udGVudD0idGV4dC9odG1sOyBjaGFyc2V0PUlTTy04ODU5LTEiPgo8TUVUQSBodHRwLWVxdWl2PSJDb250ZW50LVN0eWxlLVR5cGUiIGNvbnRlbnQ9InRleHQvY3NzIj4KCgk8U1RZTEUgVFlQRT0idGV4dC9jc3MiPgoJCVRSIHsgYmFja2dyb3VuZC1jb2xvcjogbGlnaHRncmV5IH0KCQkudGREYXRhIHsKCSAJCWZvbnQtc2l6ZTo4cHQ7CgkJCWNvbG9yOiMwMDAwOUM7CgkJCWZvbnQtZmFtaWx5OlZlcmRhbmEsIEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7IH0KCQkudGRMYWJlbCB7CgkgCQlmb250LXNpemU6OHB0OwoJCQlmb250LXdlaWdodDpib2xkZXI7CgkJCWZvbnQtZmFtaWx5OlZlcmRhbmEsIEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7IH0KCQkudGRIZWFkIHsKCQkJYmFja2dyb3VuZC1jb2xvcjojODA4MDgwOwoJCQl0ZXh0LWFsaWduOmNlbnRlcjsKCSAJCWZvbnQtc2l6ZTo4cHQ7CgkJCWZvbnQtd2VpZ2h0OmJvbGRlcjsKCQkJZm9udC1mYW1pbHk6VmVyZGFuYSwgQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjsgfQoJPC9TVFlMRT4KCjxUSVRMRT5JbnF1aXJ5PC9USVRMRT4KPC9IRUFEPgo8Qk9EWT4KPHRhYmxlIEJPUkRFUj0iMCIgQ0VMTFBBRERJTkc9IjAiIENFTExTUEFDSU5HPSIwIiBXSURUSD0iNzAwIj4KCTx0cj4gCgkJPHRkIEJHQ09MT1I9IiNmZmZmZmYiPgoJCQk8VEFCTEUgQk9SREVSPSIxIiBDRUxMUEFERElORz0iMiIgQ0VMTFNQQUNJTkc9IjEiIFdJRFRIPSI3MDAiPgoJCQkJPFRSPjxURCBDT0xTUEFOPSI0IiBBTElHTj0iY2VudGVyIiBCR0NPTE9SPSIjZmZmZmZmIj4KCQkJCQlUZXN0IG1lc3NhZ2UgCgkJCQk8L1REPjwvVFI+CgkJCTwvVEFCTEU+CQoJCTx0ZD48L3RkPgoJPHRyPjwvdHI+CjwvdGFibGU+Cgo8L0JPRFk+CjwvSFRNTD4=
--75df5969-8400-11d5-c000-0021ffffff97--

What kind of response object do I return? How do I combine the XML and encoded HTML together into one response object?
There's no such a built-in result type. However, it would be easy to create a custom MultipartResult that will contains the XML + HTML (encoded). For example:
public class XmlAndEncodedHtmlMultipartResult : IActionResult
{
private StringContent _xmlContent;
private StringContent _htmlContent;
private MultipartContent _multipartContent=new MultipartContent(Subtype); // the final content
public static string Subtype = "my-xml+html" ; // `content-type: multipart/{Subtype}`
public XmlAndEncodedHtmlMultipartResult SetXmlContent(string xml, string contentId = "XMLROOT")
{
var xmlContent = new StringContent(xml, Encoding.UTF8, "text/xml");
xmlContent.Headers.Add("Content-id", contentId);
this._xmlContent = xmlContent;
return this;
}
public XmlAndEncodedHtmlMultipartResult SetEncodedHtmlContent(string rawHtml,string contentId="Inquiry.html", string description="Response to View Inquiry")
{
var bytes= Encoding.UTF8.GetBytes(rawHtml);
var encodedHtml = Convert.ToBase64String(bytes);
var htmlContent = new StringContent(encodedHtml, Encoding.UTF8, "application/html");
htmlContent.Headers.Add("Content-id", contentId);
htmlContent.Headers.Add("Content-description",description);
this._htmlContent = htmlContent;
return this;
}
public async Task ExecuteResultAsync(ActionContext context)
{
if(this._htmlContent ==null || this._xmlContent ==null){ throw new Exception("html content & xml must be null");}
this._multipartContent.Add(this._xmlContent);
this._multipartContent.Add(this._htmlContent);
var response = context.HttpContext.Response;
response.ContentType = this._multipartContent.Headers.ContentType.ToString();
// ... custom other headers as you like
await _multipartContent.CopyToAsync(response.Body);
}
}
In your action, just invoke it as below :
public IActionResult Privacy()
{
var xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XMLROOT><Service><StatusCd>0</StatusCd></Service></XMLROOT>";
var html = "<html><head></head><body>hello,world</body></html>";
return new XmlAndEncodedHtmlMultipartResult().SetEncodedHtmlContent(html).SetXmlContent(xml);
}

Related

Reading and DeserializeObject Response Header using Asp Core 3.1

I'm try to make a paging for collection, I have a Web API and I'm sending paging data into header like total count and next page link so I can read the response header and put it into string as a Json here is my consume code
public async Task<MultibleValuesHelper> GetAllTags()
{
IEnumerable<TagModelDto> tags;
IEnumerable<PagedDataModel> Header=null;
using (var response = await _httpClient.GetAsync($"tag"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
if( response.Headers.TryGetValues("X-Pagenation", out var Pagenation))
{
var TryHeader = Pagenation.FirstOrDefault();
Header = JsonConvert.DeserializeObject<List<PagedDataModel>>(TryHeader);
}
tags = JsonConvert.DeserializeObject<List<TagModelDto>>(apiResponse);
}
return new MultibleValuesHelper {
TagServiceCollection = tags,
HeaderPagenation = Header
};
}
my problem when I'm try to Deserialize header it showen Error :JsonSerializationException: Cannot deserialize the current JSON object So how I can Deserialize Header and put it into IEnumerable Header so I can handle it
Okay the answer is a little bit change in the code we will use PagedDataModel instead List, and here is the full code
public async Task<MultibleValuesHelper> GetAllTags()
{
IEnumerable<TagModelDto> tags;
PagedDataModel Header=new PagedDataModel ;
using (var response = await _httpClient.GetAsync($"tag"))
{
string apiResponse = await response.Content.ReadAsStringAsync();
if( response.Headers.TryGetValues("X-Pagenation", out var Pagenation))
{
var TryHeader = Pagenation.FirstOrDefault();
Header = JsonConvert.DeserializeObject<PagedDataModel>(TryHeader);
}
tags = JsonConvert.DeserializeObject<List<TagModelDto>>(apiResponse);
}
return new MultibleValuesHelper {
TagServiceCollection = tags,
HeaderPagenation = Header
};
}
this is will work Thanks for all of you

How to avoid JSON deserialize / serialize in Web API?

I have the following code in a Web API controller in ASP.NET 2.0:
[HttpGet]
[Route("{controllerName}/{nodeId}/ConfigurationValues")]
public async Task<IActionResult> GetConfigurationValues(string controllerName, byte nodeId, string code)
{
string payload = ...
HttpResponseMessage response = await deviceControllerRepository.ExecuteMethodAsync(controllerName, "GetNodeConfigurationValues", payload);
string responseJson = await response.Content.ReadAsStringAsync();
var configurationValues = JsonConvert.DeserializeObject<List<ConfigurationValue>>(responseJson);
return Ok(configurationValues);
}
How can I avoid having to de-serialize the responseJson into a .NET object before returning it as it already is in the correct JSON format?
I tried to change the method into returning HttpResponseMessage, but that resulted in incorrect JSON being passed back to the caller.
ControllerBase class has a Content() method for this purpose. Make sure you set correct Content-Type header in output packet:
return Content(responseJson, "application/json");
You can return ContentResult instead of Ok() for avoiding unnecessary serialization:
return new ContentResult
{
Content = responseJson,
ContentType = "application/json",
StatusCode = 200
};

Logging the XML or JSON sent by RestSharp

I'm using RestSharp to send information to a API. I would like to log the XML that I've sent to this API so I can debug later.
I would like to do something like this:
var request = new RestRequest(resourcePath, method);
request.AddBody(dto);
Logger.Log(request.Content);
var response = Client.Execute(request);
But, the actual request sent by RestSharp does not seem to be exposed.
Everything sent in the request is available in request.Parameters.
To make getting the request body easier I created this extension method:
public static class RestSharpExtensions
{
public static string GetBody(this IRestRequest request)
{
var bodyParameter = request.Parameters
.FirstOrDefault(p => p.Type == ParameterType.RequestBody);
return bodyParameter == null
? null
: bodyParameter.Value.ToString();
}
}

Sending Multipart form data from windows phone to web api

Hi I want to send(post/put) some data(containing string, int and Stream) from windows phone 8.1 using HttpClient to web api. what is the best way to do that.
public async void Put(string uri)
{
var httpClient = new System.Net.Http.HttpClient();
MultipartFormDataContent content = new MultipartFormDataContent();
var stringContent = new StringContent("FirstName=MUH&LastName=Test", Encoding.UTF8, "multipart/form-data");
var test = new StreamContent(new MemoryStream());
content.Add(test);
content.Add(stringContent);
var message = await httpClient.PutAsync(url+"/UpdateTest", content);
message.EnsureSuccessStatusCode();
string content1 = await message.Content.ReadAsStringAsync();
}
api method in my mvc app
[AllowAnonymous]
[Route("~/api/account/UpdateTest")]
[HttpPut]
public async Task<object> UpdateTest()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
try
{
var requestParts = await Request.Content.ReadAsMultipartAsync();
foreach (var part in requestParts.Contents)
{
//part is always StreamContent
var test = await part.ReadAsStreamAsync();
var test1 = await part.ReadAsStringAsync();
}
}
catch (Exception ex)
{ }
}
In my windows phone project I have passed 2 HttpContent, one is StreamContent where as other is StringContent. but in my web api put method both are StreamContent I do't know why.
and other problem is I have to parse the string key value in StingContnet. My question is what is the best way of sending/receiving multipart form data from windows phone 8.1 to web api,
Thanks
Following is an example(change this accordingly to your scenario):
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(baseAddress);
HttpRequestMessage request = new HttpRequestMessage();
MultipartFormDataContent mfdc = new MultipartFormDataContent();
mfdc.Add(new StringContent("Michael"), name: "FirstName");
mfdc.Add(new StringContent("Jordan"), name: "LastName");
mfdc.Add(new StreamContent(content: new MemoryStream(Encoding.UTF8.GetBytes("This is from a file"))),
name: "Data",
fileName: "File1.txt");
HttpResponseMessage response = await client.PostAsync(baseAddress + "/api/values", mfdc);
public async Task Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
MultipartFormDataStreamProvider prov = await Request.Content.ReadAsMultipartAsync<MultipartFormDataStreamProvider>(new MultipartFormDataStreamProvider(#"C:\uploadedfiles"));
// example of how you can read the form data
string firstName = prov.FormData["FirstName"];
// Get list of all files that have been uploaded and stored in the above provided root folder
Collection<MultipartFileData> files = prov.FileData;
}
Following is how request looks like in Fiddler tool:
POST http://localhost:9095/api/values HTTP/1.1
Content-Type: multipart/form-data; boundary="7560a854-a71a-4e55-9571-5c2de520f45f"
Host: kirandesktop:9095
Content-Length: 474
Expect: 100-continue
Connection: Keep-Alive
--7560a854-a71a-4e55-9571-5c2de520f45f
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=FirstName
Michael
--7560a854-a71a-4e55-9571-5c2de520f45f
Content-Type: text/plain; charset=utf-8
Content-Disposition: form-data; name=LastName
Jordan
--7560a854-a71a-4e55-9571-5c2de520f45f
Content-Disposition: form-data; name=Data; filename=File1.txt; filename*=utf-8''File1.txt
This is from a file
--7560a854-a71a-4e55-9571-5c2de520f45f--
Also note that you can read the StreamContent anyway you want...in the following examples, I am simulating a request's body stream and reading it as a simple string or deserializing into an object of type Person.
StreamContent requestStream = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello World!")));
string data = await requestStream.ReadAsStringAsync();
//---------------------
StreamContent requestStream = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("{ \"FirstName\" : \"Michael\" }")));
requestStream.Headers.ContentType = new MediaTypeHeaderValue("application/json");
Person person = await requestStream.ReadAsAsync<Person>();
I use a custom media type formatter that's based on the code in this article.
ASP.NET WebApi: MultipartDataMediaFormatter
One of the advantages over the MultipartFormDataStreamProvider is that I don't need to specify a folder to save the file data, so I can inspect the contents in memory. You might not want to do this with huge files though. There's alot a github repo for it too if you want to look at the code. Also, I get strongly typed objects for file and form data

File Upload with Additonal Form Data to Web Api from MVC

I am trying to upload a file with additional form data and post to Web API via MVC but i couldn't accomplish.
MVC Side
Firstly i got the submitted form at MVC. Here is the action for this.
[HttpPost]
public async Task<ActionResult> Edit(BrandInfo entity) {
try {
byte[] logoData = null;
if(Request.Files.Count > 0) {
HttpPostedFileBase logo = Request.Files[0];
logoData = new byte[logo.ContentLength];
logo.InputStream.Read(logoData, 0, logo.ContentLength);
entity.Logo = logo.FileName;
entity = await _repo.Update(entity.BrandID, entity, logoData);
}
else
entity = await _repo.Update(entity,entity.BrandID);
return RedirectToAction("Index", "Brand");
}
catch(HttpApiRequestException e) {
// logging, etc
return RedirectToAction("Index", "Brand");
}
}
Below code post the Multipartform to Web API
string requestUri = UriUtil.BuildRequestUri(_baseUri, uriTemplate, uriParameters: uriParameters);
MultipartFormDataContent formData = new MultipartFormDataContent();
StreamContent streamContent = null;
streamContent = new StreamContent(new MemoryStream(byteData));
streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") {
FileName = "\"" + fileName + "\"",
Name = "\"filename\""
};
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
formData.Add(streamContent);
formData.Add(new ObjectContent<TRequestModel>(requestModel, _writerMediaTypeFormatter), "entity");
return _httpClient.PutAsync(requestUri, formData).GetHttpApiResponseAsync<TResult>(_formatters);
As you can see i am trying to send file data and object with same MultipartFormDataContent. I couldn't find better way to send my entity as ObjectContent. Also i am using JSON.Net Serializer
Regarding to fiddler, post seems successfull.
PUT http://localhost:12836/api/brand/updatewithlogo/13 HTTP/1.1
Content-Type: multipart/form-data; boundary="10255239-d2a3-449d-8fad-2f31b1d00d2a"
Host: localhost:12836
Content-Length: 4341
Expect: 100-continue
--10255239-d2a3-449d-8fad-2f31b1d00d2a
Content-Disposition: form-data; filename="web-host-logo.gif"; name="filename"
Content-Type: application/octet-stream
GIF89a��L������X�������wW����������xH�U�)�-�k6�������v6�������̥�v�J���������7����V:�=#�ի�I(�xf�$�������
// byte data
// byte data
'pf�Y��y�ؙ�ڹ�(�;
--10255239-d2a3-449d-8fad-2f31b1d00d2a
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=entity
{"BrandID":13,"AssetType":null,"AssetTypeID":2,"Logo":"web-host-logo.gif","Name":"Geçici Brand","Models":null,"SupplierBrands":null}
--10255239-d2a3-449d-8fad-2f31b1d00d2a--
Web API Side
Finally i am catching post at Web API side and trying to parse but i couldn't. Because MultipartFormDataStreamProvider's FileData and FormData collections are allways empty.
[HttpPut]
public void UpdateWithLogo(int id) {
if(Request.Content.IsMimeMultipartContent()) {
var x = 1; // this code has no sense, only here to check IsMimeMultipartContent
}
string root = HttpContext.Current.Server.MapPath("~/App_Data");
var provider = new MultipartFormDataStreamProvider(root);
try {
// Read the form data.
Request.Content.ReadAsMultipartAsync(provider);
foreach(var key in provider.FormData.AllKeys) {
foreach(var val in provider.FormData.GetValues(key)) {
_logger.Info(string.Format("{0}: {1}", key, val));
}
}
// This illustrates how to get the file names.
foreach(MultipartFileData file in provider.FileData) {
_logger.Info(file.Headers.ContentDisposition.FileName);
_logger.Info("Server file path: " + file.LocalFileName);
}
}
catch(Exception e) {
throw new HttpApiRequestException("Error", HttpStatusCode.InternalServerError, null);
}
}
I hope you can help to find my mistake.
UPDATE
I also realized that, if i comment out StreamContent or ObjectContent and only add StringContent, still i can't get anything from MultipartFormDataStreamProvider.
Finally i resolved my problem and it was all about async :)
As you can see at API action method i had called ReadAsMultipartAsync method synchrously but this was a mistake. I had to call it with ContinueWith so after i changed my code like below my problem solved.
var files = Request.Content.ReadAsMultipartAsync(provider).ContinueWith<HttpResponseMessage>(task => {
if(task.IsFaulted)
throw task.Exception;
// do additional stuff
});