I’m working on an ASP.NET Core project where I need to store multiple KYC documents for a legal entity in an Azure Storage Account Container.
Questions:
- How do I actually upload these documents?
IFormFileCollection
in some way? - Are
DocumentType
,DocumentName
,DocumentDescription
, andDocumentUrl
enough for Azure Storage Account, or have I missed any other property that’s required?
Payload Example
{
"legalEntity": {
"name": "Acme Corporation",
"shortName": "Acme Corp",
"type": "Organization",
"registrationNumber": "REG123456",
"dateOfIncorporation": "2000-01-01T00:00:00",
"country": "United States",
"taxIdentificationNumber": "TAX987654",
"industryType": "Technology",
"contactInformation": {
"primaryContactName": "John Doe",
"primaryContactPhoneNumber": "+1 (555) 123-4567",
"primaryContactEmail": "[email protected]",
"secondaryContactName": "Jane Smith",
"secondaryContactPhoneNumber": "+1 (555) 987-6543",
"secondaryContactEmail": "[email protected]",
"website": "https://www.acmecorp.com"
},
"addressInformation": {
"streetAddress": "123 Tech Street",
"city": "Silicon Valley",
"stateProvince": "CA",
"postalCode": "94000",
"country": "United States"
},
"bankInformation": {
"accountName": "Acme Corporation Operating Account",
"bankName": "Tech Bank",
"accountNumber": "1234567890",
"swiftCode": "TECHUS123",
"iban": "DE89370400440532013000",
"bankAddress": "456 Bank Street, Silicon Valley, CA 94000, USA",
"bankContactPerson": "Michael Banking"
},
"financialInformation": {
"annualRevenue": 10000000.00,
"creditLimit": 1000000.00,
"paymentTerms": "Net 30",
"currencyPreference": "USD"
},
"kycInformation": {
"status": "Pending",
"documents": [
{
"documentType": "Certificate of Incorporation",
"documentName": "acme_incorporation.pdf",
"documentDescription": "Certificate of Incorporation for Acme Corp",
"documentUrl": "https://storage.example.com/documents/acme_incorporation.pdf"
}
]
},
"uboInformation": [
{
"name": "John Doe",
"ownershipPercentage": 51.00
},
{
"name": "Jane Smith",
"ownershipPercentage": 49.00
}
]
}
}
{
"legalEntity": {
"name": "TechVision Solutions",
"shortName": "TechVision",
"type": "Organization",
"registrationNumber": "REG789012",
"dateOfIncorporation": "2015-03-15T00:00:00",
"country": "Canada",
"taxIdentificationNumber": "TAX456789",
"industryType": "Software Development",
"contactInformation": {
"primaryContactName": "Michael Chen",
"primaryContactPhoneNumber": "+1 (604) 555-7890",
"primaryContactEmail": "[email protected]",
"secondaryContactName": "Sarah Johnson",
"secondaryContactPhoneNumber": "+1 (604) 555-4321",
"secondaryContactEmail": "[email protected]",
"website": "https://www.techvision.com"
},
"addressInformation": {
"streetAddress": "456 Innovation Drive",
"city": "Vancouver",
"stateProvince": "BC",
"postalCode": "V6B 1A1",
"country": "Canada"
},
"bankInformation": {
"accountName": "TechVision Solutions Operating Account",
"bankName": "Royal Bank of Canada",
"accountNumber": "9876543210",
"swiftCode": "ROYCCAT2",
"iban": "CA34ROYC3456789012345",
"bankAddress": "789 Financial Street, Vancouver, BC V6C 1A2, Canada",
"bankContactPerson": "David Banking"
},
"financialInformation": {
"annualRevenue": 5000000.00,
"creditLimit": 500000.00,
"paymentTerms": "Net 45",
"currencyPreference": "CAD"
},
"kycInformation": null,
"uboInformation": [
{
"name": "Michael Chen",
"ownershipPercentage": 60.00
},
{
"name": "Sarah Johnson",
"ownershipPercentage": 40.00
}
]
}
}
Snippet
public record LegalEntityDto
{
public required string Name { get; init; }
public string? ShortName { get; init; }
public required EntityType Type { get; init; }
public string? RegistrationNumber { get; init; }
public DateTime? DateOfIncorporation { get; init; }
public required string Country { get; init; }
public string? TaxIdentificationNumber { get; init; }
public required string IndustryType { get; init; }
public required ContactInformationDto ContactInformation { get; init; }
public required AddressInformationDto AddressInformation { get; init; }
public required BankInformationDto BankInformation { get; init; }
public required FinancialInformationDto FinancialInformation { get; init; }
public KycInformationDto? KycInformation { get; init; }
public ICollection<UboInformationDto> UboInformation { get; init; } = new List<UboInformationDto>();
private class Mapping : Profile
{
public Mapping()
{
CreateMap<LegalEntity, LegalEntityDto>();
CreateMap<LegalEntityDto, LegalEntity>();
}
}
}
public record KycDocumentDto
{
public required string DocumentType { get; init; }
public required string DocumentName { get; init; }
public string? DocumentDescription { get; init; }
public required string DocumentUrl { get; init; }
private class Mapping : Profile
{
public Mapping()
{
CreateMap<KycDocument, KycDocumentDto>();
CreateMap<KycDocumentDto, KycDocument>();
}
}
}
public sealed record CreateLegalEntityCommand : IRequest<Result<Guid>>
{
public required LegalEntityDto LegalEntity { get; init; }
}
public sealed class CreateLegalEntityCommandHandler(AppDbContext dbContext, IMapper mapper) : IRequestHandler<CreateLegalEntityCommand, Result<Guid>>
{
public async Task<Result<Guid>> Handle(CreateLegalEntityCommand request, CancellationToken cancellationToken)
{
var entity = mapper.Map<LegalEntity>(request.LegalEntity);
dbContext.LegalEntities.Add(entity);
await dbContext.SaveChangesAsync(cancellationToken);
return entity.Id;
}
}
public class CreateLegalEntityEndpoint : IEndpoint
{
public static void MapEndpoint(IEndpointRouteBuilder endpoints)
{
ArgumentNullException.ThrowIfNull(endpoints);
endpoints.MapPost("/legalentities",
async (CreateLegalEntityCommand command, ISender sender, CancellationToken cancellationToken) =>
{
var result = await sender.Send(command, cancellationToken);
return result.Match(id => Results.Created($"/legalentities/{id}", id), CustomResults.Problem);
})
.Produces<Guid>(StatusCodes.Status201Created)
.ProducesValidationProblem()
.ProducesProblem(StatusCodes.Status500InternalServerError)
.WithTags(nameof(LegalEntity))
.WithOpenApi(operation => new OpenApiOperation(operation)
{
Summary = "Create a new legal entity",
Description = "Creates a new legal entity with all its related information"
});
}
}
2
Answers
Yes, you can use
IFormFileCollection
to upload multiple KYC documents to an Azure Storage Account Container.In my environment, I created sample asp.net core project and you can see the below step by step approach to upload multiple KYC documents to an Azure Storage Account Container using
IFormFileCollection
.appsettings.json
Program.cs
Models:
LegalEntity.cs
KycDocument.cs
LegalEntitiesController.cs
Now you can use the
dotnet run
to run the above code.Now you can use the post request with
http://localhost:5057/api/legalentities/upload-kyc-documents
to upload the multiple files with legal name.For example, I’m executed through
echo Api
to upload multiple files withDocumentType
,DocumentName
,DocumentDescription
, andDocumentUrl
and they are sufficient to upload the property.Output:
Portal:
To handle the upload of multiple KYC documents for a legal entity to Azure Blob Storage in your ASP.NET Core project, here’s a detailed approach addressing each part of your setup.
1. Uploading Documents Using IFormFileCollection
You can use IFormFileCollection in your API endpoint to receive multiple files from the client, and then upload each file to Azure Blob Storage.
Here’s how you could set it up in a controller:
2. Upload Method to Azure Blob Storage
This method uploads a single IFormFile to Azure Blob Storage and returns the file URL. You’ll need to configure the Azure Blob Service client for this:
3. Metadata Structure for KYC Document Storage
Your payload structure appears to be comprehensive for document metadata, and it includes all essential fields:
These fields are generally enough for KYC document handling. Azure Blob Storage does allow adding custom metadata properties if needed, though storing detailed metadata like document type or description is usually better in a database for easier querying.
4. Using DTOs and Entity Mapping
In your provided setup, you’re already using DTOs and a CreateLegalEntityCommand for handling the business logic. After uploading files and obtaining their URLs, you could map the LegalEntityDto with populated KycInformation back to your entity model and save it to your database.
5. Example of Complete Legal Entity Command with Documents
When creating the legal entity with CreateLegalEntityCommand, ensure that the KycInformation (with the uploaded document URLs) is fully populated in the command handler before saving it to the database.