So, the below code used to work in .NET 4 to get a System.Net.Mail.MailMessage object as a MemoryStream, however with the release of .NET 4.5 beta a runtime exception occurs.
Assembly assembly = typeof(SmtpClient).Assembly;
Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
using (MemoryStream stream = new MemoryStream())
{
ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
object mailWriter = mailWriterContructor.Invoke(new object[] { stream });
MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true }, null);
.....
}
Runtime exception occurs on sendMethod.Invoke().
Managed to figure out how to get this working again in .NET 4.5 beta. The private API Send() method in MailMessage has changed to: internal void Send(BaseWriter writer, bool sendEnvelope, bool allowUnicode)
Please find updated code below.
Assembly assembly = typeof(SmtpClient).Assembly;
Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
using (MemoryStream stream = new MemoryStream())
{
ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
object mailWriter = mailWriterContructor.Invoke(new object[] { stream });
MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true, true }, null);
.....
}
This might be usable if you don't want to go with unsupported hacks and don't mind extra performance hit.
public static class MailMessageExtensions
{
public static string RawMessage(this MailMessage m)
{
var smtpClient = new SmtpClient { DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory };
using (var tempDir = new TemporaryDirectory())
{
smtpClient.PickupDirectoryLocation = tempDir.DirectoryPath;
smtpClient.Send( m );
var emlFile = Directory.GetFiles( smtpClient.PickupDirectoryLocation ).FirstOrDefault();
if ( emlFile != null )
{
return File.ReadAllText( emlFile );
}
else
return null;
}
return null;
}
}
class TemporaryDirectory : IDisposable
{
public TemporaryDirectory()
{
DirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory( DirectoryPath );
}
public string DirectoryPath { get; private set; }
public void Dispose()
{
if ( Directory.Exists( DirectoryPath ) )
Directory.Delete( DirectoryPath, true );
}
}
for checking if extra boolean i use :
If _sendMethod.GetParameters.Length = 2 Then
_sendMethod.Invoke(Message, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Object() {_mailWriter, True}, Nothing)
Else
_sendMethod.Invoke(Message, BindingFlags.Instance Or BindingFlags.NonPublic, Nothing, New Object() {_mailWriter, True, True}, Nothing)
End If
The proposed solution with the extra TRUE works beautifully.
I started to getting the error while running my project in VS2012 even though I am not using .net 4.5 but 4.0 in all my libraries.
The error only happens on the machine where you have installed VS2012, looks like VS2012 makes reference to .net 4.5 while you are debugging. When you deploy and run the application in clients running .net 4.0 everything works fine.
Thus : If you run 4.0 - do not add the extra TRUE, if you run 4.5 add it.
We fought with the mail message conversion for a long time. Ultimately the solution was to use MimeKit.
var memoryStream = new MemoryStream();
var mimeMessage = MimeMessage.CreateFromMailMessage(message);
mimeMessage.WriteTo(memoryStream);
If you use the methods above you will get really close and it will work in most cultures but eventually the subject encoding will defeat you.
For those, who are struggling with mailWriterContructor being null in .NET 5 or facing Parameter count mismatch exception, take a closer look on my solution usable for any stream. Link here
Related
I'm using MvcMailer to save emails to a specified directory locally in my asp.net mvc web application. However I would like to save the file name (e.g. 90b871cd-038f-400a-b4d7-01f87e8c3c26.eml) of the email in the database which will later be accessed using another exe to send emails from the pick up folder.
Could you please advise me on how to retrieve the file name from the mail object?
var mail = Mailer.Example_Mail()
mail.To.Add("some#somedomain.com");
mail.Send();
<smtp from="some#somedomain.com" deliveryMethod="SpecifiedPickupDirectory">
<network host="localhost" />
<specifiedPickupDirectory pickupDirectoryLocation="c:\temp\" />
</smtp>
Thanks in advance!
I thought it might be helpful for someone who will be seeking the answer for the same question. I managed to overcome the issue writing a reflection of System.Net.Mail.MailMessage.Send() as follows.
public static string SaveToTemp(this MailMessage Message)
{
SmtpClient smtp = new SmtpClient();
string fileName = Guid.NewGuid().ToString() + ".eml";
string fileNameWithPath = Path.Combine(smtp.PickupDirectoryLocation, fileName);
Assembly assembly = typeof(SmtpClient).Assembly;
Type _mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
using (FileStream _fileStream = new FileStream(fileNameWithPath, FileMode.Create))
{
// Get reflection info for MailWriter contructor
ConstructorInfo _mailWriterContructor =
_mailWriterType.GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new Type[] { typeof(Stream) },
null);
// Construct MailWriter object with our FileStream
object _mailWriter = _mailWriterContructor.Invoke(new object[] { _fileStream });
// Get reflection info for Send() method on MailMessage
MethodInfo _sendMethod =
typeof(MailMessage).GetMethod(
"Send",
BindingFlags.Instance | BindingFlags.NonPublic);
// Call method passing in MailWriter
_sendMethod.Invoke(
Message,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { _mailWriter,true, true },
null);
// Finally get reflection info for Close() method on our MailWriter
MethodInfo _closeMethod =
_mailWriter.GetType().GetMethod(
"Close",
BindingFlags.Instance | BindingFlags.NonPublic);
// Call close method
_closeMethod.Invoke(
_mailWriter,
BindingFlags.Instance | BindingFlags.NonPublic,
null,
new object[] { },
null);
}
return fileNameWithPath;
}
Caller:
var mail = Mailer.Example_Mail()
mail.To.Add("some#somedomain.com");
var fileName = mail.SaveToTemp(); // Instead of mail.Send();
I have below mentioned code:
string urlEncodedData = URL.Text;
byte[] encryptedData = HttpServerUtility.UrlTokenDecode(urlEncodedData);
Type machineKeySection = typeof(System.Web.Configuration.MachineKeySection);
Type[] paramTypes = new Type[] { typeof(bool), typeof(byte[]), typeof(byte[]), typeof(int), typeof(int) };
MethodInfo encryptOrDecryptData = machineKeySection.GetMethod("EncryptOrDecryptData", BindingFlags.Static | BindingFlags.NonPublic, null, paramTypes, null);
try
{
byte[] decryptedData = (byte[])encryptOrDecryptData.Invoke(null, new object[] { false, encryptedData, null, 0, encryptedData.Length });
string decrypted = Encoding.UTF8.GetString(decryptedData);
decryptedLabel.BackColor = Color.Lime;
decryptedLabel.Text = decrypted;
}
catch (TargetInvocationException)
{
decryptedLabel.BackColor = Color.Red;
decryptedLabel.Text = "Error decrypting data. Are you running your page on the same server and inside the same application as the web resource URL that was generated?";
}
It Decrypts and tell me details about webresource.
locally it works fine.
But on production it always gives me below message from catch block
Error decrypting data. Are you running your page on the same server and inside the same application as the web resource URL that was generated?
The only difference I have is production being on HTTPS. Is above code valid for HTTPS also, or do I have to make change(s) to it?
I also was using this code snippet to decrypt webresource.axd parameter, but lately it stopped working.
Maybe it's change of framework to 4.5, because I found this comment in .net sources - Page class, method DecryptString http://referencesource.microsoft.com/#System.Web/UI/Page.cs,18cf7b1fe99faea6
if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider) {
// ASP.NET 4.5 Crypto DCR: Go through the new AspNetCryptoServiceProvider
// if we're configured to do so.
ICryptoService cryptoService = AspNetCryptoServiceProvider.Instance.GetCryptoService(purpose, CryptoServiceOptions.CacheableOutput);
clearData = cryptoService.Unprotect(protectedData);
}
else {
// If we're not configured to go through the new crypto routines,
// fall back to the standard MachineKey crypto routines.
#pragma warning disable 618 // calling obsolete methods
clearData = MachineKeySection.EncryptOrDecryptData(fEncrypt: false, buf: protectedData, modifier: null, start: 0, length: protectedData.Length, useValidationSymAlgo: false, useLegacyMode: false, ivType: IVType.Hash);
#pragma warning restore 618 // calling obsolete methods
}
Are you sure the only difference is http and https, maybe framework version also?
Nevertheless I used method DecryptString instead EncryptOrDecryptData and below code is working for me. You can check if this working for you too :)
private static string Decrypt(string webResourceParameter)
{
var purposeType = Type.GetType("System.Web.Security.Cryptography.Purpose, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
if (purposeType == null)
return null;
try
{
var purpose = Activator.CreateInstance(purposeType, "AssemblyResourceLoader.WebResourceUrl");
const BindingFlags decryptFlags = BindingFlags.NonPublic | BindingFlags.Static;
var decryptString = typeof (Page).GetMethod("DecryptString", decryptFlags);
var decrypt = decryptString.Invoke(null, new[] {webResourceParameter, purpose}) as string;
return decrypt;
}
catch (Exception ex)
{
return null;
}
}
The following call is resulting in a
TargetException
HResult = -2146232829
Message = Error in the application.
CALL:
builderType.CreateType();
// Exception on the following line:
int res1 = (int) builderType.GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new Object [] { ((object) (new string [] { "Test" })), });
CONTEXT:
This call is placed after creation of the TypeBuilder and MethodBuilderas follows:
var domain = AppDomain.CurrentDomain;
var name = new AssemblyName("HouseOfSynergy.PowerTools.ProcessRestarter");
var builderAssembly = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Save, directory.FullName);
var builderModule = builderAssembly.DefineDynamicModule("HouseOfSynergy.PowerTools.ProcessRestarter", "HouseOfSynergy.PowerTools.ProcessRestarter.exe", Global.Instance.Debug);
var builderType = builderModule.DefineType("Program", TypeAttributes.Class | TypeAttributes.Public);
var builderMethod = builderType.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Private, typeof(int), new Type [] { typeof(string []) });
Please note that if I remove the Invoke call which is causing the error, the dynamic assembly loads and executes properly. The call to Invoke seems about right. Any thoughts on what is going wrong?
In the line
var builderAssembly = domain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Save, directory.FullName);
you only allow your assembly to be saved, not be run. See AssemblyBuilderAccess documentation.
In your case, you should use AssemblyBuilderAccess.RunAndSave.
As a requirement of our application, we add files with other file's properties within document libraries using the following method:
private static SPFile AddFile(SPListItem item, Stream stream, string filename, SPFolder destinationFolder, string comment)
{
string destinationFilePath = destinationFolder.Url + "/" + filename;
using (var web = item.Web)
{
object file;
switch (SPFarm.Local.BuildVersion.Major)
{
case 12:
var parameters2007 = new object[] { destinationFilePath, stream, false, item.File.Author, web.CurrentUser, item.File.TimeCreated, item.File.TimeLastModified, null, comment, true };
file = destinationFolder.Files.GetType().GetMethod("AddInternal", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(Stream), typeof(Boolean), typeof(SPUser), typeof(SPUser), typeof(DateTime), typeof(DateTime), typeof(Hashtable), typeof(string), typeof(Boolean) }, null).Invoke(destinationFolder.Files, parameters2007);
break;
default:
case 14:
var parameters2010 = new object[] { destinationFilePath, stream, null, item.File.Author, web.CurrentUser, item.File.TimeCreated, item.File.TimeLastModified, comment, true };
file = destinationFolder.Files.GetType().GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(Stream), typeof(Hashtable), typeof(SPUser), typeof(SPUser), typeof(DateTime), typeof(DateTime), typeof(string), typeof(Boolean) }, null).Invoke(destinationFolder.Files, parameters2010);
break;
}
return file as SPFile;
}
}
The reason we use reflection is that, we need to override created_by and modified_by properties of the file when we create it; which had proved very painful to do by other methods. And Sharepoint 2007's API was limited to accomplish that, hence reflection.
There is a scenario where the web.CurrentUser is sharepoint/system (usually because the context is in elevated state) which is fine as long it is used for modified_by. However, in Sharepoint 2013, SPFileCollection.Add method does not override created_by property, it simply keeps it as CurrentUser(independent to modified_by parameter) whatever we do about it. That results messing the whole workflow of our application.
I've checked Sharepoint 2013's dll file using reflector and it seems the method and its parameters are the same as 2010.
Updating properties after creating the files is something I'd like to avoid since I have bad memories with it. Usually fixing one problem creates several, and I don't recall the problems in detail. Not to mention that that change would need extensive testing in 3 different versions.
Is there a functional way to override created_by property of a file using SPFileCollection.Add on Sharepoint 2013?
I'm trying to create a mail item and add some attachments to it using late binding. I've already managed to create the mail item, but I cannot invoke the Attachments property.
object objApp;
object objEmail;
Type objClassType = Type.GetTypeFromProgID("Outlook.Application");
objApp = Activator.CreateInstance(objClassType);
// Microsoft.Office.Interop.Outlook.OlItemType.olMailItem = 0
objEmail = objApp.GetType().InvokeMember("CreateItem", BindingFlags.InvokeMethod, null, objApp, new object[] { 0 });
mailItemType.InvokeMember("Subject", BindingFlags.SetProperty, null, objEmail, new object[] { subject });
// THIS RETURNS NULL?!
PropertyInfo att = mailItemType.GetProperty("Attachments", BindingFlags.GetProperty);
What can I do when there's no Attachments property (or method) to invoke? With early binding it's simply objEmail.Attachments.Add(...)
The problem was I called the GetProperty directly. It should be InvockeMember with BindingFlags.GetProperty. I think this is because the interface is IUnknown and only method invoking works.
I also discovered that you can get the Attachments type from CLSID
Type attachmentsType = Type.GetTypeFromCLSID(new Guid("0006303C-0000-0000-C000-000000000046"));
and then call
attachmentsType.InvokeMember("Add", BindingFlags.InvokeMethod, null, attachments, new object[] { ... });
This example is for Office 2003.
I think the GetProperty stmt isn't quite right, I got this to work by doing the following:
object oMailItemAttachments = oMailItem.GetType().InvokeMember("Attachments", System.Reflection.BindingFlags.GetProperty, null, oMailItem, null);
parameter = new object[4];
parameter[0] = #sFileName;
parameter[1] = 1;
parameter[2] = Type.Missing;
parameter[3] = Type.Missing;
oMailItemAttachments.GetType().InvokeMember("Add", System.Reflection.BindingFlags.InvokeMethod, null, oMailItemAttachments, parameter);