JsonApiDotNetCore Documentation

repository·master·Indexed 20 days ago

https://github.com/json-api-dotnet/jsonapidotnetcore

A framework for building JSON:API compliant REST APIs using ASP.NET Core and Entity Framework Core. It provides built-in support for sorting, filtering, pagination, sparse fieldsets, and side-loading to minimize boilerplate. The documentation covers resource definition using attributes like [Resource] and [Attr], request pipeline management via JsonApiController, query string parsing, and integration with OpenAPI via the openapi:discriminator extension.

Tokens
52.8K
Snippets
140
Records
198
Agent score
71%

What's inside JsonApiDotNetCore

  1. Archiving vs Soft Deletion

    master

    When deciding between Archiving and Soft Deletion in JsonApiDotNetCore, use the following mental model:

    FeatureClient AccessibilityUse Case
    ArchivingAccessible via ID, hidden from searchesHiding items from general lists without losing the ability to reference them directly.
    Soft DeletionNever accessible to JSON:API clientsRemoving items from the system entirely while retaining them in the database for audit/recovery.
  2. Understand the JSON:API Extension for OpenAPI

    master

    The JSON:API Extension for OpenAPI is designed to improve the effectiveness of OpenAPI client generators when working with JSON:API documents.

    Standard OpenAPI can use allOf inheritance and a discriminator property for the top-level data member, but it cannot natively express that discriminators should recursively apply to nested objects within attributes and relationships. This extension solves that by introducing an openapi:discriminator property that guides code generation tools to correctly identify types for nested structures.

  3. How multi-tenancy works with related resources

    master

    In a multi-tenant system, not every table needs a TenantId column. If a resource (e.g., WebProduct) has a foreign key to a tenant-specific resource (e.g., WebShop), the tenant isolation is maintained through the relationship chain.

    When querying the related resource, the HasQueryFilter applied to the parent resource (the one with the TenantId) ensures that only products belonging to the tenant's specific shops are returned, even if the products themselves do not explicitly store a TenantId.

  4. Perform atomic operations with NSwag or Kiota clients

    master

    Both NSwag and Kiota generated clients fully support Atomic operations. This allows you to group multiple operations (like creating a tag and a person, then linking them) into a single request to ensure consistency.

    • NSwag: Use TrackChangesFor from the JsonApiDotNetCore.OpenApi.Client.NSwag package to handle attribute clearing during updates.
    • Kiota: Uses built-in backing-stores to manage changes for atomic operations.
  5. Understand JsonApiDotNetCore versioning and breaking changes

    master

    JsonApiDotNetCore follows Semantic Versioning (SemVer) with some flexibility regarding minor updates to balance feature velocity with stability.

    Major Updates

    Major updates contain breaking changes that may affect user code or API clients. These changes are documented in the release notes and official documentation.

    Minor Updates

    To avoid frequent major version bumps, the project may introduce minor breaking changes in specific areas. However, the following core components are guaranteed to remain stable (no breaking changes) in minor updates:

    • Extensibility points: Controllers, resource services, resource repositories, resource definitions, and the Identifiable interface.
    • Annotations: [Attr], [HasOne], and [HasMany].
    • API Surface: URL routes, JSON structure of request/response bodies, and query string syntax.

    Note on 'Pubternal' types: The project may introduce breaking changes to types in Internal namespaces or less common classes (e.g., OperationsProcessor) during minor updates. These changes might be binary-breaking (e.g., adding an optional constructor parameter), which requires you to recompile your existing code to resolve compiler errors. If you use these types, expect to recompile your project during minor version upgrades.

  6. How content negotiation and extensions work in JsonApiDotNetCore

    master

    Content negotiation in JsonApiDotNetCore allows the server to support specific JSON:API extensions based on the Accept and Content-Type HTTP headers. This is achieved through a coordinated flow across several extensibility points:

    1. Registration: Extensions are added to JsonApiOptions during application startup to make them available to the framework.
    2. Negotiation: A JsonApiContentNegotiator evaluates the incoming request's headers to determine which extensions are active for that specific request.
    3. Request Capture: An IDocumentAdapter is used to intercept and capture specific properties from the incoming request body (such as values inside the top-level meta object).
    4. Response Augmentation: An IResponseMeta implementation uses the information from the active extensions and the captured request data to inject additional data into the top-level meta of the JSON:API response.
  7. Use ObfuscatedIdentifiable and ObfuscatedIdentifiableController

    master
    To enable obfuscated IDs for a resource, ensure your model inherits from ObfuscatedIdentifiable and your controller inherits from ObfuscatedIdentifiableController. This ensures that the id parameters in your controller actions are treated as string types, allowing the framework to handle the de-obfuscation of the incoming client string into the internal numeric ID.
  8. How Resource Definitions work for query customization

    master

    Resource definitions provide a way to intercept and modify JSON:API queries before they are executed. By inheriting from JsonApiResourceDefinition<TResource, TId>, you can override specific methods to manipulate the query.

    Instead of working directly with Entity Framework Core IQueryable, you work with an intermediate format (such as QueryExpression, SortExpression, PaginationExpression, etc.). This abstraction separates the JSON:API protocol implementation from the underlying database execution.

    Common customization points include:

    • Excluding fields: Conditionally hiding attributes or relationships (e.g., hiding a Password field for non-admins).
    • Default sort order: Providing a fallback sort if the client doesn't specify one.
    • Enforcing page size: Limiting the maximum number of items returned in a single page.
    • Changing filters: Adding mandatory filters (e.g., always filtering out 'suspended' accounts).
    • Blocking includes: Preventing clients from requesting specific related resources by throwing a JsonApiException.
  9. How the JsonApiDotNetCore query pipeline works

    master

    JsonApiDotNetCore processes incoming JSON:API requests through a multi-stage pipeline that transforms HTTP query strings into optimized SQL via Entity Framework Core.

    1. Request Collection: JsonApiMiddleware identifies resource information from routing.
    2. Parsing: IQueryStringParameterReader uses QueryParser to convert query string text into QueryExpression objects. It uses prefix notation for filters (e.g., and(equals(a, b), c)) to avoid operator precedence issues.
    3. Composition: QueryLayerComposer (via JsonApiResourceService) gathers constraints, applies default options and IResourceDefinition overrides, and builds a tree of QueryLayer objects. This stage handles secondary endpoints (e.g., /blogs/1/articles) and rewrites includes.
    4. Translation: The EntityFrameworkCoreRepository uses a QueryableBuilder to transform the QueryLayer tree into LINQ IQueryable expression trees.
    5. Execution: Entity Framework Core translates the LINQ expressions into SQL for the database.
    6. Response: JsonApiWriter converts the resulting resource objects back into a JSON:API compliant response.
  10. Use the [EagerLoad] attribute for calculated properties

    master

    When a resource has a calculated property that depends on related data, use the [EagerLoad] attribute on the relationship to ensure the necessary data is fetched from the database.

    Important Behavior: Even when using [EagerLoad], the related resources are not returned in the JSON response to the client unless the client explicitly requests them using the include query string parameter. The attribute's purpose is to optimize database fetching for internal logic, not to change the default JSON output structure.

    // Example pattern for a calculated property depending on a relationship
    public class Street
    {
        // The DoorTotalCount property depends on Buildings
        [EagerLoad]
        public ICollection<Building> Buildings { get; set; }
    
        public int DoorTotalCount => Buildings.Count; // Calculated property
    }