PascalCase broken in v8?

Hi,
I think perhaps the PascalCase is broken in the newest version 8 w. .NET 8.
I have the following block in my AppHost.config


        JsConfig.Init(new Config
        {
            DateHandler = DateHandler.ISO8601,
            AlwaysUseUtc = false,
            TextCase = TextCase.PascalCase, 
            ExcludeDefaultValues = false,        
            IncludeNullValues = false
        });

And after upgrading from NET7 to NET8 (and SS versions too) I’m getting regular camelCase.
This is of course prettier and more in line with JSON, but it’ll break running programs updating to SS 8.

1 Like

I’ve tested this with some of our example applications like BlazorDiffusion, and I get Pascal case as expected.

Are you using the ASPNET HostingStartup to configure different parts of your application or the legacy modular startup?

If you are able to share a minimal reproduction of the issue I can help find why these options aren’t applying. If you apply your config above to one of our templates or example applications you should be seeing what I am seeing, but if you can reproduce the problem with one of those I’ll also be able to help out.

1 Like

I’ll try to reproduce with a small repo, however I already went through the pain of rewriting my front-end to use CamelCase, instead of fighting against it.

My project is relatively new, it was based on a NET7 template.
The JSON config was in here:

[assembly: HostingStartup(typeof(Mindcell.AppHost))]

namespace Mindcell;

public class AppHost : AppHostBase, IHostingStartup {
// ...
public override void Configure(Container container)
{
//...
 JsConfig.Init(new Config // ...
    TextCase = TextCase.PascalCase,
}
}

It was working fine with NET7 and SS 6 dot something.

But now I’ve got another problem, I’ll post a new case about that.

Do you remember which template? Our templates target LTS versions only, eg .NET6 and .NET8 where as .NET7 wasn’t an LTS release.

Pascal case is working with .NET 8 and latest SS release, hence why I was trying to see if where was a difference in how/when the configuration is applied.

You’re right. I actually had the readme, so:

# .NET 6.0 Vue 3 + Vite + Tailwindcss Template

Then I upgraded to NET7 at some point, just by changing in the .csproj files.

But of course I’ve been using ServiceStack forever, so the JsConfig.Init() is something I’ve used other places.

No worries, if you do happen to be able to share a reproduction, I’ll have a look to find root cause and look at possible fix if required or update our docs to help others to avoid the issue.

It’s strange. I’m getting the same issue as well. I’ve set the following in the Configure section:

JsConfig.Init(new()
{
    TextCase = TextCase.PascalCase,
    IncludeNullValues = true,
    ExcludeTypeInfo = true,
});

And then when I run a query, I’m getting Camel case instead. If I put a breakpoint in and check before it returns, and in the Immediate window put in:

?ServiceStack.Text.JsConfig.TextCase

It returns:

PascalCase

So the setting is definitely there. What makes this even more strange is that if I add either:

jsconfig=TextCase:PascalCase

or

jsconfig=epcn

To the query, it gives me the proper PascalCase result.

Can you please provide a stand-alone repro on GitHub and I’ll take a look

Note if you’ve switched to using Endpoint Routing which uses System.Text.Json APIs by default, you’d need to customize the System.Text.Json APIs instead, e.g:

services.ConfigureJsonOptions(options => {
    options.PropertyNamingPolicy = null;
})
.ApplyToApiJsonOptions();  // Apply to ASP.NET Core's JSON APIs

I’m not sure how I would have switched to Endpoint Routing. I don’t see anything that looks like I did. However, if I use the following:

services.ConfigureJsonOptions(options => { options.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower; }).ApplyToApiJsonOptions();

It is using Snake Case Lower, so it sounds like that’s the right track. There’s no PascalCase in the JsonNamingPolicy, so I guess I’d need to write my own if that’s the case?

I should note, that setting it to null doesn’t change it - it still uses CamelCase if it’s set to null.

It’s not something that could be done by accident, i.e. you would’ve needed to explicitly migrate to use Endpoint Routing.

I’m not able to reproduce this issue, so if you can provide a stand-alone repro on GitHub and I’ll be able to resolve the issue.

So, I’ve done a bit more investigation and created a minimal repro, but in doing so and testing it, I’ve noticed a couple of things:

  1. While adding services.ConfigureJsonOptions(options => { options.PropertyNamingPolicy = null; }).ApplyToApiJsonOptions(); didn’t work originally, it works now. It’s a bit odd, and the only thing that’s changed since then is that I updated NPM to the latest version on this machine (that doesn’t seem like it should affect it, though).
  2. I see in the Program.cs file the line app.UseServiceStack(new AppHost(), options => { options.MapEndpoints(); }); This line is included in the template that was downloaded from ServiceStack, so the MapEndpoints option wasn’t added by me. I’m assuming this must enable the Endpoint Routing? What are the issues if I remove the option? If I do remove it, the issue goes away.

(As a note, I’m migrating from a .NET Framework 4.6 implementation with a ServiceStack release of 5.1.0 to a .NET 8 implementation with a ServiceStack release of 8.1.2.)

I see in the Program.cs file the line:

app.UseServiceStack(new AppHost(), options => { 
    options.MapEndpoints(); 
});

Yeah that’s what enables Endpoint Routing which is the default for new ASP .NET Core Identity Auth templates. Using Endpoint Routing also uses System.Text.Json APIs by default, which requires using services.ConfigureJsonOptions() to configure.

If you don’t use Endpoint Routing your App reverts back to previous behavior, i.e. using ServiceStack Routing and executed with the ServiceStack Request Pipeline.

It’s the less integrated option where you wont be able to use the latest ASP .NET Core Open API v3 and share the same “base route” (e.g. /api) with other ASP .NET Core Minimal or Web APIs. Using Endpoint Routing makes this possible since all requests utilize the same routing system.

Ok, thanks for the information. I think we can remove the option in our implementation, given how we’re using it.

Note that removing the line also resolves another issue we were having with preflight requests, and CORS (with it on we were getting 504 errors and it was complaining about Access-Control-Allow-Credentials, specifically with a JavaScript client). Because it resolves both, we’re just going to remove that option.

If you would like a repro of the CORS issue, I should be able to provide you with it, but since it’s working for us now, it’s not necessary for our purposes.

Were you trying to use ServiceStack’s CorsFeature plugin or ASP .NET services.AddCors feature with Endpoint Routing?

Yes, we had both:

app.UseServiceStack(new AppHost(), options => { 
    options.MapEndpoints(); 
});

and a configuration that looks like this:

SetConfig(new HostConfig
{
    EnableFeatures = Feature.All.Remove(Feature.Metadata),
    DefaultContentType = MimeTypes.Json,
    AllowSessionCookies = false,
});

Plugins.Add(new CorsFeature("*", allowedHeaders: "Content-Type, Authorization, Product"));

What ASP.NET CORS Configuration did you use? (When using Endpoint Routing you should just be using built-in ASP .NET Core features)

I meant both MapEndpoints, and the Plugins.Add(new CorsFeature...).

We didn’t use builder.Services.AddCors. Like I said, we’re upgrading from .NET Framework to .NET 8, so we didn’t realize that we’d need to handle the CORS differently.

If I remove the Plugins.Add(new CorsFeature...) line and put the following lines in the Program.cs file (as you are suggesting), it works properly (i.e. no CORS error with the JavaScript client):

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "myCorsPolicy", policy =>
    {
        policy.WithOrigins("*").WithHeaders("Content-Type", "Authorization", "Product");
    });
});

...

app.UseCors("myCorsPolicy");

That seems fine. Should I be using this, or should I use the Plugins one if I don’t include the options.MapEndpoints() option?

If you’re not using Endpoint Routing you can continue using the CorsFeature plugin which continues to only apply CORS headers for ServiceStack Requests.

The ASP .NET CORS configuration applies it to all Endpoint API Requests, which you’d use instead if you were using Endpoint Routing.

Awesome! Thanks so much for your help, we really appreciate it!

1 Like