IHTMLDocument5 and IHTMLDocument6 in C#

Unexpectedly, I found myself needing to use IHTMLDocument5/6 last evening to fetch a few properties that aren’t directly exposed via any of the Web Browser options in classic .NET programming (like WinForms/WPF). I couldn’t find them anywhere, so I whipped up something simple/quick/dirty:

[Guid("3050f80c-98b5-11cf-bb82-00aa00bdce0b")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
internal interface IHTMLDocument5
{
    void SetOnmousewheel(object p);
    object GetOnmousewheel();
    object docType { get; }
    object implementation { get; }
    object createAttribute([In] string attrName);
    object createComment([In] string comment);
    void SetOnfocusin(object p);
    object GetOnfocusin();
    void SetOnfocusout(object p);
    object GetOnfocusout();
    void SetOnactivate(object p);
    object GetOnactivate();
    void SetOndeactivate(object p);
    object GetOndeactivate();
    void SetOnbeforeactivate(object p);
    object GetOnbeforeactivate();
    void SetOnbeforedeactivate(object p);
    object GetOnbeforedeactivate();
    string compatMode { get; }
}


[Guid("30510417-98b5-11cf-bb82-00aa00bdce0b")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)]
internal interface IHTMLDocument6
{
    object compatible { get; }
    object documentMode { get; }
    void SetOnStorage([In] object p);
    object GetOnStorage();
    void SetOnStorageCommit([In] object p);
    object GetOnStorageCommit();
    object getElementById([In] string id);
    void updateSettings();
}

As you can see, I didn’t spend a lot of time with the details, but it was enough to make progress (in particular I wanted compatMode and documentMode).

Lovin’ .NET 4.5’s CallerMemberNameAttribute

Check this out:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx

Hate the INotifyPropertyChanged pattern in .NET, and especially the syntax of needing to pass the name of the property being changed (via a string or an Expression or …)?

There’s finally a built-in solution in .NET 4.5!

private string _testValue;
public string TestValue
{
    get { return _testValue; }
    set {
        if (_testValue != value)
        {
            _testValue = value;
            RaisePropertyChanged();
        }            
    }
}

protected void RaisePropertyChanged([CallerMemberName] String propertyName = null)
{
    var eventHandler = this.PropertyChanged;
    if (eventHandler != null)
    {
        eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

A new attribute you can apply to optional parameters which emits the name of the Caller! So in the example above, it’s “TestValue.”

Rock on!

WPF & System.Windows.Baml2006.TypeConverterMarkupExtension "The image format is unrecognized"

SNAGHTML4534e6a2If you recently added an icon to your WPF project (any .NET version, including .NET 3.5, and .NET 4.0) and it has support for an alpha channel (often referred to as the Vista icon format), stop. Why? Your WPF application won’t run on the latest service pack of XP as it’s not capable of decoding the format unfortunately. It’s a very frustrating error and a stupid "feature that Microsoft overlooked. I’ve hit this a few times unfortunately.

To fix, remove all of the alpha channel images from the ICO file and recompile.

Alternative to ApplicationSettings in .NET

After dealing with lost settings, an unclear upgrade path, and my own confusion surrounding the magic of Settings in a .NET client application, I decided to build my own.

You’re probably familiar with this UI in Visual Studio. It hasn’t changed much since it was first created:

image

A list of properties, data type, scope and a default value. Admittedly, it makes things simple. However, with my WPF .NET application that I created a few years ago (SnugUp), I’ve always been troubled by the magic of the settings. It was too easy to get in a situation where a user would loose their settings doing uninstalls, reinstalls, upgrades.

While I’m sure it’s possible to make the built in settings classes to work, it wasn’t worth the effort for me to understand them and learn what the nuances of where they’re placed, how to do a decent upgrade, how not to loose them, etc.

In the SnugUp WPF UI, the code uses a two-way bindings to directly edit the settings of the application (like: "{Binding AppSettings.DebugMode}"). It was simple, and all I needed. It’s handy that ApplicationSettingsBase implements the INotifyPropertyChanged interface which WPF needs for simple two-way data bindings.

My solution, which I admit is heavier than the original as it requires a large additional assembly is to use JSON.NET as the serializer/deserializer for a new settings class I created.

So, the basic pattern:

   1: public class ApplicationSettings : INotifyPropertyChanged

   2: {

   3:     public event PropertyChangedEventHandler PropertyChanged;

   4:  

   5:     private bool _debugMode;

   6:     private string _albumNameFormat;

   7:     private string _extraFileExtensions;

   8:     private bool _automaticRun;

   9:     private string _galleryCreationSubCategory;

  10:     private bool _filenameOnlyCheck;

Properties created the standard way for INotifyPropertyChaged:

   1: private DateTime _nextUpdateCheck;

   2: public DateTime NextUpdateCheck

   3: {

   4:     get { return _nextUpdateCheck; }

   5:     set

   6:     {

   7:         if (_nextUpdateCheck != value)

   8:         {

   9:             _nextUpdateCheck = value;

  10:             RaisePropertyChanged("NextUpdateCheck");

  11:         }

  12:     }

  13: }

I wanted a predictable path for storing settings (so it would be easy to document and backup for users). I used the AssemblyCompany attribute and the AssemblyProduct attribute as the folder names:

   1: internal static string GetSettingsDirectory()

   2: {

   3:     string path = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

   4:     var attrs = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyCompanyAttribute), false);

   5:     if (attrs.Length == 1)

   6:     {

   7:         path = Path.Combine(path, ((AssemblyCompanyAttribute)attrs[0]).Company);

   8:  

   9:     }

  10:     attrs = Assembly.GetEntryAssembly().GetCustomAttributes(typeof(AssemblyProductAttribute), false);

  11:     if (attrs.Length == 1)

  12:     {

  13:         path = Path.Combine(path, ((AssemblyProductAttribute)attrs[0]).Product);

  14:     }

  15:     return path;              

  16: }

In this WPF application, in the AssemblyInfo.cs file, the attributes are set as follows:

   1: [assembly: AssemblyCompany("WiredPrairie.us")]

   2: [assembly: AssemblyProduct("SnugUp")]

On my machine, that maps to this path:

d:\Users\Aaron\AppData\Roaming\WiredPrairie.us\SnugUp\

Loading settings then is straightforward using JSON.NET:

   1: public static ApplicationSettings Load(string filename)

   2: {

   3:     ApplicationSettings settings = null;

   4:     var directory = GetSettingsDirectory();

   5:     var path = Path.Combine(directory, filename);

   6:  

   7:     if (File.Exists(path))

   8:     {

   9:         string fileData = File.ReadAllText(path);

  10:         try

  11:         {

  12:             settings = JsonConvert.DeserializeObject<ApplicationSettings>(fileData, new JsonSerializerSettings { });

  13:         }

  14:         catch { }

  15:     }

  16:     if (settings == null)

  17:     {

  18:         settings = new ApplicationSettings();

  19:         SetDefaults(settings);

  20:         // initialize settings once

  21:         Save(settings, filename);

  22:     }

  23:     return settings;

  24: }

In my code, if the settings file didn’t exist or fails to serialize into something meaningful, a new settings file is created with a few defaults. (I haven’t decided what to do when there’s an exception when reading the file, hence the empty catch).

Saving the settings is just as easy:

   1: public static void Save(ApplicationSettings settings, string filename)

   2: {

   3:     Debug.Assert(settings != null);

   4:     var directory = GetSettingsDirectory();

   5:     var path = Path.Combine(directory, filename);

   6:  

   7:     JsonConvert.SerializeObject(settings);

   8:  

   9:     if (!Directory.Exists(directory))

  10:     {

  11:         Directory.CreateDirectory(directory);

  12:     }

  13:  

  14:     var fileData = JsonConvert.SerializeObject(settings, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate });

  15:     try

  16:     {

  17:         using (StreamWriter writer = File.CreateText(path))

  18:         {

  19:             writer.Write(fileData);

  20:             writer.Close();

  21:         }

  22:     }

  23:     catch { }

  24: }

The SerializeObject method returns a string which is then written to a file using a StreamWriter.

I added a Save method to the instance of the ApplicationSettings:

   1: public void Save()

   2: {

   3:     ApplicationSettings.Save(this);

   4: }

This preserved the functionality that exists in the built in Settings support in .NET (which was being used in my application).

By keeping all of the property names the same and making a few tweaks to the types of some fields in my application, I had swapped out the entire “settings” infrastructure in about 45 minutes.

I’m planning some other JSON activities within my application, so the overhead of using JSON.NET is acceptable.

The best part of this alternative is that there isn’t any magic. It’s all easy to manage. Further, I can easily modify my installer to properly handle/update, etc., the settings file with just a few clicks.

I’m not going back to the built-in .NET settings support again. I’ve learned my lesson.  Smile

What have you done for “user” settings?

Sending an email using SMTP in .NET 4.0

I had need of sending embedded images within an e-mail. .NET has had a few handy classes for sending an email using SMTP for a few versions. While there were a few examples floating around the internet, none were as clean and easy to follow as I expected. So, I decided to create a simple sample application in C# which demonstrates how to embed an image (or other content) in a email that contains both plain text and HTML content. It’s really not difficult. A number of examples ignore the fact that almost all of the objects need to be disposed, so I corrected that. This code is intentionally written synchronously to keep the sample simpler and easier to follow (and I didn’t need asynchronous sending for my learning exercise).

image

Above is the output (if you happen to have a picture of NYC). The example code uses Gmail as the SMTP host (it has the settings that generally work for gmail in most situations, including using SSL).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;
using System.Net;
using System.Net.Mime;

namespace TestEmailAttachments
{
    public class EmailSettings
    {
        public string ToAddress { get; set; }
        public string FromAddress { get; set; }
        public string AccountName { get; set; }
        public string AccountPassword { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            EmailSettings settings = new EmailSettings()
            {
                ToAddress = "sample@example.com",
                FromAddress = "sender@example.com",
                AccountName = "sample@example.com",
                AccountPassword = "mypa$$w0rd$ux"
            };
            SendEmail(settings);
            Console.WriteLine("Done");
            Console.ReadKey();
        }


        static void SendEmail(EmailSettings settings)
        {
            if (settings == null) { throw new ArgumentNullException("need settings!");  }
            // Almost everything used my the mail system is disposable
            // so, we'll use 'using' liberally
            using (MailMessage mail = new MailMessage())
            {
                //set the e-mail address
                mail.From = new MailAddress(settings.FromAddress);
                mail.To.Add(settings.ToAddress);

                //set the subject
                mail.Subject = "New York City!";

                // create some content
                string textPlain = "I'm sorry, but you won't see the pretty photos inline. Look for an attachment.";
                string textHtml = "Here is an embedded image.<img src=cid:NewYorkCity1>";

                // setup the alternate views (so different type of e-mail clients can see the content)
                using (AlternateView 
                        plainView = AlternateView.CreateAlternateViewFromString(textPlain, null, 
                            MediaTypeNames.Text.Plain), 
                        htmlView = AlternateView.CreateAlternateViewFromString(textHtml, null, 
                            MediaTypeNames.Text.Html) )
                {
                    //create the LinkedResource (embedded image)
                    using (LinkedResource photo = 
                        new LinkedResource(@"D:\Temp\nyc2009\NewYorkCity (1 of 1).jpg"))
                    {
                        // the content id here must match the content id used in the html as the 'cid:NNNNNNNN'
                        photo.ContentId = "NewYorkCity1";
                        // set the content type to match the image (in this case, it's a jpeg)
                        photo.ContentType = new ContentType(MediaTypeNames.Image.Jpeg) 
                            { Name = "NewYorkCity (1 of 1).jpg" };

                        // the htmlView needs the resource
                        htmlView.LinkedResources.Add(photo);

                        // add each view to the alternate views collection
                        mail.AlternateViews.Add(plainView);
                        mail.AlternateViews.Add(htmlView);

                        // send the message, again diposable
                        using (SmtpClient smtp = new SmtpClient())
                        {
                            // these are gmail settings... you'll need to adjust them as needed
                            smtp.EnableSsl = true;
                            smtp.Host = "smtp.gmail.com";
                            smtp.Port = 587;
                            smtp.UseDefaultCredentials = false;
                            smtp.Credentials = new NetworkCredential(settings.AccountName, 
                                settings.AccountPassword);
                            smtp.Send(mail);
                        }
                    }
                }                                
            }
        }
    }
}

Use this only for good. Smile