Tag Archives: c#

Compacting Paths

using System.Runtime.InteropServices;
using System.Text;

public static class Shlwapi
{
    [DllImport("shlwapi.dll")]
    public static extern bool PathCompactPathEx(StringBuilder pszOut, string pszSrc, uint cchMax, uint dwFlags);

    public static string CompactPath(string path, int length)
    {
        StringBuilder builder = new StringBuilder(length + 1);
        PathCompactPathEx(builder, path, (uint)length + 1, 0);
        return builder.ToString();
    }
}

Messing with Message Loops

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;

public class MessageLoop
{
    private Thread thread;
    private SynchronizationContext context;
    private ManualResetEvent initialized;

    public MessageLoop()
    {
        thread = new Thread(Initialize);
        thread.SetApartmentState(ApartmentState.STA);
        initialized = new ManualResetEvent(false);
    }

    private void Initialize()
    {
        Application.Idle += Application_Idle;
        Application.Run();
    }

    private void Application_Idle(object sender, EventArgs e)
    {
        if (Thread.CurrentThread.ManagedThreadId != thread.ManagedThreadId)
        {
            return;
        }
        Application.Idle -= Application_Idle;
        context = SynchronizationContext.Current;
        initialized.Set();
    }

    public void Start()
    {
        thread.Start();
        initialized.WaitOne();
    }

    public void Post(Action action)
    {
        context.Post(obj => action(), null);
    }

    public void Send(Action action)
    {
        context.Send(obj => action(), null);
    }

    public void Stop()
    {
        Send(() => Application.ExitThread());
        thread.Join();
    }
}

internal static class Program
{
    private const string Url = "http://www.stevencwilliams.com/";
    private static readonly string Path = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "screenshot.png");
    private static readonly Size Size = new Size(640, 480);
    private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(0.1);

    private static ManualResetEvent done;
    private static Bitmap screenshot;

    private static void Main(string[] args)
    {
        File.Delete(Path);
        done = new ManualResetEvent(false);
        MessageLoop loop = new MessageLoop();
        loop.Start();
        loop.Send(Download);
        Wait();
        loop.Stop();
        if (done.WaitOne(TimeSpan.Zero))
        {
            screenshot.Save(Path);
        }
    }

    private static void Download()
    {
        WebBrowser browser = new WebBrowser()
        {
            Width = Size.Width,
            Height = Size.Height,
            ScriptErrorsSuppressed = true
        };
        browser.DocumentCompleted += (sender, e) =>
        {
            screenshot = new Bitmap(Size.Width, Size.Height);
            browser.DrawToBitmap(screenshot, new Rectangle(0, 0, Size.Width, Size.Height));
            done.Set();
        };
        browser.Navigate(Url);
        Console.WriteLine("Downloading {0}...", Url);
    }

    private static void Wait()
    {
        Console.WriteLine("Press any key to cancel...");
        foreach (char spinner in GetSpinners())
        {
            try
            {
                Console.Write(spinner);
                if (done.WaitOne(Timeout) || Console.KeyAvailable)
                {
                    break;
                }
            }
            finally
            {
                Console.Write('\b');
            }
        }
    }

    private static IEnumerable<char> GetSpinners()
    {
        while (true)
        {
            yield return '|';
            yield return '/';
            yield return '-';
            yield return '\\';
        }
    }
}

Taming ClickOnce Deployments

Build your solution’s projects into a common directory:

<PropertyGroup>
  <BuildDir>$(SolutionDir)Build\</BuildDir>
  <Rebuilding>false</Rebuilding>
</PropertyGroup>
<PropertyGroup Condition="'$(OutputType)' == 'Exe' Or '$(OutputType)' == 'WinExe'">
  <StartAction>Program</StartAction>
  <StartWorkingDirectory>$(BuildDir)</StartWorkingDirectory>
  <StartProgram>$(BuildDir)$(TargetFileName)</StartProgram>
</PropertyGroup>
<Target Name="FlagRebuild" BeforeTargets="BeforeRebuild">
  <PropertyGroup>
    <Rebuilding>true</Rebuilding>
  </PropertyGroup>
</Target>
<Target Name="CopyBuildFiles" AfterTargets="Build">
  <ItemGroup>
    <BuildFile Include="$(TargetDir)**\*" />
  </ItemGroup>
  <Copy SourceFiles="@(BuildFile)" DestinationFolder="$(BuildDir)%(RecursiveDir)" SkipUnchangedFiles="true" />
</Target>
<Target Name="CleanBuildDir" AfterTargets="Clean" Condition="'$(Rebuilding)' == 'false'">
  <RemoveDir Directories="$(BuildDir)" />
</Target>

Create a separate launcher project that does not get built into the common directory. The launcher simply invokes the main executable, assuming it is in the same directory as the launcher executable:

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace Project
{
    public class Program
    {
        [STAThread]
        internal static void Main(string[] args)
        {
            try
            {
                DirectoryInfo directory = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory;
                string executable = Path.Combine(directory.FullName, "Project.Presentation.exe");
                Process.Start(new ProcessStartInfo
                {
                    UseShellExecute = false,
                    WorkingDirectory = directory.FullName,
                    FileName = executable
                });
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }
    }
}

Include the other projects’ build products (i.e., the contents of the common directory) as content files in the launcher project:

<PropertyGroup>
  <BuildDir>$(SolutionDir)Build\</BuildDir>
</PropertyGroup>
<ItemGroup>
  <Content Include="$(BuildDir)**" Exclude="$(BuildDir)**\*.pdb">
    <Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>
<Target Name="CleanPublishDir" AfterTargets="Clean">
  <RemoveDir Directories="publish" />
</Target>

Rolling Your Own Settings

using System;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;

public partial class Settings
{
    public static readonly string FilePath;

    private static XmlSerializer serializer;

    public static Settings Default { get; private set; }

    static Settings()
    {
        Assembly assembly = Assembly.GetExecutingAssembly();
        string company = assembly.GetCustomAttribute<AssemblyCompanyAttribute>()?.Company ?? "Temp";
        string product = assembly.GetCustomAttribute<AssemblyProductAttribute>()?.Product ?? assembly.GetName().Name;
        string fileName = typeof(Settings).FullName + ".xml";
        FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), company, product, fileName);
        serializer = new XmlSerializer(typeof(Settings));
        Default = Load();
    }

    private static Settings Load()
    {
        try
        {
            using (Stream stream = File.OpenRead(FilePath))
            {
                return (Settings)serializer.Deserialize(stream);
            }
        }
        catch
        {
            return new Settings();
        }
    }

    public void Save()
    {
        Directory.CreateDirectory(Path.GetDirectoryName(FilePath));
        using (Stream stream = File.Create(FilePath))
        {
            serializer.Serialize(stream, this);
        }
    }
}

Usage:

using System;

public partial class Settings
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine(Settings.FilePath);
        if (string.IsNullOrWhiteSpace(Settings.Default.FirstName))
        {
            Console.Write("What is your first name? ");
            Settings.Default.FirstName = Console.ReadLine();
        }
        if (string.IsNullOrWhiteSpace(Settings.Default.LastName))
        {
            Console.Write("What is your last name? ");
            Settings.Default.LastName = Console.ReadLine();
        }
        Settings.Default.Save();
        Console.WriteLine("Hello, {0} {1}!", Settings.Default.FirstName, Settings.Default.LastName);
    }
}

Single-Instance Applications

using System;
using System.Reflection;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;

public class SingleInstanceExecuter
{
    private static Mutex GetMutex()
    {
        string name = string.Format(@"Global\{0}", Assembly.GetEntryAssembly().GetName().Name);
        bool created;
        MutexSecurity security = new MutexSecurity();
        security.AddAccessRule(new MutexAccessRule(
            new SecurityIdentifier(WellKnownSidType.WorldSid, null),
            MutexRights.FullControl,
            AccessControlType.Allow));
        return new Mutex(false, name, out created, security);
    }

    public int Timeout { get; set; }

    public SingleInstanceExecuter(int timeout = 1000)
    {
        Timeout = timeout;
    }

    public event EventHandler Executing;
    private void OnExecuting(EventArgs e)
    {
        Executing?.Invoke(this, e);
    }
    private void OnExecuting()
    {
        OnExecuting(EventArgs.Empty);
    }

    public void Execute()
    {
        using (Mutex mutex = GetMutex())
        {
            bool owned = false;
            try
            {
                try
                {
                    owned = mutex.WaitOne(Timeout);
                    if (!owned)
                    {
                        throw new TimeoutException("Timed out while waiting to execute.");
                    }
                }
                catch (AbandonedMutexException)
                {
                    owned = true;
                }
                OnExecuting();
            }
            finally
            {
                if (owned)
                {
                    mutex.ReleaseMutex();
                }
            }
        }
    }
}

Usage:

public static void Main(string[] args)
{
    SingleInstanceExecuter executer = new SingleInstanceExecuter();
    executer.Executing += (sender, e) =>
    {
        // ...
    };
    try
    {
        executer.Execute();
    }
    catch (TimeoutException)
    {
        MessageBox.Show("An instance of this application is already running.");
    }
}

MSBuild Task: FindFilesInList

using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public class FindFilesInList : Task
{
    [Required]
    public ITaskItem[] Items { get; set; }

    [Required]
    public ITaskItem[] ItemsToFind { get; set; }

    [Output]
    public ITaskItem[] FoundItems { get; set; }

    public override bool Execute()
    {
        ICollection<string> pathsToFind = ItemsToFind.Select(taskItem => new FileInfo(taskItem.ItemSpec).FullName).ToList();
        ICollection<ITaskItem> foundItemsList = new List<ITaskItem>();
        foreach (ITaskItem item in Items)
        {
            FileInfo file = new FileInfo(item.ItemSpec);
            if (pathsToFind.Contains(file.FullName))
            {
                foundItemsList.Add(item);
            }
        }
        FoundItems = foundItemsList.ToArray();
        return true;
    }
}

Mapping Qualified Column Names in Dapper

using Dapper;
using System;
using System.Reflection;

public class QualifiedTypeMap : SqlMapper.ITypeMap
{
    private static string Unqualify(string columnName)
    {
        return columnName.Substring(columnName.LastIndexOf('.') + 1);
    }

    private DefaultTypeMap @base;

    public QualifiedTypeMap(Type type)
    {
        @base = new DefaultTypeMap(type);
    }

    public ConstructorInfo FindConstructor(string[] names, Type[] types)
    {
        return @base.FindConstructor(names, types);
    }

    public ConstructorInfo FindExplicitConstructor()
    {
        return @base.FindExplicitConstructor();
    }

    public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
    {
        return @base.GetConstructorParameter(constructor, Unqualify(columnName));
    }

    public SqlMapper.IMemberMap GetMember(string columnName)
    {
        return @base.GetMember(Unqualify(columnName));
    }
}

Specifying Extensions for Temporary Files

using System;
using System.IO;

public static class IOExtensions
{
    public static FileInfo GetTemporaryFile(string extension = null)
    {
        string path = Path.GetTempPath();
        FileInfo file;
        do
        {
            string fileName = Path.ChangeExtension(Guid.NewGuid().ToString("N"), extension);
            file = new FileInfo(Path.Combine(path, fileName));
        } while (file.Exists);
        using (file.OpenWrite()) { }
        return file;
    }
}

Nonempty Collection Validation in ASP.NET MVC

using System;
using System.Collections;
using System.ComponentModel.DataAnnotations;

[AttributeUsage(AttributeTargets.Property)]
public sealed class NotEmptyAttribute : RequiredAttribute
{
    public override bool IsValid(object value)
    {
        ICollection valueCollection = value as ICollection;
        return valueCollection != null && valueCollection.Count > 0;
    }
}

ASP.NET MVC HTML Helper: Once

public static HtmlString Once(this HtmlHelper @this, Func<dynamic, HelperResult> template)
{
    string key = "Once";
    ISet<string> cache;
    if (@this.ViewContext.HttpContext.Items.Contains(key))
    {
        cache = (ISet<string>)@this.ViewContext.HttpContext.Items[key];
    }
    else
    {
        cache = new HashSet<string>();
        @this.ViewContext.HttpContext.Items[key] = cache;
    }
    string html = template(null).ToString();
    if (!cache.Contains(html))
    {
        cache.Add(html);
        return new HtmlString(html);
    }
    else
    {
        return null;
    }
}

Usage:

@for (int i = 0; i < 10; i++)
{
    @Html.Once(
        @<script type="text/javascript">
            alert("You will only see this once.");
        </script>);
}