Skip to content

Integration

Backend-for-frontend configuration

In order to receive the requests from the BFF we need to configure the Yarp module and add exposed headers. First, go to appsettings.json of your application BFF and add the service to Service Discovery Providers.

JSON
1
2
3
"file-storage-service": {
    "addresses": [ "localhost:22005" ]
}

Now add the following Yarp Routes configuration:

JSON
"YarpModuleOptions": {
    "Routes": {
        // [..] other routes. TODO: Review indexes
        "0": {
            "RouteId": "file-storage-route",
            "Match": {
                "Path": "files/{**catch-all}"
            },
            "ClusterId": "file-storage-cluster"
        }
    },
    "Clusters": {
        // [..] other clusters. TODO: Review indexes
        "0": {
            "ClusterId": "file-storage-cluster",
            "Destinations": {
                "0": {
                    "Address": "http://file-storage-service"
                }
            }
        }
    }
},

Also, we need to add a reference for the FileStorage BFF Client Module:

XML
<ProjectReference Include="$(ServicesPath)FileStorage\ITsynch.Suite.FileStorage.BffClientModule\ITsynch.Suite.FileStorage.BffClientModule.csproj" />

And add the dependency in the BFF Module:

C#
1
2
3
4
5
6
public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<FileStorageBffClientModule>();
}

Important

Remember to add your Application service to the FileStorage federations.json located at FileStorage\ITsynch.Suite.FileStorage.Application\.

Backend Integration

Imports

  • Add the following import to Domain layer:
XML
<ProjectReference Include="$(ServicesPath)FileStorage\ITsynch.Suite.FileStorage.Abstractions\ITsynch.Suite.FileStorage.Abstractions.csproj" />
  • Add the following import to Application.Contracts layer:
XML
<ProjectReference Include="$(ServicesPath)FileStorage\ITsynch.Suite.FileStorage.ClientModule.Contracts\ITsynch.Suite.FileStorage.ClientModule.Contracts.csproj" />
  • Add the following import to Application layer:
XML
<ProjectReference Include="$(ServicesPath)FileStorage\ITsynch.Suite.FileStorage.ClientModule\ITsynch.Suite.FileStorage.ClientModule.csproj" />
  • Depend on FileStorageClientModule in your Suite Application Module:
C#
public override void SetupModule(IModuleBuilder builder)
{
    base.SetupModule(builder);

    builder.DependsOn<FileStorageClientModule, FileStorageClientModuleOptions>(
        opts =>
        {
            opts.SetDbContext<EntityDbContext>();
        });
}

Update entities and consumer or saga

  • In your domain entity implement IHasAttachments interface:
C#
using ITsynch.Suite.FileStorage.Domain;

namespace ITsynch.Suite.Entity.Domain
{
    public class Entity : IHasAttachments
    {
        /// <inheritdoc />
        public ICollection<Attachment> Attachments { get; set; } = new List<Attachment>();
    }
}
  • In the corresponding GQL entity implement IHasAttachments interface from the GraphQL namespace:
C#
using ITsynch.Suite.FileStorage.Client.Contracts;

namespace ITsynch.Suite.Entity.Application.GraphQL
{
    public class Entity : IHasAttachments
    {
        /// <inheritdoc />
        public IEnumerable<Attachment> Attachments { get; set; } = new List<Attachment>();
    }
}
  • We need to update the MassTransit CreateOrUpdate message as well, implementing the IHasAttachmentsMessage interface:
C#
using ITsynch.Suite.FileStorage.Client.Contracts;

namespace ITsynch.Suite.Entity.Application
{
    public record CreateOrUpdateEntity : IHasAttachmentsMessage
    {
        /// <inheritdoc />
        public IEnumerable<AttachmentMessage> Attachments { get; set; } = new List<AttachmentMessage>();
    }
}
  • Update your consumer/saga methods:
C#
1
2
3
4
5
6
7
var specs = AggregateSpecification.For<Entity>()
           .AndFilter(new EntityByIdFilterSpecification(entity.CorrelationId))
           .AddInclude(new AttachmentsIncludeSpecification<Entity>());

var entity = await this.repository.FirstOrDefaultAsync(specs);

await entity.UpdateAttachments(message.Attachments, context);

Note

The UpdateAttachments method is an extension method from ITsynch.Suite.FileStorage.Abstractions. Also this method is idempotent, meaning that only the attachments from the message will remain in the entity so this can be used in a creation or an edition.

Now we have two separate ways to follow, depending if we have the GraphQL mutation in the backend service or in the Application service (E.g.: Admin Center).

Locate the GraphQL creation/update message record and implement IHasAttachment interface:

C#
using ITsynch.Suite.FileStorage.Client.Contracts;

namespace ITsynch.Suite.AdminCenter.Application.GraphQL
{
    public record CreateOrUpdateEntity : IHasAttachments
    {
        /// <inheritdoc />
        public IEnumerable<Attachment> Attachments { get; set; } = new List<Attachment>();
    }
}

Now we should have all the following classes with its GQL counterpart interfaced:

  • Domain Entity
  • GraphQL Domain Entity
  • Creation/Update MT Message
  • GraphQL Creation/Update MT Message

This is needed in order to not add any mappings since this is being mapped under the hood by implementing the interfaces.

You can check how all of this is implemented in Discussions or Admin Center.

Note

Remember to add new Migrations.

UI Integration

In order to add the Uploader to your component, first we need to add the FileStorageUploaderModule to your app module.

The first step is modify your Entity model and add the following properties:

TypeScript
import {
    Attachment,
    AttachmentMessage
} from '@itsynch/file-storage/file-storage-utils';

interface Entity {
    // ...
    attachments?: Attachment[];
}

interface UpdateEntity {
    // ...
    attachments?: AttachmentMessage[];
}

interface CreateEntity {
    // ...
    attachments?: AttachmentMessage[];
}

The Uploader supports multiple view types/modes and you can pass that configuration through inputs, right now it supports cards, chips, carousel view types and crud or readonly modes. Those inputs are optional, the default values are cards for type and crud for mode. Example of configuration:

TypeScript
import { FILE_UPLOADER_FORM_TYPE } from '@itsynch/file-storage/uploader';

this.customizeForm((b) =>
    b.field('attachments', (b) =>
        b.customType(FILE_UPLOADER_FORM_TYPE, {
            name: 'my-uploader',
            type: 'chips',
            mode: 'readonly'
        })
    )
);

Creation Form Page

Now, if you want to implement it in your creation form page add the customizeForm method in your form constructor:

TypeScript
1
2
3
4
5
6
7
8
9
import { FILE_UPLOADER_FORM_TYPE } from '@itsynch/file-storage/uploader';

this.customizeForm((b) =>
    b.field('attachmentIds', (b) =>
        b.customType(FILE_UPLOADER_FORM_TYPE, {
            name: 'my-uploader'
        })
    )
);

Edition Form Page

Another example for the edition page (from Spares):

TypeScript
import { FILE_UPLOADER_FORM_TYPE } from '@itsynch/file-storage/uploader';

this.customizeForm((b) =>
     b.field('attachmentIds', (b) =>
        b
            .displayName('Attachments')
            .customType(FILE_UPLOADER_FORM_TYPE, {
                name: 'my-uploader'
            })
            .initialValue((i) => i?.attachments)
            )
    )
);

Note

Keep in mind that the form control value of the Uploader component is an array of file correlation IDs. So, you should add something like attachmentIds: string[] to your entity in order to call mutations. You can check SpareEntity for an example.

Template import

You can also add the component in your template, this way could be used if you only want to view the attached files to an entity.

HTML
1
2
3
4
5
6
7
8
<its-form-control
    file-uploader
    name="my-uploader"
    type="chips"
    mode="readonly"
    [files]="entity.attachments"
>
</its-form-control>

The property [files] are the already uploaded files related to your entity and the type is FileMetadata[].

Important

Do not forget to add the FileStorageUploaderModule in your app module for this to work.

Events (Optional)

The File Storage Service offers some message events which can be consumed to be kept up to date with the state of a File in case that you need it.

First of all, we need a reference to the FileStorage.Application.Contracts project, as it's shown below:

XML
<ProjectReference Include="$(ServicesPath)FileStorage\ITsynch.Suite.FileStorage.Application.Contracts\ITsynch.Suite.FileStorage.Application.Contracts.csproj" />

You can consume the following events and perform the logic that you need in your respective service:

  • FileCreated: This message is fired when a File has been created. It provides all the File metadata.

Note

If a File upload is aborted/paused for more than 5 minutes (TBD), that File will be deleted and the corresponding message event will also be fired.

  • FileUploaded: This message is fired when a File is completely uploaded. Only provides the File Correlation Id.
  • FileUpdated: This message is fired when the metadata of a File was updated. Provides all the file metadata.
  • FileDeleted: This message is fired when a File has been deleted. Only provides the File Correlation Id.