skip to Main Content

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:

  1. How do I actually upload these documents? IFormFileCollection in some way?
  2. Are DocumentType, DocumentName, DocumentDescription, and DocumentUrl 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


    1. How do I actually upload these documents? IFormFileCollection in some way?
    2. Are DocumentType, DocumentName, DocumentDescription, and DocumentUrl enough for Azure Storage Account, or have I missed any other property that’s required?

    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

    {
      "AzureBlobStorage": {
        "ConnectionString": "xxxxx",
        "ContainerName": "xxx"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      }
    }
    

    Program.cs

    using Azure.Storage.Blobs;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllers();
    
    // Register BlobServiceClient as a singleton
    builder.Services.AddSingleton(x =>
    {
        var configuration = x.GetRequiredService<IConfiguration>();
        var connectionString = configuration["AzureBlobStorage:ConnectionString"];
        return new BlobServiceClient(connectionString);
    });
    
    var app = builder.Build();
    
    app.UseHttpsRedirection();
    app.MapControllers();
    app.Run();
    

    Models:

    LegalEntity.cs

    namespace KycDocumentsApi.Models
    {
        public class LegalEntity
        {
            public string Name { get; set; }
            public List<KycDocument> KycDocuments { get; set; }
        }
    }
    

    KycDocument.cs

    namespace KycDocumentsApi.Models
    {
        public class KycDocument
        {
            public string DocumentType { get; set; }
            public string DocumentName { get; set; }
            public string DocumentDescription { get; set; }
            public string DocumentUrl { get; set; }
        }
    }
    

    LegalEntitiesController.cs

    using Azure.Storage.Blobs;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using KycDocumentsApi.Models;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace KycDocumentsApi.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class LegalEntitiesController : ControllerBase
        {
            private readonly BlobServiceClient _blobServiceClient;
            private readonly IConfiguration _configuration;
    
            public LegalEntitiesController(BlobServiceClient blobServiceClient, IConfiguration configuration)
            {
                _blobServiceClient = blobServiceClient;
                _configuration = configuration;
            }
    
            [HttpPost("upload-kyc-documents")]
            public async Task<IActionResult> UploadKycDocuments([FromForm] string legalEntityName, IFormFileCollection files)
            {
                // Validate the legal entity name
                if (string.IsNullOrWhiteSpace(legalEntityName))
                {
                    return BadRequest("Legal entity name is required.");
                }
    
                // Validate files
                if (files == null || files.Count == 0)
                {
                    return BadRequest("No files uploaded.");
                }
    
                // Retrieve the container name from configuration and create container client
                var containerName = _configuration["AzureBlobStorage:ContainerName"];
                var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
                await containerClient.CreateIfNotExistsAsync();
    
                var documents = new List<KycDocument>();
    
                // Upload each file to Blob Storage
                foreach (var file in files)
                {
                    // Validate file properties
                    if (file.Length > 0)
                    {
                        var blobClient = containerClient.GetBlobClient($"{legalEntityName}/{file.FileName}");
                        await using var stream = file.OpenReadStream();
                        await blobClient.UploadAsync(stream, overwrite: true);
    
                        // Create document entry with metadata
                        var document = new KycDocument
                        {
                            DocumentType = "KYC Document",  // Adjust as needed
                            DocumentName = file.FileName,
                            DocumentDescription = "Uploaded KYC Document", // Customize as needed
                            DocumentUrl = blobClient.Uri.ToString()
                        };
                        documents.Add(document);
                    }
                    else
                    {
                        return BadRequest($"File {file.FileName} is empty.");
                    }
                }
    
                // Create LegalEntity object to include uploaded documents if necessary
                var legalEntity = new LegalEntity
                {
                    Name = legalEntityName,
                    KycDocuments = documents
                };
    
                return Ok(legalEntity);
            }
        }
    }
    

    Now you can use the dotnet run to run the above code.

    info: Microsoft.Hosting.Lifetime[14]  
    Now listening on: http://localhost:5057  
    info: Microsoft.Hosting.Lifetime[0]  
    Application started. Press Ctrl+C to shut down.  
    info: Microsoft.Hosting.Lifetime[0]  
    Hosting environment: Development  
    info: Microsoft.Hosting.Lifetime[0]
    

    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 with DocumentType, DocumentName, DocumentDescription, and DocumentUrl and they are sufficient to upload the property.

    Post http://localhost:5057/api/legalentities/upload-kyc-documents
    body:
     form-data 
     legalEntityName : TechVision Solutions(text or string) type
     files           : data.png(file)type
     files           : demo.pdf(file)type
    

    Output:

    {
        "name": "TechVision Solutions",
        "kycDocuments": [
            {
                "documentType": "KYC Document",
                "documentName": "data.png",
                "documentDescription": "Uploaded KYC Document",
                "documentUrl": "https://xxxx.blob.core.windows.net/xxx/TechVision Solutions/data.png"
            },
            {
                "documentType": "KYC Document",
                "documentName": "demo.pdf",
                "documentDescription": "Uploaded KYC Document",
                "documentUrl": "https://xxx.blob.core.windows.net/xxx/TechVision Solutions/demo.pdf"
            }
        ]
    }
    

    enter image description here

    Portal:
    enter image description here

    Login or Signup to reply.
  1. 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:

    [HttpPost("uploadKycDocuments")]
    public async Task<IActionResult> UploadKycDocuments(IFormFileCollection files, [FromBody] LegalEntityDto legalEntity)
    {
        if (files == null || !files.Any())
        {
            return BadRequest("No files uploaded.");
        }
    
        var uploadedDocuments = new List<KycDocumentDto>();
    
        foreach (var file in files)
        {
            var documentUrl = await UploadToAzureBlobAsync(file);
            
            var documentDto = new KycDocumentDto
            {
                DocumentType = "Specify type", // Populate with document type
                DocumentName = file.FileName,
                DocumentDescription = "Specify description", // Populate with document description
                DocumentUrl = documentUrl
            };
            
            uploadedDocuments.Add(documentDto);
        }
    
        // Attach uploaded document details to the legal entity's KYC information
        legalEntity.KycInformation = new KycInformationDto
        {
            Status = "Pending",
            Documents = uploadedDocuments
        };
    
        // Continue processing the legal entity or store in the database
        // ...
    
        return Ok(uploadedDocuments);
    }
    

    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:

    private async Task<string> UploadToAzureBlobAsync(IFormFile file)
    {
        var blobContainerClient = _blobServiceClient.GetBlobContainerClient("your-container-name");
        var blobClient = blobContainerClient.GetBlobClient(file.FileName);
    
        using (var stream = file.OpenReadStream())
        {
            await blobClient.UploadAsync(stream, overwrite: true);
        }
    
        return blobClient.Uri.ToString(); // This URL can be stored as DocumentUrl
    }
    

    3. Metadata Structure for KYC Document Storage
    Your payload structure appears to be comprehensive for document metadata, and it includes all essential fields:

    • DocumentType: Describes the type of document, e.g., "Certificate of Incorporation."
    • DocumentName: The original or customized name of the document file.
    • DocumentDescription: Optional, but helpful for further context.
    • DocumentUrl: URL for accessing the document in Azure Blob Storage.

    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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search