supabase-community/supabase-csharp

Can't convert simple model to JSON in ASP.NET

TranquilMarmot opened this issue · 4 comments

Bug report

Not entirely sure if this belongs here or in https://github.com/supabase-community/postgrest-csharp

Describe the bug

I have this simple model:

using Postgrest.Attributes;
using Postgrest.Models;

[Table("games")]
public class Game : BaseModel
{
    [PrimaryKey("id", false)]
    public string? Id { get; set; }

    [Column("name")]
    public string? Name { get; set; }

    [Column("owner_id")]
    public string? OwnerId { get; set; }

    [Column("status")]
    public string? Status { get; set; }
}

And I'm trying to list it from Supabase like so:

var builder = WebApplication.CreateBuilder();

// not sure if this line is needed?
builder.Services.AddControllers().AddNewtonsoftJson();

var app = builder.Build();

var client = new Supabase.Client(SupabaseSettings.Url, SupabaseSettings.Key);
await client.InitializeAsync();

app.MapGet("/games", async (HttpContext context) =>
{
   var games = await client.From<Game>().Get();
  
   // this works just fine! It logs a JSON string
   app.Logger.LogInformation(games.Content);

   // this throws the exception below
   var models = games.Models;
})

Here is the exception being thrown:

System.NotSupportedException: The type 'Postgrest.Attributes.PrimaryKeyAttribute' is not a supported dictionary key using converter of type 'System.Text.Json.Serialization.Converters.SmallObjectWithParameterizedConstructorConverter`5[Postgrest.Attributes.PrimaryKeyAttribute,System.String,System.Boolean,System.Object,System.Object]'. Path: $.PrimaryKey.
       ---> System.NotSupportedException: The type 'Postgrest.Attributes.PrimaryKeyAttribute' is not a supported dictionary key using converter of type 'System.Text.Json.Serialization.Converters.SmallObjectWithParameterizedConstructorConverter`5[Postgrest.Attributes.PrimaryKeyAttribute,System.String,System.Boolean,System.Object,System.Object]'.
         at System.Text.Json.ThrowHelper.ThrowNotSupportedException_DictionaryKeyTypeNotSupported(Type keyType, JsonConverter converter)

System information

In my .csproj:

<TargetFramework>net7.0</TargetFramework>

<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.5" />
<PackageReference Include="supabase-csharp" Version="0.11.1" />

Am I doing something wrong here? This seems like the most basic select that I can write here 😭

This seems like the same issue as #72 but the fix there and in the README isn't working for me 🤔

Hm... I must be missing something? The following works just fine for me:

SupabaseASP.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
// .....
    <PropertyGroup>
        <TargetFramework>net7.0</TargetFramework>
        <Nullable>enable</Nullable>
        <ImplicitUsings>enable</ImplicitUsings>
        <UserSecretsId>aspnet-SupabaseASP-D315E912-8516-4C88-B4D7-5E1D0528E2D6</UserSecretsId>
        <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.5" />
        <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.5" />
        <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.5" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
        <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5" />
        <PackageReference Include="supabase-csharp" Version="0.11.1" />
    </ItemGroup>
// .....
</Project>

Models/RSVPAddress.cs

using Postgrest.Attributes;
using Postgrest.Models;

namespace SupabaseASP.Models;

[Table("rsvp_addresses")]
public class RSVPAddress : BaseModel
{
    [PrimaryKey]
    public int Id { get; set; }
    
    [Column]
    public string? Name { get; set; }
    
    [Column]
    public string? Email { get; set; }
    
    [Column]
    public int? Count { get; set; }
    
    [Column]
    public string? Address { get; set; }
    
    [Column]
    public string? Locality { get; set; }
    
    [Column("postal-code")]
    public int? PostalCode { get; set; }
    
    [Column]
    public string? State { get; set; }
    
    [Column("submitted-at")]
    public DateTime SubmittedAt { get; set; }
}

Program.cs

// ...
const string url = "https://PROJECT_ID.supabase.co";
const string publicKey =
    @"....";

builder.Services.AddSingleton(_ => new Supabase.Client(url, publicKey));

// ...
var app = builder.Build();

await app.Services.GetRequiredService<Supabase.Client>().InitializeAsync();


//...
app.MapGet("/test", async (HttpContext context) =>
{
    var response = await app.Services.GetRequiredService<Supabase.Client>()
        .From<RSVPAddress>()
        .Get();
    
    app.Logger.LogInformation(response.Content);

    var models = response.Models;
    
    await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(response.Content!));
});

In #72 and in the README, it's suggested that this line is needed...

builder.Services.AddControllers().AddNewtonsoftJson();

But I don't see that in your code. Did you not need it?


Some more info, I'm doing in a very unique environment. I'm actually running my ASP.NET server from inside of a Godot script.

Here's my full .csproj file (note the Sdk):

<Project Sdk="Godot.NET.Sdk/4.0.3">
  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <EnableDynamicLoading>true</EnableDynamicLoading>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <FrameworkReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.5" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
    <PackageReference Include="supabase-csharp" Version="0.11.1" />
  </ItemGroup>
</Project>

I'm not sure if this would have an effect on how the JSON is being parsed, though! I wonder if I'm missing some packages that would otherwise be included in the Microsoft.NET.Sdk.Web SDK.


As an (unfortunate) workaround, I'm just manually parsing the raw Content from the Supabase call using a separate class that doesn't have any annotations on it.

Okay, I think I figured out what's going on.

This is what was really causing my issues:

var games = await client.From<Game>().Get();

return Results.Ok(games.Models);

This tries to use the default System.Text.Json.JsonSerializer, which doesn't work.

But, I can .Select() on the Models to transform them into something else just fine.

For example,

  var mapped = games.Models.Select(game =>
      new Dictionary<String, String?>{
          { "id", game.Id },
          { "name", game.Name }
      }
  );

  return Results.Ok(mapped);

And yes, it looks like dealing with NewtonsoftJson isn't necessary here 😄