<?xml version="1.0" encoding="UTF-8"?>
<!--Generated by Squarespace V5 Site Server v5.13.477-266 (http://www.squarespace.com) on Tue, 20 Jun 2017 16:51:56 GMT--><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Geoff Hudik's Tech-E Blog</title><link>http://www.geoffhudik.com/tech/</link><description>Programming, .net, IT, gadget and geek related stuff</description><lastBuildDate>Fri, 11 Oct 2013 14:33:10 +0000</lastBuildDate><copyright>Copyright 2016 Geoff Hudik</copyright><language>en-US</language><generator>Squarespace V5 Site Server v5.13.477-266 (http://www.squarespace.com)</generator><item><title>A Remotely Managed Bing Image Search Wallpaper App - Part 2</title><category>.net</category><category>Bing</category><category>Coding4Fun</category><category>diagnostics</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Fri, 11 Oct 2013 07:01:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2013/10/11/a-remotely-managed-bing-image-search-wallpaper-app-part-2.html</link><guid isPermaLink="false">632421:7353983:34276138</guid><description><![CDATA[<a href="http://www.geoffhudik.com/tech/2013/10/11/a-remotely-managed-bing-image-search-wallpaper-app-part-1.html">Part 1</a> gave an overview introduction to the 
app and covered job scheduling, searching for images on Bing, and downloading images. Part 2 covers setting the wallpaper, remote app settings, 
remote logging and more.
<br/><br/>

<h3>Setting the Wallpaper</h3>

The switch wallpaper job is scheduled to start running later than the download job to give time for images to download.  
Plus it makes for a better prank to wait for the wallpaper change to set in later. The job starts 
by checking the app's enabled status and bailing if disabled. It then deletes old images according to app settings and 
the create date of the local images. Next if <span class="inline-code">AppSettings.Instance.WallpaperOverrideUrl</span> 
is set that image is downloaded and used as the background. Otherwise the job randomly picks one of the images downloaded 
from the Bing image search results. 
<br><br>

The actual code that sets the wallpaper isn't that interesting; it's a modified version 
from <a href="http://stackoverflow.com/questions/1061678/change-desktop-wallpaper-using-code-in-net">this StackOverflow post</a>. 
Finally the job uses the <a href="https://github.com/thnk2wn/TrollWallpaper/blob/master/WIO/WIO/Imaging/MetadataManager.cs">metadata manager</a> to get the image URL where the local filename came from; that's used in the remote logging so we can see what image we set the wallpaper to.

<pre class="brush:csharp">
internal class SwitchWallpaperJob : IJob
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();

	public const string OutputPathKey = "OutputPath";

	public async void Execute(IJobExecutionContext context)
	{
		try
		{
			if (!AppSettings.Instance.CheckStatus()) return;

			var path = AppSettings.ImagePath.FullName;
			Ensure.That(path, "path").IsNotNullOrWhiteSpace();

			ImageCleanup.Execute();

			var imageFile = await GetWallpaperImageFile(path);
			if (null == imageFile) return;

			//TODO: setting for wallpaper style or smart set via desktop &amp; image size
			Logger.Troll("Changing wallpaper to {0}", imageFile.Name);
			Wallpaper.Set(imageFile.FullName, Wallpaper.Style.Centered);

			var meta = new MetadataManager().Get(imageFile.FullName);
			var changedTo = (null != meta) ? meta.RemoteLocation + " via term " 
				+ meta.Term : imageFile.Name;

			Logger.Troll("Changed wallpaper to {0}", changedTo);
		}
		catch (Exception ex)
		{
			Logger.Error(ex.ToString());
			throw new JobExecutionException(ex);
		}
	}

	private static async Task&lt;FileInfo&gt; GetWallpaperImageFile(string path)
	{
		if (!string.IsNullOrWhiteSpace(AppSettings.Instance.WallpaperOverrideUrl))
		{
			Logger.Troll("Using image override url {0}", 
				AppSettings.Instance.WallpaperOverrideUrl);
			var uri = new Uri(AppSettings.Instance.WallpaperOverrideUrl);
			var file = uri.Segments[uri.Segments.Length - 1];
			var ext = file.Substring(file.LastIndexOf(".") + 1);
			var outFilename = new FileInfo(Path.Combine(
				AppSettings.ImagePath.FullName, "Override." + ext));
			await ImageDownloader.DownloadImage(AppSettings.Instance.WallpaperOverrideUrl, 
				outFilename.FullName);
			return outFilename;
		}

		Logger.Troll("Enumerating files in {0}", path);
		var files = Directory.GetFiles(path, "*.jpg").ToList();

		if (!files.Any())
		{
			Logger.Troll("No images yet; will try again later");
			return null;
		}

		Logger.Troll("Found {0} wallpaper images in {1}", files.Count, path);
		var r = new Random();
		var randFile = new FileInfo(files[r.Next(0, files.Count)]);
		return randFile;
	}
}
</pre>
<br>

<h3>Remote App Settings</h3>
The only app.config setting the app uses is ConfigSource which is expected to be a URL to retrieve the settings from. 
In this way we can change the wallpaper app behavior remotely which is useful when you're pranking someone or if you're 
using yourself to synchronize wallpaper settings between multiple computers. DropBox, SkyDrive, Google Drive and the like are 
simple solutions for hosting the file though that might trace the app back to you.
<br><br>

The config data is in JSON format and a sample follows. 
For pranking purposes I might use "Justin Bieber", "Justin Bieber Wallpaper", "Justin Bieber 2013" etc. in search terms but for testing 
or personal use, not so much. For debugging I set the all the job intervals to short amounts but use much longer durations for deployment.
<br><br>

<script src="https://gist.github.com/thnk2wn/6206321.js"></script>

<br>

<h4>Trying It Out</h4>

Images are downloaded to AppData\Local\WIO; WIO is the app name and stands for Windows Image Optimization :) and keeps the process name short and esoteric.
<br/><br/>

<a class="fancybox" href="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/BingDownloadImages.jpg">
	<img border="0" src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/BingDownloadImagesSmall.jpg"/>
</a>

<br/><br/>

Also in this directory is the metadata index file, mapping image filenames to the source URL downloaded from, 
in BSON format with some base64 encoding<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallPaper/MetaIdx.jpg"/>

<h4>Fetching App Settings</h4>

When retrieving the settings, a simple check is done on the data returned from the HTTP call to see if it is JSON. 
If not, the code assumes it is encrypted. When using the app in a prank fashion, encrypting the data helps mask app activity if 
the target discovers the URL in the app config or notices the HTTP traffic. Of course encrypting the config makes changing settings 
more difficult, so the app also contains a <a href="https://github.com/thnk2wn/TrollWallpaper/blob/master/FileEncrypt/Program.cs">FileEncrypt</a> 
command line app to easily encrypt the data. Adding a Send To shortcut means only having to right-click a plain text JSON file to generate an 
encrypted version in the same directory.
<br/>

<pre class="brush:csharp">
	public enum AppStatus { Enabled, Paused, Disabled }

    public sealed class AppSettings
    {
        private static volatile AppSettings _instance;
        private static readonly object SyncRoot = new Object();

        private AppSettings()
        {
            this.Search = new SearchSettings();
            this.Job = new JobSettings();
            this.Log = new LogSettings();
        }

        public string ImageDeleteAfterTimespan { get; set; }
        public string WallpaperOverrideUrl { get; set; }

        [JsonConverter(typeof(StringEnumConverter))]
        public AppStatus Status { get; set; }

        public static AppSettings Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        _instance = new AppSettings();
                    }
                }
                return _instance;
            }
        }

        public static async Task&lt;AppSettings&gt; Load()
        {
            string configData;
            using (var client = new HttpClient())
            {
                configData = await client.GetStringAsync(
					ConfigurationManager.AppSettings["ConfigSource"]);

                var isJsonPlain = (configData.TrimStart().StartsWith("{"));
                if (!isJsonPlain)
                    configData = CryptoManager.Decrypt3DES(configData);
            }

            lock (SyncRoot)
                _instance = JsonConvert.DeserializeObject&lt;AppSettings&gt;(configData);

            return _instance;
        }

        public bool CheckStatus()
        {
            if (Status == AppStatus.Disabled)
            {
                Application.Exit();
                return false;
            }

            return Status == AppStatus.Enabled;
        }

        [JsonIgnore]
        public static DirectoryInfo ImagePath
        {
            get
            {
                var path = Path.Combine(Environment.GetFolderPath(
					Environment.SpecialFolder.LocalApplicationData), "WIO");
                var di = new DirectoryInfo(path);
                if (!di.Exists) di.Create();
                return di;
            }
        }

        public SearchSettings Search { get; set; }
        public JobSettings Job { get; set; }
        public LogSettings Log { get; set; }
    }
</pre>

<br/>

<h3>Obfuscating the Code</h3>
With punking a fellow IT coworker, I wanted to obfuscate the code so it'd be more difficult to figure out the app's logic 
should it be discovered and viewed in a disassembler. For that I used <a href="http://www.gapotchenko.com/eazfuscator.net">eazfuscator</a>.

<a href="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/Obfuscate.jpg" class="fancybox">
	<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/ObfuscateSmall.jpg">
</a>
<br/><br/>

It was free to use for 30 days which was all I needed and it's available via <a href="http://www.nuget.org/packages/eazfuscator.net/">NuGet</a>. It slowed the build down 
some but it's only done in Release mode and usually that's only done at the very end.<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/ObfuscatedCode.jpg">
<br/><br/>

Unfortunately when running in Release mode with the obfuscated code I received the below exception. Removing the obfuscation build step 
and running in Release mode removed the exception so that means the process changed the behavior of the code.
<br/>

<pre style="overflow-x: auto;">
Quartz.SchedulerException was unhandled by user code
  HResult=-2146233088
  Message=Repeat Interval cannot be zero.
  Source=Quartz
  StackTrace:
       at Quartz.Impl.Triggers.SimpleTriggerImpl.Validate() in c:\Work\OpenSource\quartznet\src\Quartz\Impl\Triggers\SimpleTriggerImpl.cs:line 727
       at Quartz.Core.QuartzScheduler.ScheduleJob(IJobDetail jobDetail, ITrigger trigger) in c:\Work\OpenSource\quartznet\src\Quartz\Core\QuartzScheduler.cs:line 720
       at Quartz.Impl.StdScheduler.ScheduleJob(IJobDetail jobDetail, ITrigger trigger) in c:\Work\OpenSource\quartznet\src\Quartz\Impl\StdScheduler.cs:line 262
       at   .       ()
       at   . (Type  )
       at System.Collections.Generic.List`1.ForEach(Action`1 action)
       at   . ()
       at  . (Task`1  )
       at System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
       at System.Threading.Tasks.Task.Execute()
  InnerException:
</pre>

This turned out to be an issue with the obfuscator not handling JSON serialization correctly. It was fixed in a later version of the tool 
but the latest version of the NuGet package was an older version. Any use of reflection can also be an issue so lesson learned - always fully 
regression test when using obfuscation. This tool now appears to be more commercialized but I applaud the quality and the after hours support 
I received when reporting an issue.
<br/><br/>

<h3>Quick Installation</h3>
To reduce the chance of getting caught, I created a simple install script that can be quickly run via a flash drive or from a network share. 
Sooner or later I knew my target would forget a workstation lock and this way even a quick bathroom or coffee trip was plenty of time.
<br/><br/>

First a post build event to copy the files needed for deployment to an Install\bin folder:<br/><br/>

if not exist "$(SolutionDir)Install\bin" mkdir "$(SolutionDir)Install\bin"<br/>
del /f /q $(SolutionDir)Install\bin\*.*<br/>
xcopy /r /d /i /s /y  /exclude:$(SolutionDir)Install\InstallStageExclude.txt<br/> 
&nbsp;&nbsp;$(TargetDir)*.* $(SolutionDir)Install\bin
<br/><br/>

Next the script would copy the contents to the appropriate location. Normally I'd use PowerShell but batch files are just faster with being able to double-click and run worry-free.
<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/Install.jpg">
<br/><br/>

<h3>Remote Logging</h3>
Logging was needed to monitor the app to see what wallpaper was set on the victim's computer or to view problems if things weren't working correctly. 
Local computer logging didn't do any good as I wouldn't have access to the data later, so I evaluated a couple of cloud based logging solutions.


<h4>Loggr.net</h4>
<a href="http://loggr.net/">Loggr.net</a> is what I started with. It was intuitive and easy to use. Where it broke down for my needs was the API limits of the 
free account - 100 log records per day. So I ended up changing the code to only send important log events there based on log type (error, warning, custom). 
The <a href="https://github.com/thnk2wn/TrollWallpaper/blob/master/WIO/WIO/Diagnostics/LoggrNetLogger.cs">code to send logging data</a> wasn't bad, though it ended up being more than typical for more control over the logged events.
<br/><br/>

<a href="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/loggr-net-events.jpg" class="fancybox">
	<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/loggr-net-events-sm.jpg">
</a>
<br/>

<h4>Loggly</h4>

Next I tried <a href="http://www.loggly.com/plans-and-pricing/">Loggly</a> which allowed up to 200 MB/day for free. 
I found it's website UI to be a bit counter-intuitive though they were upgrading to a GEN 2 platform near the end of my usage. 
I liked the JSON logging and the command line style website. It offered a lot of searching functionality and from an API perspective it 
was easy to work with the log data programmatically. The <a href="https://github.com/thnk2wn/TrollWallpaper/blob/master/WIO/WIO/Diagnostics/LogglyLogger.cs">code to send logging data</a> 
was dead simple, though I was doing more with the Loggr.net API.
<br/><br/>

<a href="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/LogglySearch.jpg" class="fancybox">
	<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/LogglySearchSm.jpg">
</a>

<br/><br/>

<h3>In Conclusion</h3>

<h4>Was it worth it?</h4>

Yes:
<ul>
	<li style="margin-bottom:8px;">It's a fun prank that keeps on giving for you.</li>
	<li style="margin-bottom:8px;">I have an automated, randomized source of wallpaper for myself, synced between computers.</li>
	<li>Most importantly I got to play with different tech, learn new things and do some coding for fun.</li>
</ul>

<h4>Disclaimers</h4>
This was a fun educational experiment and one-time prank. I only offer the <a href="https://github.com/thnk2wn/TrollWallpaper">source code</a> here; no binaries, setups, support, or API keys.
<br/><br/>

Should you use some or all of this or do something similar, keep in mind:

<ul>
	<li style="margin-bottom:8px;">Depending on your use and legalese interpretation, you may be violating Bing's terms of use.</li>
	
	<li style="margin-bottom:8px;">Web search is like a box of chocolates; you never know what you're gonna get.</li>
	
	<li style="margin-bottom:8px;">Used in prank fashion, you may run the risk of offending someone with a random image, on top of already annoying him/her.</li>
	
	<li style="margin-bottom:8px;">Used at work you may slow down someone's machine or the work network with downloading hundreds of high resolution images in a short time span.</li>
</ul>]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-34276138.xml</wfw:commentRss></item><item><title>A Remotely Managed Bing Image Search Wallpaper App - Part 1</title><category>.net</category><category>Bing</category><category>Coding4Fun</category><category>Quartz</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Fri, 11 Oct 2013 07:00:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2013/10/11/a-remotely-managed-bing-image-search-wallpaper-app-part-1.html</link><guid isPermaLink="false">632421:7353983:34053175</guid><description><![CDATA[<a href="http://www.spidergroup.com/blog/2012/01/youve-been-biebered/"><img style="float:right; width:275px; height:275px; margin-left: 5px; border:0" src="http://www.geoffhudik.com/storage/blogs/tech/2013/10/BingWallpaper/you-ve-been-biebered.jpg"/></a>
At a previous job I made the mistake of leaving my computer unlocked on rare occasions and I ended up getting <a href="http://www.spidergroup.com/blog/2012/01/youve-been-biebered/">Biebered</a>. We were constantly trolling each other and playing pranks which kept things fun. Changing someone's wallpaper is pretty basic so I decided to take it up a notch for striking back. One day I had too much time on my hands and started a remotely controlled wallpaper changing app powered by online image searches.

<br/><br/>

<h3>The Highlights</h3>

<ul>
	<li>Powered by <a href="http://go.microsoft.com/fwlink/?LinkID=272626&clcid=0x409">Bing Image Search API</a> for variety</li>
	<li>Windowless Windows background app</li>
	<li>Remotely managed / configured / controlled</li>
	<li>Silently changes victim's desktop wallpaper at scheduled intervals</li>
	<li>Jobs scheduled with <a href="http://quartznet.sourceforge.net/">Quartz.net</a></li>
	<li>HTTPS remote logging using both <a href="http://loggly.com/">loggly.com</a> and <a href="http://loggr.net/">loggr.net</a></li>
	<li>Encrypted configuration</li>
	<li>Release compiled code obfuscation</li>
	<li>Sets itself to run at Windows startup</li>
	<li>Allows explicitly specifying an image URL to be used as background</li> 
	<li>Delete previously downloaded images by age according to remote config</li>
</ul>

<h3>Initial Decisions</h3>

<h4>App Type</h4>
I was pretty sure I would not be able to set the desktop wallpaper if the app was a Windows service but that didn't stop me from trying it :). 
Yeah it didn't work. In the old days you could just set the service property "Allow service to interact with desktop" but security is tighter with modern operating 
systems and that's a good thing. Maybe there's still a way to make that work but even if there was it probably wouldn't be worth the hassle. 
It would set off security alarms, it requires more hassle to install/uninstall, and similar functionality can be achieved with a Windows app that doesn't show any 
UI.
  
<h4>Image Sources</h4>
I considered different options for where to get the wallpaper images from including a preselected collection bundled with the app, 
reading from a network share, reading a URL from a remote config file, and using Google Image Search or Bing Image Search. I started out 
with Google's Image Search API and quickly found that to be a dead end, at least for any free version that didn't involve web scraping. 
Bing's Image Search API provided a free, quality, easy to use and diverse source of images. Later I decided to compliment that with the 
ability to override random images from a search with a specific image URL, if so desired.
<br/><br/>

<h3>App Lifecycle Skeleton</h3>

Rather than use any hidden form, the app spins up an <a href="http://msdn.microsoft.com/en-us/library/system.windows.forms.applicationcontext.aspx">ApplicationContext</a>
in the Main method of Program.cs with <span class="inline-code">Application.Run(new AppContext())</span>. On startup it first loads app settings from a remote source 
and then sets up a job schedule for work to be fired off later.

<pre class="brush:csharp">
internal class AppContext : ApplicationContext
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();
	private readonly JobScheduler _scheduler = new JobScheduler();

	public AppContext()
	{
		AppSettings.Load().ContinueWith(AfterSettingsLoad);
	}

	private void AfterSettingsLoad(Task&lt;AppSettings&gt; task)
	{
		if (AppSettings.Instance.Status == AppStatus.Disabled)
		{
			Application.Exit();
			return;
		}

		Logger.Info("Setting up scheduler");
		RegisterAppForWindowsStartup();
		_scheduler.Setup();
		ImageCleanup.Execute();
	}

	protected override void ExitThreadCore()
	{
		Logger.Info("In ExitThreadCore");
		base.ExitThreadCore();
		AppTeardown();
	}

	private void AppTeardown()
	{
		Logger.Info("Tearing down app");
		if (null != _scheduler)
		{
			Logger.Info("Disposing scheduler");
			_scheduler.Dispose();
		}

		ImageCleanup.Execute();

		RegisterAppForWindowsStartup();
	}

	private static void RegisterAppForWindowsStartup()
	{
		if (!DebugMode.IsDebugging)
			WindowsStartup.Register();
	}
}
</pre>
<br>

<h3>Job Scheduling</h3>

Job scheduling is done with <a href="http://quartznet.sourceforge.net/">Quartz.net</a> and JobScheduler starts it's scheduler. It 
then scans the app's assembly for any scheduler classes and instantiates each to setup the scheduling for the different jobs. 

<pre class="brush:csharp">
internal class JobScheduler : DisposableObject
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();

	public void Setup()
	{
		Logger.Info("Creating job scheduler");
		ISchedulerFactory schedFact = new StdSchedulerFactory();
		Scheduler = schedFact.GetScheduler();
		Scheduler.Start();

		var types = Assembly.GetExecutingAssembly().GetTypes()
			.Where(x =&gt; x.BaseType == typeof(ScheduleBase)).ToList();
		Logger.Info("Found {0} schedules. Setting up each", types.Count);
		types.ForEach(t=&gt; ((ScheduleBase) Activator.CreateInstance(t, Scheduler)).Setup());
		Logger.Debug("Schedules setup");
	}

	private IScheduler Scheduler { get; set; }
   
	protected override void DisposeManagedResources()
	{
		if (null != this.Scheduler)
			this.Scheduler.Shutdown(waitForJobsToComplete: false);
	}
}
</pre>

There are jobs for refreshing remote app settings, downloading images from Bing Image Search and for changing the wallpaper. 
A sample job schedule builder for downloading images:

<pre class="brush:csharp">
internal class DownloadSchedule : ScheduleBase
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();

	public DownloadSchedule(IScheduler scheduler) : base(scheduler)
	{
	}

	public override void Setup()
	{
		Logger.Info("Setting up download images job");

		if (!AppSettings.Instance.Search.Enabled)
		{
			Logger.Info("Search and download images isn't enabled; exiting");
			return;
		}

		var job = JobBuilder.Create&lt;DownloadImagesJob&gt;()
			.WithIdentity("downloadImages")
			.Build();

		var trigger = TriggerBuilder.Create()
			.WithIdentity("downloadImagesTrigger")
			.StartAt(AdjustOffset(DateBuilder.EvenMinuteDateAfterNow()))
			.WithSimpleSchedule(x =&gt;
				x.WithIntervalInMinutes(AppSettings.Instance.Job.DownloadImagesIntervalMinutes)
				.RepeatForever())
			.Build();

		Scheduler.ScheduleJob(job, trigger);
		Logger.Info("Download job setup. Next fire time is {0}", GetNextFireTimeText(trigger));
	}
}
</pre>
<br>

<h3>Downloading Images</h3>

<h4>Download Job</h4>
In the remote app settings there's a search section that defines the phrase(s) to search for with 
Bing Image Search, along with supporting data such as options and API credentials. The download 
job enumerates each search to be run and sets up each to be executed. TaskDelayer is based on 
<a href="http://stackoverflow.com/questions/4229923/how-to-schedule-a-task-for-future-execution-in-task-parallel-library">this StackOverflow post</a> 
and was added to prevent consuming too many resources at once in parallel or for too long continuously. If there is only one search to be run then it's a moot point.

<pre class="brush:csharp">
internal class DownloadImagesJob : IJob
{
	//...
	public void Execute(IJobExecutionContext context)
	{
		try
		{
			if (!ShouldDownloadImages()) return;

			var outPath = AppSettings.ImagePath.FullName;
			Ensure.That(outPath, "outputPath").IsNotNullOrWhiteSpace();

			ImageCleanup.Execute();
			Logger.Info("Downloading images");
				
			if (AppSettings.Instance.Search.Queries.Count &gt; 5)
				throw new InvalidOperationException("Please limit number of queries to 5");

			for (var q = 0; q &lt; AppSettings.Instance.Search.Queries.Count; q++)
			{
				var search = AppSettings.Instance.Search.Queries[q];

				// try not to overwhelm system all at once, may draw too much attention
				var delaySeconds = q*AppSettings.Instance.Search.DelaySecondsBetweenSearches;
				var q1 = q;
				Logger.Info("Starting batch {0} for term {1} w/delay seconds {2}", 
					q1 + 1, search.Term, delaySeconds);

				TaskDelayer.RunDelayed(delaySeconds * 1024, () =&gt;
				{
					var fetcher = new SearchImageFetcher(outPath);
					Logger.Info("Fetching images for {0}", search.Term);
					fetcher.Fetch(search.Term, search.Options).Wait();
					return fetcher;
				}).ContinueWith(t=&gt; 
					Logger.Info("Finished batch {0} for term {1} w/delay seconds {2}", 
					q1 + 1, search.Term, delaySeconds));
			}
		}
		catch (Exception ex)
		{
			Logger.Error(ex.ToString());
			throw new JobExecutionException(ex);
		}
	}
	// ...
}
</pre>

<h4>Searching for Images</h4>
The search image fetcher class begins by initializing the bing search container, downloaded from the 
.NET Framework C# Service Proxy Class Library link inside the <a href="http://go.microsoft.com/fwlink/?LinkID=272626&clcid=0x409">Bing API Quick Start &amp; Code</a>. 
The <a href="https://datamarket.azure.com/dataset/5ba839f1-12ce-4cce-bf57-a49d98d29a44#schema">Bing Data Search API</a> allows 5,000 transactions per month for free; more than enough for my uses but keep that limit in mind.
<br/><br/>

The <span class="inline-code">Fetch</span> method takes any options specific to that search or the default if none were provided. It passes that 
to <span class="inline-code">RunSearches</span> to get the matching image URLs from Bing which are  
fed to <span class="inline-code">DownloadImages</span> for local download.
<br/>

<pre class="brush:csharp">
internal class SearchImageFetcher
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();
	private readonly BingSearchContainer _bingSearchContainer;
	private readonly string _outputPath;

	public SearchImageFetcher(string outputPath)
	{
		_outputPath = outputPath;
		Logger.Info("Creating Image Search client");

		_bingSearchContainer = new BingSearchContainer(
			new Uri(AppSettings.Instance.Search.ImageSearchUrl))
		{
			IgnoreMissingProperties = true,
			Timeout = AppSettings.Instance.Search.Timeout,
			Credentials = new NetworkCredential(AppSettings.Instance.Search.Username,
												AppSettings.Instance.Search.ApiKey)
		};
	}

	public async Task Fetch(string searchTerm, SearchOptions options = null)
	{
		Logger.Info("Inspecting output directory {0}", _outputPath);
		var dir = new DirectoryInfo(_outputPath);

		if (!dir.Exists) dir.Create();
		
		try
		{
			var searchOptions = options ?? AppSettings.Instance.Search.DefaultOptions;
			var results = RunSearches(searchTerm, searchOptions);

			Logger.Info("Image searches finished; {0} results", results.Count);
			await DownloadImages(results, searchTerm, searchOptions);
		}
		catch (Exception ex)
		{
			Logger.Error("Error fetching images for term '{0}' : {1}", searchTerm, 
				ex.ToString());
			throw;
		}
	}
	// ...
}
</pre>

The <span class="inline-code">RunSearches</span> method invokes the bing image search method multiple times 
for paging, which <a href="http://stackoverflow.com/questions/11056628/add-paging-capabilites-to-dataservicequery-for-bing-search-api">required some customizations to BingSearchContainer.cs</a>. 
I was unsure how to combine multiple filters from their documentation so I just used "Size:Large" in the settings.

<pre class="brush:csharp">
private List&lt;ImageResult&gt; RunSearches(string searchTerm, SearchOptions options, 
int take = 50)
{
	var requests = options.Max/take;
	Logger.Info("Fetching images for term '{0}', options: {1}. Requests to make: {2}", 
		searchTerm, options, requests);
	var results = new List&lt;ImageResult&gt;();

	for (var i = 0; i &lt; requests; i++)
	{
		var skip = i * take;
		Logger.Info("Setting up search. Query: {0}, Options: {1}, Skip: {2}", searchTerm, 
			options, skip);
		
		var query = _bingSearchContainer.Image(
			Query: searchTerm,
			Options: null,
			Market: null,
			Adult: options.Adult,
			Latitude: null,
			Longitude: null,
			ImageFilters: options.Filters,
			//ImageFilters:"Size:Height:768&Size:Width:1024", // how to combine multiple?
			top: 50, // 50 is the max we can request in one shot
			skip:skip);
		var currentResults = query.ToList();
		results.AddRange(currentResults);
	}

	return results;
}
</pre>

<h4>Downloading Search Results</h4>

<span class="inline-code">DownloadImages</span> post-filters the bing results to exclude images smaller than 
the size specified in settings/options. Again it'd be better to specify that when querying Bing but I didn't see 
how to combine filters at first glance. Images are saved locally with the same name returned from Bing and are not 
downloaded again if already there. The <span class="inline-code">MetadataManager</span> associates the Bing image 
URL with the local filename and persists that to disk for later remote logging use when the wallpaper is changed.

<pre class="brush:csharp">
private async Task DownloadImages(IEnumerable&lt;ImageResult&gt; results, string searchTerm, 
SearchOptions options)
{
	var sw = Stopwatch.StartNew();
	var filteredResults = results.Where(x =&gt; x.Width &gt;= options.MinWidth 
		&& x.Height &gt;= options.MinHeight).ToList();
	Logger.Info("Filtered result count: {0}", filteredResults.Count);
	var downloadCount = 0;

	var metadataMgr = new MetadataManager();
	foreach (var result in filteredResults)
	{
		var imageUrl = result.MediaUrl;
		var outputFilename = Path.Combine(_outputPath, string.Format("{0}.jpg", 
			result.ID.ToString("N")));

		// don't redownload image if it already exists locally, tho' it could have changed
		if (!File.Exists(outputFilename))
		{
			Logger.Debug("Image Url: {0}, destination: {1}", imageUrl, outputFilename);
			await ImageDownloader.DownloadImage(imageUrl, outputFilename);
			metadataMgr.Add(new Metadata
				{
					RemoteLocation = imageUrl,
					LocalLocation = outputFilename,
					Term = searchTerm
				});
			downloadCount++;
		}
		else 
			Logger.Debug("File already exists locally, not redownloading {0}", imageUrl);
	}

	metadataMgr.Save();
	sw.Stop();
	Logger.Info("Saved {0} images in {1:000.0} seconds", downloadCount, 
		sw.Elapsed.TotalSeconds);
}
</pre>

<h4>Downloading an Image</h4>

<span class="inline-code">ImageDownloader</span> takes care of downloading a single image using 
<a href="http://msdn.microsoft.com/en-us/library/system.net.webclient.aspx">WebClient</a>; I'd probably use 
<a href="http://msdn.microsoft.com/en-us/library/system.net.http.httpclient.aspx">HttpClient</a> if I was 
writing this today. I looked into throttling image downloads using <a href="http://sharpbits.codeplex.com/">SharpBits</a> to utilize 
idle network bandwidth with Microsoft's Background Intelligent Transfer Service. Ultimatiely that was too much fuss 
and took far too long to download. I also researched utilizing <a href="http://www.codeproject.com/Articles/18243/Bandwidth-throttling">this CodeProject ThrottledStream class</a> 
but ultimiately I decided not to throttle downloads, mostly out of laziness.

<pre class="brush:csharp">
class ImageDownloader
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();

	public static async Task DownloadImage(string url, string outputFilename)
	{
		using (var webClient = new WebClient())
		{
			try
			{
				Logger.Debug("Downloading {0} to {1}", url, outputFilename);
				var sw = Stopwatch.StartNew();
				var imageBytes = await webClient.DownloadDataTaskAsync(url);
				sw.Stop();
				Logger.Debug("Downloaded {0} bytes in {1:00.0} second(s)", 
					imageBytes.Length, sw.Elapsed.TotalSeconds);

				Logger.Debug("Writing image bytes to disk");
				var result = ImageWriter.Write(imageBytes, outputFilename);
				Logger.Debug("Image write complete with result: {0}", result);
			}
			catch (Exception ex)
			{
				Logger.Error("Error downloading image '{0}' to '{1}'. Likely corrupt "
					+ "and will be deleted. Error: {2}", url, outputFilename, ex.ToString());
				try
				{
					if (File.Exists(outputFilename))
						File.Delete(outputFilename);
				}
				catch (Exception inner)
				{
					Logger.Error(string.Format("Error deleting image '{0}': {1}", 
						outputFilename, inner));
				}
			}
		}
	}
}
</pre>

<h4>Writing Image Bytes to Disk</h4>

I found that <a href="http://msdn.microsoft.com/en-us/library/system.drawing.image.save.aspx">Image.Save()</a> 
threw an exception for some large images so I attempted that first and if that failed I wrote the image bytes 
to disk in chunks using a Stream. 

<pre class="brush:csharp">
internal class ImageWriter
{
	private static readonly IAppLogger Logger = LoggerFactory.Create();

	public static bool Write(byte[] imageBytes, string outputFilename)
	{
		Logger.Debug("Creating memory stream from byte array of length {0}", 
			imageBytes.Length);
		using (var stream = new MemoryStream(imageBytes))
		{
			Logger.Debug("Creating image from stream");
			using (var image = Image.FromStream(stream))
			{
				Logger.Debug("Saving image {0} in Jpeg format", outputFilename);

				try
				{
					image.Save(outputFilename, ImageFormat.Jpeg);
					Logger.Info("Saved image {0}", outputFilename);
				}
				catch (Exception ex)
				{
					Logger.Error("Error saving {0}. Will attempt saving in chunks. Error was : {1}",
						outputFilename, ex.ToString());

					try
					{
						SaveImageInChunks(imageBytes, outputFilename);
					}
					catch (Exception inner)
					{
						Logger.Error("Saving image in chunks failed: {0}", inner);
					}
				}
			}
		}
		
		return true;
	}

	private static void SaveImageInChunks(byte[] imageBytes, string outputFilename)
	{
		// this is to handle a large image where Image.Save croaked
		using (Stream source = new MemoryStream(imageBytes))
		using (Stream dest = File.Create(outputFilename))
		{
			var buffer = new byte[1024];
			int bytes;
			while ((bytes = source.Read(buffer, 0, buffer.Length)) &gt; 0)
			{
				dest.Write(buffer, 0, bytes);
			}
		}
	}
}
</pre>
<br>

<h3>Part 2</h3>

<a href="http://www.geoffhudik.com/tech/2013/10/11/a-remotely-managed-bing-image-search-wallpaper-app-part-2.html">Part 2</a> 
- remote app settings, setting the wallpaper, remote logging, source code and more...]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-34053175.xml</wfw:commentRss></item><item><title>ASP.NET NLog Sql Server Logging and Error Handling Part 2</title><category>.net</category><category>NLog</category><category>Ninject</category><category>asp.net mvc</category><category>error handling</category><category>logging</category><category>sql server</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Fri, 24 May 2013 19:00:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2013/5/24/aspnet-nlog-sql-server-logging-and-error-handling-part-2.html</link><guid isPermaLink="false">632421:7353983:33713201</guid><description><![CDATA[Be sure to check out <a href="http://www.geoffhudik.com/tech/2013/5/20/aspnet-nlog-sql-server-logging-and-error-handling-part-1.html">Part 1</a> as this post builds upon it and the two go hand in hand.
<br/><br/>

<h3>Series Overview</h3>

<a href="http://www.geoffhudik.com/tech/2013/5/20/aspnet-nlog-sql-server-logging-and-error-handling-part-1.html">Part 1</a> - Setting up logging with ASP.NET MVC, NLog and SQL Server<br/><br/>

Part 2 - Unhandled exception processing, building an error report, emailing errors, and custom error pages.
<br/><br/>

<h3>Custom Error Handling Attribute</h3>
Added in FilterConfig.RegisterGlobalFilters and bound in DiagnosticModule, AppErrorHandlerAttribute invokes reporting the unhandled exception and setting the error view to be displayed to the end user. An enableErrorPages appSetting controls whether any of this is done; for local debugging or a dev web server having this off might be desirable.<br/>

<pre class="brush:csharp">
namespace NLogSql.Web.Infrastructure.ErrorHandling
{
    public class AppErrorHandlerAttribute : FilterAttribute, IExceptionFilter
    {
        [Inject]
        public IErrorReporter Reporter { get; set; }

        public void OnException(ExceptionContext exceptionContext)
        {
            if (exceptionContext.ExceptionHandled) return;

            if (ConfigurationManager.AppSettings["enableErrorPages"] == "false")
            {
                AppLogFactory.Create&lt;AppErrorHandlerAttribute&gt;().Error(
                    "Unexpected error. enableErrorPages is false, skipping detailed "
					+ "error gathering. Error was: {0}",
                    exceptionContext.Exception.ToString());
                return;
            }

            Ensure.That(Reporter, "Reporter").IsNotNull();
            Reporter.ReportException(exceptionContext);

            SetErrorViewResult(exceptionContext);
        }

        private static void SetErrorViewResult(ExceptionContext exceptionContext)
        {
            var statusCode = new HttpException(null, exceptionContext.Exception)
				.GetHttpCode();

            exceptionContext.Result = new ViewResult
            {
                ViewName = MVC.Shared.Views.ViewNames.Error,
                TempData = exceptionContext.Controller.TempData,
                //ViewData = new ViewDataDictionary&lt;ErrorModel&gt;(new ErrorModel())
            };

            exceptionContext.ExceptionHandled = true;
            exceptionContext.HttpContext.Response.Clear();
            exceptionContext.HttpContext.Response.StatusCode = statusCode;
            exceptionContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }
    }
}
</pre>
<br/>

<h3>Logging and Reporting the Error</h3>
In the same namespace the ErrorReporter class invokes generation of an error report and logs and emails the error report. 
The overload with customActivityMessage would generally be used with handled exceptions where it may still be desirable to 
report the exception in cases.<br/>

<pre class="brush:csharp">
public class ErrorReporter : IErrorReporter
{
	private readonly ILog _log;

	public ErrorReporter(ILog log)
	{
		_log = Ensure.That(log, "log").IsNotNull().Value;
	}

	private string CustomActivityMessage { get; set; }

	public void ReportException(ControllerContext controllerContext, 
		Exception exception, string customActivityMessage = null)
	{
		this.CustomActivityMessage = customActivityMessage;
		ReportException(new ExceptionContext(controllerContext, exception));
	}

	public void ReportException(ExceptionContext exceptionContext)
	{
		var errorInfo = new ErrorReportInfo(exceptionContext, this.CustomActivityMessage);
		errorInfo.Generate();
		_log.Error("Unexpected error: {0}", errorInfo.ReportText);

		if (errorInfo.Errors.Any())
			_log.Error("Error generating error report. Original exception: {0}", 
				exceptionContext.Exception);

		// sending mail can be a little slow, don't delay end user seeing error page
		Task.Factory.StartNew(
		state =&gt;
		{
			var errorReport = (ErrorReportInfo)state;
			DependencyResolver.Current.GetService&lt;IErrorEmailer&gt;()
				.SendErrorEmail(errorReport);
		},
		errorInfo).ContinueWith(t =&gt;
		{
			if (null != t.Exception)
				_log.Error("Error sending email: " + t.Exception.ToString());    
		});
	}
}
</pre>
<br/>

<h3>Building the Error Report</h3>

<table border="0" cellspacing="0" cellpadding="0"><tr>
<td valign="top" style="padding-right: 10px;">
Various diagnostic info classes are responsible for building different diagnostic "sub reports".  
Each inherits from DiagnosticInfoBase which is a glorified StringBuilder, with functionality to build both a plain text version of the report (logged to DB) as well as an HTML formatted version (used for emails). 
<br/><br/>
The base class has safe appending and formatting functionality and ensures that any error in generating a part of the report doesn't stop the whole process. 
</td>
<td>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/DiagnosticClasses.jpg"/>
</td>
</tr>
</table>

A sample implementation:<br/>

<pre class="brush:csharp">
public class FormInfo : DiagnosticInfoBase
{
	private readonly HttpRequestBase _request;

	public FormInfo(HttpRequestBase request)
	{
		_request = request;
	}

	protected override void GenerateReport()
	{
		StartTable();
		var keys = _request.Form.AllKeys.OrderBy(s =&gt; s).ToList();

		foreach (var name in keys)
		{
			var value = _request.Form[name];

			if (null != value && name.Contains("password", StringComparison.OrdinalIgnoreCase))
			{
				value = new string('*', value.Length);
			}

			AppendRow(name, value);
		}

		EndTable();
	}
}
</pre>

The ErrorReportInfo class combines the output of each section for the full error report.<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/ErrorReportInfo.jpg"/>
<br/>
<br/>

In the end this produces a <a href="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/SampleErrorReport.html" target="_blank">sample HTML email report like this</a>.
<br/><br/>

<h3>Sending the Email</h3>

Sending the email logs the result on failure or success, 

<pre class="brush:csharp">
public interface IErrorEmailer
{
	void SendErrorEmail(ErrorReportInfo errorInfo);
}

public class ErrorEmailer : IErrorEmailer
{
	private readonly IMailer _mailer;
	private readonly ILog _log;

	public ErrorEmailer(IMailer mailer, ILog log)
	{
		_mailer = Ensure.That(mailer, "mailer").IsNotNull().Value;
		_log = Ensure.That(log, "log").IsNotNull().Value;
	}

	public void SendErrorEmail(ErrorReportInfo errorInfo)
	{
		try
		{
			var subject = string.Format("{0} Error", 
				Assembly.GetExecutingAssembly().GetName().Name);

			if (null != errorInfo.Server && null != errorInfo.Location
				&& !string.IsNullOrWhiteSpace(errorInfo.Location.ControllerAction)
				&& !string.IsNullOrWhiteSpace(errorInfo.Server.HostName))
			{
				subject = string.Format("{0}: {1} - {2}", subject, 
					errorInfo.Server.HostName, errorInfo.Location.ControllerAction);
			}

			var to = AppSettings.Default.Email.ErrorMessageTo;
			_mailer.SendMail(to, subject, errorInfo.ReportHtml);

			_log.Info("Sent email: {0} to {1}", subject, to);
		}
		catch (Exception ex)
		{
			_log.Error("Error sending error report email: {0}", ex);
		}
	}
}
</pre>
<br/>

<h3>The Error View</h3>

At the end of AppErrorHandlerAttribute the SetErrorViewResult invoked the Views\Shared\Error.cshtml page. According to the HTTP status code, content such as text, images and styles differ. Separate error pages may have more advantages if there are a larger number of differences; a single page was less work here.<br/>

<pre class="brush:html">
@section styles{
    &lt;link rel="stylesheet" href="~/Content/Error.css"/&gt;
}

@{
    var statusTitleMap = new Dictionary&lt;int, string&gt; {
             {404, "Something got lost in the shuffle"},
             {410, "Gone like yesterday"},
             {500, "Something go boom"}
         };
}

@section hero{
    &lt;div id="error-body" class="container-fluid"&gt;
        &lt;div class="row-fluid"&gt;
            &lt;div class="span3"&gt;
                @{ var errorClass = (Response.StatusCode == 404 || Response.StatusCode == 410) ? "_" + Response.StatusCode : "_500";}
                &lt;div id="errorImageBlock" class="@errorClass"&gt;&lt;/div&gt;
            &lt;/div&gt;
    
            &lt;div class="span8 offset1"&gt;
                &lt;div class="row-fluid"&gt;
                    &lt;div class="span12 text-center" id="status-code"&gt;
                        @Response.StatusCode
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div class="row-fluid"&gt;
                    &lt;div class="span12" id="well-this"&gt;
                        @statusTitleMap[Response.StatusCode]
                    &lt;/div&gt;
                &lt;/div&gt;
        
                &lt;div class="row-fluid"&gt;
                    &lt;div class="span11" id="message"&gt;
                    @switch (Response.StatusCode)
                    {
                        case 404:
                            @:We cannot find the page you are looking for. If you typed in the address, double check the spelling. If you got here by clicking a link, 
                            @: &lt;a href="mailto:customerservice@domain.com?subject=Page%20Not%20Found"&gt;let us know&lt;/a&gt;.
                            break;
                        case 410:
                            @:The page you are looking for is gone (permanently). If you feel you reached this page incorrectly, &lt;a href="mailto:customerservice@domain.com?subject=Link%20Gone"&gt;let us know&lt;/a&gt;.
                            break;
                        case 500:
                            @:Oh dear, something's gone wrong. Our team has already been alerted to the problem and will fix it as soon as possible! 
                            break;
                    }  
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
}
</pre>
<br/>

<h3>Testing Errors</h3>

At the bottom of the home page are some links to test out error functionality.<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/HomePage.jpg"/>
<br/><br/>

<a href="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/ErrorPage500.jpg" class="fancybox" title="500 Error Page" alt="Click for larger version">
	<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/ErrorPage500Small.jpg"/>
</a>
<br/><br/>

<h3>Handling Not Found and Gone Permanently</h3>

404s have some special handling in Global.asax.cs:<br/>

<pre class="brush:csharp">
protected void Application_EndRequest()
{
	if (Context.Response.StatusCode == 404)
	{
		if (Request.RequestContext.RouteData.Values["fake404"] == null)
		{
			Response.Clear();

			var rd = new RouteData();
			rd.Values["controller"] = MVC.Error.Name;
			rd.Values["action"] = MVC.Error.ActionNames.NotFound;

			var c = (IController)DependencyResolver.Current.GetService&lt;ErrorController&gt;();
			Request.RequestContext.RouteData = rd;
			c.Execute(new RequestContext(new HttpContextWrapper(Context), rd));
		}
	}
}
</pre>

That code makes me itch a bit and could be done better but there is a reason for it. If you're wondering why not just use custom error pages, the answer is that doing so for a 404 page ends up producing a 301 redirect to then 404 on the custom not found page. For SEO purposes that is usually considered a bad practice. If you don't have a public site or as much SEO concern, that may be acceptable but in this case it wasn't.
<br/><br/>

<h3>Error Controller</h3>

The error controller sets and logs the response status codes and returns the error view. For the Gone action, usually there'd be routes defined for legacy URLs that would direct to that action.<br/>
 
<pre class="brush:csharp">
public partial class ErrorController : Controller
{
	private readonly ILog _log;

	public ErrorController(ILog log)
	{
		_log = Ensure.That(log, "log").IsNotNull().Value;
	}

	public virtual ActionResult NotFound()
	{
		Response.StatusCode = (int)HttpStatusCode.NotFound;
		RouteData.Values["fake404"] = true;
		_log.Write(LogType.Warn, new { Code = "404" }, 
			"404 Not Found for {0}", Request.Url);
		return View("Error");
	}

	public virtual ActionResult Gone()
	{
		Response.StatusCode = (int)HttpStatusCode.Gone;
		Response.Status = "410 Gone";
		Response.TrySkipIisCustomErrors = true;
		_log.Write(LogType.Warn, new {Code = "410"}, 
			"410 gone permanently for {0}", Request.Url);
		return View("Error");
	}
}
</pre>
<br/>

<h3>Future Enhancements</h3>

<ul>
	<li>The diagnostic info report building classes are pretty quick and messy in spots and could use cleanup.</li>
	
	<li>Error view work including better responsive design and user-acceptable images :)</li>

	<li>Admin error view to inspect log data and errors</li>
	
	<li>Use and integration of apps such as <a href="http://appfail.net/">appfail.net</a> and <a href="https://newrelic.com/">New Relic</a> to monitor errors and performance. This app used those tools in conjunction with the custom error handling and logging functionality.</li>
</ul>
<br/>

<h3>The Code</h3>

The code for this series is available at <a href="https://github.com/thnk2wn/NLogSql.Web">https://github.com/thnk2wn/NLogSql.Web</a>]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-33713201.xml</wfw:commentRss></item><item><title>ASP.NET NLog Sql Server Logging and Error Handling Part 1</title><category>.net</category><category>NLog</category><category>Ninject</category><category>asp.net mvc</category><category>error handling</category><category>logging</category><category>sql server</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Tue, 21 May 2013 05:51:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2013/5/20/aspnet-nlog-sql-server-logging-and-error-handling-part-1.html</link><guid isPermaLink="false">632421:7353983:33685523</guid><description><![CDATA[It was the eleventh hour before a web app was to go live and there was nothing in place for error handling or logging. You're shocked I'm sure (that's my sarcastic voice). Normally with web apps in the past I've used <a href="https://code.google.com/p/elmah/">ELMAH</a> for unhandled error logging and NLog with text file targets for other logging.
<br/><br/>

ELMAH works great but in this case I was told it was pulled for some reason. With no time to find out the story there I threw together something to capture basic error details and send an email. Later that grew and was customized and ELMAH never returned though perhaps it should have. Still there are benefits of doing this yourself including more customization, less bloat, less hunting through perhaps lacking documentation etc.
<br/><br/>

On the logging front I still wanted to use NLog but text file logging was no longer a fit. This app had a few instances behind a load balancer and central logging was needed plus it needed a higher volume of logging in some troublesome workflows, more so than the occasional errors or warnings here and there. I had not used NLog with SQL Server before but this seemed like the perfect time to do so.
<br/><br/>

<h3>Series Overview</h3>

Part 1 - Setting up logging with ASP.NET MVC, NLog and SQL Server<br/><br/>

<a href="http://www.geoffhudik.com/tech/2013/5/24/aspnet-nlog-sql-server-logging-and-error-handling-part-2.html">Part 2</a> - Unhandled exception processing, building an error report, emailing errors, and custom error pages.
<br/><br/>

<h3>Setup and Configuration</h3>
I began by installing the <a href="http://nuget.org/packages/NLog/">NLog</a> NuGet package and the <a href="http://nuget.org/packages/NLog.Extended/">NLog.Extended</a> package for asp.net specific information into the ASP.NET MVC 4 project. The <a href="http://nuget.org/packages/NLog.Config/">NLog.Config</a> package is useful for a standalone NLog config file but for web apps with existing config transforms I find embedding the configuration in Web.config easier.
<br/><br/>

In web.config under configuration/configSections, the NLog section is defined:<br/>
<pre class="brush:xml">
	&lt;section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" /&gt;
</pre>

Under &lt;configuration&gt; my initial NLog config looked like the below, using the async attribute on targets to make each target work asynchronously:<br/>
<pre class="brush:xml">
&lt;nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwExceptions="true" 
    internalLogLevel="Debug" internalLogFile="${basedir}/NlogInternal.log"&gt;
    &lt;!-- go to http://nlog-project.org/wiki/Configuration_file for more information --&gt;
    &lt;extensions&gt;
      &lt;add assembly="NLog.Extended" /&gt;
    &lt;/extensions&gt;
    &lt;targets async="true"&gt;
      &lt;target xsi:type="Database" name="dbTarget" connectionStringName="Logger" 
	  commandText="exec sLogEvent_Insert @time_stamp, @level, @logger, @userName, @url, 
	    @machineName, @sessionId, @threadId, @referrer, @userAgent, @code, @message"&gt;
        &lt;parameter name="@time_stamp" layout="${date}" /&gt;
        &lt;parameter name="@level" layout="${level}" /&gt;
        &lt;parameter name="@logger" layout="${logger}" /&gt;
        &lt;parameter name="@userName" layout="${identity}" /&gt;
        &lt;parameter name="@url" layout="${aspnet-request:serverVariable=Url}" /&gt;
        &lt;parameter name="@machineName" layout="${machinename}" /&gt;
        &lt;parameter name="@sessionId" layout="${aspnet-sessionid}" /&gt;
        &lt;parameter name="@threadId" layout="${threadid}" /&gt;
        &lt;parameter name="@referrer" 
			layout="${aspnet-request:serverVariable=HTTP_REFERER}" /&gt;
        &lt;parameter name="@userAgent" 
			layout="${aspnet-request:serverVariable=HTTP_USER_AGENT}" /&gt;
		&lt;parameter name="@code" layout="${event-context:item=Code}" /&gt;
        &lt;parameter name="@message" layout="${message}" /&gt;
      &lt;/target&gt;
      &lt;target name="debugTarget" xsi:type="Debugger" 
		layout="${time}|${level:uppercase=true}|${logger}|${message}" /&gt;
    &lt;/targets&gt;
    &lt;rules&gt;
      &lt;!-- Levels: Off, Trace, Debug, Info, Warn, Error, Fatal --&gt;
      &lt;logger name="*" minlevel="Trace" writeTo="debugTarget,dbTarget" /&gt;
    &lt;/rules&gt;
&lt;/nlog&gt;
</pre>

Later I swapped to explicit use of <a href="https://github.com/nlog/nlog/wiki/AsyncWrapper-target">AsyncWrapper</a> targets as it allowed me to control the behavior more explicitly and mirrored the reality at runtime, at the cost of being more verbose.<br/>
<pre class="brush:xml">
&lt;nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" throwExceptions="true" 
    internalLogLevel="Debug" internalLogFile="${basedir}/NlogInternal.log"&gt;
    &lt;!-- go to http://nlog-project.org/wiki/Configuration_file for more information --&gt;
    &lt;extensions&gt;
      &lt;add assembly="NLog.Extended" /&gt;
    &lt;/extensions&gt;
    &lt;targets&gt;
      &lt;target name="asyncDbWrapperTarget" xsi:type="AsyncWrapper" queueLimit="10000" 
	  timeToSleepBetweenBatches="50" batchSize="100" overflowAction="Block"&gt;
        &lt;target xsi:type="Database" name="dbTarget" connectionStringName="Logger" 
		commandText="exec sLogEvent_Insert @time_stamp, @level, @logger, @userName, @url, 
		@machineName, @sessionId, @threadId, @referrer, @userAgent, @code, @message"&gt;
          &lt;parameter name="@time_stamp" layout="${date}" /&gt;
          &lt;parameter name="@level" layout="${level}" /&gt;
          &lt;parameter name="@logger" layout="${logger}" /&gt;
          &lt;parameter name="@userName" layout="${identity}" /&gt;
          &lt;parameter name="@url" layout="${aspnet-request:serverVariable=Url}" /&gt;
          &lt;parameter name="@machineName" layout="${machinename}" /&gt;
          &lt;parameter name="@sessionId" layout="${aspnet-sessionid}" /&gt;
          &lt;parameter name="@threadId" layout="${threadid}" /&gt;
          &lt;parameter name="@referrer" 
			layout="${aspnet-request:serverVariable=HTTP_REFERER}" /&gt;
          &lt;parameter name="@userAgent" 
			layout="${aspnet-request:serverVariable=HTTP_USER_AGENT}" /&gt;
          &lt;parameter name="@code" layout="${event-context:item=Code}" /&gt;
          &lt;parameter name="@message" layout="${message}" /&gt;
        &lt;/target&gt;
      &lt;/target&gt;
      &lt;target name="asyncDebugWrapperTarget" xsi:type="AsyncWrapper" queueLimit="10000" 
	  timeToSleepBetweenBatches="50" batchSize="100" overflowAction="Block"&gt;
        &lt;target name="debugTarget" xsi:type="Debugger" 
			layout="${time}|${level:uppercase=true}|${logger}|${message}" /&gt;
      &lt;/target&gt;
    &lt;/targets&gt;
    &lt;rules&gt;
      &lt;!-- Levels: Off, Trace, Debug, Info, Warn, Error, Fatal --&gt;
      &lt;logger name="*" minlevel="Trace" 
		writeTo="asyncDebugWrapperTarget,asyncDbWrapperTarget" /&gt;
    &lt;/rules&gt;
 &lt;/nlog&gt;
</pre>

The database target's connectionStringName value of Logger points to the DB connection string to use.<br/>
<pre class="brush:xml">
&lt;connectionStrings&gt;
&lt;add name="Logger" providerName="System.Data.SqlClient" 
   connectionString="Data Source=(local);Initial Catalog=Chinook.Logs;Persist Security Info=True;User ID=ChinookLogger;Password=L0gger!" /&gt;
&lt;/connectionStrings&gt;
</pre>
<br/>

<h3>The Database</h3>

Our DBA setup a separate database for logging data to set DB properties differently as log data and transactional app data are different beasts. The sample app for this post is written against the <a href="http://chinookdatabase.codeplex.com/">Chinook</a> sample database so I named the log database Chinook.Logs. The primary table script follows, minus the indexes, SET statements and the like.<br/>
<pre class="brush:sql">
CREATE TABLE [dbo].[LogEvent](
	[LogId] [int] IDENTITY(1,1) NOT NULL,
	[LogDate] [datetime] NOT NULL,
	[EventLevel] [nvarchar](50) NOT NULL,
	[LoggerName] [nvarchar](500) NOT NULL,
	[UserName] [nvarchar](50) NULL,
	[Url] [nvarchar](1024) NULL,
	[MachineName] [nvarchar](100) NOT NULL,
	[SessionId] [nvarchar](100) NULL,
	[ThreadId] [int] NULL,
	[Referrer] [nvarchar](1024) NULL,
	[UserAgent] [nvarchar](500) NULL,
	[Code] [nvarchar](10) NULL,
	[LogMessage] [nvarchar](max) NOT NULL,
	[PartitionKey] [tinyint] NOT NULL,
PRIMARY KEY CLUSTERED 
(
	[LogId] ASC,
	[PartitionKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)
</pre>

The table is pretty straightforward with perhaps a couple of exceptions. The Code field was to be used for custom error codes (HTTP or otherwise) or "tagging" log statements. PartitionKey was added by our DBA mainly for archiving log data but also for efficient querying by day; more on this to come. Finally I originally had the string columns as varchar but we found that NLog used nvarchar and a conversion was happening with each insert that had a slight performance impact.
<br/><br/>

The insert sproc grabs the day of the week and inserts that as the partition key and the rest of the data just passes through.<br/>
<pre class="brush:sql">
CREATE PROCEDURE [dbo].[sLogEvent_Insert]
	@time_stamp datetime,
	@level nvarchar(50),
	@logger nvarchar(500),
	@userName nvarchar(50),
	@url nvarchar(1024),
	@machineName nvarchar(100),
	@sessionId nvarchar(100),
	@threadId int,
	@referrer nvarchar(1024),
	@userAgent nvarchar(500),
	@code nvarchar(10),
	@message nvarchar(max)
AS
BEGIN
	SET NOCOUNT ON;
	Declare @currentDate Datetime
	declare @partitionKey tinyint
	
	set		@currentDate = getdate()
	set		@partitionKey = DATEPART(weekday, @currentDate)

	INSERT INTO [dbo].[LogEvent]
           ([LogDate]
           ,[EventLevel]
           ,[LoggerName]
           ,[UserName]
           ,[Url]
           ,[MachineName]
           ,[SessionId]
           ,[ThreadId]
           ,[Referrer]
           ,[UserAgent]
		   ,[Code]
           ,[LogMessage]
		   ,[PartitionKey])
     VALUES
           (@time_stamp
           ,@level
           ,@logger
           ,@userName
           ,@url
           ,@machineName
           ,@sessionId
           ,@threadId
           ,@referrer
           ,@userAgent
		   ,@code
           ,@message
		   ,@partitionKey);
END
</pre>

An identical log table was created but with the name LogEvent_Switched. The cleanup sproc was created by our DBA:<br/>

<pre class="brush:sql">
CREATE PROCEDURE [DBA].[WeekdayPartitionCleanup_PartitionSwitching] 
AS
BEGIN
	SET NOCOUNT ON;
	declare @partitionKey int
	declare @SQLCommand nvarchar(1024)
	truncate table [Chinook.Logs].dbo.LogEvent_Switched;

	set @partitionKey = datePart(weekday, getdate()) + 1
	if(@partitionkey &gt;7)
		set @partitionKey = 1

	set @SQLCommand = 'alter table [Chinook.Logs].dbo.LogEvent switch partition  ' 
	  + cast(@partitionKey as varchar) + ' to [Chinook.Logs].dbo.LogEvent_Switched;'
	exec sp_executesql @SQLCommand
END
</pre>

That was scheduled to run daily. So if today is Friday, datePart(weekday, getdate()) returns 6, +1 is Saturday so all of last Saturday's log records get automagically switched over to the LogEvent_Switched table. This leaves the last 6 days of log records in LogEvent and the 7th day in LogEvent_Switched. If you don't have the Enterprise edition and can't use partitioning, regular deletes may work but could be <a href="http://stackoverflow.com/questions/4169283/maintenance-stored-procedure-how-to-delete-without-blocking-replication">problematic</a> when deleting a large number of rows during frequent inserts or selects.
<br/><br/>

Finally permission to execute the sproc was granted and then it was back to .net land.
<pre class="brush:sql">
GRANT EXECUTE on dbo.sLogEvent_Insert to ChinookLogger;
</pre>
<br/>

<h3>Wiring up the Logging Code</h3>

First an interface that mostly mirrors NLog's Logger class.<br/>

<pre class="brush:csharp">
namespace NLogSql.Web.Infrastructure.Diagnostics.Logging
{
    public interface ILog
    {
        void Debug(string format, params object[] args);
        void Error(string format, params object[] args);
        void Fatal(string format, params object[] args);
        void Info(string format, params object[] args);
        void Trace(string format, params object[] args);
        void Warn(string format, params object[] args);

        // custom
        void Write(LogType type, object properties, string message, params object[] args);

        bool IsDebugEnabled { get; }
        bool IsErrorEnabled { get; }
        bool IsFatalEnabled { get; }
        bool IsInfoEnabled { get; }
        bool IsTraceEnabled { get; }
        bool IsWarnEnabled { get; }
    }
}
</pre>

The functionality of the Log class is provided mostly by inheriting from NLog's Logger class. 
One custom method is defined to allow passing custom log properties much like you would in ASP.NET (new {code = "404"}).<br/>

<pre class="brush:csharp">
using System.ComponentModel;
using System.Globalization;
using NLog;

namespace NLogSql.Web.Infrastructure.Diagnostics.Logging
{
    public class Log : Logger, ILog
    { 
        public void Write(LogType type, object properties, string message, 
			params object[] args)
        {
            var info = new LogEventInfo(LogLevel.FromOrdinal((int)type), Name, 
				CultureInfo.CurrentCulture, message, args);

            if (null != properties)
            {
                foreach (PropertyDescriptor propertyDescriptor 
					in TypeDescriptor.GetProperties(properties))
                {
                    var value = propertyDescriptor.GetValue(properties);
                    info.Properties[propertyDescriptor.Name] = value;
                }
            }

            Log(info);
        }
    }

    public enum LogType
    {
        Trace, Debug, Info, Warn, Error, Fatal
    }
}
</pre>

A log instance is typically created via injecting ILog into the constructor of the class needing logging. 
Here I use <a href="http://www.ninject.org/">Ninject</a> to wire this up.<br/>

<pre class="brush:csharp; highlight:[17,25]">
using System;
using System.Linq;
using System.Web.Mvc;
using NLog;
using NLogSql.Web.Infrastructure.Diagnostics.Logging;
using NLogSql.Web.Infrastructure.ErrorHandling;
using Ninject.Activation;
using Ninject.Modules;
using Ninject.Web.Mvc.FilterBindingSyntax;

namespace NLogSql.Web.DI
{
    public class DiagnosticsModule : NinjectModule
    {
        public override void Load()
        {
            Bind&lt;ILog&gt;().ToMethod(CreateLog);
            Bind&lt;IErrorReporter&gt;().To&lt;ErrorReporter&gt;();
            Bind&lt;IEventLogWriter&gt;().To&lt;EventLogWriter&gt;();
            Bind&lt;IErrorEmailer&gt;().To&lt;ErrorEmailer&gt;();

            Kernel.BindFilter&lt;AppErrorHandlerAttribute&gt;(FilterScope.Controller, 0);
        }

        private static ILog CreateLog(IContext ctx)
        {
            var p = ctx.Parameters.FirstOrDefault(x =&gt; 
				x.Name == LogConstants.LoggerNameParam);
            var loggerName = (null != p) ? p.GetValue(ctx, null).ToString() : null;

            if (string.IsNullOrWhiteSpace(loggerName))
            {
                if (null == ctx.Request.ParentRequest)
                {
                    throw new NullReferenceException(
                        "ParentRequest is null; unable to determine logger name; " 
						+ "if not injecting into a ctor a parameter for the "
						+ "logger name must be provided");
                }

                var service = ctx.Request.ParentRequest.Service;
                loggerName = service.FullName;
            }

            var log = (ILog)LogManager.GetLogger(loggerName, typeof(Log));
            return log;
        }
    }
}
</pre>

In some cases we can't easily do ctor injection - for example in the application class of Global.asax.cs or in a filter attribute. 
For these exception cases AppLogFactory is used to create an instance of ILog.<br/>

<pre class="brush:csharp; highlight: [37,38]">
using System;
using System.Diagnostics;
using System.Web.Mvc;
using Ninject;
using Ninject.Parameters;

namespace NLogSql.Web.Infrastructure.Diagnostics.Logging
{
    /// &lt;summary&gt;
    /// Creates a log object for those instances where one cannot be injected (i.e. app startup).
    /// Generally you should just ctor inject ILog
    /// &lt;/summary&gt;
    public static class AppLogFactory
    {
        public static ILog Create()
        {
            var declaringType = new StackTrace(1, false).GetFrame(1)
				.GetMethod().DeclaringType;

            if (declaringType != null)
            {
                var loggerName = declaringType.FullName;
                return Create(loggerName);
            }

            throw new InvalidOperationException(
				"Could not determine declaring type; specify logger name explicitly");
        }

        public static ILog Create&lt;T&gt;()
        {
            return Create(typeof(T).FullName);
        }

        public static ILog Create(string loggerName)
        {
            var log = Kernel.Get&lt;ILog&gt;(
				new ConstructorArgument(LogConstants.LoggerNameParam, loggerName));
            return log;
        }

        private static IKernel Kernel
        {
            get
            {
                return DependencyResolver.Current.GetService&lt;IKernel&gt;();
            }
        }
    }

    public class LogConstants
    {
        public const string LoggerNameParam = "loggerName";
    }
}
</pre>
<br/>

<h3>Exercising the Log</h3>

First a simple test, injecting ILog into a controllor ctor, ensuring it isn't null using the 
<a href="http://nuget.org/packages?q=ensure.that">Ensure.That</a> NuGet package and logging a count around a database fetch.<br/>

<pre class="brush:csharp; highlight: [10,16,18]">
public partial class HomeController : Controller
{
	private readonly IMappingService _mappingService;
	private readonly ILog _log;
	private readonly IMusicService _musicService;

	public HomeController(IMusicService musicService, ILog log, IMappingService mappingService)
	{
		_mappingService = Ensure.That(mappingService, "mappingService").IsNotNull().Value;
		_log = Ensure.That(log, "log").IsNotNull().Value;
		_musicService = Ensure.That(musicService, "musicService").IsNotNull().Value;
	}
	
	public virtual ActionResult Index()
	{
		_log.Debug("Retrieving genres");
		var genres = GetGenres().Result;
		_log.Info("Retrieved {0} music genres from the database", genres.Count);
		// ...
	}
}
</pre>

To get a little more value, an action filter to log each controller action executed along with the execution time.<br/>

<pre class="brush:csharp; highlight: [29,46,47]">
public class ActionTrackerAttribute : ActionFilterAttribute
{
	private Stopwatch Watch { get; set; }
	private ILog Log { get; set; }
	private ActionExecutingContext FilterContext { get; set; }

	private string ActionName
	{
		get { return FilterContext.ActionDescriptor.ActionName; }
	}

	private string ControllerName
	{
		get { return FilterContext.ActionDescriptor.ControllerDescriptor.ControllerName; }
	}

	private Uri Url
	{
		get { return FilterContext.RequestContext.HttpContext.Request.Url; }
	}

	public override void OnActionExecuting(ActionExecutingContext filterContext)
	{
		base.OnActionExecuting(filterContext);

		try
		{
			FilterContext = filterContext;
			Log = AppLogFactory.Create&lt;ActionTrackerAttribute&gt;();
			Log.Trace("Executing {0}.{1}", ControllerName, ActionName);
			Watch = Stopwatch.StartNew();
		}
		catch (Exception ex)
		{
			Trace.WriteLine(ex);
		}
	}

	public override void OnResultExecuted(ResultExecutedContext filterContext)
	{
		base.OnResultExecuted(filterContext);

		try
		{
			Watch.Stop();
			Log.Info("Executed {0}.{1} for {2} in {3:##0.000} second(s)", 
				ControllerName, ActionName, Url, Watch.Elapsed.TotalSeconds);
		}
		catch (Exception ex)
		{
			Trace.WriteLine(ex);
		}
	}
}
</pre>
<br/>

<h3>Troubleshooting Failed Logging</h3>

When setting up logging initially or making changes later, logging might not work. 
Usually I'd first fire up SQL Server Profiler and watch for calls to the log insert sproc. 
If I didn't see them then I'd know it's probably a configuration issue. In the nlog tag in the 
config file I set throwExceptions="true" internalLogLevel="Debug" internalLogFile="${basedir}/NlogInternal.log" 
but in practice that rarely seemed to do anything; turning off async temporarily may further help troubleshoot.
<br/><br/>

First I'd check when creating the log if the log levels were enabled as expected from the config.<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/LogWatch.jpg"/> 
<br/><br/>

Next I'd check the configuration in code with a small class, also useful for changing config at runtime.<br/>
<pre class="brush:csharp">
public class LogManagement
{
	public static AsyncTargetWrapper GetAsyncDbWrapperTarget()
	{
		var target = (AsyncTargetWrapper)LogManager.Configuration
			.FindTargetByName("asyncDbWrapperTarget");
		return target;
	}
}
</pre>

From there I'd further inspect the configuration to see if everything looks okay.<br/> 
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/LogManagementWatch.jpg"/> 
<br/><br/>

If I did see the insert sproc call in SQL Server Profiler then I'd check DB setup and permissions and grab the SQL from Profiler...<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/Profiler.jpg"/> 
<br/><br/>

... and paste into a query window, then format the Sql (I use <a href="http://architectshack.com/PoorMansTSqlFormatter.ashx">Poor Man's TSql Formatter</a>) and execute to check for errors.<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/SqlInsert.jpg"/> 
<br/><br/>

<h3>Evaluating Async Logging</h3>
Because I'm paranoid, OCD, detailed and curious I wanted to ensure the async behavior and compare to non-async. 
On the home page I added a link to generate batch log records, corresponding to this controller action.<br/>

<pre class="brush:csharp">
public virtual ActionResult BatchTest()
{
	var sw = Stopwatch.StartNew();
	const int count = 10000;
	for (var i = 0; i &lt; count; i++)
	{
		_log.Trace("Testing {0}", i);
	}
	sw.Stop();
	var msg = string.Format("{0} log invokes in {1:##0.000} seconds", count, 
		sw.Elapsed.TotalSeconds);
	_log.Info(msg);

	return new ContentResult { Content = msg };
}
</pre>

The async result:<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/BatchTestAsync.jpg"/> 
<br/><br/>

And the non-async result:<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/BatchTestNonAsync.jpg"/> 
<br/><br/>

<h3>Evaluating Log Queries</h3>
Usually when inspecting log records I'd select just those columns I'm interested in and order by LogId DESC.<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/LogEventRecords.jpg" width="750" height="195"/> 
<br/><br/>

Once enough log data was there it was helpful to query on the columns that would typically be filtered on and evaluate the execution plan and response times. 
From there indexes were added as needed for columns such as EventLevel, URL, UserName, etc. and execution times compared afterwards.
<br/><br/>

<h3>Preparing for Deployment</h3>
In Web.Config.Release NLog config transformations are performed to change the log level from Trace to Info, to remove debugger output, and to tone down NLog internal issue reporting.<br/>
<pre class="brush:xml">
&lt;nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xdt:Transform="SetAttributes"
        throwExceptions="false" internalLogLevel="Warn" 
		internalLogFile="${basedir}/NlogInternal.log"&gt;
    &lt;rules&gt;
       &lt;!-- Levels: Off, Trace, Debug, Info, Warn, Error, Fatal --&gt;
       &lt;logger name="*" minlevel="Info" writeTo="asyncDbWrapperTarget" 
	      xdt:Transform="SetAttributes" xdt:Locator="Match(name)" /&gt;
    &lt;/rules&gt;
&lt;/nlog&gt;
</pre>

<a href="http://visualstudiogallery.msdn.microsoft.com/69023d00-a4f9-4a34-a6cd-7e854ba318b5">Slow Cheetah</a> preview:<br/>
<a href="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/SlowCheetah.jpg" class="fancybox" title="Slow Cheetah Web.Config.Release" alt="Click for larger version">
	<img src="http://www.geoffhudik.com/storage/blogs/tech/2013/05/nlogsqlweb/SlowCheetahSmall.jpg" border="0"/> 
</a>

Note that removing the nlog xsd attribute in the root web.config (and all transforms) was needed for the transformation to work correctly. Also at one point I tried a replace transform to replace the entire nlog tag but found it didn't work correctly and it created too much duplicate content between configurations.
<br/><br/>

<h3>Some Queries</h3>
<br/>
Everything today:<br/>
<pre class="brush:sql">
SELECT *
FROM LogEvent WITH (NOLOCK)
WHERE PartitionKey = (
		SELECT DATEPART(weekday, getdate())
		)
ORDER BY logid DESC;
</pre>

Errors and warnings in the past 2 hours:<br/>
<pre class="brush:sql">
SELECT *
FROM LogEvent WITH (NOLOCK)
WHERE eventlevel IN (
		'Error'
		,'Warn'
		)
	AND DATEDIFF(hh, LogDate, GETDATE()) &lt;= 2
ORDER BY logid DESC;
</pre>

Errors by day and url:<br/>
<pre class="brush:sql">
SELECT cast(logdate AS DATE) [Day]
	,url
	,count(*) ErrorCount
FROM LogEvent WITH (NOLOCK)
WHERE eventlevel = 'Error'
	AND len(url) &gt; 0
GROUP BY cast(logdate AS DATE)
	,url
ORDER BY Day DESC
	,Url;
</pre>

Errors by logger name / class name / component area:<br/>
<pre class="brush:sql">
SELECT LoggerName
	,count(*) AS ErrorCount
FROM LogEvent WITH (NOLOCK)
WHERE EventLevel = 'Error'
GROUP BY LoggerName
ORDER BY count(*) DESC;
</pre>

Unhandled exceptions today:<br/>
<pre class="brush:sql">
SELECT *
FROM LogEvent WITH (NOLOCK)
WHERE PartitionKey = (
		SELECT DATEPART(weekday, getdate())
		)
	AND eventlevel = 'Error'
	AND LoggerName IN ('NLogSql.Web.Infrastructure.ErrorHandling.IErrorReporter')
ORDER BY logid DESC;
</pre>
<br/>

<h3>Future Enhancements / Considerations</h3>

<ul>
	<li>Web view to read, search, filter log maybe with some SignalR goodness</li>
	<li>AppId type column if logging from multiple apps w/o desire for multiple log databases</li>
	<li>Possibly a separate error table that the log table could link to for errors with the full error report split up</li>
</ul>
<br/>

<h3>The Code</h3>

The code for this series is available at <a href="https://github.com/thnk2wn/NLogSql.Web">https://github.com/thnk2wn/NLogSql.Web</a>]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-33685523.xml</wfw:commentRss></item><item><title>Using T4 to Generate Typesafe Enum Classes and Resource Files</title><category>I18N</category><category>T4</category><category>domain</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Tue, 02 Oct 2012 04:25:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2012/10/2/using-t4-to-generate-typesafe-enum-classes-and-resource-file.html</link><guid isPermaLink="false">632421:7353983:29567170</guid><description><![CDATA[In working on the domain layer of an application, I wrote a couple of typesafe enumeration classes that mirrored data in a couple of reference tables in a database. If you are not familiar with the pattern, Jimmy Bogard's <a href="http://lostechies.com/jimmybogard/2008/08/12/enumeration-classes/">Enumeration classes post</a> explains the pattern and rationale. The classes I wrote inherit from a modified version of the Enumeration class presented in Jimmy's post. The basic idea was to avoid switches and provide more functionality than an enum could offer. In our case the enum members mirrored a reference table and would be used for domain logic and for dropdown lists.
<br/><br/>

As the app progressed we had more of a need to do this for other reference tables, some of which had several records. At this point we decided to look into T4 to generate the typesafe enum classes. Since this application has a worldwide audience, the display names of the enum members needed globalization consideration since some of the enum values would end up in dropdown lists displayed to end users. I decided to create two T4 files, one to generate resource files with display name strings, and another to generate the enum classes that would use the resource strings.
<br/><br/>

<h3>Reference Datasource</h3>
This application uses XML files for various database definitions including reference data; these files get parsed to build the SQL Server database in automated fashion.
<br/><br/>

A sample of reference_data.xml looks something like:<br/>

<pre class="brush:xml; toolbar: false;">
&lt;referenceData&gt;
	&lt;refTable name="CONTACT_TYPE" alias="ContactType" default="Person"&gt;
		&lt;record CONTACT_TYPE_ID="1" CONTACT_TYPE_NAME="Company" RANK="2"&gt;
			&lt;langs field="CONTACT_TYPE_NAME"&gt;
				&lt;lang name="en"&gt;Company&lt;/lang&gt;
				&lt;lang name="de"&gt;Gesellschaft&lt;/lang&gt;
				&lt;lang name="es"&gt;Empresa&lt;/lang&gt;
				&lt;lang name="zh-CN"&gt;公司&lt;/lang&gt;
				&lt;lang name="fr"&gt;Soci&#233;t&#233;&lt;/lang&gt;
			&lt;/langs&gt;
		&lt;/record&gt;
		&lt;record CONTACT_TYPE_ID="2" CONTACT_TYPE_NAME="Person" RANK="1"&gt;
			&lt;langs field="CONTACT_TYPE_NAME"&gt;
				&lt;lang name="en"&gt;Person&lt;/lang&gt;
				&lt;lang name="de"&gt;Person&lt;/lang&gt;
				&lt;lang name="es"&gt;Persona&lt;/lang&gt;
				&lt;lang name="zh-CN"&gt;人&lt;/lang&gt;
				&lt;lang name="fr"&gt;Personne&lt;/lang&gt;
			&lt;/langs&gt;
		&lt;/record&gt;
	&lt;/refTable&gt;
&lt;/referenceData&gt;
</pre>

<br/>

<h3>Schema Metadata and Parsing</h3>

Since multiple projects needed to perform T4 code generation off database schema information, I defined some T4 include files in a shared project to define the schema structure and XML parsing logic. This would make the T4 code easier and prevent duplicating XML parsing logic in multiple T4 files.
<br/><br/>

First SchemaMetadata.ttinclude defines the classes that hold the schema information in a T4-friendly manner: <br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/SchemaMetadata.ttinclude.png"/>
<br/><br/>

Next SchemaReader.ttinclude would get consumed by T4 files to parse the XML data and return friendly objects defined in SchemaMetadata.ttinclude:<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/SchemaReader.ttinclude.png"/>
<br/><br/>

This shared T4 folder also included MultipleOutputHelper.ttinclude from <a href="http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited">this Damien Guard post</a> to make splitting T4 output into multiple files a bit easier.
<br/><br/>

<h3>RefDataResources.tt</h3>
This T4 file generates one resource file per language and will setup the custom tool to produce the designer generated file to reference the resources in code:<br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/RefDataResources_1.png"/>
<br/>

Inside the function block of this file the schema data is loaded and the resource files are generated:<br/>

<pre class="brush:csharp; toolbar: false;">
const string ReferenceData = "ReferenceData";

private SchemaMetadata LoadSchema()
{
	var loader = new SchemaReader(Host);
	var schema = loader.Load(SchemaReader.LoadOption.ReferenceData);
	return schema;
}

private void GenerateResourceFiles(SchemaMetadata schema, Manager manager, 
	DirectoryInfo t4DirInfo)
{
	var distinctLanguages = schema.ReferenceData.DistinctLanguages;
	foreach (var lang in distinctLanguages)
	{
		var resxNameNoExt = ("en" != lang) ? ReferenceData + "." + lang : ReferenceData;
		var resxName = resxNameNoExt + ".resx";
		manager.StartNewFile(resxName);

		var resxFilename = Path.Combine(t4DirInfo.FullName, resxName);
		// use .net's ResXResourceWriter so we don't have to worry about the XML format
		using (ResXResourceWriter  resx = new ResXResourceWriter(resxFilename))
		{
			var strings = schema.ReferenceData.StringsForLanguage(lang);

			foreach (var warn in schema.Warnings)
			{
				base.Warning(warn);
			}

			foreach (var de in strings)
			{
				try 
				{
					resx.AddResource(de.Key, de.Value);
				}
				catch (Exception ex)
				{
					base.Warning(ex.ToString());
				}
			}

			resx.Generate();
			resx.Close();
		}

		// we've written the file but outside the process of T4. 
		// In order to get the file to automatically added as a new output file 
		// underneath the t4 file, we must write the generated content to output stream
		Write(File.ReadAllText(resxFilename));

		manager.EndBlock();
		
	} // end for each lang loop
}
</pre>
<br/>

In the above code block there are a couple of things worth pointing out. First, the filename of the main / default language resource file will be ReferenceData.resx for English (en), otherwise ReferenceData.<b>lang</b>.resx for other languages. Second, the output from .NET's ResXResourceWriter gets read in with File.ReadAllText and written to T4 output with Write; otherwise the generated content would just exist on disk and would not get added into the project nested under the T4 file.
<br/><br/>

Finally, to get the designer generated class created, a function is created to set the custom tool property on the main ReferenceData.resx file that was generated. For the initial add that would be enough. However we also invoke execution of the custom tool with RunCustomTool() to handle the case where reference data is modified later on and the T4 transformation is performed again:<br/>

<pre class="brush:csharp; toolbar: false;">
private void SetCustomToolOnMainResourceFile(DirectoryInfo t4DirInfo)
{
	// WARNING: You are entering the dark land of EnvDTE COM. You've been warned
	var hostServiceProvider = (IServiceProvider) Host;
	var dte = (EnvDTE.DTE) hostServiceProvider.GetService(typeof(EnvDTE.DTE));
	var filename = Path.Combine(t4DirInfo.FullName, "ReferenceData.resx");
	var projectItem = dte.Solution.FindProjectItem(filename);
	projectItem.Properties.Item("CustomTool").Value = "PublicResXFileCodeGenerator";
	projectItem.Properties.Item("CustomToolNamespace").Value = "App.Domain";
	var projItemObj = (VSProjectItem)projectItem.Object;
	projItemObj.RunCustomTool();
}
</pre>
<br/>

Running the T4 produces the following files:<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/RefDataResourcesFiles.png"/>
<br/><br/>

The resource strings are created with a TableName_FieldNameValue format:<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/ReferenceDataResXData.png"/>
<br/>
<br/>

<h3>Enums.tt</h3>
Enums.tt reads the schema data just as in RefDataResources.tt and generates the C# typesafe enum class. The full source is available with the sample code for this post.<br/>
<a class="fancybox" href="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/Enums.tt.png" title="click for more">
	<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/Enums.tt.png"/>
</a>
<br/><br/>

A small example of the generated output (ContactType.generated.cs):<br/>

<pre class="brush:csharp; toolbar: false;">
//------------------------------------------------------------------------------
// &lt;auto-generated&gt;
//     This code was generated from a template.
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// &lt;/auto-generated&gt;
//------------------------------------------------------------------------------
using System;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;

namespace App.Domain
{
	public partial class ContactType : Enumeration
	{		
		/// &lt;summary&gt;
		/// ContactType of Company (ID: 1, "Company")
		/// &lt;/summary&gt;
		[GeneratedCode("TextTemplatingFileGenerator", "11")]
		public static readonly ContactType Company 
			= new ContactType( 1, ReferenceData.ContactType_Company );

		/// &lt;summary&gt;
		/// ContactType of Person (ID: 2, "Person")
		/// &lt;/summary&gt;
		[GeneratedCode("TextTemplatingFileGenerator", "11")]
		public static readonly ContactType Person 
			= new ContactType( 2, ReferenceData.ContactType_Person );


		[ExcludeFromCodeCoverage, DebuggerNonUserCode, GeneratedCode("TextTemplatingFileGenerator", "11")]
		private ContactType( int value, string displayName ) : base( value, displayName ) { }

		[ExcludeFromCodeCoverage, DebuggerNonUserCode, GeneratedCode("TextTemplatingFileGenerator", "11")]
		public static ContactType Default()
		{
			return Person;
		}

		[UsedImplicitly, Obsolete("ORM runtime use only")]
		[ExcludeFromCodeCoverage, DebuggerNonUserCode, GeneratedCode("TextTemplatingFileGenerator", "11")]
		private ContactType() { }
	}
}
</pre>
<br/>

Of course there's more value in automatically generating all of the reference enums or at least those with more members:<br/>
<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/Holiday.generated.cs.png"/>
<br/><br/>

<h3>Testing It Out</h3>

First some basic tests just to ensure the resource files and strongly typed resource class generated correctly:<br/>

<pre class="brush:csharp; toolbar: false;">
using System.Globalization;
using NUnit.Framework;
using System.Threading;

namespace App.Domain.Tests
{
    [TestFixture, Category("Unit")]
    public class ReferenceDataI18NTests
    {
        [Test]
        public void English_To_French_Strings_Change()
        {
            var orig = Thread.CurrentThread.CurrentUICulture;
            Assert.AreEqual("Company", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR");
            Assert.AreEqual("Société", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = orig;
        }

        [Test]
        public void English_To_Spanish_Strings_Change()
        {
            var orig = Thread.CurrentThread.CurrentUICulture;
            Assert.AreEqual("Company", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("es");
            Assert.AreEqual("Empresa", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = orig;
        }

        [Test]
        public void English_To_German_Strings_Change()
        {
            var orig = Thread.CurrentThread.CurrentUICulture;
            Assert.AreEqual("Company", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
            Assert.AreEqual("Gesellschaft", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = orig;
        }

        [Test]
        public void English_To_Chinese_Taiwan_Strings_Change()
        {
            var orig = Thread.CurrentThread.CurrentUICulture;
            Assert.AreEqual("Company", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = new CultureInfo("zh-CN");
            Assert.AreEqual("公司", ReferenceData.ContactType_Company);
            Thread.CurrentThread.CurrentUICulture = orig;
        }
    }
}
</pre>
<br/>

Just to make sure the Enumeration class works as expected, some tests to exercise it through one of the concrete classes:
<br/>

<pre class="brush:csharp; toolbar: false;">
using System.Linq;
using FluentAssertions;
using NUnit.Framework;

namespace App.Domain.Tests
{
    [TestFixture(
        Description = "Tests base Enumeration class via PhoneType concrete class")]
    [Category("Unit")]
    public class EnumerationTests
    {
        [Test]
        public void FromValue_Matches_Type_Value()
        {
            var fromValue = Enumeration.FromValue&lt;PhoneType&gt;(PhoneType.Cell.Value);
            Assert.AreEqual(PhoneType.Cell, fromValue);

            fromValue = Enumeration.FromValue&lt;PhoneType&gt;(PhoneType.Voice.Value);
            Assert.AreEqual(PhoneType.Voice, fromValue);
        }

        [Test]
        public void FromDisplayName_Matches_Type_Name()
        {
            var fromName = Enumeration.FromDisplayName&lt;PhoneType&gt;(PhoneType.Fax.DisplayName);
            Assert.AreEqual(PhoneType.Fax.DisplayName, fromName.DisplayName);

            fromName = Enumeration.FromDisplayName&lt;PhoneType&gt;(PhoneType.Pager.DisplayName);
            Assert.AreEqual(PhoneType.Pager.DisplayName, fromName.DisplayName);
        }

        [Test]
        public void ToString_Equals_DisplayName()
        {
            Assert.AreEqual(PhoneType.Cell.DisplayName, PhoneType.Cell.ToString());
        }

        [Test]
        public void Absolute_Difference_Math_Is_Correct()
        {
            var diff = Enumeration.AbsoluteDifference(PhoneType.Cell, PhoneType.Voice);
            Assert.AreEqual(3, diff);
            diff = Enumeration.AbsoluteDifference(PhoneType.Voice, PhoneType.Cell);
            Assert.AreEqual(3, diff);
        }

        [Test]
        public void GetAll_Contains_Expected_Members()
        {
            var all = Enumeration.GetAll&lt;PhoneType&gt;().ToList();
            Assert.AreEqual(4, all.Count);
            Assert.NotNull(all.FirstOrDefault(x =&gt; x == PhoneType.Cell));
            Assert.NotNull(all.FirstOrDefault(x =&gt; x == PhoneType.Fax));
            Assert.NotNull(all.FirstOrDefault(x =&gt; x == PhoneType.Pager));
            Assert.NotNull(all.FirstOrDefault(x =&gt; x == PhoneType.Voice));
            Assert.NotNull(all.FirstOrDefault(x =&gt; x == PhoneType.Default()));
        }

        [Test]
        public void Equality_Two_Are_Equal()
        {
            var one = PhoneType.Cell;
            var two = PhoneType.Cell;
            Assert.AreEqual(one, two);
            Assert.True(one == two);
        }

        [Test]
        public void Equality_Two_Different_Not_Equal()
        {
            var one = PhoneType.Cell;
            var two = PhoneType.Voice;
            Assert.AreNotEqual(one, two);
            Assert.True(one != two);
        }

        [Test]
        public void Equality_One_Null_Not_Equal()
        {
            PhoneType.Cell.Equals(null).Should().BeFalse();
        }

        [Test]
        public void Compare_To_Succeeds()
        {
            var diff = PhoneType.Cell.CompareTo(PhoneType.Fax);
            diff.Should().Be(1);

            diff = PhoneType.Fax.CompareTo(PhoneType.Cell);
            diff.Should().Be(-1);
        }

        [Test]
        public void Invalid_Parse_Number_Throws()
        {
            Assert.That(() =&gt; { Enumeration.FromValue&lt;PhoneType&gt;(999); }, Throws.Exception);
        }
    }
}
</pre>
<br/>

<h3>There's More</h3>

<ul>
	<li style="margin-bottom:8px;">For simplicity some complexity was removed from the T4. This includes reference tables that have foreign keys to other reference tables (including strongly typed properties and ctor params) and resource strings for fields other than the primary display name.</li>
	
	<li style="margin-bottom:8px;">One thing that bothered me initially with this was it felt a bit like introducing Data Driven Design into an otherwise Domain Driven Design paradigm. This was offset somewhat with aliases for table names (or a naming convention pattern) and reference tables could be selectively ignored in code generation through the use of attributes or other means. Another possible issue is class name conflicts with existing types in the domain project; this could be offset with a different namespace and/or naming convention.</li>
	
	<li style="margin-bottom:8px;">Many apps load reference data from the database each time it's needed, or load it once and cache it until invalidated. Other apps may let users edit select sets of reference type data. If the data is likely to change during an app session or if users can edit some of it, chances are it isn't truly reference data to begin with. With a good deployment process, any compiled reference data info can be easily deployed and various reference data is likely to be tied to app business and presentation rules anyway.</li>
	
	<li style="margin-bottom:8px;">Several of the enum classes would have corresponding partial classes for extended logic. For this reason, various code generation type attributes were places directly on generated members and not on the class itself, per <a href="http://blogs.msdn.com/b/codeanalysis/archive/2007/04/27/correct-usage-of-the-compilergeneratedattribute-and-the-generatedcodeattribute.aspx">various guidance</a>.
	</li>
	
	<li style="margin-bottom:8px;">An example use in the domain would be a ContactType enum property on a Contact object and requiring different fields when attempting to add contacts of different types. This app uses <a href="http://stackoverflow.com/questions/6366956/mapping-an-iusertype-to-a-component-property-in-fluent-nhibernate">this component strategy</a> in Fluent NHibernate to map the data from the database reference table into the domain class. For the UI side, Enumeration.GetAllMembers can be used along with AutoMapper to get the id and text values into simple ViewModel types for select lists.
	</li>
</ul>

<h3>Code</h3>

<a href="http://www.geoffhudik.com/storage/blogs/tech/2012/10/t4refdata/T4RefDataCode.zip">T4RefDataCode.zip</a>]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-29567170.xml</wfw:commentRss></item><item><title>AutoMapper's ForMember</title><category>AutoMapper</category><category>C#</category><category>mapping</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Fri, 03 Aug 2012 21:24:17 +0000</pubDate><link>http://www.geoffhudik.com/tech/2012/8/3/automappers-formember.html</link><guid isPermaLink="false">632421:7353983:21272323</guid><description><![CDATA[Calling AutoMapper's ForMember has been bugging me lately with having to deal with its member configuration options like so:
<br/>

<pre class="brush:csharp; toolbar: false;">
Mapper.CreateMap&lt;Contact, AddressBookDetailsModel&gt;()
	.ForMember( d =&gt; d.PhoneNumbers, o =&gt; o.MapFrom( s =&gt; s.Phones ) );
</pre>

The member configuration options provides much more than just MapFrom but 99 times out of 100 I'm only dealing with MapFrom. 
What I'd really like is something like this:
<br/>

<pre class="brush:csharp; toolbar: false;">
Mapper.CreateMap&lt;Contact, AddressBookDetailsModel&gt;()
	.ForMember( d =&gt; d.PhoneNumbers).MapFrom( s=&gt; s.Phones );				
</pre>

In looking around I did find <a href="http://trycatchfail.com/blog/post/A-More-Fluent-API-For-AutoMapper.aspx">http://trycatchfail.com/blog/post/A-More-Fluent-API-For-AutoMapper.aspx</a>. 
It looked promising but (a) the code was incomplete and (b) it broke down for more complex mappings.
<br/><br/>

I'd like the syntax above but for now I went with something quicker to implement that gave me a shorter syntax though it might not be as syntactically sweet:
<br/>	
	
<pre class="brush:csharp; toolbar: false;">
public static class AutoMapperExtensions
{
	public static IMappingExpression&lt;TSource, TDestination&gt; MapItem&lt;TSource, TDestination, TMember&gt;(
		this IMappingExpression&lt;TSource, TDestination&gt; target,
		Expression&lt;Func&lt;TDestination, object&gt;&gt; destinationMember,
		Expression&lt;Func&lt;TSource, TMember&gt;&gt; sourceMember )
	{
		return target.ForMember( destinationMember, opt =&gt; opt.MapFrom( sourceMember ) );
	}
}
</pre>

Now when creating maps I don't need to fuss with an extra lambda and method call:
<pre class="brush:csharp; toolbar: false;">				
Mapper.CreateMap&lt;Contact, AddressBookDetailsModel&gt;()
	.MapItem( d =&gt; d.PhoneNumbers, s=&gt; s.Phones );				
</pre>

I can do something similar to simplify Ignore calls:<br/>

<pre class="brush:csharp; toolbar: false;">		
public static class AutoMapperExtensions
{
	public static IMappingExpression&lt;TSource, TDestination&gt; Ignore&lt;TSource, TDestination&gt;(
		this IMappingExpression&lt;TSource, TDestination&gt; target,
		Expression&lt;Func&lt;TDestination, object&gt;&gt; destinationMember)
	{
		return target.ForMember( destinationMember, opt =&gt; opt.Ignore() );
	}
	
	/* other code removed for brevity */
}
</pre>]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-21272323.xml</wfw:commentRss></item><item><title>A Week With the New MacBook Air</title><category>Apple</category><category>Mac</category><category>MacBook</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Mon, 16 Jul 2012 01:00:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2012/7/15/a-week-with-the-new-macbook-air.html</link><guid isPermaLink="false">632421:7353983:18174076</guid><description><![CDATA[<h3>Time for a New Computer</h3>

My mid-2009 MacBook Pro has been good to me as my first Mac. However after 3+ years it was showing its age with slow performance, particularly with any heavy disk access and especially running Windows. It stopped resuming from sleep quite a bit and started locking up more. I had previously upgraded it from 4 to 8 GB of memory and considered switching it to use an SSD but wanted more. I usually end up replacing my computers every 2-4 years or so anyway with 3 being the average of late. So I decided to start backing everything up with <a href="http://www.crashplan.com/">CrashPlan</a> and start shopping around.
<br/><br/>

<h3>To Stick with Apple or Not</h3>

I've always been a Windows guy and a Microsoft developer and have been happy there personally and professionally. I originally bought a Mac for iPhone development and because I thought it was wise to diversify and mix things up. I've certainly learned a lot and have enjoyed the Mac life for the most part. However I still prefer Windows 7 over Mac OS X in many ways; I'm more a fan of iOS than OS X. I still feel a Mac at home and Windows at work is a good arrangement, I like Apple's innovation and hardware, and I can run both operating systems well on a Mac but not the other way around. That's enough to keep me locked into the "dark side" for now.
<br/><br/>

<h3>Apple's New Lineup</h3>

I was really tempted to buy the new retina MackBook Pro after drooling a bit at the Apple Store. The display was beautiful to look at, especially text readability and any high-res images, and I liked the smaller form factor and extra power, among other things. 
<br/><br/>

However there were a few things with the rMBP that gave me pause:

<ul>
	<li style="margin-bottom: 8px;"><b>Price</b> - A 256GB HD isn't large enough for me so that means the $2800 model; add in tax and AppleCare and it's up to nearly $3,500. Talk about $ticker $hock.</li>

	<li style="margin-bottom: 8px;"><b>Portability</b> - After using a MacBook Air for a bit, that light, super portable form factor is hard to give up. The retina MBP is quite lighter than my MBP but it still feels heavy and large next to the Air.</li>

	<li style="margin-bottom: 8px;"><b>The Bleeding Edge</b> - The icons in many third party apps look pretty bad with such an HD display. While major apps were/are updated quickly, others will take some time. I have more concerns about website images which aren't likely to be upgraded any time soon just to clean them up for HD displays at the cost of slower load times. I've also seen posts regarding plasma style image burn-in, overheating, and resolution issues in Windows to name a few.
	</li>
</ul>

Those things were enough for me to decide against a rMBP for now. I considered the standard MBP which was still refreshed enough and a decent middle ground between the extremes of the rMPB and the Air. Ultimately though it was still pricey and portability is more important to me these days. So I decided it was time to look seriously at the MBA.
<br/><br/>

<h3>Evaluating the Air</h3>

The initial Air model seemed more like a lightweight netbook for email and surfing by execs. Last year's model seemed beefed up enough for several developers to be happy with them as dev machines. With the 2012 model especially I felt like they had sufficient power and the benchmarks beat my MBP. I decided to go ahead and buy a fully loaded 13" Air with 512GB flash storage, 8GB RAM, and a 2.0GHz dual core i7. I figured I had 14 days to return it if it didn't work for me. 
<br/><br/>

<h3>Initial Impressions</h3>

<h4>Connections</h4>
Connecting my Apple LED Cinema display (prior generation model) was a little awkward. First the power cord doesn't fit the Air; I knew this ahead of time but wasn't fond of another cord to run and leaving the monitor's laptop power cord hanging felt strange. The other minor annoyance was that the thunderbolt port was on the right side of the Air, instead of the left side where the Mini DisplayPort is on my MBP. This meant a further, tighter cord stretch with my desk setup. However those are minor complaints and at least Thunderbolt is backwards compatible with Mini DisplayPort connections and the monitor still works great.
<br/><br/>

I decided to bite the bullet and spring for a $99 super drive. While I rarely use optical discs, I did have some installs of large software packages that I preferred not to re-download, and some older software that can't easily be found online. Additionally I have data on some discs that I occasionally need to read and didn't want to connect a really old and bulky external disc drive I had. 

<h4>Weight and Size</h4>
I got an idea of weight and size of the Air in the Apple store but it wasn't until using it at home that I really appreciated it. My MBP which never felt *that* heavy suddenly felt like a tank. Before I rarely unplugged it and removed it from my desk but with the Air I found myself taking it all over instead of reaching for my iPad as much or returning to my desk.

<h4>Speed</h4>
Cold boot time with the SSD (my first) was just over 20 seconds, including my fumbling around with the mouse and keyboard to type my password. Installs and app launches were very zippy and I was quite pleased. Going to and from sleep was nearly instantaneous.
<br/><br/>

<h3>Bootcamp Debacle</h3>

<h4>To Bootcamp or not to Bootcamp</h4>
Bootcamp bothered me in the past with subpar drivers from Apple and having to partition the hard disk and dual boot or pay a performance penalty using a VM like Parallels or Fusion against Bootcamp. However there were enough issues with going VM-only that brought me back to Bootcamp:

<ul>
	<li style="margin-bottom: 8px;"><b>Phone emulators</b> - Some mobile dev emulators such as Windows Phone wouldn't run under Parallels; the VM inside a VM Inception type problem. However I believe Fusion worked and maybe this has since been fixed in Parallels.</li>

	<li style="margin-bottom: 8px;"><b>Silverlight</b> - there was at least one Silverlight runtime issue that was specific to running inside a VM.</li>

	<li style="margin-bottom: 8px;"><b>Compatibility, performance</b> - some other select apps and games had problems running virtually either compatibility wise or performance wise.</li>
</ul>

To the best of my knowledge the situation with these types of issues hasn't changed much. Issues aside, if I'm going to be doing longer periods of Windows development on my Mac, it is convenient to just boot into Windows in ways (especially with the speedy SSD). So bootcamp it is.
<br/>

<h4>Bootcamp Setup While Half Asleep</h4>
I made the mistake of setting up Bootcamp while half asleep and without coffee and I gave it no forethought whatsoever. I did this 3 years ago after all, who needs forethought? So have a laugh at my expense. Go on.
<br/><br/>

<div style="text-decoration:underline;">Fail 1</div>
Attempted to get away with 4GB USB thumb drive. Win7 was a bit over 3GB but w/bootcamp drivers etc. it wasn't enough. 8GB thumb drive it is.
<br/><br/>

<div style="text-decoration:underline;">Fail 2</div>
Tried creating an ISO from a Win7 DVD using Toast and my MBP. Somehow it produced a corrupt ISO.
<br/><br/>

<div style="text-decoration:underline;">Fail 3</div>
Tried creating an ISO from a Win7 DVD using Disk Utility and my MBP:
<ul>
	<li>Selected disc in Disk Utility</li>
	<li>File-&gt;New Disc Image From</li>
	<li>Chose the CD/DVD .cdr option</li>
	<li>Saved .cdr to my public folder</li>
	<li>Renamed .cdr to .iso (seriously Apple why not .iso)</li>
	<li>Pointed Bootcamp assistant on my Air to Win7 iso on my MBP public folder</li>
	<li>Copied 25% of Windows files and hung indefinitely</li>
</ul>

<div style="text-decoration:underline;">Fail 4</div>
At this point I start to wake up and remembered I had the superdrive. Forget the dang thumbdrive. In Bootcamp Assistant I dedicated 200GB to the Windows partition, leaving some 230GB to the Mac side. However Bootcamp assistant did not give the option of using the external disc drive; only a thumb drive. I was able to uncheck that but when rebooting it tried to boot off the thumb drive and not the DVD. I tried holding down the Option key and selecting the disc drive but received a "CDBOOT: Couldn't find BOOTMGR" error.
<br/><br/>

<div style="text-decoration:underline;">Fail 5 / Win 1</div>
Okay well that disc should have been bootable but maybe the Air doesn't allow booting off a Windows disc from an external disc drive. Fine, back to the thumb drive it is; I copied the iso from my Air to it and went back to Bootcamp Assistant. I chose the same partition size as before and I rebooted. Now I was in Windows setup and after formatting the bootcamp partition I was into Windows and all looked good for a moment. Then I noticed networking wasn't working along with about everything else. I realized the bootcamp drivers weren't installed and for some crazy reason I thought for a moment that they should have been automagically installed already. Coffee Geoff, coffee. I browsed to the bootcamp setup on the thumb drive and afterwards all was right with the world.
<br/><br/>

<h3>Setting Up For Development</h3>
After about a hundred Windows Updates, I installed Visual Studio 2010 w/SP1, VS2012 RC, ReSharper, various VS extensions, DropBox, SnagIt, SkyDrive, and other misc apps. I fired up Visual Studio 2012 and opened some projects and all felt good. However I was more curious of how it performed from the Mac side. I installed Parallels 7 and dedicated 3GB of RAM and 2 CPUs to the bootcamp VM.
<br/><br/>

<h3>Performance</h3>
Here is screencast I captured that gives an idea of performance.

<br/><br/>
<iframe src="http://player.vimeo.com/video/45681666" width="500" height="313" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
<br/><br/>

So I could launch Windows and startup apps in a VM, open VS 2012 w/various addons, load a modest sized solution like SignalR, build and run it in ~2 minutes which is certainly acceptable to me.
<br/>

<h4>Other Misc. Timings</h4>
<ul>
	<li>Windows and VS 2012 fully loaded and ready via Parallels: ~45 seconds</li>
	<li>git clone SignalR: &lt; 8 seconds</li>
	<li>Initial SignalR build.cmd run (includes executing unit tests): under 2 mins</li>
	<li>Cold boot time into Mac including entering password and startup apps: &lt; 22 seconds</li>
</ul>

<h4>MBP Comparision</h4>
The performance blows away my MBP. Getting Windows and VS 2012 fully loaded via Parallels for example could take as long as 13 minutes on my MBP, compared with 45 seconds on the Air. Although then again my MBP doesn't have an SSD and that's the majority of the difference along with 3 years of age. It would be more interesting to compare development related timings between the Air and the rMBP.
<br/>

<h4>The Boot Selection Screen</h4>
When holding down the Option key at startup to boot Windows, it took longer than on my MBP to kick off Windows after selecting it and hitting Enter. My best guess was the addition of the WiFi dropdown on the Air that I do not get on my MBP. I have several wireless networks in my area and it appears the delay is trying to scan and list them all. I wonder if that feature can be turned off?
<br/><br/>

<h3>Putting it to Work</h3>

<h4>Heat and Fan</h4>
Over more extended Visual Studio development sessions (Parallels or straight boot camp) or during movie editing with Quicktime or iMovie, the laptop got a bit warm and the fan would kick on for a while and seemed rather noisy. Part of that is more noticeable because of the otherwise silent operation I think. In general the Air didn't get too hot or too loud for too long. 
<br/>

<h4>Battery Life</h4>
Battery life was excellent, coming close to the the advertised 7 hour mark, at least while staying in Mac land. Rebooting into Windows and staying there drained it a good deal quicker but still pretty good life.
<br/>

<h4>Windows</h4>
It is a shame the bootcamp drivers dumb things down so Windows cannot take full advantage of the hardware like the Mac side can (graphics switching, CPU boost, smarter power mgt). I'm not sure whether to buy the conspiracy theories that Apple is trying to make Windows look bad or if it is more that there is not much incentive for them to take the kind of time to support that.
<br/>

<h4>A Freak Dock Issue</h4>
One strange issue happened when I plugged in the Air to charge it and then accidentally deleted the Desktop background image. The fan kicked on hard and the CPU usage went up to almost 100%. When I checked things out it was the Dock process. Killing it and other apps didn't help. A reboot fixed the issue and it hasn't happened since. I did see others mentioning a similar issue online in reference to an older version of Parallels.
<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/07/DockCPU.jpg"/>

<br/><br/>

<h3>Summary</h3>
At the end of the day this is still a modestly powered portable laptop and not a power workstation. So far though I do not see any indication that it can't serve well as my primary personal development machine. I'm sure over time as I load it up more it'll slow down and it won't be ideal for heavy HD video editing or games. Worst case scenario I'll switch over to my work laptop or back to a desktop for heavy duty work should it get to that but I'm not sure it will. I would rather sacrifice some power for the portability, price and battery life and I cannot see myself giving this up yet. Time will be the ultimate test but so far, so good.]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-18174076.xml</wfw:commentRss></item><item><title>Trying Out Jobs in PowerShell</title><category>Build</category><category>powershell</category><category>release management</category><category>scripting</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Wed, 04 Jul 2012 20:51:51 +0000</pubDate><link>http://www.geoffhudik.com/tech/2012/7/4/trying-out-jobs-in-powershell.html</link><guid isPermaLink="false">632421:7353983:17316594</guid><description><![CDATA[An older app in our workplace stack is a webforms website project and it has a large enough directory structure to take a while to compile the site. 
I have to run the site a fair amount for a rewrite effort and it changes enough to make the initial build and run painfully slow. 

<br/><br/>
Since I have been working on a build related PowerShell module lately, I thought precompiling the "legacy" site might be a good candidate for a background job. 
I have not used jobs in PowerShell before and for whatever reason I was having a hard time finding good, complete examples of using them. There were also 
some things that tripped me up, so here is an example for the future version of me to reference some day.
<br/>

<pre class="brush:powershell">
param (
	$TrunkPath = "D:\Projects\MyApp\trunk"
)	

function Start-WebsitePrecompile
{    
    $logFile = (join-path $TrunkPath "\build\output\logs\BackgroundCompile.log")
    "Build log file is $logFile"
        
    $msg = @"
Starting background compile of site. Use Get-Job to check progress. 
You may go on about your merry way but may want to leave the host open until complete.
"@
    
    Write-Output $msg
    $job = Start-Job -InputObject $TrunkPath -Name MyAppPageCompile -ScriptBlock {
        # doesn't appear transcription is supported here
        $trunk = $input

        Set-Alias aspnetcompile $env:windir\Microsoft.NET\Framework\v4.0.30319\aspnet_compiler.exe
        
        # see website solution file for these values
        $virtualPath = "/web"
        $physicalPath = (join-path $trunk "\web")
        $compilePath = $trunk + "\PrecompiledWeb\web"
        aspnetcompile -v $virtualPath -p $physicalPath -f -errorstack $compilePath        
    }
    
    # output details
    $job    
   
    Register-ObjectEvent $job -MessageData $logFile -EventName StateChanged `
        -SourceIdentifier Compile.JobStateChanged `
        -Action {            
            $logFile = $event.MessageData
            Set-Content -Force -Path $logFile `
            	-Value $(Receive-Job -Id $($Sender.Id) -Keep:$KeepJob | Out-String)
            #$eventSubscriber | UnregisterEvent
            Unregister-event -SourceIdentifier Compile.JobStateChanged
            $eventSubscriber.Action | Remove-Job
            Write-Host "Job # $($sender.Id) ($($sender.Name)) complete. Details at $logFile." 
        }
}
</pre>
<br/>

<h3>Some Notes</h3>

<ul>
	<li style="margin-bottom: 9px;">Everything inside the job's script block will be executed in another PowerShell process; anything from outside the script block 
	that needs to be used inside must be passed into the script block with the InputObject parameter ($input). While this might be obvious it does mean potential refactoring 
	considerations.  
	</li>	
	<li style="margin-bottom: 9px;">It didn't appear transcription was supported inside the script block which was disappointing.</li>
	<li style="margin-bottom: 9px;">I half expected that Start-Job would provide a parameter for a block to call when the job was complete. 
		Register-ObjectEvent works but it's a bit verbose and isn't even mentioned in many posts talking about job management.</li>
	<li style="margin-bottom: 9px;">Like the job script block, the event handler action script block cannot refer to anything from the outside other than 
		anything passed into the block with the MessageData parameter and automatic variables such as $event, $eventSubscriber, $sender, $sourceEventArgs, and $sourceArgs.</li>
	<li style="margin-bottom: 9px;">I went through some trial and error in getting the output from the job in the completed event. The code on line 36 and 37 worked fine 
		but it was not the most obvious initial syntax.
	</li>
	<li style="margin-bottom: 9px;">There are a couple of ways to unregister events such as line 38, but I found that when I called the function again I received an error 
		that the event was already subscribed to, so it was clear the unregistration was not working for some reason. The current code is working but similar code did not 
		work previously. I dunno man, gremlins. 
	</li>
	<li style="margin-bottom: 9px;">Event handler cleanup strikes me as a bit odd and this <a href="http://poshcode.org/2205">Register-TemporaryEvent</a> script is worth a look.</li>
</ul>

I am tempted to refactor more of this developer build module process to use more background jobs to do more in parallel. However it is a bit tricky in that many of the functions 
are called both individually and chained together in driver functions and they need to work both in "foreground" and "background" modes. It would also mean a loss of rich progress 
reporting and things get more difficult in terms of debugging, output, code sharing, etc. Also, while multiple cores may help with parallel work, there's a law of diminishing returns 
to be considered as well as machine performance while attempting to do other work while PowerShell crunches away.]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-17316594.xml</wfw:commentRss></item><item><title>PowerShell Activity Progress with Time Estimates</title><category>powershell</category><category>scripting</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Mon, 25 Jun 2012 03:00:00 +0000</pubDate><link>http://www.geoffhudik.com/tech/2012/6/24/powershell-activity-progress-with-time-estimates.html</link><guid isPermaLink="false">632421:7353983:16924241</guid><description><![CDATA[<h3>The Goal</h3>
Recently I worked on a PowerShell module to do various application build related functions on development machines. One side objective was multi-level activity progress reporting with time estimation so activity duration could be gauged over time. The desire was for this to be done generically and quickly as it was more of a nice-to-have.
<br/><br/>

<h3>In Action</h3>
While running this, progress reporting looked something like the below.<br/><br/>

<img src="http://www.geoffhudik.com/storage/blogs/tech/2012/06/powershellactivitytime/PowerShellActivityTime.jpg">
<br/><br/>

<h3>Calls to Record Activity Progress</h3>

Some low-level functions in this module might be called directly at times and other driver functions might get called to chain together several activities. Either way progress is recorded at each level. Each function simply invokes a call to record the start of an activity as the first step and stopping it as the last.
<br/>

<pre class="brush:powershell">
function New-Build() # params omitted, body simplified for brevity
{	
    Start-Activity "Performing a new build"
	$percentComplete = 0
	
	Log-Progress "Creating code" -PercentComplete $percentComplete		
	New-CodeGen
	$percentComplete += 33
	
	Log-Progress "Creating config" -PercentComplete $percentComplete		
	New-Config
	$percentComplete += 33
	
	Log-Progress "Creating DB" -PercentComplete $percentComplete
	New-DB -common
	
	Stop-Activity
}

function New-CodeGen ([bool]$buildStatusUpdate = $true)
{	
    Start-Activity "Running CodeGen with NAnt"
	# ... real work done here ...
	Stop-Activity
}

# ...
</pre>
<br/>

<h3>Starting an Activity</h3>

First a stack is created at the script level to store the activities. The Start-Activity function creates a new object to store the activity name, start time, total duration and estimated duration (more on that later). This information is pushed onto the stack and output is sent to Write-Progress and Write-Output. 
<br/>

<pre class="brush:powershell">
$_activityStack = new-object Collections.Stack

function Start-Activity([string]$activity = $(throw "activity is required"))
{
    Write-Output "Activity starting: $activity"
    
    $act = new-object PSObject
    $act | add-member -membertype NoteProperty -name "StartTime" -value $(get-date)
	$act | add-member -membertype NoteProperty -name "Name" -value $activity    
	$act | add-member -membertype NoteProperty -name "TotalSeconds" -Value 0
    $act | add-member -membertype NoteProperty -name "EstimatedSeconds" `
		-Value (Get-ActivityEstimatedSeconds $activity)
    
    $_activityStack.Push($act)
    Log-Progress $activity
}
</pre>
<br/>

<h3>Stopping an Activity</h3>

Stop-Activity will pop the most recent activity off the stack and calculate the duration. It then writes the completion data to progress and output as well as to a stats file that stores the durations by activity name.
<br/>

<pre class="brush:powershell">
function Stop-Activity
{
    $id = $_activityStack.Count
    $act = $_activityStack.Pop()
    $ts = $(get-date) - $act.StartTime
	$act.TotalSeconds = $ts.TotalSeconds
    $time = ""
    
    if ($ts.TotalMinutes -ge 1) { $time = "{0:##.00} minute(s)" -f $ts.TotalMinutes }
    else { $time = "{0:##.00} second(s)" -f $ts.TotalSeconds }
    
	# TODO: add in $act.EstimatedSeconds if > 0
    $status = ("'{0}' complete in {1}" -f $act.Name, $time)
    Write-Progress -Activity $act.Name -Status $status -Completed -Id $id
    Write-Output $status
	
	Write-Stats $act 
}
</pre>
<br/>

<h3>Writing Activity Stats</h3>

Write-Stats takes in the activity object and adds it to an array. If the stats (CSV) filename exists it reads it in, sorts the data in descending time order, 
adds up to $maxKeep (50) existing records into the array, and removes the existing file. The stats filename is then written out with the most recent records.
<br/>

<pre class="brush:powershell">
function Write-Stats ($act = $(throw "activity is required"))
{	
	# initialize an array to hold recent stats for this activity
 	$recent=@()
	$recent += $act
	$statsFile = Get-StatsFilename
	$maxKeep = 50 # across all activities; several different, want a few of each
	
	if (Test-Path $statsFile)
	{
	 	# get a list of the $maxKeep-1 most recent stats and add each path to the $recent array
		# | Where-Object {$_.Name -eq $act.Name}
	 	Import-CSV $statsFile | Sort StartTime -Descending `
			| Select -Last ($maxKeep -1) | foreach {$recent+=$_}
		# remove the file as we have the data in memory and want to re-write w/top item # and desc time sort
		Remove-Item $statsFile -force
	}
	
	$recent | select StartTime, Name, TotalSeconds | Export-Csv $statsFile -NoTypeInformation
}
</pre>
<br/>

<h3>Getting Activity Estimated Time</h3>

Time estimation is done via reading in the CSV file, filtering on the activity name, adding the completion time for each to an array, and averaging those values.
<br/>

<pre class="brush:powershell">
function Get-ActivityEstimatedSeconds([string]$activityName)
{
    $statsFile = (Get-StatsFilename)
    $avg = -1
    
    if (Test-Path $statsFile) 
    {
        $totalSeconds=@()
        Import-CSV $statsFile | Where-Object {$_.Name -eq $activityName} `
			| foreach {$totalSeconds+=$_.TotalSeconds}
        $m = $totalSeconds | measure-object -ave
        $avg = $m.Average
    }	
	
    return $avg
}
</pre>
<br/>

<h3>Misc. Functions</h3>

Log-Progress writes both to standard out and to progress. It retrieves the current activity without removing it from the stack, formats the estimated completion time 
calculated earlier, and adds that to the progress information. The number of current activities is used as the progress bar id since there will be multiple levels; in 
my case everything is done serially. Initially I set the estimated seconds argument on Write-Progress but found it misleading; my script shells out to various other apps 
and that is blocking - it won't update via a timer or anything like that.
<br/>

<pre class="brush:powershell">
function Log-Progress([string]$msg, [int]$percentComplete = 0)
{
    Write-Output $msg
    $act = $_activityStack.Peek()
    $id = $_activityStack.Count
	# the problem with -SecondsRemaining is it won't auto update w/timer or anything 
	# so it will be helpful at first and then quickly misleading
	
	$actName = $act.Name
	
	if ($act.EstimatedSeconds -ge 0)
	{
		$estFinish = $act.StartTime.AddSeconds($act.EstimatedSeconds)
		$actName += " - Est. Finish @ " + $estFinish.ToString("hh:mm:ss tt")
	}
	
    Write-Progress -Activity $actName -Status $msg -PercentComplete $percentComplete `
        -Id $id #-SecondsRemaining $act.EstimatedSeconds
}
</pre>

For simplicity all activity details are stored in the same file, up the max limit defined in Write-Stats.
<br/>

<pre class="brush:powershell">
function Get-StatsFilename
{
	# considered a separate file per activity but we can filter out of one file
	# too much clutter w/sep and would have to build a safe filename with activity name
	$file = (Join-Path (Get-StatsFilePath) "Stats.csv")
	return $file
}

function Get-StatsFilePath
{
	$path = (join-path $env:LOCALAPPDATA "MyCompany\MyApp\build_output\stats")
	if (!(Test-Path $path)) { New-Item $path -type directory | Out-Null }
	return $path
}
</pre>
<br/>

<h3>In Conclusion</h3>

Other options on timing activities include things like the <a href="http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx">Stopwatch</a> class 
or PowerShell's <a href="http://technet.microsoft.com/library/ee176899.aspx">Measure-Command</a>. I'll use those more for ad-hoc measuring here or there. In 
the case of my module though, I found this "CSV stack" approach to work well as a simple, generic way to time everything across the board. If there are a large number 
of activity records being kept, deep function chaining, and/or threading work this approach might be a bit more problematic and slow.]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-16924241.xml</wfw:commentRss></item><item><title>CodeStock 2012 Windows Phone App</title><category>WP7</category><category>conferences</category><category>mobile</category><category>windows phone</category><dc:creator>Geoff Hudik</dc:creator><pubDate>Wed, 16 May 2012 18:50:37 +0000</pubDate><link>http://www.geoffhudik.com/tech/2012/5/16/codestock-2012-windows-phone-app.html</link><guid isPermaLink="false">632421:7353983:16294898</guid><description><![CDATA[<table cellspacing="18" style="margin-top:-10px;">
<tr>
<td valign="top">
The <a href="http://codestock.org">CodeStock</a> conference is coming up soon on June 15th in downtown Knoxville. As such I have upgraded the 
<a href="http://www.geoffhudik.com/tech/2011/5/10/codestock-2011-app-for-windows-phone-7.html">CodeStock 2011 Windows Phone app</a> for this year's 
conference and am pleased to announce it has been published to the marketplace.
<br/><br/>
</td>
<td valign="top" width="200">

<div style="margin-bottom:10px;">
<a style="font-size: larger;" href="http://windowsphone.com/s?appid=8b065e23-79bb-4a3c-8f65-0bc26a17ffbb">Marketplace</a><br/>
</div>


<div style="margin-bottom:10px;">
	<a style="font-size: larger;" href="https://vimeo.com/42290848">Video</a>
</div>

<div style="margin-bottom:0px;">
	<a style="font-size: larger;" href="https://github.com/thnk2wn/codestock-winphone">Source Code</a>
</div>

</td>
</tr>
<tr>
<td colspan="2">
<h3>Changes Over Last Year's Version</h3>
I have not had much time this year to enhance the application but below are highlights of what changed for the 2012 version. For more details see the 
<a href="https://github.com/thnk2wn/codestock-winphone/commits/master">commit history</a>. 

<ul>
	<li style="margin-bottom: 7px;"><b>Mango upgrade</b> - OS target upgrade from 7.0 to 7.1 and many fixes to address breaking changes</li>
	<li style="margin-bottom: 7px;"><b>Map view</b> - new map view with various points of interest and directions. Points of interest are 
		externally configured so if you have suggestions, let me know.</li>
	<li style="margin-bottom: 7px;"><b>Branding</b> - logo, title, date, image and theme related changes</li>
	<li style="margin-bottom: 7px;"><b>Source control and hosting</b> - switch from HG and bitbucket to git and github</li>
	<li style="margin-bottom: 7px;"><b>New app, not an upgrade</b> - Due to various factors this year's version was submitted as a new app and last year's was hidden. If by chance you have last year's version installed, uninstall it first.</li>
</ul>
<br/>

<h3>Potential Future Changes</h3>
There is a chance I might make additional updates before the conference and might consider pull requests if you are so inclined. 
Feel free to <a href="https://github.com/thnk2wn/codestock-winphone/issues">add an issue</a> for any bugs or feature requests.
<br/><br/>

Some changes I'd like to see in the future:<br/>

<ul>
	<li style="margin-bottom: 7px;"><b>Live tile</b> - maybe showing next session via schedule and/or favorite</li>
	<li style="margin-bottom: 7px;"><b>Better twitter integration</b> - i.e. more of a native twitter client instead of mobile twitter links.</li>
	<li style="margin-bottom: 7px;"><b>More graceful public WiFi handling</b> - better error message when an HTML response (from a WiFi logon page) 
		was received instead of expected JSON. (change pending Marketplace approval in v2.2)</li>
</ul>

On another Windows Phone project I started refactoring the Phone.Common assembly out into multiple NuGet packages that are a bit more generic. 
I have not had the time to finish that or incorporate it into the CodeStock app yet.
<br/><br/>

<h3>Overview Video</h3>
The below video gives an overview of most of the features minus a few miscellaneous ones.
</td>
</tr>
</table>

<iframe src="http://player.vimeo.com/video/42290848?title=0&amp;byline=0&amp;portrait=0" width="398" height="728" frameborder="0"></iframe>

<br/><br/>

This page can also be accessed via <a href="http://www.geoffhudik.com/codestock-wp7">http://www.geoffhudik.com/codestock-wp7</a>.]]></description><wfw:commentRss>http://www.geoffhudik.com/tech/rss-comments-entry-16294898.xml</wfw:commentRss></item></channel></rss>