/// <summary>
        /// Returns the information associated to a blob.
        /// </summary>
        /// <param name="blobPath"></param>
        /// <returns></returns>
        public async Task <BlobInfo> GetBlobInfoAsync(string blobPath)
        {
            BlobInfo result = null;

            CloudBlobClient blobClient = m_LazyBlobClient.Value;
            CloudBlob       blob       = new CloudBlob(GetAbsoluteBlobUri(blobPath), blobClient.Credentials);

            //`ExistsAsync` also refreshes properties and metadata at the same times
            if (await blob.ExistsAsync())
            {
                result = AzureBlob.FromBlobProperties(GetBlobPath(blob), blob.Properties, blob.Metadata);
            }

            return(result);
        }
        /// <summary>
        /// Returns the list of blobs matching the specified path.
        /// </summary>
        /// <param name="blobPathPrefix"></param>
        /// <returns></returns>
        public async Task <List <BlobInfo> > ListBlobAsync(string blobPathPrefix)
        {
            //$root container is not implicitly supported, at least an explicit container name is expected
            string prefix = blobPathPrefix ?? throw new ArgumentNullException(nameof(blobPathPrefix));

            if (string.IsNullOrWhiteSpace(prefix))
            {
                throw new ArgumentException(nameof(blobPathPrefix));
            }

            CloudBlobClient       blobClient        = m_LazyBlobClient.Value;
            BlobContinuationToken continuationToken = null;

            //pad prefix path for container only with trailing slash '/', otherwise storage SDK list in $root container only
            if (!prefix.Contains('/'))
            {
                prefix += '/';
            }

            var result = new List <BlobInfo>();

            try
            {
                do
                {
                    BlobResultSegment segment = await blobClient.ListBlobsSegmentedAsync(prefix, true, BlobListingDetails.Metadata, null, continuationToken, null, null);

                    continuationToken = segment.ContinuationToken;

                    result.AddRange(segment.Results.Cast <CloudBlob>().Select(b => AzureBlob.FromBlobProperties(GetBlobPath(b), b.Properties, b.Metadata)));
                } while (continuationToken != null);
            }
            catch (StorageException ex) when(ex.RequestInformation.ErrorCode == BlobErrorCodeStrings.ContainerNotFound)
            {
                //swallow container not found and let the default return the currently empty list instead
            }

            return(result);
        }
        /// <summary>
        /// Opens a stream for reading from the blob.
        /// </summary>
        /// <param name="blobPath">The relative path, from the storage root, to the blob.</param>
        /// <exception cref="IdNotFoundException">The blob specified in <paramref name="blobPath"/> does not exist (<see cref="ErrorCodes.BlobNotFound"/>).</exception>
        /// <returns></returns>
        public async Task <BlobStreamDecorator> OpenBlobAsync(string blobPath)
        {
            CloudBlobClient blobClient = m_LazyBlobClient.Value;
            CloudBlob       blob       = new CloudBlob(GetAbsoluteBlobUri(blobPath), blobClient.Credentials);

            try
            {
                //blob properties and metadata are automatically fetched when opening the blob stream
                Stream blobStream = await blob.OpenReadAsync();

                BlobInfo blobInfo = AzureBlob.FromBlobProperties(GetBlobPath(blob), blob.Properties, blob.Metadata);
                return(new BlobStreamDecorator(blobStream, blobInfo));
            }
            catch (StorageException ex) when(ex.RequestInformation.ErrorCode == BlobErrorCodeStrings.BlobNotFound || ex.RequestInformation.ErrorCode == BlobErrorCodeStrings.ContainerNotFound)
            {
                if (ex.RequestInformation.ErrorCode == BlobErrorCodeStrings.ContainerNotFound)
                {
                    m_Logger.LogWarning($"Container '{blob.Container.Name}' does not exist");
                }

                throw new IdNotFoundException(ErrorCodes.BlobNotFound, blobPath);
            }
        }