Keeping .NET app settings secrets out of source control

Keeping .NET app settings secrets out of source control

Whether your app is a desktop client app or a website, it’s important to keep passwords, connection strings, and API keys out of source control. In .NET, these settings are stored in a app.config or web.config file depending on the type of app you are building and those files would be checked into source control. I’ve done this in the past for many projects. Of course, I never committed production secrets, but it was still a big no-no, but I just never had the time to investigate the proper way to handle this situation…until today.

A quick Google search returned this result: http://www.asp.net/identity/overview/features-api/best-practices-for-deploying-passwords-and-other-sensitive-data-to-aspnet-and-azure. Config files have a simple way of handling this by adding a file attribute to the appSettings section of the web.config file, like so

<appSettings file="secrets.config">
  <add key="testSetting" value="not a secret" />
</appSettings/>

And the secrets.config file looks like this

<appSettings>
  <add key="secretTestSetting" value="very secret" />
  <add key="testSetting" value="I will overwrite" />
</appSettings>

This is the entire file, it is important that the root element is <appSettings> , otherwise you will get a compile or runtime error. Any new keys defined will be added and any existing keys will overwrite the value from the web.config.

Overwriting values in the web.config is very useful for local development. Since the secrets.config file is never checked in, each developer can keep their own local app settings values without worrying about mistakenly checking them in and overriding the web.config default values. No more commenting out values in your web.config!

The same thing works for the connectionStrings section, except in this case the entire section is overwritten. In fact, the web.config can’t have any elements under it or you will get errors. The web.config will look like this

<connectionStrings configSource="connectionStrings.config">
</connectionStrings>

And the connectionStrings.config will look like this

<connectionStrings>
  <clear/>
  <add name="Database" connectionString="very secret" />
</connectionStrings>

As long as your extra files have the .config extension, IIS will never serve them. Also, you should never add these secrets config files to your project to avoid mistakenly deploying them to your servers. Instead, your release process should deploy secrets files separately from code files.

And finally, always add secrets.config and connectionStrings.config to your .gitignore and to get first-time developers setup with secrets, you could include these files somewhere outside of source control, like a file share.

That’s all! Super easy way to keep all your secrets safe in both desktop and web applications. Find the code here: https://github.com/svarcoe/AppSecretsDemoProject

Supporting iOS 6.1 and iOS 7 (Xamarin.iOS)

As a developer it’s always way more fun to play with the new “toys” a new framework or OS provides. The issue we usually run into is at what point are we able to start using the new functionality in applications we develop. With Apple and iOS, the adoption rate of new versions is so high and so quick that you only really need to worry about supporting the 2 latest versions. With that said, if you’re starting something brand new you’ll probably just want to build against the newest version, but if you’ve got an existing application that you want to function in both versions you’ll need to do some work.

1st: If you don’t have Xcode 5, copy the iPhoneOS6.1.sdk and the iPhoneSimulator6.1.sdk folders from your existing Xcode 4.6.3 installation somewhere else so you can still dev for iOS 6. The simulator SDK can also be downloaded after the upgrade, but this is faster since you already have it on your machine. The SDKs are located under /Applications/Xcode.app/Contents/Developer/Platforms. In Finder choose Go => Go to Folder to get to the desired directories. Copy both of these folders somewhere else for use later.

  • iPhoneOS6.1.sdk is located here /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/
  • iPhoneSimulator6.1.sdk is located here /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/

NOTE: If you’ve already upgraded to Xcode 5 you’ll need to download the Xcode 4.6.3 installer from the Apple Developer site located here. Once downloaded, open the .dmg file and right-click on the Xcode icon, choose “Show Package Contents” and then follow the same directory structure to get to the SDK folders.

2nd: In order to support iOS 7 make sure you are upgraded to OS X 10.8.5 and Xcode 5.

3rd: With Xcode 5 installed copy the iPhoneOS6.1.sdk and iPhoneSimulator6.1.sdk folders back to the directories you copied them out of. You should notice 7.0 versions of each SDK now in those directories as well.

4th: In Xamarin Studio under the iOS Build view in your project options, you’ll now notice that you have the ability to specifiy 6.1 as the SDK version as well as 7.0. Under the iOS Application view is the more important Deployment Target property. Set this to 6.1 in order for the application to still be deployable. Otherwise only iOS 7 devices will be able to get it from the app store. Making this change will allow it to be deployable for both versions, but you still have one more thing.

5th & Last: Apple made some drastic changes with how certain properties work, how the NavigationController’s title bar and the iOS status bar look and work, and a bunch of other things. You’ll need to add version specific code to handle/fix these changes. I created a VersionHelper static class to do the comparison logic for me. There are definitely other ways to accomplish this but here it is:

public static class VersionHelper
{
	private static Version _systemVersion;
	public static bool CurrentVersionIsGreaterThanOrEqualTo( Version versionToCompareAgainst )
	{
		if ( _systemVersion == null )
		{
			_systemVersion = new Version( UIDevice.CurrentDevice.SystemVersion );
		}

		return _systemVersion >= versionToCompareAgainst;
	}
}

With that you should be good to go.

Creating an iOS Settings Bundle (Xamarin.iOS)

With the investigation I’ve been doing in building an iOS application using Xamarin, I’ve now gotten to the point where I wanted to put some settings into the iOS Setting app for my application. I found this nice thread that gave the 3 easy steps to set up a settings bundle.

It literally is as simple as written, but there were a couple gotchas that I ran into that I wanted to forward along.

  1. The Settings.bundle folder needs to be in the project root, NOT under the Resources folder (where some examples showed it). I’m not sure if this is a new change or not, but I spent some time banging my head against the wall over this one.
  2. If you do not register default values for your settings they’ll return the default for the data type. (ie. null for String, false for bool, etc….). The DefaultValue you specify in the Root.plist file for a setting is the default value the control will show, NOT the value of the setting. Below is an example of registering default values for settings.
  3. NSUserDefaults userDefaults = NSUserDefaults.StandardUserDefaults;
    NSMutableDictionary appDefaults = new NSMutableDictionary(); 
    appDefaults.SetValueForKey( NSObject.FromObject( true ), new NSString( "BooleanSettingKey" ) );
    userDefaults.RegisterDefaults( appDefaults );
    userDefaults.Synchronize();
    

Accessing the value for a setting is a simple as the following:

bool settingValue = NSUserDefaults.StandardUserDefaults.BoolForKey( "BooleanSettingKey");

One last thing that I found in an example solution here was how to listen in your app for when settings have been changed and wanted to pass it along:

NSObject observer = NSNotificationCenter.DefaultCenter.AddObserver( (NSString)"NSUserDefaultsDidChangeNotification", DefaultsChanged ); 
private void DefaultsChanged( NSNotification obj )
{ 	
// Handle the settings changed 
}

The biggest bummer I found in the iOS Settings Bundle is that everything is statically defined. So if you happen to have a collection of items that you want updated from a server, you’ll have to do this on your own inside your application.

Working with UITableView (Xamarin.iOS)

After setting up my solution, my next step was to figure out how to display a list in iOS with the ability to select an item. In WPF, my first thought would be to utilize the ListBox control, bind its ItemsSource to the underlying data, and define an ItemTemplate for how I want each item to look. Not so simple in iOS. There’s a nice list control called the UITableView that provides support for a number of neat things (indexed list, splitting the items into sections/grouping them, selection, etc…). However, to accomplish the most important part, hooking it up to data, you have to define a data source object that you assign to the .Source property of the UITableView. There are a number of ways to accomplish this, but I went with creating a source that inherits from UITableViewSource. Xamarin has a nice guide that I used for guidance (Working with Tables and Cells).

Coming from WPF and MVVM I wanted to make this source reusable rather than following all the examples that were hardcoded to a specific object type. So I decided to create an ObjectTableSource, and since I’m still in the early stages of development I decided to make use of the built-in styles for the cell appearance rather than making a custom cell. With this in mind I needed to make sure that the objects provided to my table source had specific properties for me to utilize so I created an interface called ISupportTableSourceand used that as the .

public interface ISupportTableSource
{
	string Text { get; }
	string DetailText { get; }
	string ImageUri { get; }
}
public class ObjectTableSource : UITableViewSource

When inheriting from UITableViewSource you must override RowsInSection and GetCell. RowsInSection is exactly what it sounds like, you return how many items are in that section. My current version only supports 1 section so it returns the total number of items. GetCell returns the prepared UITableViewCell. The UITableView supports virtualization of its cell controls so in order to get the cell you need to call the DequeueReusableCellmethod on the table view. In versions earlier than iOS 6 this will return null if the cell hasn’t been created yet. In iOS 6 and later you can choose to register a cell type with the UITableView and that will make it so a cell is always returned. However, going this path means that you can’t specify which of the 4 build in styles to use (since it is specified in the constructor only), so I refrained from registering the cell type and handle null. When preparing the cell I also loaded any images on a background thread so the UI is still responsive, but I’ll cover that in another post.

public override int RowsInSection( UITableView tableview, int section )
{
	return _items.Count;
}

public override UITableViewCell GetCell( UITableView tableView, NSIndexPath indexPath )
{
	// if there are no cells to reuse, create a new one
	UITableViewCell cell = tableView.DequeueReusableCell( CellId )
			       ?? new UITableViewCell( _desiredCellStyle, CellId );

	ISupportTableSource item = _items[indexPath.Row];
	if ( !String.IsNullOrEmpty( item.Text ) )
	{
		cell.TextLabel.Text = item.Text;
	}
	if ( !String.IsNullOrEmpty( item.DetailText ) )
	{
		cell.DetailTextLabel.Text = item.DetailText;
	}
	if ( !String.IsNullOrEmpty( item.ImageUri ) )
	{
		cell.ImageView.ShowLoadingAnimation();
		LoadImageAsync( cell, item.ImageUri );
	}
	return cell;
}

The remaining thing for making this usable was to provide a way to notify when an item has been selected. There isn’t any event on the UITableViewSource like I expected, instead I needed to override the RowSelectedmethod and fire my own event providing the object found at the selected row.

public override void RowSelected( UITableView tableView, NSIndexPath indexPath )
{
	ISupportTableSource selectedItem = _items[indexPath.Row];

	// normal iOS behaviour is to remove the blue highlight
	tableView.DeselectRow( indexPath, true );

	OnItemSelected( selectedItem );
}

Beginning Xamarin and Xamarin.iOS

I’ve recently started delving into using Xamarin and Xamarin.iOS to get an understanding of its capabilities and see what I could do with it. After my first little app, I have to say it is really impressive what the people at Xamarin have done!

I’m coming at this from years of experience in C# and WPF/Silverlight/XAML and I wanted to describe my initial findings. For this post I plan to cover the general setup.

Installation

My goal was to have an iPad application that does something similar to a Master-Detail set of views where I could display a collection of images. Because of this I wanted to get a grasp on both Xamarin Studio and the Visual Studio integration so I installed Xamarin for Windows (on my primary dev machine) and Xamarin for OS X (on a Mac Mini we have at our office) from here. NOTE: On Windows 8 run the Xamarin installer as Administrator otherwise it will error out near the end. The installation took a while because it downloaded everything that I needed to develop for iOS (and for Android since I wanted to look into that in the future too.), but once everything was installed I was able to start developing instantly.

The installation guides for Xamarin.iOS, located here, provided excellent instruction for hooking up Visual Studio 2012 to remote debug on the Mac Mini (Section: 6.2. Connecting to the Mac Build Host), and the Xamarin services that were installed made it so that I found and connected to the Mac immediately. On another note, Synergy is an amazing tool to use to share your keyboard and mouse between the 2 devices. You need to make sure both machines are on the same network though in order for it to work (quickly at least).

Solution Setup

With Xamarin installed, I decided to get my feet wet by following Xamarin’s Hello, iPhone guide. (They’ve done a very nice job with their guides covering a nice range of topics.) For someone who has never developed in Xcode (and is pretty much a Mac beginner) this provided a nice tutorial of the tool.

With a general idea of how to get started, I created my solution in VS 2012 and on the Mac I used “Finder” to connect to my machine and open up the solution in Xamarin Studio. I’ve found that if I want to use Xcode’s Interface Builder to design my UI I need to add the iPad View Controller via Xamarin Studio, since adding it in Visual Studio didn’t create the .xib file.

My next step was to set up a core library, since I desire to try Xamarin’s Android functionality in the future, to enable code reuse. Xamarin is currently developing support for referencing Portable Class Libraries (PCLs), but until they’ve got that functioning we have to go more manual routes. I created the PCL to hold my core files (model objects primarily), but went the route of linking to all those files in the iOS project. Once I work on the Android counterpart I’ll be able to update with whether I think it’s the right way to go.

EDIT: With the release of Xamarin.iOS 7.0.1, they’ve apparently fixed the PCL build issue I was running into. So I was able to remove the links to all the files and reference the PCL library instead!

Debugging

It is extremely nice to be able to dev and debug in VS 2012 while connected to the iOS simulator on the Mac. When running into exceptions being thrown, I found that, if it wasn’t immediately apparent as to what the exception was caused by, debugging from Xamarin Studio on the Mac provides better exception information.

I’ll go into more detail on discoveries I ran into in other posts, but overall the development process using Xamarin and Xamarin.iOS has been very interesting and enjoyable. I would definitely recommend it.

EventHandler<T> or Action<T>

If you’ve used C# for any length of time, you’ve used events. Most likely, you wrote something like this:

public class MyCoolCSharpClass {
     public event EventHandler MyCoolEvent;
}

public class MyOtherClass {
     public void MyOtherMethod(MyCoolCSharpClass obj)
     {
          obj.MyCoolEvent += WhenTheEventFires;
     }

     private void WhenTheEventFires(object sender, EventArgs args)
     {
          Console.WriteLine("Hello World!");
     }
}

Later, you need parameters to be passed in along with the event, so you changed it to something like this:

public event EventHandler<MyEventArgs> MyCoolEvent;

public class MyEventArgs : EventArgs 
{
     public string Name { get; set; }
     public DateTime WhenSomethingHappened { get; set; }
}
...
     private void WhenTheEventFires(object sender, MyEventArgs args)
     {
          var theCoolCSharpSendingClass = (MyCoolCSharpClass)sender;
          Console.WriteLine("Hello World! Good to meet you " + args.Name);
     }

You add two or three more events, some property change and changing events, and finally a class with about 4 properties, 3 events, and a little bit of code now has 3 supporting EventArgs classes, casts for every time you need the sender class instance (In this example, I’m assuming the event is always fired by MyCoolCSharpClass, and not through a method from a 3rd class). There’s a lot of code there to maintain even for just a simple class with some very simple functionality.

Lets look at this for a minute. First, EventHandler and EventHandler<T> are simply delegates, nothing more nothing less (If you’re not sure what a delegate is, don’t sweat it, it’s not really the point of this discussion). What makes the magic happen for events is that little event keyword the prefaces the event that turns that internally turns the delegate type into a subscribe-able field. Essentially, it simplifies adding and removing multiple methods that are all called when the event is invoked. With the introduction of generics in C# 2.0, and the introduction of LINQ in 3.5, we have generic forms of most of the delegates we could ever use in the form of Action<T1, T2, T3...> and Func<TRes, T1, T2...>. What this means, is that we can change an event declarations to use whatever delegate we want. Something like this is perfectly valid:

public event Action<MyCoolCSHarpClass, string, DateTime> MyCoolEvent;

And what about when we subscribe? Well, now we get typed parameters:

...
     private void WhenTheEventFires(MyCoolCSHarpClass sender, string name, DateTime theDate)
     {
          Console.WriteLine("Hello World! Good to meet you " + name);
     }

That’s cool. I’ve now reduced the amount of code I have to maintain from 4 classes to 1 and I don’t have to cast my sender. As a matter of fact, I don’t even have to pass a sender. How often have you written an event that’s something like this:

public event EventHandler TheTableWasUpdatedGoCheckIt;

Whoever is subscribed to this event doesn’t care about who sent it, or what data specifically was updated, all the subscribe cares about was that it was fired, nothing more than that. Even then, in a “you can only use EventHandler delegate world” you’re still stuck creating a method to subscribe to the event that looks like this:

     private void WhenTheTableWasUpdated(object sender, EventArgs args)
     {
          // Go check the database and update stuff...
     }

If we use what we’ve learned and change the event to something like this:

public event Action TheTableWasUpdatedGoCheckIt;

We can write our method like this:

     private void WhenTheTableWasUpdated()
     {
          // Go check the database and update stuff...
     }

Since we never cared about the parameters in the first place.

Thats awesome fine and dandy, but just blindly replacing every instance of EventHandler delegates to Actions isn’t always the best idea, there are a few caveats:

First, there are some practical physical limitations of using Action<T1, T2, T2... > vs using a derived class of EventArgs, three main ones that I can think of:

  • If you change the number or types of parameters, every method that subscribes to that event will have to be changed to conform to the new signature. If this is a public facing event that 3rd party assemblies will be using, and there is any possibility that the number or type of arguments would change, its a very good reason to use a custom class that can later be inherited from to provide more parameters. Remember, you can still use an Action<MyCustomClass>, but deriving from EventArgs is still the Way Things Are Done
  • Using Action<T1, T2, T2... > will prevent you from passing feedback BACK to the calling method unless you have a some kind of object (with a Handled property for instance) that is passed along with the Action, and if you’re going to make a class with a handled property, making it derive from EventArgs is completely reasonable.
  • You don’t get named parameters by using Action<T1, T2 etc...> so if you’re passing 3 bool‘s, an int, two string‘s, and a DateTime, you won’t immediately know what the meaning of those values. Passing a custom args class provides meaning to those parameters.

Secondly, consistency implications. If you have a large system you’re already working with, it’s nearly always better to follow the way the rest of the system is designed unless you have an very good reason not too. If you have publicly facing events that need to be maintained, the ability to substitute derived classes for args might be important.

Finally, real life practice, I personally find that I tend to create a lot of one off events for things like property changes that I need to interact with (Particularly when doing MVVM with view models that interact with each other) or where the event has a single parameter. Most of the time these events take on the form of public event Action<[classtype], bool> [PropertyName]Changed; or public event Action SomethingHappened;. In these cases, there are two benefits that you might be able to guess from what you’ve already seen.

  • I get a type for the issuing class. If MyClass declares and is the only class firing the event, I get an explicit instance of MyClass to work with in the event handler.
  • For simple events such as property change events, the meaning of the parameters is obvious and stated in the name of the event handler and I don’t have to create a myriad of classes for these kinds of events.

Food for thought. If you have any comments, feel free to leave them in the comment section below.

XamlReader, Loose ResourceDictionary Files, and the ParserContext

In a few WPF projects that I have been on I have needed to load in loose ResourceDictionary files to provide different runtime styling. When there isn’t any external content being referenced (ie. images, videos, etc…) it is as simple as calling XamlReader.Load and then merging the dictionary into the application’s resources:

var resourceDictionary = XamlReader.Load(fileStream) as ResourceDictionary;
if(resourceDictionary != null)
{
    Resources.MergedDictionaries.Add(resourceDictionary)
}

When external content is being referenced there is the possibility that the XamlReader won’t be able to find those files, even if they are in the exact same folder as the ResourceDictionary files. To solve this issue, you will need to create a ParserContext and set its BaseUri to the root folder containing the files. For example, if the ResourceDictionary and content files are located in a folder called RuntimeResources, which is in the same folder as the application’s exe, then the BaseUri needs to point to that RuntimeResources folder. Then when the XamlReader loads the ResourceDictionary, it uses the provided ParserContext to know where to look for the actual files. NOTE: In the ResourceDictionary you will still need to include the root folder (RuntimeResources) at the beginning of the uri otherwise it will look in the parent directory for the desired file.

Below is the related code:

C#

var applicationDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if(!String.IsNullOrEmpty(applicationDirectory ))
{

    var runtimeResourcesDirectory = Path.Combine(applicationDirectory , "RuntimeResources");
    var pc = new ParserContext
    {
        BaseUri = new Uri(runtimeResourcesDirectory , UriKind.Absolute)
    };
    if(Directory.Exists(runtimeResourcesDirectory ))
    {
        foreach (string resourceDictionaryFile in Directory.GetFiles(runtimeResourcesDirectory , "*.xaml"))
        {
            using (Stream s = File.Open(resourceDictionaryFile, FileMode.Open, FileAccess.Read))
            {
                try
                {
                    var resourceDictionary = XamlReader.Load(s, pc) as ResourceDictionary;
                    if (resourceDictionary != null)
                    {
                        Resources.MergedDictionaries.Add(resourceDictionary);
                    }
                }
                catch
                {
                    MessageBox.Show("Invalid xaml: " + resourceDictionaryFile);
                }
            }
        }
    }
}

Resource Dictionary

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
			    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

	<ImageBrush x:Key="LogoImage"
			  ImageSource="RuntimeResources/logo.png" />

</ResourceDictionary>

Now you’ve got the ability to change your WPF application’s styling at runtime.

Merging a WPF application into a single EXE

I always dislike handing off little applications to people. Not because I can’t, but because of the steps involved to make sure it all just works. Small apps are the most problematic because I never want to take the time to create a whole installer project for just a few assemblies, and packaging up a zip file must be accompanied by “Unzip this into a folder in your programs directory and create a shortcut…” which brings us back to the whole installer business we started with.

There are a few tools already out there such as ILMerge by Microsoft Research (Which works great for most .NET-y things, but chokes on WPF applications) and a few paid tools by third party vendors that you could fork over a few hundred for to get. But, I’m a developer, which means I want to do it the Hard Way™. I did a little research and found the following blog posts on setting up and merging in DLL’s as resources into the main assembly and then extracting and loading them into memory when you run your application.

Links:

There were a few things I didn’t like about each solution. The first one (richarddingwall.name) ends up having you directly adding the .dll’s as resources directly. I hate maintaining things manually, especially when it will run fine on my machine but break when when I move it somewhere else because I forgot to update the resources when I added a new project. The one from blog.mahop.net builds on the previous one and changes the resolve location to a custom class with its own startup method. Better, because it resolves the resources earlier. Finally, the one from Daniel Chambers (digitallycreated.net) added in the final piece that automatically including the assemblies as resources. Unfortunately, the way he looks for culture specific assemblies didn’t work and I had to remove / change it to be closer to the one on mahop.net.

Final solution I’m currently using is as follows:

To the main executable project, unload and edit the .csproj file, and below the following line:

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Add this XML to the project file, save, and load it back up.

 <Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>

It should look something like this when your done:

You’ll then add a new code file to the main project and add the following code to it (modified to fit how your application is named / structured):

        [STAThread]
        public static void Main()
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;

            App.Main(); // Run WPF startup code.
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
        {
            var thisAssembly = Assembly.GetExecutingAssembly();

            // Get the Name of the AssemblyFile
            var assemblyName = new AssemblyName(e.Name);
            var dllName = assemblyName.Name + ".dll";

            // Load from Embedded Resources - This function is not called if the Assembly is already
            // in the same folder as the app.
            var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
            if (resources.Any())
            {

                // 99% of cases will only have one matching item, but if you don't,
                // you will have to change the logic to handle those cases.
                var resourceName = resources.First();
                using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
                {
                    if (stream == null) return null;
                    var block = new byte[stream.Length];

                    // Safely try to load the assembly.
                    try
                    {
                        stream.Read(block, 0, block.Length);
                        return Assembly.Load(block);
                    }
                    catch (IOException)
                    {
                        return null;
                    }
                    catch(BadImageFormatException)
                    {
                        return null;
                    }
                }
            }

            // in the case the resource doesn't exist, return null.
            return null;
        }

Finally, make sure you update the target method for your main application to be the main method for the project you just added:

And, that’s it!

When you build your application you’ll still see all the assemblies in the output directory, but you should be able to take just the executable, move it somewhere else, and run it just as it is.