Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ViewModel uses "+" splicing, and the swagger request body parameter is missing #2703

Closed
netnr opened this issue Sep 4, 2023 · 4 comments
Closed

Comments

@netnr
Copy link

netnr commented Sep 4, 2023

<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

image

Resolver error at paths./api/MS/MsSenderPost.post.requestBody.content.application/json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/Netnr.Admin.Domain.Models.ViewModel+MSModel does not exist in document

image


If I change to "." splicing, the request body parameters are displayed normally

image

@netnr
Copy link
Author

netnr commented Sep 6, 2023

builder.Services.AddSwaggerGen(c =>
{
    c.CustomSchemaIds(type => type.FullName.Replace("+", "."));
});

Got it

@fakhrulhilal
Copy link

builder.Services.AddSwaggerGen(c =>
{
    c.CustomSchemaIds(type => type.FullName.Replace("+", "."));
});

Got it

I wish it's that simple. It brings another issue whenever a type contains generic parameter. Generic parameter has no Type.FullName defined.

@netnr
Copy link
Author

netnr commented Sep 2, 2024

@fakhrulhilal
Can you give the key sample code?

@fakhrulhilal
Copy link

@fakhrulhilal Can you give the key sample code?

The solution works for this scenario

[JsonDerivedType(typeof(Success))]
[JsonDerivedType(typeof(Partial))]
[JsonDerivedType(typeof(Failed))]
[JsonDerivedType(typeof(Success.WithValue<>))]
[JsonDerivedType(typeof(Partial.WithValue<>))]
[JsonDerivedType(typeof(Failed.WithValue<>))]
public abstract record UploadResult
{
    private UploadResult(bool succeeded) {
        Succeeded = succeeded;
    }

    public bool Succeeded { get; }

    public abstract record FailedBase(string Reason) : UploadResult(false);
    public sealed record Failed(string Reason) : FailedBase(Reason)
    {
        public sealed record WithNotUploaded(string Reason, IEnumerable<NonUploadedDoc> NotUploaded)
            : FailedBase(Reason);
    }

    public abstract record SuccessBase(IEnumerable<UploadedDoc> Uploaded) : UploadResult(true);
    public sealed record Success(IEnumerable<UploadedDoc> Uploaded) : SuccessBase(Uploaded)
    {
        public sealed record WithValue<T>(T Value, IEnumerable<UploadedDoc> Uploaded) : SuccessBase(Uploaded)
            where T : struct;
    }

    public abstract record PartialBase(IEnumerable<UploadedDoc> Uploaded,
        IEnumerable<NonUploadedDoc> NotUploaded) : SuccessBase(Uploaded);
    public sealed record Partial(IEnumerable<UploadedDoc> Uploaded,
        IEnumerable<NonUploadedDoc> NotUploaded) : PartialBase(Uploaded, NotUploaded)
    {
        public sealed record WithValue<T>(
            T Value,
            IEnumerable<UploadedDoc> Uploaded,
            IEnumerable<NonUploadedDoc> NotUploaded)
            : PartialBase(Uploaded, NotUploaded)
            where T : struct;
    }
}

[JsonDerivedType(typeof(Failed))]
[JsonDerivedType(typeof(Partial))]
[JsonDerivedType(typeof(Success))]
public abstract record CopyResult
{
    public bool Succeeded { get; }

    private CopyResult(bool succeeded) {
        Succeeded = succeeded;
    }

    public sealed record Success(IEnumerable<AttachedDoc> Attached) : CopyResult(true);
    public sealed record Partial
        (IEnumerable<AttachedDoc> Attached, IEnumerable<NonAttachedDoc> NotAttached) : CopyResult(true);
    public sealed record Failed
        (string Reason, IEnumerable<NonAttachedDoc> NotAttached) : CopyResult(false);
}

public abstract record AttachedDocBase(int SourceDocumentId);

public sealed record NonAttachedDoc
    (int SourceDocumentId, string Reason, string? Name = null) : AttachedDocBase(SourceDocumentId);

public sealed record AttachedDoc(int SourceDocumentId, string Name, int FileId) :
    AttachedDocBase(SourceDocumentId);

Notice that both UploadResult and CopyResult has same nested types: Partial, Success, Failed. The solution can deal with this issue. But there is another problem when generating schema UploadResult.Success.WithValue<T>. It fails to generate generic parameter of T.

I have solved this using combined solution from others (GenerateSchemaId 2nd switch expression):

private static string GenerateSchemaId(Type type) => type switch {
    { IsNested: true } when !string.IsNullOrEmpty(type.FullName) => type.FullName.Split('.')[^1].Remove("+", string.Empty),
    { IsGenericParameter: true, DeclaringType: not null } => $"{GenerateSchemaId(type.DeclaringType)}_{type.Name}",
    _ => DefaultSchemaIdSelector(type)
};

private static string DefaultSchemaIdSelector(Type type) {
    if (!type.IsConstructedGenericType) {
        return type.Name.Replace("[]", "Array");
    }

    string prefix = type.GetGenericArguments()
        .Select(DefaultSchemaIdSelector)
        .Aggregate((previous, current) => previous + current);
    return prefix + type.Name.Split('`').First();
}

Then use it like usual builder.Services.AddSwaggerGen(options => options.CustomSchemaIds(GenerateSchemaId)).

I got the idea from this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants