﻿<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Genre Of: Blog</title>
  <id>https://blog.genreof.com/</id>
  <subtitle>Braindump for encountered code mysteries and solutions</subtitle>
  <generator uri="https://github.com/madskristensen/genreof_blog" version="1.0">genreof_blog</generator>
  <updated>2020-12-21T05:01:15Z</updated>
  <entry>
    <id>https://blog.genreof.com/blog/f-mods/</id>
    <title>Font Metrics Override Descriptors</title>
    <updated>Mon, 21 Dec 2020 05:01:15 GMT</updated>
    <published>Mon, 21 Dec 2020 05:01:15 GMT</published>
    <link href="https://blog.genreof.com/blog/f-mods/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="seo" />
    <category term="pagespeed insights" />
    <category term="css" />
    <category term="lighthouse" />
    <content type="html">&lt;p&gt;As mentioned by my previous blog post, fallback fonts can have a significant impact on the CLS portion of the Web Vitals score. Your main font is a web font from somewhere like Google fonts, but browsers may take some time to fully load it so the page does the initial render with a fallback font and then swap in the web font once it is ready. The closer your fallback font is to the web font, the less the page will shift and the lower the impact on your CLS score. You may have selected one fallback font because it most closely resembles your web font, but something about it isn't quite identical and you see a visible shift during testing. The first tool to try is using @font-face and adjusting some of the CSS properties such as letter spacing, but it will only get you so far because there are some elements of the fallback font which can't be changed with existing CSS. That's where the horribly named font metrics override descriptors (or f-mods) come in.&lt;/p&gt;
&lt;p&gt;F-mods are a set of @font-face descriptors &lt;a href="https://chromestatus.com/feature/5651198621253632" target="_blank" rel="noopener"&gt;added in Chrome 87&lt;/a&gt; which allow changing some of the subtle font differences to try and make the fallback font hold the same space as the web font. I am not a font expert by any means, so I apologize if this could be more accurately described with better terminology. I would highly recommend watching one of the videos from Chrome Dev Summit 2020, &lt;a href="https://youtu.be/Z6wjUOSh9Tk?t=176"&gt;Beyond Fast by Jake Archibald&lt;/a&gt;, which has an excellent brief segment on these.&lt;/p&gt;
&lt;p&gt;Ascent-override - specifies the amount of the font which appears above the baseline.&lt;/p&gt;
&lt;p&gt;Descent-override - specifies the amount of the font which appears below the baseline.&lt;/p&gt;
&lt;p&gt;Line-gap-override - specifies the amount of whitespace gaps at the top and bottom of each line.&lt;/p&gt;
&lt;p&gt;Good stuff, but only supported by Chrome 87 at the moment. These will be ignored by other browsers, so there's no harm in using them now. You get the benefits for those users now so that, most importantly, it will be reflected in the CLS CrUX field data Google will increasingly be using as part of their ranking algorithm. With how new these are, there isn't much information about them yet, and no helpful tools. I needed to develop something to help figure out what values to use for these new bits, which I've made public in Github. It will let you feed in a web font URL and a named fallback font, overlaying the two fonts to help align them. My use case was Google fonts, so the Javascript may need to be adjusted if you're using something else which delivers the actual font file rather than a stylesheet with @font-face values, as Google fonts do. Hope this helps!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/smlync/FmodsHelper/blob/main/FmodsHelper.htm"&gt;F-mods alignment helper&lt;/a&gt;&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/googlebot-fonts/</id>
    <title>Probing for Googlebot Font Support</title>
    <updated>Mon, 14 Sep 2020 21:24:17 GMT</updated>
    <published>Mon, 14 Sep 2020 20:52:30 GMT</published>
    <link href="https://blog.genreof.com/blog/googlebot-fonts/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="seo" />
    <category term="pagespeed insights" />
    <category term="css" />
    <category term="lighthouse" />
    <content type="html">&lt;p&gt;Wow, it's been a long time since I posted anything. Nothing like having kids to keep you from blogging.&lt;/p&gt;
&lt;p&gt;One of the things I keep an eye on for work are the Google Lighthouse scores. The metrics used to calculate the performance score began shifting in v6 to &lt;a href="https://web.dev/vitals/" target="_blank" rel="noopener"&gt;Core Web Vitals&lt;/a&gt;. Pagespeed Insights uses Lighthouse for this score as well, which will be important later in the post. &lt;a href="https://developers.google.com/speed/pagespeed/insights" target="_blank" rel="noopener"&gt;Pagespeed Insights&lt;/a&gt; gives a good window into the Chrome User Experience Report, so I like to check that regularly as well. The Core Web Vitals switch seems like a good one to me, the metrics are more transparent and seem to have less variance between runs.&lt;/p&gt;
&lt;p&gt;We ran into an interesting issue with the CLS, Content Layout Shift, portion of Core Web Vitals on some of our pages. We would run Lighthouse locally in Chrome and see a CLS of bascially zero on all network conditions. We would then run it from Pagespeed Insights against the same page and see a failing score of greater than 0.1. The culprit ended up being the fallback fonts set in our font-family delaration. It had probably been at least 20 years since we thought about it, other than adding a Google font to the font of it a few years ago. We had the fallback font installed locally, but it turned out Googlebot didn't have it. Googlebot fell back to the next font in the list. The next font in the list was not a great alternative to the first two, in hindsight. The browser initially renders text with one of the fallback fonts under the Lighthouse throttling conditions, and then swaps in the Google font once it finishes downloading. The offending font in the list had much different spacing and letter widths than either the Google font or our first fallback font, so some text wrapped in spots for Googlebot and resulted in a CLS shift when the Google font finished loading and swapped in.&lt;/p&gt;
&lt;p&gt;The most interesting part of this is figuring out what fonts Googlebot supports. You can't just change the user agent in Chrome, as that will still use all of the fonts from your machine. I wasn't able to find anywhere Google has published a list of fonts installed on thier Googlebot VMs. So there are a couple of options I could think of. Either way required publishing something public facing.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a page with some client side code which detects the Googlebot user agent, attempts to display fonts, and logs the results somewhere. Could maybe trigger this by doing a live inspection from Google search console.&lt;/li&gt;
&lt;li&gt;Create a page with the fonts in question and then run it through Pagespeed Insights and view the resulting screenshot.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Using the Pagespeed Insights method seemed much easier, and the screenshot seemed like a much better way of seeing the results. From the original page, I was curious about what web safe fonts are supported by Googlebot, since our initial fallback font was supposedly web safe. I built a page to try each of the fonts in the W3Schools list of &lt;a href="CSS%20Web Safe Font Combinations" target="_blank" rel="noopener"&gt;CSS Web Safe Fonts&lt;/a&gt; that could be run through Pagespeed Insights where they would all be visible. An image of the result is below, along with links to the test page and to run the test page through PageSpeed Insights.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.genreof.com/Home/GooglebotFonts" target="_blank" rel="noopener"&gt;Googlebot installed fonts test page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developers.google.com/speed/pagespeed/insights/?url=https%3A%2F%2Fwww.genreof.com%2FHome%2FGooglebotFonts&amp;amp;tab=mobile" target="_blank" rel="noopener"&gt;Run PageSpeed Insights against page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="data:image/jpeg;base64,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" alt="GooglebotFonts.jpg" width="280" height="498" data-filename="GooglebotFonts.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;San-serif is used as the fallback for the serif fonts, which are the first 6. Conversely, serif was the fallback for the remaining fonts, which are sans serif fonts. Maybe that's a little weird, but I wanted an obvious visual cue. It's quite surprising which were supported and how many were not. Only half of the serif fonts were supported, and less than half of the sans serif were. (Comic Sans MS? You've gotta be kidding me Googlebot!) Hope this helps someone else!&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/custom-environments-in-aspnet-core-20/</id>
    <title>Custom Environments in ASP.Net Core 2.0</title>
    <updated>Mon, 26 Mar 2018 22:15:24 GMT</updated>
    <published>Mon, 26 Mar 2018 22:13:00 GMT</published>
    <link href="https://blog.genreof.com/blog/custom-environments-in-aspnet-core-20/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="ASP.Net Core" />
    <content type="html">&lt;p&gt;The Development, Staging and Release environments are good enough for most projects. But what if you have one of the situations where you need more? &lt;/p&gt; &lt;p&gt;One of the solutions I work with has some fun requirements and adding more environments allows us to easily cope for additional scenarios. The &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments"&gt;documentation&lt;/a&gt; Microsoft has put together for this is actually quite good and shows much of the flexibility for it. Too good of a job, actually, it’s a little overwhelming. So this is as much to help me remember how to do this in the future as anything.&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Add the additional environments to launchSettings.json. These will now show as options in the box next to the play arrow in Visual Studio. We have multiple dev and release scenarios, so we’ll add DBG-X, DBG-Y, REL-X, and REL-Y.&lt;br&gt;&lt;/li&gt; &lt;p&gt;"DBG-X": {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "commandName": "IISExpress",&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "launchBrowser": true,&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "environmentVariables": {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; "ASPNETCORE_ENVIRONMENT": "DBG-X"&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; },&lt;/p&gt; &lt;li&gt;Add appsettings.json files for each of the new environments, such as appsettings.DBG-X.json. The call in Program.cs to WebHost.CreateDefaultBuilder() will now pick up the respective version for each environment. And it even magically knows to nest these under appsettings.json in Visual Studio.&lt;/li&gt; &lt;li&gt;Since we have multiple development environments (DBG-X and DBG-Y) and are no longer using the Development environment, the condition in Startup.Configure using env.IsDevelopment will no longer work. We need a custom version. ASP.Net Core gives multiple ways of doing this, so you could create custom versions of Startup.cs, Configure(), or ConfigureServices() for each environment if you need to. That’s overkill for me at the moment, so I’ll just check if the environment name starts with “DBG-“.&lt;br&gt;if(env.EnvironmentName.StartsWith("DBG-")) {…&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;And that’s really it. Hope this helps!&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/azure-media-services–indexing-existing-videos/</id>
    <title>Azure Media Services–Indexing Existing Videos</title>
    <updated>Fri, 03 Mar 2017 18:40:47 GMT</updated>
    <published>Fri, 03 Mar 2017 18:33:00 GMT</published>
    <link href="https://blog.genreof.com/blog/azure-media-services–indexing-existing-videos/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="Azure" />
    <category term="HTML5" />
    <category term="C#" />
    <category term="Azure Media Services" />
    <content type="html">&lt;p&gt;At my current employer, we moved all of our videos to Azure using Azure Media Services a little under 2 years ago. This allowed us to upgrade them from using a Flash player to an HTML5 based video system, using the Azure Media Player for playback. I can’t say enough good things about using Azure for this. The videos went from looking mediocre and generally only playing on desktop machines to looking crystal clear at multiple resolutions, playable on every desktop and mobile device we throw at it.&lt;/p&gt; &lt;p&gt;We’ve now circled back to fill in a gap we missed at that point in time, captions. (Or subtitles, if you prefer.) Videos without captioning or subtitles excludes a portion of users, and that’s not cool. Since we’re already using Media Services for the video encoding, it made sense to use the Azure Media Indexer to generate the captions for us. However, most of the examples out there around doing this seem to be targeted at doing the indexing when you upload a video. We are certainly doing that moving forward, but there were a significant number of videos already out there which needed to be processed and that doesn’t seem to be a well documented scenario. Hopefully I can fill in that gap a little with this post.&lt;/p&gt; &lt;p&gt;First thing, start with the upload scenario from this link:&lt;br&gt;&lt;a title="https://docs.microsoft.com/en-us/azure/media-services/media-services-index-content" href="https://docs.microsoft.com/en-us/azure/media-services/media-services-index-content"&gt;https://docs.microsoft.com/en-us/azure/media-services/media-services-index-content&lt;/a&gt;&lt;/p&gt; &lt;p&gt;That will get you most of the way, but there are a couple changes when using existing files. The first change is load the existing video Asset using the Asset ID. Replace the function called CreateAssetAndUploadSingleFile with one which looks something like this:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;static IAsset LoadExistingAsset(string AssetId)&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var matchingAssets = (from a in _context.Assets&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; where a.Id.Equals(AssetId)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; select a);&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; IAsset asset = null;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; foreach (IAsset ia in matchingAssets)&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; asset = ia;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;/p&gt; &lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return asset;&lt;br&gt;}&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;You’ll need to know the Asset Id for the video. Hopefully you’ve been storing those someplace as you’ve encoded videos; we had them in SQL Azure so I pulled them back from there. If you don’t have them, playing around with the LINQ on _context.Assets will probably return them in some way. I haven’t needed to do that myself.&lt;/p&gt; &lt;p&gt;Now that you have a reference to the video asset, you can work your way down the code in RunIndexingJob and update a few things. I would recommend renaming the job to something which uniquely identifies the video, as that will show up in the Jobs section of the media services account in the Azure portal. If it fails, it makes it much easier to figure out which one to redo. Same thing with the indexing task and output asset name, renaming them makes them easier to track in the logs. For the configuration file, follow the &lt;a href="https://msdn.microsoft.com/library/dn783454.aspx"&gt;Task Preset for Media Indexer&lt;/a&gt; link and load the file from wherever seems appropriate to you. I put some string placeholders into the config file for the metadata fields, which I’m replacing with some data pulled from the same database I’m getting the Asset ID from. So that section for me looks like:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;&amp;lt;input&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;metadata key="title" value="{0}" /&amp;gt;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lt;metadata key="description" value="{1}" /&amp;gt;&lt;br&gt;&amp;nbsp; &amp;lt;/input&amp;gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;That should get you through RunIndexingJob. This is where the examples really fell flat for me. There are some additional steps required now. I changed RunIndexingJob to return the output media asset, as the caption files now have a different Asset ID than the video. Since Azure Blob Storage underpins Media Services, the Asset ID is actually the blob container name as well. Since the files the indexer generated have a different Asset ID, it means they’re actually in a different container than the video. This is important for actually consuming the captioning file. So rather than returning true like the example code, mine returns job.OutputMediaAssets[0]. There are three steps left to actually be able to use the caption files.&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Publish the caption asset.  &lt;li&gt;Change the blob storage permissions on the Asset ID. (Remember the Asset ID is the same as the blob container name.)  &lt;li&gt;Save the path to the published caption file in blob storage.&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;Publish the Caption Asset&lt;/h3&gt; &lt;p&gt;This is really easy, and very similar to publishing the video files after encoding. From code which calls RunIndexingJob:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;var asset = RunIndexingJob(AssetId);&lt;br&gt;ILocator loc = PublishAsset(asset);&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;The definition for PublishAsset looks something like so:&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;static ILocator PublishAsset(IAsset asset)&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; var locator = _context.Locators.Create(LocatorType.Sas, asset, AccessPermissions.Read, TimeSpan.FromDays(35600));&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return locator;&lt;br&gt;}&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;The major difference between the video publish and this is the different LocatorType. Using Sas creates a Progressive download locator, whereas OnDemandOrigin creates a streaming locator. If you use the latter, it won’t work. You return the locator back as it has the URL to the container, which will be helpful for the next step.&lt;/p&gt; &lt;h3&gt;Change Blob Storage Permissions&lt;/h3&gt; &lt;p&gt;Now that the Asset is published, the blob container is out there and available, but requires a SAS token to access it. If that’s what you want, skip this step. I want it to be available publicly, however, so the blob container permissions need a quick update. Since the Asset ID is the same as the blob container name, we’ll use the blob storage API to alter this.&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;var videoBlobStorageAcct = CloudStorageAccount.Parse(_blobConnStr);&lt;br&gt;CloudBlobClient videoBlobStorage = videoBlobStorageAcct.CreateCloudBlobClient();&lt;br&gt;string destinationContainerName = (new Uri(loc.Path)).Segments[1];&lt;br&gt;CloudBlobContainer assetContainer = videoBlobStorage.GetContainerReference(destinationContainerName);&lt;/p&gt; &lt;p&gt;if (assetContainer.Exists()) // This should always be true&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; assetContainer.SetPermissions(new BlobContainerPermissions&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; PublicAccess = BlobContainerPublicAccessType.Blob&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; });&lt;br&gt;}&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;I’m setting up the blob container there and grabbing the container name from the locator just to be safe. Then it gets the reference and sets the container access to blob. No more SAS required to get the captions!&lt;/p&gt; &lt;h3&gt;Save the path to the caption file&lt;/h3&gt; &lt;p&gt;The last thing to do now is build the path to the caption file or files and save them so they can be retrieved and used by the player. I’m only generating the WebVTT format, since I’m only concerned with playing the videos via a website.&lt;/p&gt; &lt;blockquote&gt; &lt;p&gt;string PublishUrl = loc.BaseUri;&lt;br&gt;string vttFileName = "";&lt;/p&gt; &lt;p&gt;// Loop through the files in the asset to find the vtt file&lt;br&gt;foreach (IAssetFile file in asset.AssetFiles)&lt;br&gt;{&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (file.Name.EndsWith(".vtt"))&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; vttFileName = file.Name;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; break;&lt;br&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br&gt;}&lt;/p&gt; &lt;p&gt;string captionUrl = PublishUrl + "/" + vttFileName;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Now you save the value in captionUrl and you’re good to go! One small additional note which I was stuck on for a little while. If you’re consuming the caption file from a different domain, which is almost guaranteed (unless you’re running your site from static files on blob storage), you’ll need to change the CORS settings for the blob storage account being used by Media Services. The easiest way I’ve found to set this is to use the Azure portal. Browse to the storage account being used via the storage blade and not the media services blade. The storage blade has a nice UI which lets you whip through it in a few seconds. Hope this helps!&lt;/p&gt; &lt;p&gt;(This post refers to Azure Media Indexer v1. At the time of writing, v2 was in preview.)&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/the-end-of-modern-delicious/</id>
    <title>The End of Modern Delicious?</title>
    <updated>Sun, 15 May 2016 23:48:56 GMT</updated>
    <published>Sun, 15 May 2016 23:48:56 GMT</published>
    <link href="https://blog.genreof.com/blog/the-end-of-modern-delicious/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <content type="html">&lt;p&gt;My original reason for writing the &lt;a href="https://www.microsoft.com/en-us/store/apps/modern-delicious/9wzdncrdcfjw" target="_blank"&gt;Modern Delicious app&lt;/a&gt; for Windows was so I could have a nice way to read through links I had saved on Windows 8. I basically wanted a reading list from my recent Delicious links. I wrote and released the app, but of course soon after, the &lt;a href="https://www.microsoft.com/en-us/store/apps/windows-reading-list/9wzdncrfj3rx" target="_blank"&gt;Reading List app&lt;/a&gt; for Windows was announced. My usage scenario was catered for, and there didn’t seem to be a reason for the app to exist any longer. There had been some &lt;a href="http://www.hanselman.com/blog/ScottHanselmans2014UltimateDeveloperAndPowerUsersToolListForWindows.aspx" target="_blank"&gt;very kind feedback&lt;/a&gt; from several people, however, so I continued development on the app as I had time. This feedback was the reason there were updates to the app for Windows 8.1 and 10, features like tag suggestions were added, and even a Windows Phone 8.1 version happened. Discovering the intracacies of developing for the Windows Store has been an incredibly interesting learning experience. One point in all this which is key – I am only a third party developer and have no affiliation with Delicious or any of the companies which have owned it.&lt;/p&gt; &lt;p&gt;For the last two months, part of the API provided by Delicious has not been functioning correctly. Most of the API is working perfectly, except for one method. This one method happens to be the key one which allows the app to retrieve each person’s entire history of posts (or bookmarks, if you prefer). Without it, the best the app can do is to get the last 100 or so posts for each user. As I’ve learned throughout the course of developing this app, many of the users of Modern Delicious are longtime Delicious users, with an extensive bookmark history. Only being able to view the last 100 posts renders the app effectively useless for these users. I’ve tried to contact Delicious through several different methods: email, &lt;a href="https://twitter.com/slynch78/status/709077708790075393" target="_blank"&gt;Twitter&lt;/a&gt;, and their &lt;a href="https://github.com/domainersuitedev/delicious-api/issues/48" target="_blank"&gt;Github repo&lt;/a&gt;. They’ve been unresponsive thus far, and I cannot allow the app to continue being downloaded and frustrating users who legitimately just want a functional Delicious client app for Windows 10. To that end, and with great sadness, I’ve removed Modern Delicious from the Windows Store. &lt;/p&gt; &lt;p&gt;This is the danger of developing against a third-party API such as the one provided by Delicious. At any time, the company controlling the API can do whatever they want with it. I have no ill will nor any hard feelings towards Delicious for breaking their API; it’s entirely possible they had legitimate engineering reasons for the breaking change. I’m incredibly grateful to Delicious and all of the engineers who designed and maintained the API over the last decade, it’s been a tremendous development resource for many developers. It would have been great if there had been some warning about impending changes potentially breaking the API, or some notice of the API being retired, which would have allowed myself and others to give some warning to our app users. But ultimately, it’s their API and they can break it as they please in order to do what is needed for their company.&lt;/p&gt; &lt;p&gt;To the many Modern Delicious users over the last few years: thank you for you feedback and continued usage. If Delicious fixes the issue, I will restore the app to the Windows Store with any updates required by API changes. Delicious has a functional API of some kind out there, as their official apps for iOS and Android both remain functional. Either they have not made the details of using the API those apps use public, or I am not clever enough to figure it out. Either way, I feel like I’ve let you all down.&lt;/p&gt; &lt;p&gt;To Delicious: it would be very nice of you to repair the API or publish the details of the API your iOS and Android apps use. You have a long history of providing a wonderful, free service while struggling to find a path to profitability. The website seems adrift after the last changes were released, and I sincerely hope it doesn’t mean the end of Delicious is near. And once again, I continue to extend the same offer I have made many times before: I will gladly give you the app for free so you can have an official Windows Store app. (The Windows 10 version is even a UWP app!)&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/visual-studio-team-services-build-and-release-management/</id>
    <title>Visual Studio Team Services Build and Release Management with Multiple Configurations</title>
    <updated>Thu, 01 Feb 2018 16:42:00 GMT</updated>
    <published>Thu, 21 Jan 2016 21:49:42 GMT</published>
    <link href="https://blog.genreof.com/blog/visual-studio-team-services-build-and-release-management/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="VSO" />
    <category term="VSTS" />
    <category term="Azure" />
    <content type="html">One of my most frequent issues when releasing to Azure websites using the old build system from VSTS/VSO is the deployment fails. By old build system, I mean the continuous deployment magic you set up from the Azure portal for an Azure website (now App Service). The worst is on a good sized solution I work on which takes about 20 minutes to build. So I wait the 20 minutes, only for it to fail for reasons beyond my control on the deployment step with errors like "invalid thumbprint" or "intermittent communications issues." Despite the build succeeding, I'm left kicking a new build and waiting 20 minutes just for the deployment. So I was really excited to see the new Build and Release Management system in VSTS/VSO where it looks that option is possible, retrying just that deployment step. On the solution with the 20 minute build time, there are also four different configurations. (This means there's potentially 80 minutes of wasted build time if the deploy fails for all.) The new build and release system also looks to have a multi-configuration option where I can build all four at once. &lt;br /&gt;&lt;br /&gt;So diving into the main &lt;a href="https://msdn.microsoft.com/Library/vs/alm/Release/getting-started/deploy-to-azure"&gt;getting started tutorial&lt;/a&gt;, it looks very promising. First thing is to set up the build. I'm following the tutorial exactly as shown by creating a Visual Studio build, but with three small additional steps in order to cope with building all four of my configurations. On the variables tab, I replaced the existing build configuration values with my four configurations, separating each with a comma. If you save and queue a build at this point, it will fail, as it treats the value as a single configuration.&lt;br /&gt;&lt;img src="/posts/files/efe2b9f1-7cbe-4727-841a-e0b7b72d74eb.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;The next piece for multiple configurations is on the &lt;strike&gt;Options tab&lt;/strike&gt;. (This has changed, see edit under the image below.) Put a check next to MultiConfiguration, and set Multipliers to "BuildConfiguration". You can optionally choose to check the options below, as I have, to potentially build in parallel and continue with other build configurations if one fails. &lt;br /&gt;&lt;img src="/posts/files/63af6abd-f450-44a1-8132-208a9741ad0b.png" alt="" /&gt;&lt;br /&gt;*Edit 2018-02-01: Microsoft moved this when multi-phase builds released. This can be enabled now in the Tasks tab by selecting the Phase. I believe this defaults to Phase 1 for both new and converted legacy builds. It's the third in the list and not obvious that it can be clicked, under Process and Get Sources. From there, it's under Execution Plan &amp;gt; Parallelism. The Multipliers field is still the same, but there's now an additional required field, Maximum Number of Agents. Using 1 will force each configuration to finish building before the next one begins, but you can increase it and it might build in parallel.&lt;br /&gt;&lt;br /&gt;The final piece is on the Build tab, selecting the Copy and Publish Build Artifacts step. If you were to build now, it would build all four configurations, but drop all four zip files in the same directory, which isn't going to work since they'll all have the same name. My solution is to put each in their own folder, naming the folder for the configuration. This is very easy to do, just add "\$(BuildConfiguration)" (without quotes) after the artifact name in the Artifact Name textbox. Now it's safe to save and queue a build!&lt;br /&gt;&lt;img src="/posts/files/ae82a866-fde1-4a94-94b5-dc8fd1f2d6a6.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;Interestingly, my build times decreased to around 14-15 minutes for each configuration. I'm pretty sure the deployment step wasn't taking 5 minutes on the old setup, but regardless, I like anything which makes the build faster!&lt;br /&gt;&lt;br /&gt;If you switch over to the Release tab from the top row now, I've followed the tutorial down to the step where you create the release definition. I've created an Azure Website Deployment, disabled the test step, and renamed the environment to match one of my build configurations. I have four configurations, so I'll need four environments. I'll come back and add those in after I get the first one right. Continuing on the tutorial, I also linked to the build definition I created earlier. Once that's done, you can see the artifact name from your build in the Artifacts tab. Back on the Environments tab, we need to line up the configuration name for the release. Click on the three dots to the right of the environment name and select Configure Variables. Next to Release Configuration, I'll set the value to match the first entry in the build configuration list in the first image above, REL-IE. &lt;br /&gt;&lt;img src="/posts/files/19fd353f-26f8-4173-85b8-aec27f8a96f3.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;Hit OK to save and return to the environments. There is a slight adjustment needed to the Web Deploy Package value in order to grab the correct deployment package for the current configuration. The default value of "$(System.DefaultWorkingDirectory)\**\*.zip" will grab all of the zip files in the build artifacts location. I have four configurations, so there are four zip files, each within a folder named for their build configuration. Using a syntax similar to the build step, modify this to look for this configuration folder using the ReleaseConfiguration variable we just set. This will make the value "$(System.DefaultWorkingDirectory)\**\$(ReleaseConfiguration)\**\*.zip". I have a staging slot set up for this web site/app service, so I've also set variables for the release to go to that slot as well.&lt;br /&gt;&lt;img src="/posts/files/02561b43-cb66-4855-ae5a-cb8ba897bf0f.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;Check the tutorial again for additional steps for continuous deployment and such. Otherwise, save and run a release - it should be good to go! The first time I tried mine, it failed for no logical reason, much like the old release system. Retried it and it worked, proving that I now have an improved way to release. Now we need to copy the existing environment over for the remaining three build configurations. The easiest way to do this is by clicking the three dots to the right of the environment name, and select Clone Environment. It will copy everything from the existing environment and let you change the name right away. The two things which need to updated now will be the ReleaseConfiguration variable value and the Web App Name for each. (If your sites are in different data centers, you may need to update the Web App Location value as well.) So I cloned the first configuration three times, updated those values, and saved, resulting in something like this:&lt;br /&gt;&lt;img src="/posts/files/ef1429f3-bccb-4c60-9f63-ba448b5693fc.png" alt="" /&gt;&lt;br /&gt;&lt;br /&gt;Now select the release option from the menu, select the build which you queued earlier (and is hopefully done by now), and select the sites/configurations you want to deploy from left to right. My one complaint at this point is you cannot pick and choose which of the environments to deploy, you have to select them in order from left to right. Hopefully that option is added in the future. After selecting all of the configurations, mine worked on the first try, taking a little over 8 minutes.&lt;br /&gt;&lt;br /&gt;To workaround the drawback around selecting a release out of order I mentioned above, I've also created individual releases for each of my build configurations in the event I need to deploy just one or two of them. All in all though, I'm really happy with how this turned out. While Release Management is still technically in preview, I'll be using this in production as it's such a significant improvement in my current release experience.&lt;br /&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/sending-a-push-notification-using-azure-mobile-services-javascript-api/</id>
    <title>Sending a Push Notification Using Azure Mobile Services Javascript API</title>
    <updated>Fri, 10 Jul 2015 00:00:16 GMT</updated>
    <published>Thu, 09 Jul 2015 23:47:00 GMT</published>
    <link href="https://blog.genreof.com/blog/sending-a-push-notification-using-azure-mobile-services-javascript-api/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="Azure Mobile Services" />
    <category term="C#" />
    <category term="Push Notifications" />
    <category term="iOS" />
    <category term="Node.js" />
    <content type="html">&lt;p&gt;Extremely easy to do, but extremely easy to mess up. I’m assuming with this post you have already configured Azure Mobile Services with your certificates. If not, there are plenty of tutorials around which can help you through the process. (I personally like &lt;a href="https://azure.microsoft.com/en-us/documentation/articles/partner-xamarin-mobile-services-ios-get-started-push/" target="_blank"&gt;this one for APNS&lt;/a&gt;.) &lt;/p&gt; &lt;p&gt;The classic Mobile Services examples all involve the Todo List app, which expect you to be using the full Mobile Services backend. It’s certainly slick if you can, but what if you only need the push notification capabilities? This is the problem I was trying to solve. The data for my mobile application is already managed elsewhere, I only need Mobile Services to manage the push subscriptions for my end users. The Javascript API portion of Mobile Services will be called from my backend and by some other applications using C# to generate the push notifications. It’s maybe not a common scenario, but it’s certainly there to allow you to do it. No amount of Google/Bing attempts landed me on a nice tutorial which satisfied my questions, so I’ll do my best.&lt;/p&gt; &lt;p&gt;Go to the API tab in your mobile service in the portal, and either click on the Create option at the bottom or click the big message in the center of the screen. Enter the name you want for the API, I choose Godzilla. (There was a marathon on El Rey last weekend, so I have Godzilla on the brain still.) Set the app permissions appropriately for your API, I’ll to stick with “Anybody with the Application Key” for the example and click the Checkmark button.&lt;/p&gt; &lt;p&gt;&lt;a href="https://genreofblog.azurewebsites.net/posts/files/57b6e3da-e1a1-4d53-b744-b43d461c4a52.png"&gt;&lt;img title="Screenshot 2015-07-09 15.39.09" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="Screenshot 2015-07-09 15.39.09" src="https://genreofblog.azurewebsites.net/posts/files/896b2eff-94d9-4b69-a92e-0022c9ac8565.png" width="563" height="484"&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Once it’s done creating the API, click on it to view the code. You’ll have the starter API with a Post and a Get.&lt;/p&gt; &lt;p&gt;There’s no need for the Get, so delete it. The Post has a hint of what you need to use on the third line of comments, “var push =…”. Let’s uncomment that line so we can use it to generate the push. (I’m only going to write the code to send pushes to iOS (APNS) to keep the example nice and simple, the code for Android should be fairly trivial to add.) After the line with “var push = …”, paste in the following code over the remaining body of Post.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-javascript"&gt;push.apns.send("atombomb", {
        alert: request.body.msg       
    },{ 
        success: function(result){
            //console.log("Godzilla push success");
       }, error: function(error){
           console.log("Godzilla push failed, error: " + error);
       }
    });
    response.send(statusCodes.OK, "{msg: 'Push executed, check log for results'}");&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So what is this doing? It will push a notification to only the users subscribed to the “atombomb” tag. If you want to push to all users who have allowed push notifications for your app, change that parameter to null, without quotes around null. The next parameter is the JSON content which will be the contents of the push notification. (&lt;a href="https://msdn.microsoft.com/en-us/library/azure/jj839711.aspx" target="_blank"&gt;Here’s a great reference for more options you can put in it&lt;/a&gt;.) You see many other examples where they only use the payload property, which is certainly nice if you need to send some data with the push, but won’t actually pop a message on the user’s device. Alert is what you need to pop a message up, and the contents of the Alert parameter get displayed on the top of the user’s screen when it is received.&lt;/p&gt;
&lt;p&gt;Quick side tangent. If you only provide the Payload property, as I did at first, it’s hugely confusing why there isn’t a notification popping up on the device. The portal will show the push succeeds, but nothing shows up on the device. The reference link above was the light bulb moment when I realized what I had done wrong. If there is no alert property as part of the content parameter, the user will not be notified in any way. You only want to use Payload by itself when you want to push an update in the background, without the user being prompted. You can certainly use both Alert and Payload at the same time, however.&lt;/p&gt;
&lt;p&gt;On my alert parameter, you can see I’m actually sending in the content as part of the body of the API call. If you know your push message will always be the same, you can change this out for a string right there, but I think a parameter from the body makes for a better real world example. The final parameter contains the success and error functions, which are really useful for debugging. They will write to the mobile service log using console.log, which I’ll get to next. So here’s the final result:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://genreofblog.azurewebsites.net/posts/files/05d79b8b-2cb3-4ea9-b24d-c76ad17a7bc1.png"&gt;&lt;img title="Screenshot 2015-07-09 16.22.22" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="Screenshot 2015-07-09 16.22.22" src="https://genreofblog.azurewebsites.net/posts/files/8ccb3171-4ce5-4279-b340-c374fa1ea9a3.png" width="1028" height="533"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also edited the response message, but you can leave it as the default if you like. Now click the save button at the bottom to save your changes. If you want to check the logs once you have called your new API, click the back arrow after saving and it’s the last option in the top menu. You may need to uncomment the body of the success function to get anything to appear there, if it all works right away.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://genreofblog.azurewebsites.net/posts/files/1dc2192d-f6f3-4cd0-b97d-69f81c6ecc83.png"&gt;&lt;img title="Screenshot 2015-07-09 16.24.33" style="border-left-width: 0px; border-right-width: 0px; background-image: none; border-bottom-width: 0px; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-top-width: 0px" border="0" alt="Screenshot 2015-07-09 16.24.33" src="https://genreofblog.azurewebsites.net/posts/files/ddee3763-a40e-4808-9a91-cedc19e630bb.png" width="1028" height="123"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So now we need to call our API. This is when Mobile Services really flexes its muscles, it’s so incredibly simple. I’ve got a fairly reusable method for this which lets me pass in the API I want to call, and the message I want to send. So when invoking the method below, I use the parameters “Godzilla” for Api and “I knew that tuna-eating monster was useless!” for Message. If you’ve swapped out the message in the body for a string as I mentioned earlier, it gets even easier as you can then remove two lines which deal with the body variable.&lt;/p&gt;&lt;pre&gt;&lt;code class="language-csharp"&gt;public async Task&lt;jtoken&gt; SendPushNotification(string Api, string Message)
        {
            JObject body = new JObject();
            body.Add("msg", Message);
	    MobileServiceClient client = new MobileServiceClient(appUrl, appKey);
            return await client.InvokeApiAsync(Api, body);
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can go much further with that method if you want, such as passing in your own custom object rather than using a JObject. If you’re looking to do something like that, I would recommend reading “&lt;a href="http://blogs.msdn.com/b/carlosfigueira/archive/2013/06/19/custom-api-in-azure-mobile-services-client-sdks.aspx" target="_blank"&gt;Custom API in Mobile Services&lt;/a&gt;,” it goes nicely into a scenario like that, and includes a little bit on handling authenticated users.&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/unsubscribing-or-unregistering-from-azure-mobile-service-push-notifications-from-xamarin-ios/</id>
    <title>Unsubscribing or Unregistering from Azure Mobile Service Push Notifications from Xamarin iOS</title>
    <updated>Sun, 05 Jul 2015 00:51:21 GMT</updated>
    <published>Sun, 05 Jul 2015 00:51:21 GMT</published>
    <link href="https://blog.genreof.com/blog/unsubscribing-or-unregistering-from-azure-mobile-service-push-notifications-from-xamarin-ios/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="Xamarin" />
    <category term="iOS" />
    <category term="Azure Mobile Services" />
    <category term="Push Notifications" />
    <content type="html">&lt;p&gt;Wow, that’s a long title. I didn’t know how to find it in Google when I was searching for terms similar to that earlier, so hopefully this post helps. So you’ve gone ahead and added push notifications to your Xamarin app using Azure Mobile Services. There are tons of tutorials out there to show you how to do it. The next logical step (if you love your users) is to add a setting someplace to allow users to turn off their push notifications. So then you go looking for good documents on how to update the tags that you subscribed to… and if they’re out there, they’re really hard to find. Here’s the quick and easy recipe: do the exact same thing as the initial registration, but with an empty (or updated) list of tags.  &lt;p&gt;Setting this up with the boilerplate declaration:&lt;br&gt;var push = new MobileServiceClient(url,key).GetPush();  &lt;p&gt;So to register one tag:&lt;br&gt;push.RegisterNativeAsync(deviceToken, new List&amp;lt;string&amp;gt;() { “tagname” });  &lt;p&gt;And to unregister:&lt;br&gt;push.RegisterNativeAsync(deviceToken, new List&amp;lt;string&amp;gt;());&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/bundling-and-minification-with-the-azure-cdn/</id>
    <title>Bundling and Minification with the Azure CDN</title>
    <updated>Mon, 12 Sep 2016 07:09:57 GMT</updated>
    <published>Sat, 21 Feb 2015 15:18:00 GMT</published>
    <link href="https://blog.genreof.com/blog/bundling-and-minification-with-the-azure-cdn/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="Azure" />
    <content type="html">&lt;p&gt;In the long list of tips to optimize your website performance are three things: use a CDN to serve content, make as few requests for Javascript and CSS as possible, and minify your Javascript and CSS files by removing whitespace and comments to make them as small as possible. ASP.Net provides a really excellent Bundling and Minification feature to handle the two latter points.This comes at a cost, however. Those static files must live within the project and can no longer be served via a CDN. (Not bundled, anyway.) &lt;/p&gt; &lt;p&gt;For a basic website, ASP.Net Bundling and Minification is fantastic. You set it up in the BundleConfig.cs file &lt;a href="http://www.asp.net/mvc/overview/performance/bundling-and-minification"&gt;as described here&lt;/a&gt;, and you get a minified version of your Javascript in as few files as you configure. The Bundling tries to cope with a CDN through the UseCdn property there, but it doesn’t really work, in my opinion. It just spits out the URL on the page to the CDN, so you’re still left with a request for each library if the user doesn’t have the file cached yet. If you’re using something like jQuery UI, for example, it will probably be a request for jQuery, another for the jQuery UI Javascript file, and another for the jQuery UI theme you’ve specified. For these, all you’ve really done is replaced the old ScriptManager with another construct, and the various performance testing tools are still going to shout at you for making too many requests.&lt;/p&gt; &lt;p&gt;One of the guys on my team at work, &lt;a href="https://github.com/Byaltek"&gt;Allan Bybee&lt;/a&gt;, took it upon himself to find a solution to this CDN bundling limitation. All of our Javascript and CSS files were previously being served from a CDN, so all of our static content lived out there rather than in the web project. Moving all of those files back into the project would have been a large task with many compromises to the development experience and web performance at the end. The critical one being that we would gain the bundling at the expense of serving files from the CDN. After a few weeks of digging around in the ASP.Net source code and diligent trial and error, he ended up with a really elegant solution which gives us the best of all worlds - source files that live in the CDN, which bundling watches for changes and then saves the resulting bundle back to the CDN. He did it by extending the built in ASP.Net bundling system, so it’s very similar to that - just improved upon. Here’s the best part: it’s now a Nuget package!&lt;/p&gt; &lt;p&gt;&amp;nbsp;&lt;/p&gt; &lt;p&gt;&lt;a href="https://www.nuget.org/packages/AzureBundling/"&gt;&lt;b&gt;Azure Bundling on Nuget&lt;/b&gt;&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Install from Nuget, and there is just a small amount of configuration in order to get things going. There are six settings that need configuring, and you have a three options for doing this. (Or read the &lt;a href="http://www.byaltek.com/"&gt;full documentation&lt;/a&gt;.)&lt;/p&gt; &lt;ol&gt; &lt;li&gt;Web.config - 6 required app settings  &lt;li&gt;Config.json - A file just for the settings  &lt;li&gt;BundleConfig - A different constructor &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;&lt;b&gt;1. Web.config&lt;br&gt;&lt;/b&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-markup"&gt;&amp;lt;add key=“AzureAccountName” value=“your account name” /&amp;gt;&lt;br&gt;&amp;lt;add key=“AzureAccessKey” value=“your access key” /&amp;gt;&lt;br&gt;&amp;lt;add key=“CdnPath” value=“your cdn or Azure storage url” /&amp;gt;&lt;br&gt;&amp;lt;add key=“SecureCdnPath” value=“your secure cdn or Azure storage url” /&amp;gt;&lt;br&gt;&amp;lt;add key=“CachePolltime” value=“integer value in seconds how often to poll for file changes” /&amp;gt; &lt;br&gt;&amp;lt;add key=“BundleCacheTTL” value=“integer value in seconds for cache time to live” /&amp;gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;b&gt;2. Config.json&lt;br&gt;&lt;/b&gt;&lt;/p&gt;&lt;pre&gt;&lt;code class="language-javascript"&gt;{&lt;br&gt;“AzureAccountName”: “your account name”,&lt;br&gt;“AzureAccessKey”: “your access key”,&lt;br&gt;“CdnPath”: “your cdn or Azure storage url”,&lt;br&gt;“SecureCdnPath”: “your secure cdn or Azure storage url”,&lt;br&gt;“CachePollTime”: “integer value in seconds how often to poll for file changes”,&lt;br&gt;“BundleCacheTTL”: “integer value in seconds for cache time to live”&lt;br&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you use either of those first two configuration options, it will pick them up automatically and your BundleConfig code ends up very, very similar to what it was prior. Set up a variable that defines the Azure blob storage container name where the files live and the bundles will be stored in. (Make sure this container’s access level is not set to private, or none of this is going to work.) That variable is called AzureContainer in the code example below.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;bundles.UseCdn = true;&lt;br&gt;BundleTable.EnableOptimizations = true;&lt;br&gt;BundleTable.VirtualPathProvider = new StorageVirtualPath(AzureContainer);&lt;br&gt;bundles.Add(new JSBundle(“~/your virtual path/jqueryVal”, “your folder”).Include(“~/your folder path/jquery.validate.js”, “~/your folder path/jquery.validate.unobtrusive.js”));&lt;br&gt;bundles.Add(new JSBundle(“~/your virtual path/modernizr”, “your folder”).Include(“~/your folder path/modernizr-2.6.2.js”));&lt;br&gt;bundles.Add(new CSSBundle(“~/your virtual path/site”, “your folder”).Include( “~/your folder path/bootstrap.css”, “~/your folder path/site.css”));&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The key highlights from the code above are setting the VirtualPathProvider property on the BundleTable to an instance of StorageVirtualPath, and changing any ScriptBundle or StyleBundles which you want to be CDN bundles to instead be JSBundle or CSSBundle, respectively.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;3. BundleConfig&lt;br&gt;&lt;/b&gt;Set up variables for each of the settings in the BundleConfig, and pass into the constructor for each of the custom bundling classes. (StorageVirtualPath, JSBundle, and CSSBundle.)&lt;br&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;BundleTable.VirtualPathProvider = new StorageVirtualPath(AzureContainer, AzureAccountName, AzureAccessKey, CachePollTime);&lt;br&gt;var jquerycombined = new JSBundle(“~/bundles/jquerycombined”, AzureContainer, AzureAccountName, AzureAccessKey, CdnPath, SecureCdnPath, BundleCacheTTL).Include( “~/your folder path/jquery-2.1.3.js”, “~/your folder path/bootstrap.js”, “~/your folder path/respond.js”));&lt;br&gt;bundles.Add(jquerycombined);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you compare that code to the snippet above, you’ll notice the additional arguments being passed in on the StorageVirtualPath, JSBundle, and CSSBundle. These are the variables you’ll want to define with the appropriate values.&lt;/p&gt;
&lt;p&gt;One last note on this is the order of precedence on the settings. Assuming you set it up in both the web.config and JSON file, the settings from the web.config will win over the JSON file. If you set it in the BundleConfig file, that will trump having either of the other two set.&lt;/p&gt;
&lt;p&gt;This could not have been achieved without Microsoft making the move to open source .Net and ASP.Net. So if there’s something you don’t like about this, or find a bug, or want to contribute additional features to it, here’s the best part - it’s all on GitHub!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/Byaltek/AzureBundling"&gt;&lt;b&gt;AzureBundling on GitHub&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This project fills a big gap in ASP.Net bundling for those of us that are intensely focused on tuning the performance of websites. Allan has done a tremendous job figuring this out and putting it out there for the world (if you’re reading this Allan, thanks again!) and I hope that other developers find this as useful as I do. If you do, please make sure to report any issues you find, or better still, help contribute to this great project and make it even better!&lt;/p&gt;</content>
  </entry>
  <entry>
    <id>https://blog.genreof.com/blog/azure-websites-staging-slots-and-search-engine-bots/</id>
    <title>Azure Websites Staging Slots and Search Engine Bots</title>
    <updated>Sun, 05 Jul 2015 15:17:36 GMT</updated>
    <published>Fri, 06 Feb 2015 15:12:00 GMT</published>
    <link href="https://blog.genreof.com/blog/azure-websites-staging-slots-and-search-engine-bots/" />
    <author>
      <name>Shaun Lynch</name>
      <email>test@example.com</email>
    </author>
    <category term="Azure Web Apps" />
    <content type="html">&lt;p&gt;Setting up CI to Azure Websites is extremely easy and convenient. Whether you choose to do it from Git or Visual Studio Online, having a staging deployment slot makes this absolutely dead simple to ensure that you’re putting high quality code into production so that a bad check in doesn’t affect live users. The problem is that search engine bots are super greedy and manage to start crawling your staging site, which you definitely don’t want - then real users may end up there. There are a few easy options you have to address this, and they aren’t very well documented at the moment.&lt;/p&gt; &lt;p&gt;The first trick is getting the name of the site you’re on, which you can do with the following:&lt;/p&gt; &lt;pre&gt;&lt;code class="csharp"&gt;Environment.GetEnvironmentVariable(“WEBSITE_HOSTNAME”);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you’re on the staging site, this will return a value ending in -staging.azurewebsites.net. So now you need a place to check for this.&lt;/p&gt;
&lt;p&gt;I chose to do this with an HTTP Module, partly because I’m old school and partly because I want this check to happen before it gets into any ASP.Net work that ultimately won’t be necessary. The key bits are this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="csharp"&gt;string CurrentEnv = Environment.GetEnvironmentVariable(“WEBSITE_HOSTNAME”);&lt;br&gt;if (!string.IsNullOrEmpty(CurrentEnv) &amp;amp;&amp;amp; CurrentEnv.ToLower().EndsWith(“-staging.azurewebsites.net”) &amp;amp;&amp;amp; IsBot()) // Redirect to main site&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;

&lt;p&gt;There’s a second trick to get this working - it will stick if you release this and swap slots at this point. Go into the latest Azure portal (portal.azure.com at the time of writing) and add an app setting to both the production and staging slots, making sure you check the Slot Setting box. This forces the site to restart before it swaps to production. When you don’t have the sticky slot setting, the site doesn’t restart, and continues to get the staging value back from WEBSITE_HOSTNAME while in production. Name your sticky app setting whatever you like, it only matters that it is there. You can also set it via Powershell, if that’s more your speed. &lt;br&gt;&lt;/p&gt;
&lt;p&gt;To see this in action, use something that allows you to change your user agent to Googlebot and visit: &lt;a href="http://stagingbotredirector-staging.azurewebsites.net/"&gt;http://stagingbotredirector-staging.azurewebsites.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For full code, visit the &lt;a href="https://github.com/smlync/StagingBotRedirect"&gt;Github repo&lt;/a&gt; for the site above. And if you do use the HTTP module method, be sure to register the module in the web.config in the system.webServer modules section. &lt;/p&gt;
&lt;p&gt;Sources:&lt;/p&gt;
&lt;p&gt;1. &lt;a href="http://blog.amitapple.com/post/2014/11/azure-websites-slots/#.VNWBLfnF_zE"&gt;See the Deployment Slot App Settings/… section&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;2. &lt;a href="http://stackoverflow.com/questions/544450/detecting-honest-web-crawlers"&gt;Detecting Honest Web Crawlers&lt;/a&gt;&lt;/p&gt;</content>
  </entry></feed>