Any idea how to generate json key as snake_case with RestSharp?
With Newtonsoft.Json, I can set json output something like this
DefaultContractResolver contractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
string json = JsonConvert.SerializeObject(requestData, new JsonSerializerSettings
{
ContractResolver = contractResolver,
Formatting = Formatting.Indented
});
But I not sure how can be done with RestSharp
var client = new RestClient(getService.MstUrl);
client.AddDefaultHeader("Authorization", string.Format("Bearer {0}", token));
var request = new RestRequest(Method.POST).AddJsonBody(requestData);
var response = await client.ExecuteAsync(request);
It keep generate as camelCase. Is there any configuration like Newtonsoft.Json?
Using RestSharp, does not mean you can't use the Newtonsoft serializer as well.
From the restsharp documentation:
RestSharp support Json.Net serializer via a separate package. You can install it from NuGet.
client.UseNewtonsoftJson();
And keep on going with what you did:
DefaultContractResolver contractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
You should be able to create your own IRestSerializer implementation and supply that in client.UseSerializer
Given
public class SimpleJsonSerializer : IRestSerializer
{
private readonly DefaultContractResolver _contractResolver;
public SimpleJsonSerializer()
{
_contractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
};
}
public string Serialize(object obj) => JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
ContractResolver = _contractResolver,
Formatting = Formatting.Indented
});
public string Serialize(Parameter bodyParameter) => Serialize(bodyParameter.Value);
public T Deserialize<T>(IRestResponse response) => JsonConvert.DeserializeObject<T>(response.Content);
public string[] SupportedContentTypes { get; } =
{
"application/json", "text/json", "text/x-json", "text/javascript", "*+json"
};
public string ContentType { get; set; } = "application/json";
public DataFormat DataFormat { get; } = DataFormat.Json;
}
Usage
var client = new RestClient(getService.MstUrl);
client.AddDefaultHeader("Authorization", string.Format("Bearer {0}", token));
client.UseSerializer(() => new SimpleJsonSerializer(){});
var request = new RestRequest(Method.POST).AddJsonBody(requestData);
var response = await client.ExecuteAsync(request);
Disclaimer : I haven't used RestSharp in years and I never intend to use it again. Further more, I really do suggest to anyone thinking of using it to switch back to HttpClient, or even better IHttpClientFactory. More so, this is completely untested, and only given as an example. You may need to modify it to it your needs
Related
I want to add global JsonSerializer options to use ReferenceHandler.Preserve, i can't configure my blazor server App to use it as a global setting for all json Serializers.
i used
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
options.SerializerOptions.PropertyNameCaseInsensitive = true;
});
builder.Services.AddRazorPages().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
builder.Services.Configure<JsonOptions>(o =>
{
o.SerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.SerializerOptions.PropertyNameCaseInsensitive = true;
});
none of them works as expected the options doesn't change from the defaults and i keep getting the same exception: "The JSON value could not be converted to"
using the same options at each request works
var options = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.Preserve, PropertyNameCaseInsensitive = true };
var httpClient = _httpFactory.CreateClient("API");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _tokenProvider.JwtToken);
var result = await httpClient.GetFromJsonAsync<List<Manufacturer>>("manufacturer", options);
but i want to define the options for all requests without explicitly writing them each time.
Instead of Named Client you could use Typed Client (or even generated with NSwag or Refit) and handle JSON formatting options inside this typed API client.
E.g. NSwag API clients generator has an option to generate UpdateJsonSerializerSettings method which you can define in the base class for type dAPI client like:
internal class BaseClient
{
protected static void UpdateJsonSerializerSettings(JsonSerializerOptions settings) => settings.ConfigureDefaults();
}
// generated API client:
[System.CodeDom.Compiler.GeneratedCode("NSwag", "13.16.1.0 (NJsonSchema v10.7.2.0 (Newtonsoft.Json v13.0.0.0))")]
internal partial class SomeClient : BaseClient, ISomeClient
{
private System.Net.Http.HttpClient _httpClient;
private System.Lazy<System.Text.Json.JsonSerializerOptions> _settings;
public ActionsClient(System.Net.Http.HttpClient httpClient)
{
_httpClient = httpClient;
_settings = new System.Lazy<System.Text.Json.JsonSerializerOptions>(CreateSerializerSettings);
}
private System.Text.Json.JsonSerializerOptions CreateSerializerSettings()
{
var settings = new System.Text.Json.JsonSerializerOptions();
UpdateJsonSerializerSettings(settings);
return settings;
}
... the rest of generated code has omitted
}
// Extension which configures the default JSON settings (as an example):
public static class JsonExtensions
{
private static JsonSerializerOptions GetDefaultOptions() => new() { WriteIndented = true };
public static JsonSerializerOptions ConfigureDefaults(this JsonSerializerOptions? settings)
{
settings ??= GetDefaultOptions();
settings.PropertyNameCaseInsensitive = true;
settings.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
settings.NumberHandling = JsonNumberHandling.AllowReadingFromString;
settings.Converters.Add(new JsonStringEnumConverter());
return settings;
}
}
Then you register on DI your ISomeClient typed client like this:
builder.Services
.AddScoped<MyLovelyAuthorizationMessageHandler>()
.AddHttpClient<ISomeClient, SomeClient>(httpClient =>
{
httpClient.BaseAddress = new Uri(apiSettings.WebApiBaseAddress);
// ...etc.
}).AddHttpMessageHandler<MyLovelyAuthorizationMessageHandler>();
And then - inject ISomeClient where you need it, and call methods with typed DTOs keeping all JSON serialization/deserialization magic under the carpet.
[Inject] private ISomeClient Client {get; set;} = default!;
Documentation: about Typed Clients
I have a .net core 3.1 application. I use the library json.net (newtonsoft) to serialize or deserialize the json . This is the app settings for newtonsoft :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.SuppressAsyncSuffixInActionNames = false;
}).AddNewtonsoftJson(options =>
{
options.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Local;
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
options.SerializerSettings.Converters.Add(new GuidJsonConverter());
});
I've put this line to ignore null json value on deserialization :
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
But I remark that it ignores also null value for serialization (when use Json method of the class Microsoft.AspNetCore.Mvc.Controller), but I don't want this behavior.
Is there a way to specify differents value of NullValueHandling for serialization and for deserialization ?
Finally I opt for this solution:
I made a BaseController class which inherits from Microsoft.AspNetCore.Mvc.Controller. I have inherited each of my controllers from this BaseController class.
In this class, I override the Microsoft.AspNetCore.Mvc.Controller.Json method :
public class BaseController : Controller
{
private readonly JsonSerializerSettings _jsonSerializerSettings;
public BaseController(IServiceProvider services)
{
IOptions<MvcNewtonsoftJsonOptions> newtonsoftOptions = services.GetService<IOptions<MvcNewtonsoftJsonOptions>>();
_jsonSerializerSettings = newtonsoftOptions.Value.SerializerSettings;
_jsonSerializerSettings.NullValueHandling = NullValueHandling.Include;
}
public override JsonResult Json(object data)
{
return Json(data, _jsonSerializerSettings);
}
Thanks to IOptions<MvcNewtonsoftJsonOptions> I was able to recover the serializer settings initialized in the startup.
EDIT
I remarks that the change of value _jsonSerializerSettings.NullValueHandling = NullValueHandling.Include; change also the init serializer settings.
So I've made an extensions method to copy all data of serializer settings in aim to update just the new settings :
public CustomerAccountController(IServiceProvider services)
{
IOptions<MvcNewtonsoftJsonOptions> newtonsoftOptions = services.GetService<IOptions<MvcNewtonsoftJsonOptions>>();
_jsonSerializerSettings = newtonsoftOptions.Value.SerializerSettings.CloneJsonSerializerSettings();
_jsonSerializerSettings.NullValueHandling = NullValueHandling.Include;
}
public static JsonSerializerSettings CloneJsonSerializerSettings(this JsonSerializerSettings settings)
{
JsonSerializerSettings cloneSettings = new JsonSerializerSettings();
cloneSettings.StringEscapeHandling = settings.StringEscapeHandling;
cloneSettings.FloatParseHandling = settings.FloatParseHandling;
cloneSettings.FloatFormatHandling = settings.FloatFormatHandling;
cloneSettings.DateParseHandling = settings.DateParseHandling;
cloneSettings.DateTimeZoneHandling = settings.DateTimeZoneHandling;
cloneSettings.DateFormatHandling = settings.DateFormatHandling;
cloneSettings.Formatting = settings.Formatting;
cloneSettings.MaxDepth = settings.MaxDepth;
cloneSettings.DateFormatString = settings.DateFormatString;
cloneSettings.Context = settings.Context;
cloneSettings.Error = settings.Error;
cloneSettings.SerializationBinder = settings.SerializationBinder;
cloneSettings.Binder = settings.Binder;
cloneSettings.TraceWriter = settings.TraceWriter;
cloneSettings.Culture = settings.Culture;
cloneSettings.ReferenceResolverProvider = settings.ReferenceResolverProvider;
cloneSettings.EqualityComparer = settings.EqualityComparer;
cloneSettings.ContractResolver = settings.ContractResolver;
cloneSettings.ConstructorHandling = settings.ConstructorHandling;
cloneSettings.TypeNameAssemblyFormatHandling = settings.TypeNameAssemblyFormatHandling;
cloneSettings.TypeNameAssemblyFormat = settings.TypeNameAssemblyFormat;
cloneSettings.MetadataPropertyHandling = settings.MetadataPropertyHandling;
cloneSettings.TypeNameHandling = settings.TypeNameHandling;
cloneSettings.PreserveReferencesHandling = settings.PreserveReferencesHandling;
cloneSettings.Converters = settings.Converters;
cloneSettings.DefaultValueHandling = settings.DefaultValueHandling;
cloneSettings.NullValueHandling = settings.NullValueHandling;
cloneSettings.ObjectCreationHandling = settings.ObjectCreationHandling;
cloneSettings.MissingMemberHandling = settings.MissingMemberHandling;
cloneSettings.ReferenceLoopHandling = settings.ReferenceLoopHandling;
cloneSettings.ReferenceResolver = settings.ReferenceResolver;
cloneSettings.CheckAdditionalContent = settings.CheckAdditionalContent;
return cloneSettings;
}
I have a function that takes an API request, checks if the result exists in a Redis cache, if it does it returns the cached value, if not it sends the API request and then caches the value.
private async Task<HttpResponseMessage> RestGetCachedAsync(string query, ILogger logger = null)
{
string key = $"GET:{query}";
HttpResponseMessage response;
var cacheResponse = await _cacheService.GetStringValue(key);
if (cacheResponse != null)
{
response = JsonConvert.DeserializeObject<HttpResponseMessage>(cacheResponse, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Ignore,
});
if(response.IsSuccessStatusCode) return response;
}
response = await RestGetAsync(query, logger);
if (response.IsSuccessStatusCode)
{
await _cacheService.SetStringValue(key, JsonConvert.SerializeObject(response, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Ignore,
}));
}
return response;
}
When reading a previously queried API
public async Task<string> DeserializeHttpResponse(HttpResponseMessage response)
{
return await response.Content.ReadAsStringAsync();
}
I get the following error.
InvalidOperationException: The stream was already consumed. It cannot be read again.
After a discussion in the comments to the question I realized my fault. I thought that the content data was stored with the HttpResponseMessage and would be there if I serialized and deserialized. However, it looks like the Content data in the HttpResponseMessage is more like a pointer that provides instructions for how to read the value which is stored elsewhere and these instructions are invoked by the ReadAsStringAsync() function in the HttpContent class.
So my quick fix was to create a wrapper object that stores the serialized HttpResponseMessage as well as the Content result returned by ReadAsStringAsync(). This wrapper looks like this.
public class WrapperHttpResponse
{
public HttpResponseMessage HttpResponseMessage { get; set; }
public string Content { get; set; }
public WrapperHttpResponse()
{
}
public WrapperHttpResponse(HttpResponseMessage httpResponseMessage)
{
HttpResponseMessage = httpResponseMessage;
Content = httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();
}
public WrapperHttpResponse(HttpResponseMessage httpResponseMessage, string content)
{
HttpResponseMessage = httpResponseMessage;
Content = content;
}
}
this method has 3 constructors which allows me to instantiate null, read, and unread instances of HttpResponseMessages. Then I rewrote my caching execution as follows.
private async Task<WrapperHttpResponse> RestGetCachedAsync(string query, ILogger logger = null)
{
string key = $"GET:{query}";
WrapperHttpResponse response;
var cacheResponse = await _cacheService.GetStringValue(key);
if (cacheResponse != null)
{
response = JsonConvert.DeserializeObject<WrapperHttpResponse>(cacheResponse, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Ignore,
});
if(response.HttpResponseMessage.IsSuccessStatusCode) return response;
}
response = await RestGetAsync(query, logger);
if (response.HttpResponseMessage.IsSuccessStatusCode)
{
await _cacheService.SetStringValue(key, JsonConvert.SerializeObject(response, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Ignore,
}));
}
return response;
}
I have a little program which should communicate with "Slack". In an older Version I used "Dictionary<string, string>" and then put them into UrlEncodedContent - which worked fine.
Now I am trying to create a Json-object, using Newtonsoft's Nuget-package and (in my opinion) formatting my object the way they say on their website.
Problem is, when I try to make a simple request, my program just runs to one specific line in the code(var response = await _httpClient.SendAsync(request);) and then it just ends. It doesn't throw an exception or display any kind of message, it simply ends on this line. I went through my code step by step while debugging, that's how I know it ends on exactly this line. And I just don't know why!
Now my code:
First, my object...
namespace BPS.Slack
{
public class JsonObject
{
//generally needed parameters
[JsonProperty("ok")]
public bool ok { get; set; }
[JsonProperty("error")]
public string error { get; set; }
[JsonProperty("channel")]
public string channel { get; set; }
[JsonProperty("token")]
private string token = "xoxp-MyToken";
[JsonProperty("as_user")]
public bool as_user = false;
[JsonProperty("username")]
public string username { get;set; }
//--------------------------------
//only needed for textmessages
[JsonProperty("text")]
public string text { get; set; }
//--------------------------------
//for posting messages with data attached
[JsonProperty("initial_comment")]
public string initial_comment { get; set; }
[JsonProperty("file")]
public string file { get; set; }
[JsonProperty("channels")]
public string channels { get; set; }
//--------------------------------
//for getting the latest message from a channel
[JsonProperty("count")]
public string count = "1";
[JsonProperty("unreads")]
public bool unreads = true;
}
}
now the client:
namespace BPS.Slack
{
public class BpsHttpClient
{
private readonly HttpClient _httpClient = new HttpClient { };
public Uri UriMethod { get; set; }
public BpsHttpClient(string webhookUrl)
{
UriMethod = new Uri(webhookUrl);
}
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
}
}
and the main
namespace TestArea
{
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WhenAll(SendMessage());
}
catch(Exception ass)
{
Console.WriteLine(ass);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(StringJson);
var Response = await client.UploadFileAsync(content);
string AnswerContent = await Response.Content.ReadAsStringAsync();
Console.WriteLine(AnswerContent);
Console.ReadKey();
}
}
}
I had the same problem in my older version, BUT only as I wanted to DEserialize an answer I got from Slack. It had to do with my object I tried do deserialize the answer into. But this time I can not figure out what's wrong. But, as I said, I do not have any experience with using serialized objects as Json-property to send requests... anyone has an idea what is wrong with my code?
EDIT: This problem is kinda solved. But there is a follow up problem.
Okay, I found out that the reason for the abprubt termination was the
Task.WhenAll(SendMessage());
it should be
Task.WaitAll(SendMessage()); Why??? Somebody said I should use WhenAll, but obviously it doesn't work properly in this case...
Now I get a response from Slack, but now a different problem has arisen. When I use this method:
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
I allways get the answer:
{"ok":false,"error":"invalid_form_data"}
so I tried to explicitly tell it the 'mediaType', I tried "application/json" and others, but with all of them I get the same error. Here is the full method that calls the upper mehtod:
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/chat.postMessage");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
JO.text = "This is so much fun :D !";
var Json = JsonConvert.SerializeObject(JO, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var StringJson = new StringContent(Json, Encoding.UTF8, "application/json");
var requestContent = new MultipartFormDataContent();
requestContent.Add(StringJson);
var Response = await client.UploadFileAsync(requestContent);
string AnswerContent = await Response.Content.ReadAsStringAsync();
}
When I use this method:
public async Task<HttpResponseMessage> SendMessageAsync(FormUrlEncodedContent content)
{
var response = await _httpClient.PostAsync(UriMethod, content);
return response;
}
so bascially I am passing "FormUrlEncodedContent" instead of "MultipartFormDataContent" in this, and then I get the response I want and can work wiht it. BUT this i of little use to me since I have to use "MultipartFormDataContent" to be able to send files with my requests.
Anyone have an idea what is failing here? Why does it not like the one content-type but the other one? I'd be gratefull for tipps and ideas!
You are serializing your object to Json and then adding it to a Multipart body, that's quite strange. Unless you're uploading binary data (eg Files), there is no need to use MultipartFormDataContent.
You are can directly post your JsonObject serialized as JSON:
public async Task<HttpResponseMessage> PostJsonAsync(StringContent content)
{
var response = await client.PostAsync(url, content);
return response;
}
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
var Response = await client.PostJsonAsync(content);
Also this is should be POST on the UploadFileAsync function.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
so I figured out that in the Main() the problem was this:
Task.WhenAll(SendMessage());
I should instead use:
Task.WaitAll(SendMessage());
Anyone who has more knowledge on this, please elaborate why!
I'm currently working with a console app which I'm using the HttpClient to interact with an Apache CouchDB database. I'm using this article: http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client
I'd like to ignore the null properties in my class when I'm serializing and sending a document to my database via the PostAsJsonSync but I'm not sure how:
public static HttpResponseMessage InsertDocument(object doc, string name, string db)
{
HttpResponseMessage result;
if (String.IsNullOrWhiteSpace(name)) result = clientSetup().PostAsJsonAsync(db, doc).Result;
else result = clientSetup().PutAsJsonAsync(db + String.Format("/{0}", name), doc).Result;
return result;
}
static HttpClient clientSetup()
{
HttpClientHandler handler = new HttpClientHandler();
handler.Credentials = new NetworkCredential("**************", "**************");
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri("*********************");
//needed as otherwise returns plain text
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
Here's the class I'm serializing....
class TestDocument
{
public string Schema { get; set; }
public long Timestamp { get; set; }
public int Count { get; set; }
public string _rev { get; set; }
public string _id { get; set; } - would like this to be ignored if null
}
Any help much appreciated.
Assuming that you are using Json.NET to serialize your object, you should use the NullValueHandling property of the JsonProperty attribute
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
Check out this great article and the online help for more details
If you need this behavior for all the properties of all the classes you're going to send (which is exactly the case that has led me to this question), I think this would be cleaner:
using ( HttpClient http = new HttpClient() )
{
var formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
TestDocument value = new TestDocument();
HttpContent content = new ObjectContent<TestDocument>( value, formatter );
await http.PutAsync( url, content );
}
This way there's no need to add attributes to your classes, and you still don't have to serialize all the values manually.
use HttpClient.PostAsync
JsonMediaTypeFormatter jsonFormat = new JsonMediaTypeFormatter();
jsonFormat.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore;
jsonFormat.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
HttpResponseMessage res = c.PostAsync<T>(url, TObj, jsonFormat).Result;
I'm not sure you can do that with the PutAsJsonAsync as you have it right now. Json.NET can do this though, if you're able to use it, and a NuGet package exists if it helps. If you can use it, I'd rewrite the InsertDocument function to look like:
public static HttpResponseMessage InsertDocument(object doc, string name, string db)
{
HttpResponseMessage result;
string json = JsonConvert.SerializeObject(doc, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
if (String.IsNullOrWhiteSpace(name)) result = clientSetup().PostAsync(db, new StringContent(json, null, "application/json")).Result;
else result = clientSetup().PutAsync(db + String.Format("/{0}", name), new StringContent(json, null, "application/json")).Result;
return result;
}