1

I am trying to get MailMessage in .NET to return a string of the MIME message, but this is not provided in the delivered class. There's another excellent answer on how to create a C# extension method to monkey patch the class to provide the functionality. I am trying to port that to F# with a type extension, but I am getting hung up on how to provide the parameters (especially given that one of them is an F# keyword).

Would really appreciate an explanation of how this is done properly with the answer.

Here's what I have gotten so far (this will, of course, not currently compile):

open System.Net.Mail

module MailExtension =
    type MailMessage with 
        member this.toEml mail =
            let stream = new MemoryStream();
            let mailWriterType = mail.GetType().Assembly.GetType("System.Net.Mail.MailWriter");
            let mailWriter = Activator.CreateInstance(
                                type: mailWriterType,
                                bindingAttr: BindingFlags.Instance | BindingFlags.NonPublic,
                                binder: null,
                                args: new object[] { stream },
                                culture: null,
                                activationAttributes: null)

            mail.GetType().InvokeMember(
                                name: "Send",
                                invokeAttr: BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
                                binder: null,
                                target: mail,
                                args: new object[] { mailWriter, true, true });


            Encoding.UTF8.GetString(stream.ToArray());

1 Answers1

3

Here is a few hints on how to translate C# to F#:

  • the ; is not longer required
  • use "use" instead of "let" for IDisposables
  • for arrays use [| member1, member2 |]
  • for named parameters use name=value
  • wrap keywords in names in ``name``
  • bitwise operators are ||| and &&&
  • use instance name instead of argument

Code that compiles:

open System
open System.IO
open System.Net.Mail
open System.Reflection
open System.Text

module MailExtension =
    type MailMessage with 
        member this.toEml () =
            use stream = new MemoryStream()
            let mailWriterType = this.GetType().Assembly.GetType("System.Net.Mail.MailWriter")
            let mailWriter = Activator.CreateInstance(
                                ``type`` = mailWriterType,
                                bindingAttr = (BindingFlags.Instance ||| BindingFlags.NonPublic),
                                binder = null,
                                args = [| stream |],
                                culture = null,
                                activationAttributes = null)

            this.GetType().InvokeMember(
                                name = "Send",
                                invokeAttr = (BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.InvokeMethod),
                                binder = null,
                                target = this,
                                args = [| mailWriter, true, true |])


            Encoding.UTF8.GetString(stream.ToArray())

Then use:

open MailExtension
let m = new MailMessage()
m.toEml () |> ignore
Alex Netkachov
  • 13,172
  • 6
  • 53
  • 85
  • This is really helpful. I should be able to now call that toEml method on my MailMessage object, but after loading the code to fsi, either as an object method or (just checking everything) as a static method on the MailMessage class, nothing appears. What I am still missing here? – lithiumfrost Jan 30 '18 at 06:09
  • Updated: use `this` instead of `mail` and `open MailExtension` before use. – Alex Netkachov Jan 30 '18 at 15:10
  • What version of the .NET Framework are you using here? The changes made are absolutely correct, so I can call the instance method, but I get this following: `System.MissingMethodException: Method 'System.Net.Mail.MailMessage.Send' not found. at System.RuntimeType.InvokeMember(String name, BindingFlags bindingFlags, Binder binder, Object target, Object[] providedArgs, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParams) at FSI_0002.MailExtension.MailExtension.MailMessage.toEml(MailMessage X1)` It looks like the added parameter is binding the wrong class. – lithiumfrost Feb 02 '18 at 00:28