Open
Description
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;
}
}