Override PropertyNamingPolicy for specific API calls - c#

My ASP.Net Core API is using default JSON property naming policy (camel case), but some API calls consumed by external modules expects the API results in Pascal Case.
Is there a way to override the default policy at individual API call level? I mean, only for 1 or 2 calls.

Yes it is possible by specifying the PropertyNamingPolicy in the JsonSerializerOptions. You can do this globally or when you are creating a JsonResult object. Since you want this only on specific calls I think the second option is what you are looking for.
Here is the sample code form the official documentation for this case.
[HttpGet]
public IActionResult Get() =>
new JsonResult(
_todoItemStore.GetList(),
new JsonSerializerOptions
{
PropertyNamingPolicy = null
});

Related

How to set camel case using IAsyncCollector with System.Text.Json in Azure Functions?

I'm migrating Azure Functions v3 from Newtonsoft.Json to System.Text.Json and trying to get camelCase working globally.
For these:
SignalR
Service Bus output bindings
Cosmos DB
I was able to explicitly pass JsonSerializerOptions or set it globally (SignalR) but I'm not able to do so for IAsyncCollector.
Here is my code:
[FunctionName(nameof(SampleFunction))]
public async Task Run(
[ServiceBusTrigger(ServiceBusQueue.QueueA)] string json,
[ServiceBus(ServiceBusQueue.QueueB)] IAsyncCollector<Delivery> queueB,
[ServiceBus(ServiceBusQueue.QueueC)] IAsyncCollector<Driver> queueC,
ExecutionContext context)
{
// ... do some work
await queueB.AddAsync(objectB).ConfigureAwait(false);
// ... do some more work
await queueC.AddAsync(objectC).ConfigureAwait(false);
}
objectB and objectC ends up in service bus not with camel case. As a workaround I'm setting property names as case insensitive on the receiving function.
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
Any idea how can I get IAsyncCollector to serialize with camel case?
I recommend not using IAsyncCollector at all. The providers for IAsyncCollector don't have many knobs for configuration:
Serialization
Batching
Retries
Error handling
What's more, some implementations have changed these implementation details between releases without warning. For these reasons, I recommend never using IAsyncCollector and just using the APIs directly, where you have full control over all of these aspects. IAsyncCollector is a nice abstraction, but it's precisely that abstract nature that makes it unsuitable in the end.

.Net Core 3.0.100-preview6 - API Json responses are always camelcase, but my classes are not

I have a .net core 3.0 preview 6 MVC application and API.
In the API, I am using a third party class library (that I can't change) which defines the class properties as Pascal cased with the JsonProperty, PropertyName snaked cased eg...
public class Company
{
[JsonProperty(PropertyName = "company_name")]
public string CompanyName { get; set; }
more properties ….
}
The problem is that when I supply these via the api they reach the MVC app as Camel case (the default for .net core 3)... and then can't be Deserialized back the to the class model.
Not matter what I try, the API always produces camel cased JSon, eg. the property above will be called companyName.
I tried,
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver { NamingStrategy = new CamelCaseNamingStrategy { OverrideSpecifiedNames = true } };
options.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new DefaultNamingStrategy { OverrideSpecifiedNames = true } };
I've tried NamingStrategy = null on both camel and default ContractResolver. Also tried setting the NamingStrategy to Snake
But nothing changes the outputted Json, it's always camelcased.
I can see the resulting string is camel cased by using ReadAsStringAsync in the MVC app... I when I use JsonConvert.DeserializeObject, the properties are always null, because neither the name or Json PropertyName match the names in the resulting string.
Is this a bug in .net core previews or am missing something else?
Thanks Mustafa, your suggested duplicate is kinda the same issue with kinda the same solutions that I've already tried i.e. changing the setting of the ContractResolver / NamingStrategy to different values.... however, My issue is that none of the suggested solutions appear to have any effect on the API response it always comes back as camelCased.
Interestingly, when I change the NamingStrategy to say Snake, Swagger shows the schema as set (i.e. snake) but the actual output is still camelCased!!!
Also, I have no control over the base classes so I can't change the names / json properties of the classes I'm attempting to transmit.
Microsoft.AspNetCore.Mvc.NewtonsoftJson
Doesn't come up default.
Try to install this nuget package manually to your service project. That worked for me.
Try this and remove all JsonProperty attributes.
From now if you don't specify any JsonPropertyy it will act like this CompanyName like company_name or ProPertyName1 like pro_perty_name1. In this examples will explain the idea of the property name.
And be sure add this configuration to bottom of the ConfigureServices method, it may overwritten by another things i dont know.
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() };
});
Not really sure where the issue was but had a feeling it was something to do with the mix of Newtonsoft.Json, Json.Net, Swagger and the fact that I was using the Microsoft.AspNet.WebApi.Client to get the HttpContent.ReadAsAsync….all having different Json's
So, I decided to start again with a real simple app and api using the new System.Text.Json included in .Net Core preview (and none of the other libraries). Also not using the HttpContent.ReadAsAsync but instead reading the response as a string and then deserializing with the new library (System.Text.Json)
Doing this I had exactly the same issue …. neither the property name or Json PropertyName match the names in the api returned string i.e class property name = "CompanyName" and Json PropertyName = "company_name" and the api supplied json name = "companyName". So the value isn't set when Deserializing.
However, in the new System.Text.Json options I'm able to specify PropertyNameCaseInsensitive = true, which fixes my problem, now companyName does equal CompanyName and the class model values are set correctly when Deserializing.
So my api call methods end up looking like this...
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format("Companies?aCompanyName={0}", aCompanyName));
using HttpResponseMessage response = await Client.SendAsync(request);
string content = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode == false)
{
throw new ApiException
{
StatusCode = (int)response.StatusCode,
Content = content
};
}
_JsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<IEnumerable<Company>> (content, _JsonOptions);
I did attempt to set the JsonSerializerOptions globally in the startup class but this didn't work.
I've transferred this approach to all my http calls in my app, removed all references to Newtonsoft and it all works.
I came across this issue when converting my api from .netcore 2.2 to .netcore 3.
My api was returning responses converted to camelCase even though my models were PascalCase.
In startup.cs:
.netcore 2:
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
.netcore 3:
// keeps the casing of models when serializing to json (default is converting to camelCase)
services.AddMvc()
.AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
This means you don't need to import newtonsoft.json.

Swagger UI - Web API versioning when using Microsoft.AspNet.WebApi.Versioning

We are using Web API 2 on our project with Swagger. My problem is that when Microsoft.AspNet.WebApi.Versioning is applied as following:
the Swagger UI is ignoring the fact that now I have version in my API which needs to be provided.
I looked at several examples but none seem to address this issue in a satisfying manner.
How do I force Swagger to let me add the API version or just add the version number automatically to the URL?
Swagger configuration so far:
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "MoovShack.ServerApi");
// If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
// In this case, you must provide a lambda that tells Swashbuckle which actions should be
// included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
// returns an "Info" builder so you can provide additional metadata per API version.
//
//c.MultipleApiVersions(
// (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
// (vc) =>
// {
// vc.Version("v2", "Swashbuckle Dummy API V2");
// vc.Version("v1", "Swashbuckle Dummy API V1");
// });
c.OperationFilter<MoovShackTokenHeaderParameter>();
})
.EnableSwaggerUi(c =>
{
// If your API has multiple versions and you've applied the MultipleApiVersions setting
// as described above, you can also enable a select box in the swagger-ui, that displays
// a discovery URL for each version. This provides a convenient way for users to browse documentation
// for different API versions.
//
//c.EnableDiscoveryUrlSelector();
});
You can see that so far MultipleApiVersions are disabled - from one good reason as it doesn't produce any results. Especially since I am not sure what "ResolveVersionSupportByRouteConstraint" should do.
I also read that "EnableDiscoveryUrlSelector" has some kind of impact but I am also not sure if that applies to my case. When I enabled it, nothing happened.
We use it like this in our project and swagger recognizes it and it looks fine
[ApiVersion( "1.0" )]
[Route("api/v{version:apiVersion}/[controller]")]
public class SomeControlelr: Controller{
[HttpGet("", Name = "Someaction"), MapToApiVersion("1.0")]
public async Task<IActionResult> SomeAction(string someParameter)

How to disable c# ODataController client side query

According to this tutorial: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api/using-select-expand-and-value
"Web API 2 adds support for the $expand, $select, and $value options in OData. These options allow a client to control the representation that it gets back from the server"
My question it, how can I disable the representation manipulation done at the client side. In other words, my server makes sure that filtering/selecting etc. are done properly, and thus I do not want the client side to do it again. It is more of an overhead.
I think you misunderstand the purpose of query options like $expand, $select, etc. They do not cause data to be manipulated on the client. Rather, they are instructions to the service. In the Web API OData implementation, query options are typically handled by the EnableQuery attribute or the Queryable attribute. If you don't use these attributes, then you are responsible for writing the code that handles query options. Or you are free to not support them.
In your controller action, like get method, add attribute [EnableQuery] (this is for OData v4)
IN your client, send out request like ~/EntitySet?$filter=... & $select = ...
Then the response will only contain the filtered and select content.
Refer to https://github.com/OData/ODataSamples/tree/master/WebApi/v4/ODataQueryableSample to see the example.
You can create a custom attribute which would inherit from EnableQueryAttribute and then override the ValidateQuery method to limit the allowed query options as well as allowed functions and page size.
using System.Net.Http;
using System.Web.OData;
using System.Web.OData.Query;
public class SecureApiQueryAttribute : EnableQueryAttribute
{
public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
{
base.AllowedQueryOptions = AllowedQueryOptions.None;
base.PageSize = 30;
base.AllowedFunctions = AllowedFunctions.AllFunctions;
base.ValidateQuery(request, queryOptions);
}
}
Then you can use this custom attribute like this
[SecureApiQuery]
public IHttpActionResult Get([FromODataUri] int? key = null)
{
}

servicestack Root route and custom xml serilization

I am currently having 2 issues in service stack. I am currently trying to build a service to imitate an existing server software. This requires a few things that i am having issues with.
This is using a self hosted servicestack instance and latest version
I need to have service on "/" that takes no paramters.
All my services need to return results using a customer XML serializer not the data contact one no matter what is in the accept header. (currently return html representation of DTO)
For issue 1 i have been using [FallbackRoute("/")] which is working but then no matter what i do i can't get my custom serializer to be used.
For issue 2 i made a custom serializer using the dotnet xml serializer that will generate the output i need and registered it as a ContentTypeFilters. I then manually set the response type header but this did not trigger my serializer. This is really starting to drive me nuts as i need to implement about 20 services and i can't even get the simple root service working let alone the rest of them.
Basically my XML is in a format the DataContract serializer can't handle and the url's and content must be an exact match for the existing system.
It looks like both issue 1 and issue 2 are really the same issue; Your custom serialiser isn't getting called. This is either an issue with registering your serialiser, returning the content type or both. Below shows how you should set it up. Using ServiceStack v4:
Register your custom serialiser:
In your AppHost Configure method you need to register your custom XML serialiser:
StreamSerializerDelegate serialize = (request, response, stream) => {
// Replace with appropriate call to your serializer and write the output to stream
var myCustomSerializer = new MyCustomSerializer(response);
stream.write(myCustomerSerializer.getResult());
};
StreamDeserializerDelegate deserialize = (type, fromStream) => {
// Implement if you expect to receive responses using your type
throw new NotImplementedException();
};
// Register these methods to run if content type 'application/xml' is sent/received
ContentTypes.Register("application/xml", serialize, deserialize);
Set the return content type:
In your service you need to set the return content type, so the serialiser knows to run. You can do this either by adding an attribute on each method than needs to use this type, or if all your methods return this type you can configure it as the default.
Per method basis:
You can use the AddHeader attribute with the ContentType parameter. i.e:
public class TestService : Service
{
[AddHeader(ContentType = "application/xml")]
public TestResponse Get(RootRequest request)
{
return new TestResponse { Message = "Hello from root" };
}
}
All methods return this type:
You can set the default content type in the AppHost Configure method. i.e:
public override void Configure(Funq.Container container)
{
SetConfig(new HostConfig {
DebugMode = true,
DefaultContentType = "application/xml"
});
}
Fully working demo app
The demo is a self hosted console app, that takes a request to the root / or to /Test and returns a custom serialised response.
Hope this helps.

Categories