System.Text.Json cannot materialise an interface without a
polymorphic converter. Until this commit, BlogPostDto.Author
was typed as the abstract interface IApplicationUser, which
crashed the "load posts" call in PostIt whenever the server
returned a post with a populated Author object (the common
case — GET /api/BlogApi).
Fix:
* Introduce a minimum-viable wire DTO BlogPostAuthorDto in
Yavsc.Abstract.Blogspot (record: Id, UserName, Avatar).
These are the only fields the client UI actually needs;
the server-side ApplicationUser navigation is preserved
for permission checks and authorisation.
* Change IBlogPost.Author and BlogPostDto.Author from
IApplicationUser to BlogPostAuthorDto? (interface change,
breaking). The EF entity BlogPost keeps its full
ApplicationUser navigation property and exposes
IBlogPost.Author via an explicit interface implementation
that projects to BlogPostAuthorDto on demand (so EF can
still lazy-load the navigation without forcing an eager
join on every read).
* Restore the using directive that was accidentally removed
when the BlogPostDto property was rewritten (needed for
ICircleAuthorization in GetACL()).
Regression coverage (the missing test Paul flagged):
* Add BlogPostAuthorDtoTests in PostIt.Tests with four
scenarios that exercise the wire shape on the client side:
- A BlogPostDto JSON with a populated Author round-trips
through JsonSerializer without throwing and the three
fields (Id, UserName, Avatar) survive intact.
- A BlogPostDto JSON with explicit "author": null
deserialises with Author == null.
- A BlogPostDto JSON without any Author field at all
deserialises with Author == null (forward compat).
- The serialised shape of BlogPostAuthorDto uses camelCase
property names (matching the server's Web defaults), so
the field names on the wire don't drift without a test
catching it.
Tests: 55/55 PostIt.Tests (+4 new), 24/24 Yavsc.Blogs.Tests,
44/44 Yavsc.Org.Tests. No regressions.
Side note: yavsc.sln picks up Yavsc.Api.Client (added by
'feat/postit-acl' in 1.0.7 but never registered in the
solution file until now — probably auto-added by a recent
'dotnet build' that discovered the .csproj).
33 lines
1.3 KiB
C#
33 lines
1.3 KiB
C#
namespace Yavsc.Blogspot;
|
|
|
|
/// <summary>
|
|
/// Minimum-viable author payload embedded in <see cref="BlogPostDto"/>.
|
|
///
|
|
/// <para>
|
|
/// Before this record existed, <c>BlogPostDto.Author</c> was typed
|
|
/// as the abstract interface <c>IApplicationUser</c>. The
|
|
/// interface is fine for server-side contract (we have a concrete
|
|
/// entity that implements it) but System.Text.Json cannot
|
|
/// materialise an interface without a polymorphic converter
|
|
/// configured on both ends. PostIt would crash on load-posts
|
|
/// because the JSON contained an <c>Author</c> object that the
|
|
/// client could not deserialise.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// This record is the wire shape: <c>Id</c> for "go to author
|
|
/// profile", <c>UserName</c> for "by @username", <c>Avatar</c>
|
|
/// for the round badge next to the title. The server-side
|
|
/// <c>BlogPost</c> entity (<c>Yavsc.Server.Models.Blog</c>) keeps
|
|
/// its full <c>ApplicationUser</c> navigation property for
|
|
/// permission checks and authorisation; the DTO is built on
|
|
/// demand by the controller / service layer when the post is
|
|
/// served to the wire.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed record BlogPostAuthorDto
|
|
{
|
|
public string Id { get; init; } = string.Empty;
|
|
public string? UserName { get; init; }
|
|
public string? Avatar { get; init; }
|
|
}
|