HttpContent Headers inconsistent enumeration - c#

I am transforming HttpContent into the following dto:
public class ContentDto
{
public string ContentType {get; set;}
public string Headers {get; set; }
public object Data { get; set; }
public ContentDto(HttpContent content)
{
Headers = content.Headers.Flatten();
// rest of the setup
}
}
And am running some unit tests on it:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
Assert.Equal(contentHeaders, dto.Headers);
}
And that test fails since the Content-Length header is not being captured on my dto. However if I do:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var contentHeaders = content.Headers.Flatten();
var dto = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
}
The test passes and all headers are captured. Even more I also tried this:
[Fact]
public void CanBuild()
{
var content = new StringContent("some json", Enconding.UTF8, "application/json");
var dto = new ContentDto(content);
var contentHeaders = content.Headers.Flatten();
var dto1 = new ContentDto(content);
Assert.Equal(contentHeaders, dto.Headers);
Assert.Equal(contentHeaders, dto1.Headers);
}
and it fails since dto doesn't have the Content-Length header, but dto1 does. I even tried getting the headers inside a Factory-like method like this:
public static ContentDto FromContent<T>(T content) where T : HttpContent
{
// same as the constructor
}
to see if there was something special about the StringContent class regarding the Content-Length headers, but it made no difference, no matter if I used the constructor (which uses the base class HttpContent) or the generic method FromContent (using the actual StringContent in this case) the result was the same.
So my questions are:
Is that the intended behavior of HttpContent.Headers?
Are there some headers specific to the actual HttpContent type?
What am I missing here?
Note: This is the code for the Flatten extension method:
public static string Flatten(this HttpHeaders headers)
{
var data = headers.ToDictionary(h => h.Key, h => string.Join("; ", h.Value))
.Select(kvp => $"{kvp.Key}: {kvp.Value}");
return string.Join(Environment.NewLine, data)
}

Your example is incomplete. I was only able to recreate your issue when I accessed the ContentLength property before calling the extension method. Somewhere in your code (most probably //rest of setup) you are either directly or indirectly calling that property which is most probably following a lazy loading pattern and it is then included in the header when next you call your extension method and it is included in the constructed string. They don't match because you are generating your manual string before accessing the content length property.
In the source code for HttpContentHeaders.ContentLength
public long? ContentLength
{
get
{
// 'Content-Length' can only hold one value. So either we get 'null' back or a boxed long value.
object storedValue = GetParsedValues(HttpKnownHeaderNames.ContentLength);
// Only try to calculate the length if the user didn't set the value explicitly using the setter.
if (!_contentLengthSet && (storedValue == null))
{
// If we don't have a value for Content-Length in the store, try to let the content calculate
// it's length. If the content object is able to calculate the length, we'll store it in the
// store.
long? calculatedLength = _calculateLengthFunc();
if (calculatedLength != null)
{
SetParsedValue(HttpKnownHeaderNames.ContentLength, (object)calculatedLength.Value);
}
return calculatedLength;
}
if (storedValue == null)
{
return null;
}
else
{
return (long)storedValue;
}
}
set
{
SetOrRemoveParsedValue(HttpKnownHeaderNames.ContentLength, value); // box long value
_contentLengthSet = true;
}
}
you can see that if you did not explicitly set a content length then it will add it (lazy load) to the headers when you first try to access it.
This proves my original theory about it being added after you generated/flatten your string and then accessed the ContentLength property and explains the inconsistent enumeration.

It seems that the HttpContent class has a pretty strange behavior with the headers properties. Somehow the content length seems to be computed as it is stated here. It does not address your issue specifically, but you can make a test with a new httpContent object similar to the initial one. I am pretty sure that you`ll be able to get the content length without a problem.

Related

System.Text.Json JsonSerializer: Serialization and deserialization of 'System.Type' instances are not supported

I got an error related with security when I tried to deserialize by using `System.Text.Json JsonSerializer`.
What do I want to achieve?
I want to give the user controle to transalte some records in my database, so use can follow this scenario:
1- User can choose model of my class library.
2- After selecting a class, user will select a property(filed) from this class.
3- User will get list of values of the selected property up.
4- Last step is not here right now, user can edit a certian value.
This my piece of code:
MyPage.razor.cs:
[Inject]
private IGenericHttpClient<Type> HttpClient { get; set; }
private Type SelectedType { get; set; }
// First select a class [Class library] from HTML Select
private void OnTypeChnage(ChangeEventArgs args)
{
string FullName = "My.Models." + args.Value.ToString();
// Create type of selected class
SelectedType = Assemble.GetType(FullName, false);
}
//Call api to get all fields of this class
private async Task OnPropertChange(ChangeEventArgs args)
{
var list = await
HttpClient.GetJsonAsync($"/api/{SelectedType.Name}/all");
}
GenericHttpClient.cs
public async ValueTask<List<T>> GetJsonAsync(string url)
{
using HttpResponseMessage response = await _client.GetAsync(url);
ValidateResponse(response);
var conetnt = await response.Content.ReadAsStringAsync();
//I got the error down
return JsonSerializer.Deserialize<List<T>>(conetnt, new JsonSerializerOptions() { PropertyNameCaseInsensitive=true});
}
System.Text.Json does not support Type class due to security reasons. You send the full assembly name as a string and again try to construct the Type at the client end.
public async ValueTask<List<T>> GetJsonAsync(string url) this wont even compile, due to not specify generic information on method signature.
And also, your problem would come from the content of http response, otherwise, the Deserialize step should work fine.
I copied your code and make a small block that prove it.
// Define somewhere
public class GenericHttpClient
{
public List<T> GetJsonAsync<T>()
{
var content = "[{\"TestProp\": \"This is some test\"}]";
return JsonSerializer.Deserialize<List<T>>(content, new JsonSerializerOptions() { PropertyNameCaseInsensitive=true});
}
}
public class Test
{
public string TestProp { get; set; }
}
// Test it
var test = new GenericHttpClient();
var result = test.GetJsonAsync<Test>();
Like what #Mayur Ekbote mentioned up, "System.Text.Json does not support Type class due to security reasons." I will add a solution but I don't think this solution is very efficient.
Change Type to Dynamic:
[Inject]
private IGenericHttpClient<dynamic> HttpClient { get; set; }
Use JsonElement to get the value as a string:
private async Task OnPropertChange(ChangeEventArgs args)
{
var langCode = CultureInfo.CurrentCulture.Name;
PropertyValueList.Clear();
var list = await HttpClient.GetJsonAsync($"/api/{SelectedType.Name}/all");
List<object> listValue = new List<object>();
SelectedProperty = args.Value.ToString();
string fieldName = char.ToLower(SelectedProperty[0]) + SelectedProperty.Substring(1);
foreach (var item in list)
{
//Convert object to JsonElement
var val = ((JsonElement)item).GetProperty(fieldName).GetString();
PropertyValueList.Add(val);
}
}
Why is it not efficient?
Because I got a list of value String instead of list of selected class.

Deserializing an api call response content without having to create a model representing the content

Is there another way to deserialize the response content of an api call into a generic object in .Net without having to create a model representing the json object? In other words, is there or does .Net provide a generic object or model that i can use to deserialize the response into. The reason being is because every api call response is not the same which means i have to create a new model for every api call. And i have about 20 different endpoints that return different responses, which means i would have to create 20 models representing the response.
My recommendation would be to create request and response models for this, even though it feels like extra work. This is because you'll need to eventually pass the parameters to functions in your business layer anyway, and you'll want to take advantage of the type safety without doing a bunch of Int32.TryParse's, which at that point, you're creating variables and doing extra work anyway. Actually, you're not going to be able to outrun the type safety the language not only provides, but generally requires. What I've done, is just copy/paste my DTO or EDMX table model into the new class, then decorate it. Pretty fast.
You may be looking for dynamic object which is late bound with unknown compile time properties.
A handy implementation of this is the JObject available with Newtonsoft.Json package and it offers similar functionality to the typeless elements of Javascript in that the properties can be of any type, with any nesting, etc. as long as it was parsed from well formed Json.
Usage is super simple, but watch out for null values, etc as default behavior for dynamic (aka ExpandoObject under the hood) is to throw exceptions for properties (aka Keys) not found...
public static void Run()
{
string apiJsonResponse = #"{
Name: 'Luke Skywalker',
Title: 'Jedi',
Skills : [
'Lightsaber',
'The Force'
]
}";
dynamic json = JObject.Parse(apiJsonResponse);
Console.WriteLine($"Name: {json.Name}");
Console.WriteLine($"Title: {json.Name}");
Console.WriteLine($"Skills: {String.Join(", ", json.Skills)}");
}
The result will be the Json dynamically parsed and rendered without any strongly typed model:
You can use generic method to have list of JObject. Based on your need you can extract model from jobject.
private static async Task<List<JObject>> CallApi(Uri uri, HttpContent data = null, string headerKey = null, string headerKeyVal = null)
{
string res = string.Empty;
try
{
var handler = new HttpClientHandler
{
//UseDefaultCredentials = true,
};
using var client = new HttpClient(handler);
if (!string.IsNullOrWhiteSpace(headerKey))
client.DefaultRequestHeaders.Add(headerKey, headerKeyVal);
var post = await client.GetAsync(uri);
//var post = await client.PostAsync(uri);
if (post.StatusCode != HttpStatusCode.InternalServerError)
{
res = await post.Content.ReadAsStringAsync();
}
return JsonConvert.DeserializeObject<List<JObject>>(res);
}
catch (Exception ex)
{
throw ex;
}
}
in .Net 3.1 after version, can use using System.Text.Json
api response data
{
"Status" : "0"
"Data" : {
"FirstName" : "firstName",
"LastName" : "lastName"
}
}
Response Model
public class ResponseModel
{
public string Status { get; set; }
public JsonElement Data { get; set; }
}
Deserialize
public void Deserialize()
{
var jsonString = "{ \"Status\": \"0\", \"Data\": { \"FirstName\": \"firstName\", \"LastName\" : \"last_name\"} }";
var response = JsonSerializer.Deserialize<ResponseModel>(jsonString);
//get first_name
var firstName = response.Data.GetProperty("FirstName").GetString();
Console.WriteLine(firstName);
}

How to change the HTTP Request Content Type for FLURL Client?

I am using flurl to submit HTTP request and this is very useful. Now I need to change the "Content-Type" header for some of the requests to "application/json;odata=verbose"
public async Task<Job> AddJob()
{
var flurlClient = GetBaseUrlForGetOperations("Jobs").WithHeader("Content-Type", "application/json;odata=verbose");
return await flurlClient.PostJsonAsync(new
{
//Some parameters here which are not the problem since tested with Postman
}).ReceiveJson<Job>();
}
private IFlurlClient GetBaseUrlForOperations(string resource)
{
var url = _azureApiUrl
.AppendPathSegment("api")
.AppendPathSegment(resource)
.WithOAuthBearerToken(AzureAuthentication.AccessToken)
.WithHeader("x-ms-version", "2.11")
.WithHeader("Accept", "application/json");
return url;
}
You can see how I tried to add the header above (.WithHeader("Content-Type", "application/json;odata=verbose"))
Unfortunately this gives me following error:
"InvalidOperationException: Misused header name. Make sure request
headers are used with HttpRequestMessage, response headers with
HttpResponseMessage, and content headers with HttpContent objects."
I also tried flurl's "ConfigureHttpClient" method but could not find how/where to set the content type header.
This answer is outdated. Upgrade to latest version (2.0 or above) and the problem goes away.
It turns out the real issue has to do with how the System.Net.Http APIs validate headers. It makes a distinction between request-level headers and content-level headers, which I've always found a bit odd since raw HTTP makes no such distinction (except perhaps in multipart scenarios). Flurl's WithHeader adds headers to the HttpRequestMessage object but is failing validation for Content-Type, which it expects to be added to the HttpContent object.
Those APIs do allow you to skip validation, and although Flurl doesn't expose it directly, you can get under the hood pretty easily, without breaking the fluent chain:
return await GetBaseUrlForGetOperations("Jobs")
.ConfigureHttpClient(c => c.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json;odata=verbose"))
.PostJsonAsync(new { ... })
.ReceiveJson<Job>();
This is probably the best way to do what you need and still take advantage of Flurl's goodness, i.e. not have to directly deal with serialization, HttpContent objects, etc.
I'm strongly considering changing Flurl's AddHeader(s) implementations to use TryAddWithoutValidation based on this issue.
The comments and another post I found (will add reference when I find it again) have pointed me to the right direction.
The solution for my problem looks like:
var jobInJson = JsonConvert.SerializeObject(job);
var json = new StringContent(jobInJson, Encoding.UTF8);
json.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; odata=verbose");
var flurClient = GetBaseUrlForOperations("Jobs");
return await flurClient.PostAsync(json).ReceiveJson<Job>();
Edit: Found the related SO question: Azure encoding job via REST Fails
public static class Utils
{
public static IFlurlClient GetBaseUrlForOperations(string resource)
{
var _apiUrl = "https://api.mobile.azure.com/v0.1/apps/";
var url = _apiUrl
.AppendPathSegment("Red-Space")
.AppendPathSegment("HD")
.AppendPathSegment("push")
.AppendPathSegment("notifications")
.WithHeader("Accept", "application/json")
.WithHeader("X-API-Token", "myapitocken");
return url;
}
public static async Task Invia()
{
FlurlClient _client;
PushMessage pushMessage = new PushMessage();
pushMessage.notification_content = new NotificationContent();
try
{
var flurClient = Utils.GetBaseUrlForOperations("risorsa");
// News news = (News)contentService.GetById(node.Id);
//pushMessage.notification_target.type = "";
pushMessage.notification_content.name = "A2";
// pushMessage.notification_content.title = node.GetValue("TitoloNews").ToString();
pushMessage.notification_content.title = "Titolo";
pushMessage.notification_content.body = "Contenuto";
var jobInJson = JsonConvert.SerializeObject(pushMessage);
var json = new StringContent(jobInJson, Encoding.UTF8);
json.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
dynamic data2 = await flurClient.PostAsync(json).ReceiveJson();
var expandoDic = (IDictionary<string, object>)data2;
var name = expandoDic["notification_id"];
Console.WriteLine(name);
}
catch (FlurlHttpTimeoutException ex)
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
}
catch (FlurlHttpException ex)
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
if (ex.Call.Response != null)
Console.WriteLine("Failed with response code " + ex.Call.Response.StatusCode);
else
Console.WriteLine("Totally failed before getting a response! " + ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType + " " + ex);
}
}
}
public class NotificationTarget
{
public string type { get; set; }
}
public class CustomData {}
public class NotificationContent
{
public string name { get; set; }
public string title { get; set; }
public string body { get; set; }
public CustomData custom_data { get; set; }
}
public class PushMessage
{
public NotificationTarget notification_target { get; set; }
public NotificationContent notification_content { get; set; }
}
Am I allowed to post 3 answers to the same question? :)
Upgrade. Flurl.Http 2.0 includes the following enhancements to headers:
WithHeader(s) now uses TryAddWithoutValidation under the hood. With that change alone, the OP's code will work as originally posted.
Headers are now set at the request level, which solves another known issue.
When using SetHeaders with object notation, underscores in property names will be converted to hyphens in the header names, since hyphens in headers are very common, underscores are not, and hyphens are not allowed in C# identifiers.
This will be useful in your case:
.WithHeaders(new {
x_ms_version = "2.11",
Accept = "application/json"
});
I'm not an OData expert and I don't know what API you're calling (SharePoint?), but based on most examples I've seen, what you typically want to do is ask the server to send verbose OData in the response, rather than declare that you're sending it in the request. In other words, you want to set the ;odata=verbose bit on the Accept header, not Content-Type. application/json should be good enough for Content-Type, and Flurl will set that for you automatically, so just try this change and see if it works:
.WithHeader("Accept", "application/json;odata=verbose");

How to post complex object to Post method of the controller via HttpClient with headers?

The question has focus on providing headers with the request, so I presume only HttpClient.SendAsync() method is to be considered. Not PostAsync or PostAsJsonAsycn()
Here is the code I use to send a POST request:
var request = new HttpRequestMessage()
{
RequestUri = new Uri(#"http://test/" + controllerUri),
Method = HttpMethod.Post,
};
request.Headers.Add(_authHeader, c_authToken);
request.Content = new ObjectContent<RequestType>(content, new JsonMediaTypeFormatter());
var result = _client.SendAsync(request).Result;
I am constantly getting 500 error saying that Duration property on the serialized object is invalid, although in fact is it set in the object.
I tried StringContent with mediatype set to "application/json" but I got the same error.
The controller's post method has the following signature:
public HttpResponseMessage Post(MyContentObject content)
{
...
}
Exception is thrown before entering to the method.
Interestingly enough, when I commented out [Required] attribute from MyContentObject:
public class MyContentObject
{
[Required]
public int Duration { get; set; }
public decimal SomePropA { get; set; }
public bool SomePropB { get; set; }
}
it works and Duration property is set correctly as expected.
So maybe the question is why adding [Required] attribute in this case is making so much problems? There may be questions from you on why there is [Required] attribute added to an int property. My answer is I don't know who added it and for me it does not make sense, but if it does, please let me know.
Thanks
I did more research on this.
So I will start from the end. The [Required] annotation issue is covered in this thread: http://aspnetwebstack.codeplex.com/workitem/270
Basically built-in InvalidModelValidatorProvider checks models validation for nonsensical annotation like this above (value type properties have always a value) and throws an exception. The article above mentions also a workaround to avoid an exception to be thrown without changing the existing annotation.
The following code enables you to post an object serialized as JSON with headers attached:
// var RequestType content <- this is an input parameter.
var request = new HttpRequestMessage()
{
RequestUri = new Uri(#"http://test/" + yourControllerUri),
Method = HttpMethod.Post,
};
request.Headers.Add("YourHeaderName", "YourHeaderValue");
request.Content = new ObjectContent<RequestType>(content, new JsonMediaTypeFormatter());
var result = _client.SendAsync(request).Result;
I hope somebody will find it useful.

How to consume a file with a ServiceStack client

I am trying to use ServiceStack to return a file to a ServiceStack client in a RESTful manner.
I have read other questions on SO (here and here) which advise using HttpResult and a FileInfo object or MemoryStream to allow the ContentType header to be changed to the relevant file type.
This works for me when I call the service via a browser, the correct file automatically starts to download. How do I consume the file using one of the ServiceStack clients though?
I'm using a Request DTO and trying to return using something similar to
return new HttpResult(new FileInfo("file.xml"), asAttachment:true) {
ContentType = "text/xml"
};
How would I consume this with the JsonServiceClient for example?
I had a similar requirement which also required me to track progress of the streaming file download. I did it roughly like this:
server-side:
service:
public object Get(FooRequest request)
{
var stream = ...//some Stream
return new StreamedResult(stream);
}
StreamedResult class:
public class StreamedResult : IHasOptions, IStreamWriter
{
public IDictionary<string, string> Options { get; private set; }
Stream _responseStream;
public StreamedResult(Stream responseStream)
{
_responseStream = responseStream;
long length = -1;
try { length = _responseStream.Length; }
catch (NotSupportedException) { }
Options = new Dictionary<string, string>
{
{"Content-Type", "application/octet-stream"},
{ "X-Api-Length", length.ToString() }
};
}
public void WriteTo(Stream responseStream)
{
if (_responseStream == null)
return;
using (_responseStream)
{
_responseStream.WriteTo(responseStream);
responseStream.Flush();
}
}
}
client-side:
string path = Path.GetTempFileName();//in reality, wrap this in try... so as not to leave hanging tmp files
var response = client.Get<HttpWebResponse>("/foo/bar");
long length;
if (!long.TryParse(response.GetResponseHeader("X-Api-Length"), out length))
length = -1;
using (var fs = System.IO.File.OpenWrite(path))
fs.CopyFrom(response.GetResponseStream(), new CopyFromArguments(new ProgressChange((x, y) => { Console.WriteLine(">> {0} {1}".Fmt(x, y)); }), TimeSpan.FromMilliseconds(100), length));
The "CopyFrom" extension method was borrowed directly from the source code file "StreamHelper.cs" in this project here: Copy a Stream with Progress Reporting (Kudos to Henning Dieterichs)
And kudos to mythz and any contributor to ServiceStack. Great project!
You wouldn't consume files with the ServiceStack's .NET ServiceClients as they're mainly for sending DTO's.
You can just use any normal WebRequest to download files, in the v3.9.33 of ServiceStack introduced some handy WebRequest extensions HTTP Utils that make this easy, e.g:
For a text file:
var xmlFile = downloadUrl.GetXmlFromUrl(responseFilter: httpRes => {
var fileInfoHeaders = httpRes.Headers[HttpHeaders.ContentDisposition];
});
Where fileInfoHeaders contains the W3C ContentDisposition HTTP Header, e.g. when returning a FileInfo, ServiceStack returns:
attachment;filename="file.xml";size={bytesLen};
creation-date={date};modification-date={date};read-date={date};
To download a binary file you can use:
var rawBytes = downloadUrl.GetBytesFromUrl(httpRes => ...);
I have found mythz answer to work well, but it is also possible to use their built in JSonServiceClient to also process file requests as well, just in a slightly non-intuitive way because you can't actually use the return type you would expect.
For a model definition like this:
[Route("/filestorage/outgoing/{Name}.{Extension}", "GET")]
[Route("/filestorage/outgoing", "GET")]
public class GetFileStorageStream : IReturn<HttpResult>
{
public string Name { get; set; }
public string Extension { get; set; }
public bool ForDownload { get; set; }
}
You can define your service to return an HttpResult:
public class FileStorageService : Service
{
public HttpResult Get(GetFileStorageStream fileInformation)
{
var internalResult = GetFromFileStorage(fileInformation);
var fullFilePath = Path.Combine("C:\Temp", internalResult.FileName);
return new HttpResult(new FileInfo(fullFilePath), asAttachment: fileInformation.ForDownload);
}
}
Then on the client side you can use this Get template to properly get the web context:
var client = new JsonServiceClient("http://localhost:53842");
var httpResponse = client.Get<HttpWebResponse>("/filestorage/outgoing/test.jpg");
pictureBox1.Image = Image.FromStream(httpResponse.GetResponseStream());
I found it was not possible to use the new API Get methods as they would attempt to deserialize the HttpResult which isn't actually a true return type but a class representing the web context that service stack has created.
You can intercept the response prior to it being handled by using a response filter, like below:
ServiceClientBase.HttpWebResponseFilter = response =>
{
if (response.Headers["Content-Disposition"] != null)
{
var t = response.DownloadText();
Console.WriteLine(t);
}
};
However, this is not the best way to handle it, since the actual call to client.Method() will result in an ArgumentException when the client attempts to read the response stream (since it has been read previously by response.DownloadFile(...). I haven't yet figured out a way to handle it gracefully, but I 'll update my answer if I do.

Categories