Sunday, July 24, 2016

Make your NuGet Server use NLog

The latest version of NuGet.Server is fast, stable, and super simple to setup. As for most .NET tools, Scott Hanselman already create a great write up about how to use it.

However, I was very disappointed at how unintuitive it was to get wire up a custom logger!

You need to take several steps to make the official NuGet server write to something like NLog:

  1. Create a log wrapper.
  2. Implement NuGet.Server.Logging.ILogger.
  3. Implement NuGet.ILogger...
  4. ...which also makes you implement NuGet.IFileConflictResolver!
  5. Implement your own NuGet.Server.IServiceResolver
  6. When instantiating ServerPackageRepository...
  7. ...pass in the ILogger...
  8. ...AND set the Logger property!

Still confused? Pull the code here, or take a look below!

Sunday, July 17, 2016

Should you open source your software?

Every Tuesday I help host the QQ Cast, where we fabricate answers to geek culture's most superfluous questions. For our 50th Quest, my good friend Matt Stevenson joined us to talk about open source software.

QQ Cast - Quest 50 - Should you open source your software?

  • 00:00 - Mic Check and Introductions
  • 05:30 - Is "Expand Enhance Extinguish" still alive and well?
  • 19:40 - Can we build a sustainable infrastructure without open source software?
  • 33:00 - How much structure do we like to see in our frameworks?
  • 40:50 - What are some great open source projects?
  • 47:15 - Was heartbleed a good thing or a bad thing?
  • 54:30 - Does contributing to open source projects help your resume?
  • 60:00 - Should you open source your software?
  • 70:00 - Wrap up!

Please always remember that all views and opinions expressed on the podcast are representative solely of the person expressing them; not of their friends and family, not of their coworkers, and certainly not of their employers, past, present, or future.

Enjoy,
Tom

Thursday, June 30, 2016

Updating Default Parameters in .NET Assemblies

One of the things that I love about C# is how so many of it's features are just very conveniently designed compiler tricks. This means that, just like any other magic trick, once you know how the trick is performed you immediately realize that there really is nothing magical about it.

So, let's talk about default parameters. They are actually just constant values that get compiled into your code when you go to use a method that has them. Let's look at an example...

The Code

public enum Country
{
    US,
    CA
}
 
public static class SharedUtility
{
    public static bool CanDrink(
        int age, 
        Country country = Country.CA)
    {
        switch (country)
        {
            case Country.US:
                return age >= 21;
 
            case Country.CA:
                return age >= 18;
 
            default:
                throw new ArgumentException(
                    "Invalid Country",
                    nameof(country));
        }
    }
}
 
public class SharedUtilityTests
{
    [Fact]
    public void CanDrink()
    {
        var result = SharedUtility.CanDrink(20);
 
        // The line above will compile into the following:
        // var result = SharedUtility.CanDrink(20, Country.CA);
        // Thus, the Assert below will succeed!
 
        Assert.True(result, "The default was not US!");
    }
}

So, now that you know how the trick is performed, how could this cause a problem for you? Specifically, what happens if you update a library that exposes methods with default parameters?

Nothing will change, until you recompile against the library!

If another library changes their default parameters, but you do not recompile your code against it, then your code will be using the old default values! Let's look back at the previous example and see why this could cause confusion...

Sunday, June 26, 2016

MP3 Playlist for Chromecast

Come to find out, I am a very retro guy when it comes to music. I have these old things that I like to use to play music, you may not have heard of them, they are called MP3s. They are kind of like 8-track tapes, but digital, not in the cloud, and not old enough to be cool yet.

How do you play MP3s via Chromecast?

The official answer is to use Google Play, but that implies that you want to both pay for that service and upload your files to the cloud. The unofficial answer is to drag and drop your MP3s into a Chrome tab and cast that tab, however this does not allow you to create a playlist.

Introducing Playlist for Chromecast

I have created a simple single page HTML 5 application that will act as a playlist for MP3s on your computer. Just download the project and open up release/playlist.html in Chrome, then drag and drop MP3s on to the page.

Development

I had a lot of fun making this, and I'm not done. I intend to use this project as a case study to talk about VSCode, TypeScript, SASS, HTML5 Audio, NPM, and unit testing. For now, I just wanted to start by getting this initial post up, but expect more to follow.

What's next?

  • Update project documentation.
  • Create unit tests.
  • Add theme support.
  • Write blog posts about development.
  • Maybe host it on a domain.
  • Maybe submit it as a Chrome application.

Enjoy,
Tom

Saturday, June 18, 2016

Client Side Caching for jQuery

Updates: 6/26/16

  • Fixed bug where triple equals null check would miss.
  • Added support for data driven cache key.
  • Removed let and const statements (some minifiers were having a hard time with them)

Original:

There is great question on Stack Overflow about caching a jquery ajax response in javascript/browser. Unfortunately, even thought it was a good solution, it did not do quite what I needed it to.

The application I was trying to optimize sometimes made redundant parallel requests, and I needed my caching solution to include a queuing system to prevent duplicate fetches.

Below is a simple solution that uses jQuery.ajaxPrefilter to check a local cache prior to making GET requests. Additionally, it will queue the callback if the request is already in flight. The cache stores the queue in both memory and local storage, ensuring that the cache will persist across page loads.

Implementation

(function ($) {
  "use strict";
 
  var timeout = 60000;
  var cache = {};
 
  $.ajaxPrefilter(onPrefilter);
 
  function onPrefilter(options, originalOptions) {
    if (options.cache !== true) {
      return;
    }
 
    var callback = originalOptions.complete || $.noop;
    var cacheKey = getCacheKey(originalOptions);
 
    options.cache = false;
    options.beforeSend = onBeforeSend;
    options.complete = onComplete;
 
    function onBeforeSend() {
      var cachedItem = tryGet(cacheKey);
 
      if (!!cachedItem) {
        if (cachedItem.data === null) {
          cachedItem.queue.push(callback);
        } else {
          setTimeout(onCacheHit, 0);
        }
 
        return false;
      }
 
      cachedItem = createCachedItem();
      cachedItem.queue.push(callback);
      setCache(cacheKey, cachedItem);
      return true;
 
      function onCacheHit() {
        invoke(callback, cachedItem);
      }
    }
 
    function onComplete(data, textStatus) {
      var cachedItem = tryGet(cacheKey);
 
      if (!!cachedItem) {
        cachedItem.data = data;
        cachedItem.status = textStatus;
        setCache(cacheKey, cachedItem);
 
        var queuedCallback;
        while (!!(queuedCallback = cachedItem.queue.pop())) {
          invoke(queuedCallback, cachedItem);
        }
 
        return;
      }
 
      cachedItem = createCachedItem(data, textStatus);
      setCache(cacheKey, cachedItem);
      invoke(callback, cachedItem);
    }
  }
 
  function tryGet(cacheKey) {
    var cachedItem = cache[cacheKey];
 
    if (!!cachedItem) {
      var diff = new Date().getTime() - cachedItem.created;
 
      if (diff < timeout) {
        return cachedItem;
      }
    }
 
    var item = localStorage.getItem(cacheKey);
 
    if (!!item) {
      cachedItem = JSON.parse(item);
 
      var diff = new Date().getTime() - cachedItem.created;
 
      if (diff < timeout) {
        return cachedItem;
      }
 
      localStorage.removeItem(cacheKey);
    }
 
    return null;
  }
 
  function setCache(cacheKey, cachedItem) {
    cache[cacheKey] = cachedItem;
 
    var clone = createCachedItem(cachedItem.data, cachedItem.status, cachedItem.created);
    var json = JSON.stringify(clone);
    localStorage.setItem(cacheKey, json);
  }
 
  function createCachedItem(data, status, created) {
    return {
      data: data || null,
      status: status,
      created: created || new Date().getTime(),
      queue: []
    };
  }
 
  function invoke(callback, cachedItem) {
    if ($.isFunction(callback)) {
      callback(cachedItem.data, cachedItem.status);
    }
  }
  
  function getCacheKey(originalOptions) {
    if (!!originalOptions.data) {
      return originalOptions.url + "?" + JSON.stringify(originalOptions.data);
    }
 
    return originalOptions.url;
  }
 
})(jQuery);

Enjoy,
Tom

Friday, May 27, 2016

Word Boundaries Regex

\b

This is the second time this week where I have had to ask myself "how did I not know about this?"

There is a regex character to identify word boundaries: \b This is a zero length match, similar to the caret and dollar sign. It finds the boundaries between words, allowing you to search for a whole word match.

Below is a sample extension method that uses this to replace words in a string.

Implementation

public static class StringExtensions
{
    private static readonly Regex WordRegex = new Regex(@"\b\w+\b", RegexOptions.Compiled);
 
    public static string ReplaceWords(
        this string input,
        string find,
        string replace,
        StringComparison comparison = StringComparison.InvariantCulture)
    {
        return WordRegex.Replace(input, m => m.Value.Equals(find, comparison)
            ? replace
            : m.Value);
    }
}

Sunday, May 22, 2016

Common Logging Extensions with Caller Information

Update: Added the BreakParameter.

I have made it an (arguably bad) habit of manually adding the class and method name as a prefix to all my log lines. It is not that I enjoy typing out the same strings over and over, it's that I do not always trust things like the NLog callsite. Using the stack frame to identify a calling method always requires a bit of cleverness on the part of the author, as you never can be totally sure when you are dealing with a wrapper class or an async call stack.

I was recently introduced to the Caller Information attributes in C# 5, and now I think I am in love.

Disclaimer: I have not used this very much yet, but I intend to start! I think that these attributes are absolutely brilliant in their simplicity: a compiler trick to insert debug information directly into your code. That is freak'n sweet, and it's performant! I am not sure how this flew under my radar, but now that I know about it....

Below is a little T4 template that I wrote up to generate overloads for Common Logging that will include caller information. To customize this for your needs, just update the the GetFormat method at the bottom of the template.

Unit Tests

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using Common.Logging.Simple;
   4:  using Xunit;
   5:   
   6:  namespace Common.Logging.Tests
   7:  {
   8:      /// <summary>
   9:      /// Tests for Common Logging extensions that use Caller Information
  10:      /// https://msdn.microsoft.com/en-us/library/mt653988.aspx
  11:      /// </summary>
  12:      public class LogCallTests
  13:      {
  14:          [Fact]
  15:          public void LogFromMethod()
  16:          {
  17:              var log = new QueueSimpleLogger();
  18:              var ex = new Exception("Boom");
  19:   
  20:              log.Debug("Hello");
  21:              log.Debug("World", ex);
  22:   
  23:              log.DebugCall("Hello");
  24:              log.DebugCall("World", ex);
  25:   
  26:              log.WarnFormat("Hello - {0}", "Zero");
  27:              log.WarnFormat("World - {0}", ex, "Zero");
  28:   
  29:              log.WarnFormatCall("Hello - {0}", "Zero");
  30:              log.WarnFormatCall("World - {0}", ex, "Zero");
  31:   
  32:              Assert.Equal(8, log.Queue.Count);
  33:   
  34:              Assert.Equal("Hello", log.Queue.Dequeue());
  35:              Assert.Equal("World - Ex: Boom", log.Queue.Dequeue());
  36:   
  37:              Assert.Equal("LogCallTests.LogFromMethod(23) - Hello", log.Queue.Dequeue());
  38:              Assert.Equal("LogCallTests.LogFromMethod(24) - World - Ex: Boom", log.Queue.Dequeue());
  39:   
  40:              Assert.Equal("Hello - Zero", log.Queue.Dequeue());
  41:              Assert.Equal("World - Zero - Ex: Boom", log.Queue.Dequeue());
  42:   
  43:              Assert.Equal("LogCallTests.LogFromMethod(29) - Hello - Zero", log.Queue.Dequeue());
  44:              Assert.Equal("LogCallTests.LogFromMethod(30) - World - Zero - Ex: Boom", log.Queue.Dequeue());
  45:          }
  46:   
  47:          [Fact]
  48:          public void LogFromAction()
  49:          {
  50:              var log = new QueueSimpleLogger();
  51:              var ex = new Exception("Boom");
  52:              Action action = () =>
  53:              {
  54:                  log.Debug("Hello");
  55:                  log.Debug("World", ex);
  56:   
  57:                  log.DebugCall("Hello");
  58:                  log.DebugCall("World", ex);
  59:   
  60:                  log.WarnFormat("Hello - {0}", "Zero");
  61:                  log.WarnFormat("World - {0}", ex, "Zero");
  62:   
  63:                  log.WarnFormatCall("Hello - {0}", "Zero");
  64:                  log.WarnFormatCall("World - {0}", ex, "Zero");
  65:              };
  66:   
  67:              action();
  68:   
  69:              Assert.Equal(8, log.Queue.Count);
  70:   
  71:              Assert.Equal("Hello", log.Queue.Dequeue());
  72:              Assert.Equal("World - Ex: Boom", log.Queue.Dequeue());
  73:   
  74:              Assert.Equal("LogCallTests.LogFromAction(57) - Hello", log.Queue.Dequeue());
  75:              Assert.Equal("LogCallTests.LogFromAction(58) - World - Ex: Boom", log.Queue.Dequeue());
  76:   
  77:              Assert.Equal("Hello - Zero", log.Queue.Dequeue());
  78:              Assert.Equal("World - Zero - Ex: Boom", log.Queue.Dequeue());
  79:   
  80:              Assert.Equal("LogCallTests.LogFromAction(63) - Hello - Zero", log.Queue.Dequeue());
  81:              Assert.Equal("LogCallTests.LogFromAction(64) - World - Zero - Ex: Boom", log.Queue.Dequeue());
  82:          }
  83:   
  84:          private class QueueSimpleLogger : AbstractSimpleLogger
  85:          {
  86:              public readonly Queue<string> Queue = new Queue<string>(); 
  87:   
  88:              public QueueSimpleLogger()
  89:                  : base(string.Empty, LogLevel.All, true, true, true, string.Empty)
  90:              {
  91:              }
  92:   
  93:              protected override void WriteInternal(LogLevel level, object message, Exception exception)
  94:              {
  95:                  var s = message.ToString();
  96:                  if (exception != null) s += " - Ex: " + exception.Message;
  97:                  Queue.Enqueue(s);
  98:              }
  99:          }
 100:      }
 101:  }
Real Time Web Analytics