Skip to content

Fluent API for building the resource graph #776

Open
@bart-degreed

Description

@bart-degreed

Over time, several users have been asking for an alternative to adding attributes like [Attr] [HasOne] etc. EF Core provides both inline attributes, as well as a fluent API. This issue tracks the design of a fluent API for JsonApiDotNetCore.

First, let's zoom out a bit and see what happens currently in services.AddJsonApi():

  • set global options, optional
  • service/definition registration (assembly scan), optional
  • resource graph building, one or both of:
    • from DbContext (scan attributes of types in model)
    • manually per resource (scan attributes of single type)

In my opinion, adding a fluent API should be part of the last step, which currently looks like:

services.AddJsonApi(
    options => options.Namespace = "api/v1",
    resources: builder =>
    {
        builder.AddResource<WorkItem>(); // <-- manual per-resource registration
    });

And I think we should replace that with support for this, similar to EF Core:

services.AddJsonApi(
    options => options.Namespace = "api/v1",
    resources: builder =>
    {
        builder.Resource<WorkItem>()
            .Attribute(workItem => workItem.Title)
            .PublicName("work-title")
            .Capabilities(AttrCapabilities.AllowFilter);

        builder.Resource<WorkItem>()
            .HasOne(workItem => workItem.Project)
            .PublicName("assigned-to-project");
    });

How would that work?

  • When builder.Resource<>() is called and the resource is not part of the graph, first it runs existing attribute scanning logic
  • Next, it processes the chained methods, overriding the registration that was added from attributes

For reference, the public API of builders could look something like this:

public class ResourceTypeBuilder<TResource>
{
    public ResourceTypeBuilder<TResource> Attribute(Func<TResource, object> propertySelector)
    {
        return this;
    }

    public ResourceTypeBuilder<TResource> PublicName(string publicName)
    {
        return this;
    }

    public ResourceTypeBuilder<TResource> Capabilities(AttrCapabilities capabilities)
    {
        return this;
    }

    public HasOneRelationshipBuilder<TResource> HasOne(Func<TResource, object> relationshipSelector)
    {
        return new HasOneRelationshipBuilder<TResource>();
    }
}

public class HasOneRelationshipBuilder<TResource>
{
    public HasOneRelationshipBuilder<TResource> PublicName(string publicName)
    {
        return this;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions