{"id":6425,"date":"2017-03-14T13:26:47","date_gmt":"2017-03-14T13:26:47","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/premier_developer\/?p=6425"},"modified":"2019-03-07T09:39:57","modified_gmt":"2019-03-07T16:39:57","slug":"building-a-simple-photo-album-using-azure-blob-storage-with-net-core","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/premier-developer\/building-a-simple-photo-album-using-azure-blob-storage-with-net-core\/","title":{"rendered":"Building a simple photo album using Azure Blob Storage with .NET Core"},"content":{"rendered":"<p>In this post, Senior Application Development Manager, <a href=\"https:\/\/www.linkedin.com\/in\/chris-tjoumas-pmp-9344558\/\"><strong>Chris Tjoumas<\/strong><\/a> builds a simple but powerful photo album using .NET Core, Azure Storage, and WebJobs.<\/p>\n<hr \/>\n<h2>Introduction<\/h2>\n<p>If you\u2019ve ever wanted to create a simple photo application to display an album of photos for each page, Azure Storage is a great technology to use. It is actually the storage foundation for Azure Virtual machines (and just about everything else), so it\u2019s built to <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/storage\/storage-performance-checklist\">handle load<\/a> from complex analytics to storage of simple data. It\u2019s highly scalable and allocates the appropriate resources to keep up with demand, if that ever becomes an issue with your ever popular photo album.<\/p>\n<p>To get started, you\u2019ll need to choose which Azure Storage type you want to use. In the case of photo storage, you\u2019ll likely want to use Azure Blob Storage, which acts like file storage in the cloud. Blob storage stores unstructured data such as documents, images, videos, application installers, etc. There are three \u201ctypes\u201d of blob storage which include: block blobs, append blobs, and page blobs. You\u2019ll need to create a storage account to host the Blobs. For images, you would want to use block blobs, which is built to handle large blobs (each block blob can be up to 4.75 TB in size). Page blobs are used to store things such as disks for VMs \u2013 disks used by Azure VMs are backed by page blobs. Append blobs are used for modifying (appending) blobs by adding to the block. You can read more about the types of blobs <a href=\"https:\/\/docs.microsoft.com\/en-us\/rest\/api\/storageservices\/fileservices\/understanding-block-blobs--append-blobs--and-page-blobs\">here<\/a>.<\/p>\n<p>Finally, to organize your data, you would want to create a container within your storage account. A storage account can contain any number of containers, and a container can contain any number of blobs, up to 500 TB capacity limit of the storage account. It is best practice to create a container for a specific purpose \u2013 such as \u201cphotos\u201d.<\/p>\n<p>You may be asking, \u201cif I have one container for my photos, can I store them in a folder structure to organize them?\u201d While containers only store blobs and there isn\u2019t a \u201cfolder structure\u201d per se, you can create a hierarchy by prepending path names. This short <a href=\"https:\/\/blogs.msdn.microsoft.com\/buckwoody\/2011\/07\/19\/windows-azure-storage-creating-a-hierarchy-view\/\">blog<\/a> post highlights the structure of accessing your blobs through the Blob Service REST API and shows how a hierarchy can be created. The major takeaway is to not use additional containers to do that \u2013 you\u2019d keep your one container, but add \u201cfolders\u201d in there for each blob.<\/p>\n<h2>Setting up storage account<\/h2>\n<p>So, let\u2019s get started. The first thing you\u2019ll want to do is to create your Storage Account. When choosing the \u201cAccount kind\u201d, you can choose either option. The General purpose will allow you to create any type of storage (blobs, files, tables, and queues) in a single account while the \u201cBlob storage\u201d is just for blobs, but allows you to choose the tier (Cool or Hot) based on access frequency.<\/p>\n<p>Once the storage account is created, you\u2019ll create your containers. Because we don\u2019t want to display full size images on each page that loads, we will scale the images and display those instead for better performance. You can then use any method you like to allow for clicking of an image on the page to display the full image. To set this up, we\u2019ll need two containers: one for full size images and one for the respective scaled images.<\/p>\n<p>Once the two containers are created, you have everything you need to start wiring things up. You\u2019ll need to remember the storage account name, the container names, and the account key. If you ever forget these things, you can easily find them within the Azure Portal. The Overview section will have the storage account name and blob containers, while the Access keys section will have your primary and secondary keys<\/p>\n<h2>Configuration<\/h2>\n<p>Before jumping into coding our solution, we first need to setup the configuration in order to easily access the storage account items within your code. Using the Configuration API, you can read values at runtime, allowing you to read our storage account values in the configuration settings (appsettings.json). <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/configuration\">This<\/a> article provides a great intro on how to set this up. You will first use the JSON configuration provider in the Startup.cs constructor as discussed in the article.<\/p>\n<p>To setup the configuration values, reference the values you captured when creating your storage account and containers in the \u201cSetting up storage account\u201d section earlier. In your appsettings.json file, include the following section:<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-35861\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob1.jpg\" alt=\"\" width=\"1028\" height=\"133\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob1.jpg 1028w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob1-300x39.jpg 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob1-768x99.jpg 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob1-1024x132.jpg 1024w\" sizes=\"(max-width: 1028px) 100vw, 1028px\" \/><\/p>\n<p>Then you will use the Options Pattern by creating an options model for the storage account settings:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image0032.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"clip_image003\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image003_thumb2.png\" alt=\"clip_image003\" width=\"644\" height=\"339\" border=\"0\" \/><\/a><\/p>\n<p>As discussed in the above article, the StorageAccountOptions class is added to the service container and bound to configuration.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-35862\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob3.jpg\" alt=\"\" width=\"1028\" height=\"664\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob3.jpg 1028w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob3-300x194.jpg 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob3-768x496.jpg 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob3-1024x661.jpg 1024w\" sizes=\"(max-width: 1028px) 100vw, 1028px\" \/><\/p>\n<p>In the controller, you then use dependency injection on IOptions&lt;TOptions&gt; to access the settings. This is stored as a member variable which can be used throughout the controller for ease of access:<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-35864\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob4.jpg\" alt=\"\" width=\"1028\" height=\"117\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob4.jpg 1028w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob4-300x34.jpg 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob4-768x87.jpg 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob4-1024x117.jpg 1024w\" sizes=\"(max-width: 1028px) 100vw, 1028px\" \/><\/p>\n<p>We\u2019re now ready to create our logic to insert images to Blob storage!<\/p>\n<h2>Creating a utility to interact with Blob storage<\/h2>\n<p>As seen in the previous section, the home controller\u2019s constructor creates the BlobUtility which can be used throughout the controller. This will encapsulate actions such as retrieving a list of blobs and blob directories, and uploading blobs.<\/p>\n<p>The BlobUtility constructor takes the account name and key, pulled from the Options configuration, making it easy to simply update the appsettings.json if the key changes or if you decide to use another storage account. Because in this case we\u2019re only using blob storage, the constructor connects to the storage account and creates a CloudBlobClient object which has access to the containers and blobs in the storage account.\n<img decoding=\"async\" class=\"alignnone size-full wp-image-35865\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob5.jpg\" alt=\"\" width=\"1028\" height=\"118\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob5.jpg 1028w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob5-300x34.jpg 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob5-768x88.jpg 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob5-1024x118.jpg 1024w\" sizes=\"(max-width: 1028px) 100vw, 1028px\" \/><\/p>\n<p>Now that the controller has a handle to the blob utility, your different actions on your page can then use the utility for various operations. As an example, I built my page to display house pictures taken from various dates throughout the construction phase. So, if you read the blog on how to create your hierarchy within your container, it will make sense that I\u2019ve stored my blobs as &lt;date&gt;\/&lt;image name&gt;.JPG. I can then implement simple operations using the CloudBlobClient to retrieve my blobs and handle them as I see fit. But first, we need a way to get the blobs in the containers.<\/p>\n<h2>Uploading Blobs<\/h2>\n<p>At the beginning of this post, I mentioned there would be two containers: one for the full images and one for scaled images. It would be pretty tedious to always have to scale an image and upload the original, full size image and the scaled image. So how can I make this more of an automatic process? Sure, you could put the images you\u2019re uploading into a queue and write a worker role to read from the queue, scale and upload the images, then delete the message upon successful completion, but there is a bit of code and maintenance involved. Introducing <em>Azure WebJobs<\/em>. What are WebJobs? Simply put, they enable you to run scripts or programs in the background and are scaled as part of Azure. Read more about it <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/app-service-web\/web-sites-create-web-jobs\">here<\/a> and also take a look at <a href=\"https:\/\/www.troyhunt.com\/azure-webjobs-are-awesome-and-you\/\">this<\/a> blog post for a bit more information.<\/p>\n<p>So how does a WebJob work? It\u2019s pretty simple actually. You set up a trigger on your container so when something is uploaded, your function is called. Since you will want this process to always monitor the container for uploads, when you create your WebJob you will schedule it as a continuously running task. When you run through the process of creating your WebJob, it will stub out a Functions class where you will create your trigger. As an example:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image0101.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"clip_image010\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image010_thumb1.png\" alt=\"clip_image010\" width=\"644\" height=\"145\" border=\"0\" \/><\/a><\/p>\n<p>This is saying, when a blob of {name} is uploaded to the images container, run some logic (SquishImage, which does what you\u2019d think \u2013 squishes the image input stream to a smaller scale and saves it to the output stream) and put that into the scaledimages container with the same {name}. You can put anything you want in the new name, but I chose to leave it the same so when a scaled image is clicked on in my page, I know the respective unscaled version is in the other container with the same blob name.<\/p>\n<p>Now we have a WebJob setup for when an image is uploaded, so now let\u2019s upload an image. It\u2019s actually pretty simple. Assuming you use a form to upload the image, you\u2019ll pass an IFormFile to the upload task in your controller along with the directory from the text field of the file selector.<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-35866\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob6.jpg\" alt=\"\" width=\"1028\" height=\"440\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob6.jpg 1028w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob6-300x128.jpg 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob6-768x329.jpg 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob6-1024x438.jpg 1024w\" sizes=\"(max-width: 1028px) 100vw, 1028px\" \/><\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image164.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb157.png\" alt=\"image\" width=\"644\" height=\"163\" border=\"0\" \/><\/a><\/p>\n<h2>Display images<\/h2>\n<p>Now that we have a way to upload images and get them into a scaled and full version format, we can display the scaled images as thumbnails on a page. Then, all that\u2019s left is getting a list of the images from your container to be displayed. So, in your Index action, you\u2019ll retrieve a list using the blob utility:<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-35867\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob7.jpg\" alt=\"\" width=\"1028\" height=\"407\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob7.jpg 1028w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob7-300x119.jpg 300w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob7-768x304.jpg 768w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob7-1024x405.jpg 1024w\" sizes=\"(max-width: 1028px) 100vw, 1028px\" \/><\/p>\n<p>The one caveat is we have hierarchies, so you\u2019ll need to see if you are looking at a CloudBlobDirectory or a CloudBlockBlob. Even though there are not truly directories, it will read your &lt;date&gt; part of the &lt;date&gt;\/&lt;image name&gt;.JPG as a CloudBlobDirectory. In your cshtml file, you\u2019d make a simple check if it\u2019s a CloudBlobDirectory:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image0151.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"clip_image015\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image015_thumb1.png\" alt=\"clip_image015\" width=\"644\" height=\"46\" border=\"0\" \/><\/a><\/p>\n<p>Or a CloudBlockBlob<\/p>\n<p><img decoding=\"async\" class=\"alignnone size-full wp-image-35868\" src=\"http:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob9.png\" alt=\"\" width=\"644\" height=\"45\" srcset=\"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob9.png 644w, https:\/\/devblogs.microsoft.com\/premier-developer\/wp-content\/uploads\/sites\/31\/2017\/03\/blob9-300x21.png 300w\" sizes=\"(max-width: 644px) 100vw, 644px\" \/><\/p>\n<p>If you determine it\u2019s a CloudBlockBlob, that means you have the image to display, so you would simply display the image using an &lt;img&gt; tag with the source being the items URL. However, if it\u2019s a CloudBlobDirectory, there\u2019s a little more work that needs to be done. Luckily, the SDK makes this pretty easy \u2013 you simply need to grab all blobs in that directory. What I did was make a ListDirectoryBlobs action in my controller and in my Index.cshtml, I create an ActionLink which displays the directory name from the CloudBlobDirectory object and clicking that link will pass that directory name to my ListDirectoryBlobs. From there, I can use the BlobUtility to get a list of blobs in that directory. The CloudBlobContainer class you\u2019ve been using allows you to get a reference to that directory:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image017.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"clip_image017\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/clip_image017_thumb.png\" alt=\"clip_image017\" width=\"1028\" height=\"57\" border=\"0\" \/><\/a><\/p>\n<p>Then you would use the ListBlobsSegmentedAsync method to initiate an asynchronous operation to return a result segment containing the blob items with this directory name. These blob items would then be passed back to the view and the same logic would execute in either displaying the image (if it\u2019s a CloudBlockBlob) or a link to the directory (if it\u2019s a CloudBlobDirectory).<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image165.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb158.png\" alt=\"image\" width=\"644\" height=\"419\" border=\"0\" \/><\/a><\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image166.png\"><img decoding=\"async\" style=\"padding-top: 0px; padding-left: 0px; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/31\/2019\/04\/image_thumb159.png\" alt=\"image\" width=\"644\" height=\"445\" border=\"0\" \/><\/a><\/p>\n<p>So that\u2019s it. It\u2019s short and sweet and quick and dirty. This obviously isn\u2019t anything super pretty, but it gets the concept across on how to use Azure Blob Storage to create a photo album, along with some powerful concepts such as WebJobs and how they work. From here, you can put your artistic touch on the front end and add a bit more complexity to make a more robust album.<\/p>\n<hr \/>\n<p><a href=\"https:\/\/blogs.msdn.com\/b\/premier_developer\/archive\/2014\/09\/15\/welcome.aspx\"><strong>Premier Support for Developers<\/strong><\/a> provides strategic technology guidance, critical support coverage, and a range of essential services to help teams optimize development lifecycles and improve software quality.\u00a0 Contact your Application Development Manager (ADM) or <a href=\"https:\/\/blogs.msdn.microsoft.com\/premier_developer\/contact-us\/\">email us<\/a> to learn more about what we can do for you.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post, Senior Application Development Manager, Chris Tjoumas builds a simple but powerful photo album using .NET Core, Azure Storage, and WebJobs. Introduction If you\u2019ve ever wanted to create a simple photo application to display an album of photos for each page, Azure Storage is a great technology to use. It is actually the [&hellip;]<\/p>\n","protected":false},"author":582,"featured_media":37840,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[25],"tags":[24,3],"class_list":["post-6425","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","tag-azure","tag-team"],"acf":[],"blog_post_summary":"<p>In this post, Senior Application Development Manager, Chris Tjoumas builds a simple but powerful photo album using .NET Core, Azure Storage, and WebJobs. Introduction If you\u2019ve ever wanted to create a simple photo application to display an album of photos for each page, Azure Storage is a great technology to use. It is actually the [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/6425","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/comments?post=6425"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/posts\/6425\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media\/37840"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/media?parent=6425"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/categories?post=6425"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/premier-developer\/wp-json\/wp\/v2\/tags?post=6425"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}