Specifying JsonSerializerOptions in .NET 6 isolated Azure Function - c#

I'm trying to call a Web API from code in an Azure Function that I've just ported to .NET 6 (isolated hosting model). I took the chance of the migration to get rid of the RestSharp and Json.NET dependencies, now only just using HttpClient and System.Text.Json for handling the HTTP calls and JSON stuff.
I did try to use this code which seemed like the perfect combo:
Project project = await _httpClient.GetFromJsonAsync<Project>(someUrl);
if (project != null)
{
HttpResponseData callResponse = req.CreateResponse(HttpStatusCode.OK);
await callResponse.WriteAsJsonAsync(project);
return callResponse;
}
The call works fine - I get back my Project object without any hitch.
But unfortunately, with this code, I cannot seem to influence the way the JSON in the response gets rendered - e.g. in my case, null values are returned (which I want to avoid), and all property names are capitalized ("Institute", instead of "institute", "LeadLanguage" instead of "leadLanguage").
No problem - just use a JsonSerializerOptions object and define what you want, I thought. Sure, I can create such an object - but where would I plug that in??
WriteAsJsonAsync doesn't seem to support any serializer options as parameter (why??), and I couldn't find a way to globally define my JsonSerializerOptions (since everything I find seems to be based on the services.AddControllers().AddJsonOptions() method - which I cannot use since my Azure Function doesn't have the AddControllers part in its startup code).
I have managed to get the results I want by doing this:
if (project != null)
{
HttpResponseData callResponse = req.CreateResponse(HttpStatusCode.OK);
callResponse.Headers.Add("Content-Type", "application/json");
string jsonResponse = JsonSerializer.Serialize(project, settings);
await callResponse.WriteStringAsync(jsonResponse, Encoding.UTF8);
return callResponse;
}
but that seems a bit convoluted and "low-level" - manually converting the result object into string, having to manually set the Content-Type and all ....
Is there really no way in an Azure Function (.NET 6 isolated hosting model) to globally specify JsonSerializerOptions - or call WriteAsJsonAsync with a specific serializer options object?

And 10 seconds after I posted the question - of course! - I ran across the way to do it with an Azure Function.
Something like this:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(s =>
{
s.AddHttpClient();
// define your global custom JSON serializer options
s.Configure<JsonSerializerOptions>(options =>
{
options.AllowTrailingCommas = true;
options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.PropertyNameCaseInsensitive = true;
});
Hope that might help someone else down the line!

Related

How to serialize/deserialize dotnet core exceptions for a client library?

In both dotnet core v3 and v5, I noticed that I cannot successfully serialize an Exception. If I try to I get this
This is due to the System.Type on the TargetSite.DeclaryingType property. This is in v5, but in v3 the serliazation just blows up because it hits the max depth of 32 (and increasing to 128 works but still has strange cyclical behaviors).
The situation I have seems pretty straight forward. I have an application exception like FooNotFoundException that I want to catch and handle in my controller like:
public IActionResult Test()
{
try
{
// try to do something
}
catch(FooNotFoundException e)
{
return NotFound(e);
}
}
Then I have a client library that I want to basically look for status code 404 for this endpoint and then deserialize to this exception type so the consumer can handle this exception as well when this endpoint is called. Seems very straight forward and sought after in my opinion, so I'm shocked to not be able to find any native MS documentation on how to properly serialize and deserialize a basic Exception across network boundaries like this without having to fully implement the translation to a custom DTO.
Curious if anyone has found an elegant way to do this in .NET Core v3 or v5?
I was typing this on the comment but it doesn't fit...
If some basic infomation like message, helpLink,.. was a few desired things,.. how about build our own response standard ?... like this
// On some upper middleware catching exceptions
if (_environment.IsDevelopment())
{
var exceptionDetail = new Dictionary<string, string>()
{
{"Type", exception.GetType().ToString()},
{"Message", exception.Message},
{"StackTrace", exception.StackTrace}
};
foreach (DictionaryEntry data in exception.Data)
exceptionDetail.TryAdd(data.Key.ToString(), data.Value?.ToString());
responseResult.Error.ExceptionDetail = exceptionDetail;
}
return context.Response.WriteAsync(JsonConvert.SerializeObject(responseResult,
new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}));
I think it's compact enough for clients like spa to handle some detail

ASP.NET Core MVC dynamic return is not camelCased

Return a strongly typed object from your service, and it renders JSON properties as camelCase, because that's the default in ASP.NET Core MVC.
However, sometimes we need to create something on the fly using dynamic keyword and ExpandoObject class.
Those properties are not camelCased any more.
Howe to force ASP.NET Core MVC To cameCase everything?
Within your Startup.cs you can specify a resolver to use for serialisation. The one you are looking for is CamelCasePropertyNamesContractResolver and it can be enabled with the following:
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
});
I have tested it with a dynamic type and it is working as expected.
On .NET 5 there is a specific flag to configure to serialize the dictionary keys
services.AddControllers()
.AddJsonOptions(o =>
o.JsonSerializerOptions.DictionaryKeyPolicy = System.Text.Json.JsonNamingPolicy.CamelCase
);
More info
JsonSerializerOptions.DictionaryKeyPolicy Property
GitHub Issue
I've recently been trying to serialise an ExpandoObject into camelCase using System.Text.Json without much luck. However, I did manage to achieve this using Newtonsoft.Json.
For those who might be looking for a unit-level or non-MVC solution as opposed to the configuration-level solution suggested above like myself, here are my findings.
dynamic d = new ExpandoObject();
d.Foo = new ExpandoObject();
d.Foo.BarBaz = null;
Using System.Text.Json with JsonSerializerOptions, the property names are serialised, however, the value given to PropertyNamingPolicy for camelCase seems to be ignored during serialisation at present (tested on .NET 5, at time of writing). It does, however, continue to serialise the object without failure or exception.
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
string json = JsonSerializer.Serialize<ExpandoObject>(d, options);
Console.WriteLine(json);
This would output
{"Foo":{"BarBaz":null}}
I was able to achieve the desired effect using the CamelCasePropertyNamesContractResolver suggested previously as the instance of ContractResolver to use for the JsonSerialierSettings instance passed into the SerializeObject method.
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
string output = JsonConvert.SerializeObject(d, settings);
Console.WriteLine(output);
The output being...
{"foo":{"barBaz":null}}

Extending the RestSharp serialization with a custom serializer issue

I'm trying to improve the default RestSharp serialization by using Json.net library. In order to customize the serialization you have to implement ISerializer interface:
public class LowerCaseSerializer : ISerializer{
public LowerCaseSerializer(){
ContentType = "application/json";
}
public string Serialize(object obj){
var settings = new JsonSerializerSettings{
ContractResolver = new LowerCaseResolver()
};
return JsonConvert.SerializeObject(obj, Formatting.None, settings);
}
string ISerializer.RootElement { get; set; }
string ISerializer.Namespace { get; set; }
string ISerializer.DateFormat { get; set; }
public string ContentType { get; set; }
}
As you see I'm also extending the ContractResolver. This is the actual code that does the lowercasing:
public class LowerCaseResolver : DefaultContractResolver{
protected override string ResolvePropertyName(string propertyName){
return propertyName.ToLower();
}
}
Once all this is setup I can use it with RestSharp:
var request = new RestRequest(url, method);
if (ShouldAddBody(method)){
request.JsonSerializer = new LowerCaseSerializer();
request.AddObject(body);
}
var response = client.Execute<T>(request);
Everything works, except the properties are not in lower case. When debugging the debuger goes into the Constructor of the serializers, but it's method is never called. When I tried exactly the same for deserializations (IDeserialize interface, which attaches to the client) the method for lower casing was called for each property.
What I have also tried:
request.RequestFormat = DataFormat.Json; // no change
// this correctly lower cases the properties
var json = new LowerCaseSerializer().Serialize(body);
// wrong request sent to the server, it tells me that some property is missing
request.AddBody(json);
// the exact same thing with this
request.AddParameter("application/json", json, ParameterType.RequestBody);
The thing I noticed with the last two: if I have lower case properties and let RestSharp serializes then the request has 5 parameters (one for each property). If I add it via upper two methods it has only one property and that's the whole json.
I check the RestSharp issues to no avail. Any suggestions?
Update:
This very strange:
forked the RestSharp, installed Json.net, works fine
copied RestRequest class from RestSharp fork, pasted to my application, used it, works fine
started new project, installed RestSharp and Json.net via Package manager, works fine
Then removed all packages from my main application, redownloaded, doesn't work. Kinda giving up on this.
Update 2:
Debugging through the forked version I noticed this: The RequestFormat has to be Dataformat.Json or it will use Xml serializer. But that doesn't fix the problem. I tried setting both (there are only two) serializers to null:
request.JsonSerializer = null;
request.XmlSerializer = null;
In the new project I did this causes NullReferrenceException as expected. But in the old one nothing happens. I tried renaming the variables, making another variable of the same type, but nothing fixes is. It just seems that in the project I have the RestRequest class is somehow bugged.
I also added a new project to the solution. And even there the code works. So it's just one project that has the problem.
Since you can't reproduce it in a new project, there must be something different going on in this particular project, that's causing the issues, you're describing.
A couple of things you could try (in no particular order):
Check that you're using the exact same version of the library in both projects (the one that works and the one that doesn't): package version and target platform (net4, net35...).
Delete the packages folder of your non-working project so that NuGet will be forced to re-download all the packages.
Lookup the exact path to the referenced library in Visual Studio Properties window when you have RestSharp from References node selected. Do a binary compare between the libraries referenced by the working and the non-working project.
Unfortunately there's no symbol package for RestSharp on SymbolSource, so you can't directly debug RestSharp in your non-working project. You could use Reflector.NET VSPro if you have the license or haven't used the trial before.
Move parts of your non-working project to the working one until it stops working.
EDIT:
Looking at the source code of RestRequest, AddObject doesn't seem to use the JsonSerializer you are setting. Have you tried using AddBody instead?

How to access IHttpRequest from my custom serializer in ServiceStack

We have custom serializers for our Models that protect sensitive data depending on the request path. (For instance, if the request does not start with "/admin").
Up until now, we've tried registering the IHttpRequest with the Funq container on a RequestFilter with ReuseScope.None or ReuseScope.Request and on the serializers, we're getting the IHttpRequest from the container.
We found out that if there are multiple requests pending, the container will return that last registered IHttpRequest, which will not always be the correct request object.
I know that we could try to implement this protection of sensitive data on our models applying filter attributes, but that's very cumbersome, since our model object are often embedded in other objects or collections.
Having the serializer to do it is by far the best solution for us, if it werent for this issue of not being able to get the correct HttpRequest object from within the serializers.
So, what would be the correct way to do this? Or is this a bad practice?
Here a few code samples:
So this is a private method that my serializers use to define if they're being serialized within a "admin" route or not:
private bool IsAdminRoute() {
var path = container.Resolve<IHttpRequest> ().PathInfo;
var res = path.StartsWith ("/admin");
return res;
}
Here's the use of it:
public Question QuestionSerializer(Question question)
{
if (!IsAdminRoute())
{
// do stuff like nullyfying certain properties
}
return question;
}
On my AppHost initialization I have:
void ConfigureSerializers(Funq.Container container)
{
Serializers = new CustomSerializers ();
// ...
JsConfig<Question>.OnSerializingFn = Serializers.QuestionSerializer;
// ...
}
public void HttpRequestFilter(IHttpRequest httpReq, IHttpResponse httpRes, object dto) {
Container.Register <IHttpRequest>(c => httpReq).ReusedWithin (Funq.ReuseScope.Request);
}
Note: I'm using ServiceStack v3.
I managed to make it work by registering the IHttpRequest this way:
container.Register(c => HttpContext.Current.ToRequestContext ().Get<IHttpRequest>()).ReusedWithin(Funq.ReuseScope.None);
Now, I am always getting the IHttpRequest object I am supposed to when I try to resolve them.
Also, after more thourough tests in my application I was able to detect that everything that was relying on registering with ReuseScope.Request was getting mixed up if the concurrency was high enough.
The solution was quite simple, I am relying now on the HttpContext.Current.Items collection for storing these request-specific dependencies and registering them on a request filter like this:
HttpContext.Current.Items ["Token"] = token;
container.Register<Token> (c => (Token)HttpContext.Current.Items["Token"]).ReusedWithin(Funq.ReuseScope.None);
Now it works as it was supposed to every time.

Replacing legacy system & creating new server code using ServiceStack + custom serialization

We have a legacy server code that we want to abandon and develop new one using ServiceStack. Existing clients are not written in .Net. We don't plan to use .Net on the client side at all.
Data between client and server is being exchanged using XML and JSON - at the moment JSON is only used as a return format for the response (just for some of the services available). XML format was defined when the first version of the server solution was created couple of years ago. We don't want to change it.
How do we use ServiceStack to build new RESTful webservices, that will serialize and deserialize data to a format that was designed in the past (please note, that clients will not be written in C#/.Net). We need to contol both: serialization & deserialization. Is that possible to use DTOs and still have control on how are these objects serialized / deserialized?
Adding custom logic via Request / Response Filters
See Request and response filters to see how to add custom logic before and after your service is called. It's best to add these filters via the Request / Response FilterAttributes as it allows you mark only the services that need these filters applied.
The problem with the Request Filter is it happens after the deserialization into the request DTO which is too late to add custom de-serialization logic. To get around this you can register a custom Request binder in your AppHost with:
base.RegisterRequestBinder<MyRequest>(httpReq => ... requestDto);
This gives you access to the IHttpRequest object and lets you add the custom deserialization logic yourself. The other option is to tell ServiceStack to not attempt to deserialize the request itself and instead inject the HttpRequest InputStream so you can deserialize the request yourself:
public class Hello : IRequiresRequestStream {
Stream RequestStream { get; set; }
}
Both these examples are explained on ServiceStack's Serialization and De-Serialization wiki page.
Registering your own Custom Media Type
Another option to be able to return strong-typed DTOs but change the output for certain requests can be done by adding a new custom media type as explained in the Northwind VCard Custom media type example, e.g:
public static void Register(IAppHost appHost)
{
appHost.ContentTypeFilters.Register( "text/x-vcard", SerializeToStream, DeserializeFromStream);
}
...
public static void SerializeToStream(IRequestContext requestContext, object response, Stream stream)
{
var customerDetailsResponse = response as CustomerDetailsResponse;
using (var sw = new StreamWriter(stream))
{
if (customerDetailsResponse != null)
{
WriteCustomer(sw, customerDetailsResponse.Customer);
}
var customers = response as CustomersResponse;
if (customers != null)
{
customers.Customers.ForEach(x => WriteCustomer(sw, x));
}
}
}
This is a good option if you can mount the custom XML responses under a different Content Type, e.g. application/v-xml so it doesn't conflict with the existing XML format/endpoint. Using the ContentType above your HTTP Client can call this custom implementation with ?format=v-xml or using the HTTP Header: Accept: application/v-xml.
If you want to override the built-in XML ContentType you still can but I recommend falling back to the original XmlSerializer implementation for the SerializeStream and DeserializeStream methods if it's not one of the legacy formats you have to support.
By-pass ServiceStack and execute using your own Custom IHttpHandler
Another option is to by-pass ServiceStack completely and instead process the request in your own custom IHttpRequest handler by registering it in ServiceStack's config in your AppHost:
SetConfig(new EndpointHostConfig {
RawHttpHandlers = {
httpReq => return IsLegacyMatch(httpReq) ? new LegacyXmlHandler() : null
}
});
Returning non-null (i.e. any handler) by-passes ServiceStack.

Categories

Resources