MvcMailer, Find the file name of the email - c#

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();

Related

Turn off Files/Folder changes in Global.asax in asp.net

Anti-virus scans the .Net deployed folders. Because of this, application gets logged out frequently for the customers.
Requires lot of approval in order to get exemption at the folder level for the project. So, I used below code:
//FIX disable AppDomain restart when deleting subdirectory
//This code will turn off monitoring from the root website directory.
//Monitoring of Bin, App_Themes and other folders will still be operational, so updated DLLs will still auto deploy.
System.Reflection.PropertyInfo p = typeof(System.Web.HttpRuntime).GetProperty("FileChangesMonitor", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
object o = p.GetValue(null, null);
System.Reflection.FieldInfo f = o.GetType().GetField("_dirMonSubdirs", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.IgnoreCase);
object monitor = f.GetValue(o);
System.Reflection.MethodInfo m = monitor.GetType().GetMethod("StopMonitoring", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); m.Invoke(monitor, new object[] { });
Used this article for above code.
Code works fine for a day. But, problem starts again next day. So, I replace the deployed folder again. Then, everything works fine.
Would like to know what causes the problem starts again. What should be done to not face again.
Also, how to stop scanning all the folders where application deployed. Because, application has custom folders where output files will be saved. This also should not be scanned.
Thanks in advance!
This is because you have your app pool being recycled by IIS (which is a good thing, because it will prevent any memory leaks you might have).
When this happens, Application_start is no longer called: https://msdn.microsoft.com/en-us/library/ms178473.aspx (same applies for IIS 7.0+)
What you should do is set up a static field in your Global.asax and on each Application_AcquireRequestState check if it is set to true. if not, run the code you posted, and then set the field to true.
This will ensure that the code will only ever run once per your worker process being alive.
private static bool hasRemovedFoldersFromMonitoring = false;
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if(!hasRemovedFoldersFromMonitoring){
System.Reflection.PropertyInfo p = typeof(System.Web.HttpRuntime).GetProperty("FileChangesMonitor", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
object o = p.GetValue(null, null);
System.Reflection.FieldInfo f = o.GetType().GetField("_dirMonSubdirs", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.IgnoreCase);
object monitor = f.GetValue(o);
System.Reflection.MethodInfo m = monitor.GetType().GetMethod("StopMonitoring", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); m.Invoke(monitor, new object[] { });
hasRemovedFoldersFromMonitoring = true;
}
}
Since there are only 2 monitors inside the FileChangesMonitor class, you simply need to stop monitoring on both, so the full listing would be:
// Add at the top of the file
using System.Reflection;
// anywhere
private static bool hasRemovedFoldersFromMonitoring = false;
private void StopMonitoring(FileChangesMonitor fcm, string monitorName)
{
var f = typeof(FileChangesMonitor).GetField(monitorName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
var monitor = f.GetValue(fcm);
var m = monitor.GetType().GetMethod("StopMonitoring", BindingFlags.Instance |BindingFlags.NonPublic);
m.Invoke(monitor, new object[] { });
}
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if(!hasRemovedFoldersFromMonitoring){
var fcmPi = typeof(System.Web.HttpRuntime).GetProperty("FileChangesMonitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
var fcm = (FileChangesMonitor)fcmPi .GetValue(null, null);
this.StopMonitoring(fcm, "_dirMonSubdirs");
this.StopMonitoring(fcm, "_dirMonAppPathInternal");
hasRemovedFoldersFromMonitoring = true;
}
}
Thanks a lot to #zaitsman for his help
With his help, modified the code like below
<%# import namespace="System.Reflection"%>
private static bool hasRemovedFoldersFromMonitoring = false;
protected void Application_AcquireRequestState(object sender, EventArgs e)
{
if (!hasRemovedFoldersFromMonitoring)
{
PropertyInfo fcmPi = typeof(System.Web.HttpRuntime).GetProperty("FileChangesMonitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
object o = fcmPi.GetValue(null, null);
FieldInfo fi_dirMonSubdirs = o.GetType().GetField("_dirMonSubdirs", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
object monitor_dirMonSubdirs = fi_dirMonSubdirs.GetValue(o);
MethodInfo m_dirMonSubdirs = monitor_dirMonSubdirs.GetType().GetMethod("StopMonitoring", BindingFlags.Instance | BindingFlags.NonPublic);
m_dirMonSubdirs.Invoke(monitor_dirMonSubdirs, new object[] { });
FieldInfo fi_dirMonAppPathInternal = o.GetType().GetField("_dirMonAppPathInternal", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase);
object monitor_dirMonAppPathInternal = fi_dirMonAppPathInternal.GetValue(o);
if (monitor_dirMonAppPathInternal != null)
{
MethodInfo m_dirMonAppPathInternal = monitor_dirMonAppPathInternal.GetType().GetMethod("StopMonitoring", BindingFlags.Instance | BindingFlags.NonPublic);
m_dirMonAppPathInternal.Invoke(monitor_dirMonAppPathInternal, new object[] { });
}
hasRemovedFoldersFromMonitoring = true;
}
}
Hope code would help someone
However, I initially received null error when accessing monitor_dirMonAppPathInternal object. If anyone could say when the object will not be null, it would be helpful

Uri canonicalization compacting FTP scheme

https://msdn.microsoft.com/en-us/library/system.uri(v=vs.110).aspx
According to the reference above, when specifying an ftp url, the uri class should not compact the url. For example, the following ftp url:
Uri uri = new Uri("ftp://myUrl/%2E%2E/%2E%2E");
Console.WriteLine(uri.AbsoluteUri);
Console.WriteLine(uri.PathAndQuery);
should result in:
AbsoluteUri: "ftp://myUrl/%2E%2E/%2E%2E"
PathAndQuery: "/%2E%2E/%2E%2E"
But, this is NOT what I'm seeing. When I execute the above code using .NET framework 4.5.1, I see:
AbsoluteUri: "ftp://myUrl/"
PathAndQuery: "/"
Moreover, adding to my app.config seems to have no effect:
<uri>
<schemeSettings>
<add name="ftp" genericUriParserOptions="DontUnescapePathDotsAndSlashes" />
</schemeSettings>
</uri>
Since several people have been able to recreate the issue, I've created a bug report with Microsoft. Feel free to upvote:
https://connect.microsoft.com/VisualStudio/feedback/details/2046491/uri-canonicalization-compacting-ftp-scheme
Created a bug report with Microsoft:
https://connect.microsoft.com/VisualStudio/Feedback/Details/2046491
At present, I am working around this issue by (hacking via reflection) removing two flags from the UriParser object within the Uri class:
I call the method below once when my application is instantiating. After instantiation, every FTP Uri object will utilize the new flag combination when parsing.
// CompressPath = 0x800000, // For an authority based Uri remove/compress /./ /../ in the path
// UnEscapeDotsAndSlashes = 0x2000000, // additionally unescape dots and slashes before doing path compression
/// <summary>
/// http://referencesource.microsoft.com/#System/net/System/_UriSyntax.cs
/// </summary>
public static void LeaveDotsAndSlashesEscaped() {
Uri uri = new Uri("ftp://myUrl/%2E%2E/%2E%2E/");
if (uri == null) {
throw new ArgumentNullException("uri");
}
FieldInfo fieldInfo = uri.GetType().GetField("m_Syntax", BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo == null) {
throw new MissingFieldException("'m_Syntax' field not found");
}
object uriParser = fieldInfo.GetValue(uri);
fieldInfo = typeof(UriParser).GetField("m_Flags", BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo == null) {
throw new MissingFieldException("'m_Flags' field not found");
}
object uriSyntaxFlags = fieldInfo.GetValue(uriParser);
// Clear the flags that we don't want
uriSyntaxFlags = (int)uriSyntaxFlags & ~0x2000000 & ~0x800000;
fieldInfo.SetValue(uriParser, uriSyntaxFlags);
}

Where to find the System.Net.Mail.MailWriter class? [duplicate]

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

HttpModule to add headers to request

This seems like a simple operation.
We have a need in our development environment (running on XP/IIS 5) to add some headers into each HttpRequest arriving at our application. (This is to simulate a production environment that we don't have available in dev). At first blush, this seemed like a simple HttpModule, along the lines of:
public class Dev_Sim: IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += delegate { app.Context.Request.Headers.Add("UserName", "XYZZY"); };
}
public void Dispose(){}
}
But on trying to do that, I find that the Headers collection of the Request is read-only, and the Add method fails with an OperationNotSupported exception.
Spending a couple hours researching this on Google, I've come up with no easy answer to what should be a relatively straight-forward problem.
Does anyone have any pointers?
Okay, with the assistance of a co-worker and some experimentation, I found that this can be done with the assistance of some protected properties and methods accessed through reflection:
var headers = app.Context.Request.Headers;
Type hdr = headers.GetType();
PropertyInfo ro = hdr.GetProperty("IsReadOnly",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy);
// Remove the ReadOnly property
ro.SetValue(headers, false, null);
// Invoke the protected InvalidateCachedArrays method
hdr.InvokeMember("InvalidateCachedArrays",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
null, headers, null);
// Now invoke the protected "BaseAdd" method of the base class to add the
// headers you need. The header content needs to be an ArrayList or the
// the web application will choke on it.
hdr.InvokeMember("BaseAdd",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
null, headers,
new object[] { "CustomHeaderKey", new ArrayList {"CustomHeaderContent"}} );
// repeat BaseAdd invocation for any other headers to be added
// Then set the collection back to ReadOnly
ro.SetValue(headers, true, null);
This works for me, at least.
You can add to the Header this way. This is a way to add credential information to the request before it enter the authentication sequence.
string cred = "UN:PW";
System.Web.HttpContext.Current.Request.Headers.Add("Authorization", "Basic " +Convert.ToBase64String(Encoding.ASCII.GetBytes(cred)));

How to add attachments to mailitem using Outlook late binding

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);

Categories

Resources