|
8 | 8 | using Azure.Identity; |
9 | 9 | using Azure.Storage; |
10 | 10 | using Azure.Storage.Blobs; |
| 11 | +using Azure.Storage.Blobs.Specialized; |
11 | 12 | using Azure.Storage.Blobs.Models; |
12 | 13 | using Azure.Storage.Sas; |
13 | 14 | using NUnit.Framework; |
14 | 15 | using System.Text; |
15 | 16 | using System.Threading.Tasks; |
| 17 | +using System.Security.Cryptography; |
16 | 18 |
|
17 | 19 | namespace Azure.Storage.Blobs.Samples |
18 | 20 | { |
@@ -608,5 +610,129 @@ public async Task SasBuilderIdentifier() |
608 | 610 | await container.DeleteIfExistsAsync(); |
609 | 611 | } |
610 | 612 | } |
| 613 | + |
| 614 | + [Test] |
| 615 | + public async Task BlobContentHash() |
| 616 | + { |
| 617 | + string data = "hello world"; |
| 618 | + using Stream contentStream = new MemoryStream(Encoding.UTF8.GetBytes(data)); |
| 619 | + |
| 620 | + // precalculate hash for sample |
| 621 | + byte[] precalculatedContentHash; |
| 622 | + using (var md5 = MD5.Create()) |
| 623 | + { |
| 624 | + precalculatedContentHash = md5.ComputeHash(contentStream); |
| 625 | + } |
| 626 | + contentStream.Position = 0; |
| 627 | + |
| 628 | + // setup blob |
| 629 | + string containerName = Randomize("sample-container"); |
| 630 | + string blobName = Randomize("sample-file"); |
| 631 | + var containerClient = new BlobContainerClient(ConnectionString, containerName); |
| 632 | + |
| 633 | + try |
| 634 | + { |
| 635 | + containerClient.Create(); |
| 636 | + var blobClient = containerClient.GetBlobClient(blobName); |
| 637 | + |
| 638 | + #region Snippet:SampleSnippetsBlobMigration_BlobContentMD5 |
| 639 | + // upload with blob content hash |
| 640 | + await blobClient.UploadAsync( |
| 641 | + contentStream, |
| 642 | + new BlobUploadOptions() |
| 643 | + { |
| 644 | + HttpHeaders = new BlobHttpHeaders() |
| 645 | + { |
| 646 | + ContentHash = precalculatedContentHash |
| 647 | + } |
| 648 | + }); |
| 649 | + |
| 650 | + // download whole blob and validate against stored blob content hash |
| 651 | + Response<BlobDownloadInfo> response = await blobClient.DownloadAsync(); |
| 652 | + |
| 653 | + Stream downloadStream = response.Value.Content; |
| 654 | + byte[] blobContentMD5 = response.Value.Details.BlobContentHash ?? response.Value.ContentHash; |
| 655 | + // validate stream against hash in your workflow |
| 656 | + #endregion |
| 657 | + |
| 658 | + byte[] downloadedBytes; |
| 659 | + using (var memStream = new MemoryStream()) |
| 660 | + { |
| 661 | + await downloadStream.CopyToAsync(memStream); |
| 662 | + downloadedBytes = memStream.ToArray(); |
| 663 | + } |
| 664 | + |
| 665 | + Assert.AreEqual(data, Encoding.UTF8.GetString(downloadedBytes)); |
| 666 | + Assert.IsTrue(Enumerable.SequenceEqual(precalculatedContentHash, blobContentMD5)); |
| 667 | + } |
| 668 | + finally |
| 669 | + { |
| 670 | + await containerClient.DeleteIfExistsAsync(); |
| 671 | + } |
| 672 | + } |
| 673 | + |
| 674 | + [Test] |
| 675 | + public async Task TransactionalMD5() |
| 676 | + { |
| 677 | + string data = "hello world"; |
| 678 | + string blockId = Convert.ToBase64String(Guid.NewGuid().ToByteArray()); |
| 679 | + List<string> blockList = new List<string> { blockId }; |
| 680 | + using Stream blockContentStream = new MemoryStream(Encoding.UTF8.GetBytes(data)); |
| 681 | + |
| 682 | + // precalculate hash for sample |
| 683 | + byte[] precalculatedBlockHash; |
| 684 | + using (var md5 = MD5.Create()) |
| 685 | + { |
| 686 | + precalculatedBlockHash = md5.ComputeHash(blockContentStream); |
| 687 | + } |
| 688 | + blockContentStream.Position = 0; |
| 689 | + |
| 690 | + // setup blob |
| 691 | + string containerName = Randomize("sample-container"); |
| 692 | + string blobName = Randomize("sample-file"); |
| 693 | + var containerClient = new BlobContainerClient(ConnectionString, containerName); |
| 694 | + |
| 695 | + try |
| 696 | + { |
| 697 | + containerClient.Create(); |
| 698 | + var blockBlobClient = containerClient.GetBlockBlobClient(blobName); |
| 699 | + |
| 700 | + #region Snippet:SampleSnippetsBlobMigration_TransactionalMD5 |
| 701 | + // upload a block with transactional hash calculated by user |
| 702 | + await blockBlobClient.StageBlockAsync( |
| 703 | + blockId, |
| 704 | + blockContentStream, |
| 705 | + transactionalContentHash: precalculatedBlockHash); |
| 706 | + |
| 707 | + // upload more blocks as needed |
| 708 | + |
| 709 | + // commit block list |
| 710 | + await blockBlobClient.CommitBlockListAsync(blockList); |
| 711 | + |
| 712 | + // download any range of blob with transactional MD5 requested (maximum 4 MB for downloads) |
| 713 | + Response<BlobDownloadInfo> response = await blockBlobClient.DownloadAsync( |
| 714 | + range: new HttpRange(length: 4 * Constants.MB), // a range must be provided; here we use transactional download max size |
| 715 | + rangeGetContentHash: true); |
| 716 | + |
| 717 | + Stream downloadStream = response.Value.Content; |
| 718 | + byte[] transactionalMD5 = response.Value.ContentHash; |
| 719 | + // validate stream against hash in your workflow |
| 720 | + #endregion |
| 721 | + |
| 722 | + byte[] downloadedBytes; |
| 723 | + using (var memStream = new MemoryStream()) |
| 724 | + { |
| 725 | + await downloadStream.CopyToAsync(memStream); |
| 726 | + downloadedBytes = memStream.ToArray(); |
| 727 | + } |
| 728 | + |
| 729 | + Assert.AreEqual(data, Encoding.UTF8.GetString(downloadedBytes)); |
| 730 | + Assert.IsTrue(Enumerable.SequenceEqual(precalculatedBlockHash, transactionalMD5)); |
| 731 | + } |
| 732 | + finally |
| 733 | + { |
| 734 | + await containerClient.DeleteIfExistsAsync(); |
| 735 | + } |
| 736 | + } |
611 | 737 | } |
612 | 738 | } |
0 commit comments