{"id":16665,"date":"2018-02-26T08:00:00","date_gmt":"2018-02-26T16:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/dotnet\/?p=16665"},"modified":"2021-09-29T16:29:29","modified_gmt":"2021-09-29T23:29:29","slug":"azure-blob-storage-as-a-network-drive","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/azure-blob-storage-as-a-network-drive\/","title":{"rendered":"Azure Blob Storage as a Network Drive"},"content":{"rendered":"<p>Many applications make use of a network drive to backup and store files. When I was in university I found myself constantly coding for fun, and one example took the form of a network share for my roommates to share files wrapped in a handy little app.<\/p>\n<p>Unfortunately, that particular app has long since been erased from whichever hard drive it was initially birthed. Fortunately, I think we can reinvent this magical piece of software (albeit to a scoped degree) with <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/storage\/blobs\/storage-blobs-introduction\">Azure Blob Storage<\/a>. In the past, network drives did the trick, but Azure Storage offers users automatic backups, better flexibility and global availability, <a href=\"https:\/\/azure.microsoft.com\/en-us\/pricing\/details\/storage\">all at a very low cost<\/a> (or no cost if you are using <a href=\"https:\/\/azure.microsoft.com\/en-us\/free\">free Azure credits<\/a>).<\/p>\n<p>I took my partial memory of the general skeleton of the former masterpiece and rewrote it using Blobs as the backing file store. We are going to build this app as it was in its glory days, which means we need a few things.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<\/h2>\n<p>I&#8217;ll build this app in <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/framework\/wpf\/getting-started\/introduction-to-wpf-in-vs\">WPF<\/a> using <a href=\"https:\/\/www.visualstudio.com\/\">Visual Studio 2017<\/a> and the <a href=\"https:\/\/www.nuget.org\/packages\/WindowsAzure.Storage\/\">Azure Storage for .NET<\/a> library. While I made the choice to build this sample in WPF, the Azure Storage portions work with any .NET app type including Windows Forms, ASP.NET, Console, etc.<\/p>\n<h2 id=\"representingtheaccountasatreeview\">Representing the Account as a TreeView<\/h2>\n<p>At its core, the app needs to display the current state of the blob account in a human-consumable fashion. In a stroke of ingenuity, I decided to represent the containers, directories, and blobs as a <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/system.windows.controls.treeview.aspx\">TreeView<\/a>.<\/p>\n<p>As expected, we must build a tree for each container. First, we must obtain a flat list of the blobs in the container.<\/p>\n<pre><code class=\"csharp language-csharp\">var blobs = container.ListBlobs(useFlatBlobListing: true)\n    .Cast&lt;CloudBlockBlob&gt;()\n    .Select(b =&gt; new TreeViewBlob\n    {\n        Name = b.Name, \n        Blob = b\n    });\n<\/code><\/pre>\n<p>The <code>TreeViewBlob<\/code> is just a convenient representation for the following tree building algorithm.<\/p>\n<pre><code class=\"csharp language-csharp\">IEnumerable&lt;TreeViewNode&gt; BuildTree(IEnumerable&lt;TreeViewBlob&gt; blobs)\n{\n    return blobs\n        .GroupBy(b =&gt; b.Name.Split('\/')[0])\n        .Select(g =&gt;\n        {\n            var children = g.Where(b =&gt; b.Name.Length &gt; g.Key.Length + 1).Select(b =&gt; new TreeViewBlob\n            {\n                Name = b.Name.Substring(g.Key.Length + 1),\n                Blob = b.Blob\n            });\n\n            var blob = g.FirstOrDefault(b =&gt; b.Name == g.Key)?.Blob;\n\n            return new TreeViewNode\n            {\n                Name = g.Key,\n                Blob = blob, \n                Children = BuildTree(children)\n            };\n        });\n}\n<\/code><\/pre>\n<p>The above two listings takes a flat list of blobs.<\/p>\n<pre><code>my\/share\/file1.jpg\nmy\/share\/file2.jpg\nmy\/share\/private\/file.jpg\n<\/code><\/pre>\n<p>Subsequent to <code>BuildTree<\/code>, we essentially have the structure of our <code>TreeView<\/code>.<\/p>\n<pre><code>my\n  share\n    file1.jpg\n    file2.jpg\n    private\n        file.jpg\n<\/code><\/pre>\n<p>Now that we have built the <code>TreeView<\/code>, we need to start implementing our Storage commands.<\/p>\n<h2 id=\"downloadingblobs\">Downloading Blobs<\/h2>\n<p>The first of our three action buttons downloads a blob. As I mentioned earlier, Blob storage makes this task excessively simple.<\/p>\n<pre><code class=\"csharp language-csharp\">async void DownloadButton_Click(object sender, RoutedEventArgs e)\n{\n    var item = View.SelectedItem as TreeViewItem;\n    var blob = item.Tag as CloudBlockBlob;\n    var name = item.Header as string;\n\n    var saveFileDialog = new SaveFileDialog\n    {\n        FileName = name,\n        Title = \"Download...\"\n    };\n\n    if (saveFileDialog.ShowDialog() != true)\n        return;\n\n    StatusText = \"Downloading...\";\n    await blob.DownloadToFileAsync(saveFileDialog.FileName, FileMode.Create);\n    StatusText = \"Success!\";\n}\n<\/code><\/pre>\n<p>This method takes the currently selected <code>BlockBlob<\/code>, displays a prompt to the user, and downloads the blob to the selected file. The real meat of this method is performed during <code>blob.DownloadToFileAsync<\/code>; the rest of the method is just gathering the proper information.<\/p>\n<p>Both <code>async<\/code> and <code>await<\/code> have drastically simplified UI thread updates in presentation frameworks, so setting the status text and using <code>await<\/code> gives us the desired UI results.<\/p>\n<p>Downloading blobs is only half the excitement: we need the ability to upload blobs, as well.<\/p>\n<h2 id=\"uploadingblobs\">Uploading Blobs<\/h2>\n<p>Upon selecting a directory, the &#8220;upload&#8221; button enables the user to select a file and upload it into the Storage directory.<\/p>\n<pre><code class=\"csharp language-csharp\">async void UploadButton_Click(object sender, RoutedEventArgs e)\n{\n    var item = View.SelectedItem as TreeViewItem;\n\n    var (containerName, directoryName) = GetContainerAndDirectory(item);\n\n    var client = Helpers.Storage.CreateCloudBlobClient();\n    var container = client.GetContainerReference(containerName);\n\n    var openFileDialog = new OpenFileDialog\n    {\n        Title = \"Upload...\"\n    };\n\n    if (openFileDialog.ShowDialog() != true)\n        return;\n\n    var filePath = openFileDialog.FileName;\n    var fileName = filePath.Split(System.IO.Path.DirectorySeparatorChar).Last();\n\n    var blobReference = container.GetBlockBlobReference($\"{directoryName}{fileName}\");\n\n    StatusText = \"Uploading...\";\n    await blobReference.UploadFromFileAsync(filePath);\n    await UpdateView();\n    StatusText = \"Success!\";\n}\n<\/code><\/pre>\n<p>Similar to the download method, the upload method prompts the user for a file, and uploads the file into a blob of the same name into the, currently selected, directory. Again, most of the method is simply gathering the proper data, while the Storage library simplifies the operation into one call (<code>blobReference.UploadFromFileAsync<\/code>). However, this is the first and only time we will come across a &#8220;reference&#8221;. Prior to uploading the file into a blob, a local <code>CloudBlockBlob<\/code> reference is ascertained via <code>container.GetBlockBlobReference<\/code>.<\/p>\n<p>After muddying up my test storage account with a bunch of uploaded files, I decided it was time to implement the ability to delete blobs.<\/p>\n<h2 id=\"deletingblobs\">Deleting Blobs<\/h2>\n<p>Selecting a block blob in the tree will also allow the user to delete the selected blob.<\/p>\n<pre><code class=\"csharp language-csharp\">async void DeleteButton_Click(object sender, RoutedEventArgs e)\n{\n    var item = View.SelectedItem as TreeViewItem;\n    var blob = item.Tag as CloudBlockBlob;\n    var name = item.Header as string;\n\n    StatusText = \"Deleting...\";\n    await blob.DeleteAsync();\n    await UpdateView();\n    StatusText = \"Success!\";\n}\n<\/code><\/pre>\n<p>As was the case for downloading blobs, the <code>CloudBlockBlob<\/code> is easily attained from the <code>TreeViewItem<\/code>, having been attached previously. Again, performing the actual operation only requires only one call to <code>blob.DeleteAsync<\/code>.<\/p>\n<h2 id=\"theuserinterface\">The User Interface<\/h2>\n<p>As I mentioned previously, the Azure Storage solution we built is applicable to any type of .NET Application. I decided to use WPF, but the choice for this specific endeavour was made out of a personal love for XAML.<\/p>\n<p>As a former developer on the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/framework\/wpf\/advanced\/xaml-overview-wpf\">XAML<\/a> Developer Platform Team in Windows, I am always excited to stretch my XAML skills after a long time off. Despite getting back into it, I did not really have much to do. So little, in fact, I decided to completely ignore using <code>Style<\/code>s. Our XAML looks something like this.<\/p>\n<pre><code class=\"xml language-xml\">&lt;Grid&gt;\n    &lt;StackPanel Orientation=\"Horizontal\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Top\" Margin=\"0,10,10,10\" Width=\"400\" Height=\"30\"&gt;\n        &lt;Button Name=\"DownloadButton\" Content=\"Download\" IsEnabled=\"{Binding IsBlobSelected}\" Click=\"DownloadButton_Click\" Margin=\"10,0,0,0\" Width=\"100\" Height=\"30\"&gt;&lt;\/Button&gt;\n        &lt;Button Name=\"UploadButton\" Content=\"Upload\" IsEnabled=\"{Binding IsDirectorySelected}\" Click=\"UploadButton_Click\" Margin=\"10,0,0,0\" Width=\"100\" Height=\"30\"&gt;&lt;\/Button&gt;\n        &lt;Button Name=\"DeleteButton\" Content=\"Delete\" IsEnabled=\"{Binding IsBlobSelected}\" Click=\"DeleteButton_Click\" Margin=\"10,0,0,0\" Width=\"100\" Height=\"30\"&gt;&lt;\/Button&gt;\n    &lt;\/StackPanel&gt;\n    &lt;TreeView Name=\"View\" Margin=\"10,50,10,50\" SelectedItemChanged=\"View_SelectedItemChanged\" \/&gt;\n    &lt;TextBlock Name=\"Status\" Text=\"{Binding StatusText}\" HorizontalAlignment=\"Left\" VerticalAlignment=\"Bottom\" Margin=\"10\" Width=\"400\" Height=\"30\"&gt;&lt;\/TextBlock&gt;\n&lt;\/Grid&gt;\n<\/code><\/pre>\n<h2 id=\"puttingitalltogether\">Putting It All Together<\/h2>\n<p>So, there we have it. We have built an app that acts as a network drive using Azure Blob Storage. Implementing more operations and polishing th UI is merely an exercise in elbow grease. If you would like to try out this app, or use the code we went through as a base, take a look at the <a href=\"https:\/\/github.com\/twitchax\/networkdrive\">source<\/a>.<\/p>\n<p>I hope the work we did here excites you to take existing applications or ideas and port them to equivalent functionality in Azure!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Many applications make use of a network drive to backup and store files. When I was in university I found myself constantly coding for fun, and one example took the form of a network share for my roommates to share files wrapped in a handy little app. Unfortunately, that particular app has long since been [&hellip;]<\/p>\n","protected":false},"author":367,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685],"tags":[37,44,63,129,158,164],"class_list":["post-16665","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","tag-azure","tag-blob","tag-dotnet","tag-storage","tag-wpf","tag-xaml"],"acf":[],"blog_post_summary":"<p>Many applications make use of a network drive to backup and store files. When I was in university I found myself constantly coding for fun, and one example took the form of a network share for my roommates to share files wrapped in a handy little app. Unfortunately, that particular app has long since been [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/16665","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/367"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=16665"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/16665\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=16665"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=16665"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=16665"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}