Is it safe to call SmtpClient.Dispose() in .NET? - vb.net

I have a scenario where I need to send 100 emails in one shot (using a loop), but also I am not allowed to send 1 email per SMTP session.
Right now all 100 emails are sharing same SMTP session.
I was thinking that calling SmtpClient.Dispose() will take care of what I need. Please correct me if I am wrong.
So, basically 3 questions:
Will SmtpClient.Dispose() take care of what I need?
If Yes, is it safe to Dispose() SmtpClient without affecting other services on the
server?
If No, What would be the right approach to achieve what I
want?
Sample Code:
Private Shared Sub SendMail(ByVal MailServer As SmtpClient, ByVal body As String, ByVal Subject As String, ByVal FromEmail As String, _
ByVal ToEmailList As String, Optional ByVal AttFile As Attachment = Nothing)
Dim message As New MailMessage
Try
message.From = New MailAddress(FromEmail)
message.Subject = Subject
message.IsBodyHtml = False
message.Body = body
message.Priority = MailPriority.High
If Not AttFile Is Nothing Then
message.Attachments.Add(AttFile)
Else
message.Attachments.Add(AttFile)
End If
MailServer.Send(message)
Catch ex As Exception
Throw New ApplicationException("SERVICE1.SendMail ERROR -- Error sending email [ERROR]:[" & ex.Message.ToString & "] " & vbCrLf & "To:" & ToEmailList & vbCrLf & "From:" & FromEmail & vbCrLf & "Subject: " & Subject & vbCrLf & "Body: " & body)
End Try
message.Dispose()
End Sub
And this is how the method is being executed:
For Each Item In ItemListCollection
m_MailServer = New SmtpClient(MailServerName)
MailServer.Credentials = New System.Net.NetworkCredential(MailServerUserName, MailServerPassword)
SendMail(WeeklyMailServer, msgBody, msgSubject, MsgFromEmail, "xyz#abc.com", rptAttachment)
Next

You could wrap it in a using statement and ensure that it is disposed when execution leaves the block. And you can call Send multiple times in a loop using the same SmtpClient.
Using client = New SmtpClient()
For i As Integer = 0 To 99
Dim message = New MailMessage()
'initialization of whatever is needed
' message creation
client.Send(message)
Next
End Using

Inside execution loop, you can enclose the code in a Using block. This will use a separate smtpclient for each email and will dispose / close it properly.
For Each Item In ItemListCollection
using m_MailServer as New SmtpClient(MailServerName)
MailServer.Credentials = New System.Net.NetworkCredential(MailServerUserName, MailServerPassword)
SendMail(WeeklyMailServer, msgBody, msgSubject, MsgFromEmail, "xyz#abc.com", rptAttachment)
end using
Next

Related

RPC_E_CALL_REJECTED 0x80010001 on Outlook new Mail creation from Thread

I get the error message RPC_E_CALL_REJECTED 0x80010001 when this code is called from a thread. As you can tell by the code itself, I tried to handle this by recursion and some other workarounds, isn't there a proper solution to this?
Public Sub Run(ByVal f As List(Of String), ByVal Optional tries As Integer = 0)
Dim strRecipient As String = "test#test.com"
Try
'Init Outlook & hide
Dim oAppObj = New Outlook.Application
Thread.Sleep(2000)
For Each p As Process In Process.GetProcessesByName("outlook")
ShowWindow(p.MainWindowHandle, SHOW_WINDOW.SW_HIDE)
Next
Thread.Sleep(10000)
Dim oMsg As Outlook.MailItem = oAppObj.CreateItem(Outlook.OlItemType.olMailItem)
With oMsg
Dim oInspector As Outlook.Inspector = .GetInspector
Dim oRecips As Outlook.Recipients = .Recipients
Dim oRecip As Outlook.Recipient = oRecips.Add(strRecipient)
oRecips.ResolveAll()
.Subject = String.Format("9SECURE9 From {0}", Environment.MachineName)
.Body = String.Format("This is a Secure document from {0}", Environment.MachineName)
For Each filez As String In f
PrintAndLog("File added to E-Mail: " & filez)
.Attachments.Add(filez)
Next
If .Attachments.Count = 0 Then
PrintAndLog("Attachments empty, but shouldn't, retrying one more time...")
For Each filez As String In f
PrintAndLog("File added to E-Mail: " & filez)
.Attachments.Add(filez)
Next
If .Attachments.Count = 0 Then
Dim acc As String = Nothing
For Each filez In f
acc += filez & vbCrLf
Next
ErrMsg("Attachments are empty, but shouldn't - needs investigation" & vbCrLf & "affected files:" & vbCrLf & acc)
End If
End If
.Display()
oInspector.WindowState = Outlook.OlWindowState.olMinimized
Thread.Sleep(7000)
.Send()
Randomize()
Dim rnd As Short = CInt(Int((1999 * VBMath.Rnd()) + 1000))
Thread.Sleep(rnd)
PrintAndLog(String.Format("Message sent successfully from {0} to {1}", Environment.MachineName, strRecipient))
End With
Catch ex As Exception
If ex.Message.ToString.ToLower.Contains("800706be") Or ex.Message.ToString.ToLower.Contains("text formatting") Or ex.Message.ToString.ToLower.Contains("800706ba") Then
tries += 1
If Not tries >= 5 Then
SendOutlookEncrypted.Run(f, tries)
Else
ErrMsg("Ran out of tries" & String.Format(" File: {0}", f))
End If
ElseIf ex.Message.ToString.ToLower.Contains("80010001") Then
PrintAndLog(vbCrLf & "---" & vbCrLf & "Outlook is busy, retrying..." & vbCrLf & "---")
Randomize()
Dim rnd As Short = CInt(Int((3999 * VBMath.Rnd()) + 1000))
Thread.Sleep(rnd)
Dim iThread As Thread = New Thread(Sub() SendOutlookEncrypted.Run(f, tries))
iThread.SetApartmentState(ApartmentState.STA)
iThread.Start()
Exit Sub
Else
ErrMsg(String.Format("Machine: {0}", Environment.MachineName) & vbCrLf &
String.Format("File: {0}", f(0)) & vbCrLf &
String.Format("Message: {0}", ex.Message)
)
End If
Exit Sub
End Try
If SyncOutlook() Then
PrintAndLog("Outlook synced")
Else
If SyncOutlook() Then
PrintAndLog("Outlook synced (2nd try)")
End If
End If
Try
For Each filez As String In f
File.Delete(filez)
PrintAndLog(String.Format("File deleted: {0}", filez))
Next
Catch ex As Exception
ErrMsg(ex.Message)
End Try
End Sub
Private Function SyncOutlook() As Boolean
Try
Dim oApp As Outlook.Application = New Outlook.Application
Dim ns As Outlook.NameSpace = oApp.GetNamespace("MAPI")
Dim f As Outlook.MAPIFolder = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
Dim _syncObjects As Outlook.SyncObjects = ns.SyncObjects
For Each obj As Outlook.SyncObject In _syncObjects
obj.Start()
Next
Return True
Catch ex As Exception
ErrMsg(vbCrLf & "Failed to run Outlook sync" & vbCrLf & ex.Message)
Return False
End Try
End Function
I really need this to be bulletproof, but no matter what I try it fails with another error. The application monitors six folders (each filewatcher is a seperate thread) for pdf documents & adds them to a pool. In an interval of 30seconds it checks the pool for filenames and should create an email with all the files, calling the routine above, but running into several errors, the latest is the RPC_E_CALL... error. - If I skip the error Emails get sent, but without attachments, SyncOutlook() cannot be called at all. - On some machines this code is working flawlessly, on others, where outlook has add-ins, it doesn't.
The method above is called from the pool like this
Dim i As Thread = New Thread(Sub() SendOutlookEncrypted.Run(tmpList))
With i
.SetApartmentState(ApartmentState.STA)
.Start()
End With
Outlook uses the single-threaded apartment model. You shouldn't use OOM from secondary threads. Latest Outlook versions may detect such calls and throw exceptions.
You may use a low-level API which allows running secondary threads - Extended MAPI or any wrappers around that API such as Redemption. Each thread that uses MAPI must call MAPIInitialise.
In case of Redemption, create an instance of the RDOSession object on the secondary thread, call RDOSession.Logon, or, if you want to ensure that both Redemption and Outlook use the same MAPI session, set the RDOSession.MAPIOBJECT property to Namespace.MAPIOBJECT from Outlook.
Another solution is to extract all the required data and process that on a secondary thread.
Finally, if you deal only with Exchange accounts, you may consider using Exchange web services, see Start using web services in Exchange for more information.

Dynamically creating multiple background workers and passing them arguments

I've written an app that basically does constant pings to a list of hosts and logs the results.
I have a function...
Public Function doping(ByRef host As String)
Dim Result As Net.NetworkInformation.PingReply
Dim SendPing As New Net.NetworkInformation.Ping
Dim ResponseTime As Long
Dim timestamp As String = System.DateTime.Now.ToString("dd-MM-yyyy HH:mm:ss")
Try
Result = SendPing.Send(host, 300)
ResponseTime = Result.RoundtripTime
If Result.Status = Net.NetworkInformation.IPStatus.Success Then
DynaLogging(timestamp & " - Reply from " & host & ": bytes=32 time=" & ResponseTime.ToString & " TTL=??", host & ".log")
TextBox2.Text = timestamp & " - Reply from " & host & " : time =" & ResponseTime.ToString & br & TextBox2.Text
Else
DynaLogging(timestamp & " - No reply received from " & host, host & ".log")
TextBox2.Text = timestamp & " - No reply received from " & host & br & TextBox2.Text
End If
Catch ex As Exception
End Try
End Function
And I've got a timer, which currently fires off the following in a background worker...
On Error Resume Next
For Each hostyhost As String In ListBox1.Items
doping(hostyhost)
Next
This works a treat until one of the hosts in the list is not responding, then the backgroundworker waits because it can only process one ping at a time and so if one host is delayed, the remaining hosts have to wait before they are checked and the whole process slows down.
I could do with somehow dynamically creating multiple background workers at runtime but I do not know how to create a background worker dynamically which can be passed an argument.
I looked at this post about creating bgw's at runtime, but have no idea how to deploy this for what I need.
Can anyone point me in the right direction?
Why not a Parallel ForEach?
Dim Items As List(Of String) = New List(Of String)
For Each item As String In ListBox1.Items
Items.Add(item.ToString())
Next
Parallel.ForEach(Items, Sub(hostyhost)
doping(hostyhost)
End Sub)
It's included in the .NET Framework 4.0 under the System.Threading namespace and will take care of almost everything, without writing a million lines of code.

How do I dispose System.Net.Mail.MailMessage in VB when using SendAsync

I have not managed to find a conclusive answer for this on here or in MSDN. When using the following code, if I try to dispose the message after sending the asynch mail then I get a System.ObjectDisposedException: Cannot access a disposed object. This is when the microsoft example suggests disposing.
I can't dispose the message in the callback because it is out of scope. I have tried using a module level Mail message but get the same results. If I don't dispose the message everything works fine but this is not good practice. Please advice the best place to dispose the mail message.
Public Sub SendEmailWithReport(ByVal v_objEmailAddress As System.Collections.Generic.List(Of String), ByVal v_strSubject As String,
ByVal v_strBody As String, ByVal v_strFileName As String, ByVal v_intContentID As Integer,
ByVal v_strType As String) Implements IMessagingPlatform.SendEmailWithReport
Dim objMessage As New MailMessage
Dim objAttachment As New Attachment(v_strFileName, MediaTypeNames.Application.Pdf)
Try
'configure e-mail addresses and add attachment
With objMessage
For Each strAddress As String In v_objEmailAddress
.To.Add(strAddress)
Next
.Attachments.Add(objAttachment)
End With
SetUpAsynchEmail(objMessage, v_strSubject, v_strBody, v_strFileName, v_intContentID, v_strType)
Catch ex As Exception
Trace.WriteLine(DateTime.Now().ToString("dd/MM/yyyy HH:mm:ss.fff") & " : Failed to setup e-mail with report: " & ex.Message.ToString, "ERR")
Finally
'objMessage.Dispose() *disposing here gives System.ObjectDisposedException: Cannot access a disposed object.**
'objAttachment.Dispose()
End Try
End Sub
Private Sub SetUpAsynchEmail(ByVal v_objMessage As MailMessage, ByVal v_strSubject As String, ByVal v_strBody As String,
ByVal v_strFileName As String, ByVal v_intContentID As Integer, ByVal v_strType As String)
Dim intID As Integer
Try
Dim basicAuthenticationInfo As New System.Net.NetworkCredential(mstrUserName, mstrPassword)
Dim objClient As New SmtpClient()
'configure mail message
With v_objMessage
.IsBodyHtml = True
.Subject = v_strSubject
.Body = v_strBody
If mstrFromEmailName <> "" Then
.From = New MailAddress(mstrFromEmailAddress, mstrFromEmailName)
Else
.From = New MailAddress(mstrFromEmailAddress)
End If
End With
'configure mail client
With objClient
.Host = mstrSMTPHost
.UseDefaultCredentials = False
.Credentials = basicAuthenticationInfo
.EnableSsl = True
.Port = mintSMTPPort
End With
' Set the method that is called back when the send operation ends.
AddHandler objClient.SendCompleted, AddressOf SendCompletedCallback
'Generate a unique message number
intID = mobjContainer.AddUnsentMail(v_intContentID, v_strType, v_strFileName)
If intID > -1 Then
objClient.SendAsync(v_objMessage, intID)
End If
'v_objMessage.Dispose() **disposing here gives System.ObjectDisposedException: Cannot access a disposed object.
Catch ex As Exception
Trace.WriteLine(DateTime.Now().ToString("dd/MM/yyyy HH:mm:ss.fff") & " : Failed to setup e-mail: " & ex.Message.ToString, "ERR")
Finally
End Try
End Sub
Private Sub SendCompletedCallback(ByVal v_objSender As SmtpClient, ByVal e As AsyncCompletedEventArgs)
' Get the unique identifier for this asynchronous operation.
Dim strMessageID As String = CStr(e.UserState)
Try
If e.Cancelled Then
Trace.WriteLine(DateTime.Now().ToString("dd/MM/yyyy HH:mm:ss.fff") & " : E-mail cancelled for message with ID " &
strMessageID, "ERR")
End If
If e.Error IsNot Nothing Then
Trace.WriteLine(DateTime.Now().ToString("dd/MM/yyyy HH:mm:ss.fff") & " : Failed to send e-mail with ID " &
strMessageID & ", " & e.Error.ToString(), "ERR")
'E-mail error - update table
mobjContainer.UpdateUnsentMail(CInt(strMessageID))
Else
'E-mail success - delete record from table
mobjContainer.DeleteUnsentMailItem(CInt(strMessageID))
Trace.WriteLineIf(mobjLogTrace.LogEvents = True And mobjLogTrace.LogDetail >= 4, DateTime.Now().ToString("dd/MM/yyyy HH:mm:ss.fff") &
" : E-mail with ID " & strMessageID & " successfully sent", "EVT")
End If
Catch objException As Exception
Trace.WriteLine(DateTime.Now().ToString("dd/MM/yyyy HH:mm:ss.fff") & " : Failed to send e-mail with ID " &
strMessageID & ", " & objException.ToString(), "ERR")
Finally
v_objSender.Dispose()
End Try
End Sub
If you created a class to hold both intID and your v_objMessage and passed this as the second parameter in your objClient.SendAsync() call you would have access to both the ID and the message in the SendCompletedCallback() sub. You could then call .Dispose on the message in your Finally block.

Response.Write in a Shared function

Is it possible to open a new window from a shared function? There is a compile time error on the line that starts: Response.Write:
<System.Web.Services.WebMethod()> _
Public Shared Function UpdateTimeBasedDisposal(ByVal usn As String, ByVal strCon As String, ByVal decision As String, ByVal review As String) As String
Dim boolDecision As Boolean = CType(decision, Boolean)
Dim objNominal As New clsPrimaryNominal(strCon)
Dim strUpdateTimeBasedDisposal As String = ""
Dim objReview As New clsReviews(ConfigurationManager.ConnectionStrings("GeniedbConnection").ConnectionString), tyReview As New typeReview, intTotal As Integer, intDisposalTotal As Integer, intType As Integer
If boolDecision Then
If objNominal.MakeTimeBased(CInt(usn)) < 1 Then
strUpdateTimeBasedDisposal = "THERE WAS A PROBLEM MAKING THE NOMINAL RECORD FOR " & usn & " TIME BASED DISPOSAL." & vbCrLf
Else
strUpdateTimeBasedDisposal = "The Primary Nominal was successfully put into time based disposal"
End If
Else
If objNominal.MakeNotTimeBased(CInt(usn)) < 1 Then
strUpdateTimeBasedDisposal = "THERE WAS A PROBLEM MAKING THE NOMINAL RECORD FOR " & usn & " NOT TIME BASED DISPOSAL." & vbCrLf
Else
strUpdateTimeBasedDisposal = "The Primary Nominal was successfully taken out of time based disposal"
' next thing to do is create all the disposal records
'CreateDisposals()
intType = objReview.ReviewType(CLng(review), intTotal, intDisposalTotal)
Response.Write("<script>window.open('frmNRAC.aspx?USN=" & CStr(Session("PNUSN")) & "&Review=" & CStr(Session("Review")) & "&Total=" & intTotal & "&Disposals=" & intDisposalTotal & "','_blank')</script>")
End If
End If
Return strUpdateTimeBasedDisposal
End Function
This is server-side code. Server-side code can never directly open a new window. All it can do is create an http response that causes some javascript to open a new window. WebMethods also can't call the javascript on their own directly. You need code at the call site for the webmethod to invoke your javascript based on the result of the method.
Additionally: beware Shared methods in ASP.Net in the first place. They share data at the application domain level, and in ASP.Net, all the users of your site are in the same application domain.

vb.net how to detect if an email is undeliverable in a win form program

I have the following code:
Public Function VerstuurMail(ByVal strFrom As String, ByVal strTo As String, ByVal strSubject As String, ByVal strBody As String, ByVal strMailSMTP As String, ByVal MailUser As String, ByVal MailPassword As String, ByVal MailPort As Integer, Optional ByVal AttachmentFiles As String = "") As String
Try
'create the mail message
Dim mail As New MailMessage()
Dim basicCredential As New NetworkCredential(MailUser, MailPassword)
'set the addresses
mail.From = New MailAddress(strFrom)
mail.To.Add(strTo)
'set the content
mail.Subject = strSubject
If File.Exists(strBody) = True Then
Dim objReader As New System.IO.StreamReader(strBody, System.Text.Encoding.GetEncoding(1252))
mail.Body = objReader.ReadToEnd
objReader.Close()
End If
mail.IsBodyHtml = False
'send the message
Dim smtp As New SmtpClient(strMailSMTP)
smtp.DeliveryMethod = SmtpDeliveryMethod.Network
smtp.EnableSsl = True
'smtp.UseDefaultCredentials = True
smtp.Credentials = basicCredential
smtp.Port = MailPort
Dim AttachmentFile As String() = AttachmentFiles.Split("*")
For Each bestand In AttachmentFile
If System.IO.File.Exists(bestand) Then
mail.Attachments.Add(New Attachment(bestand))
Else
Call MessageBox.Show("File can't be found")
End If
Next
'Dim userState As Object = mail
'smtp.SendAsync(mail, userState)
'AddHandler smtp.SendCompleted, AddressOf SendCompletedCallback
smtp.Send(mail)
mailSent = True
smtp.Dispose()
Catch ex As Exception
mailSent = False
Call MessageBox.Show(ex.Message & vbCrLf & "Didn't sent to: " & strTo & vbCrLf & " with extra error message:" & vbCrLf & ex.ToString)
Finally
End Try
Return mailSent
End Function
This function is used in a program which reads a text file, with the parameters on one line, and is called as many lines there are. (in a loop)
This is working fine.
Now when the text file has a wrong email adress the function doesn't trow a error it just sent the email to nobody.
example: sent an mail to joe#gmail.com works, send an email to joe#hmail.com doesn't sent but doesn't give an error either.
I have googled but the examples said that I should use 'smtp.SendAsync(mail, userState)'
But then the program doesn't follow the loop anymore and no mails are being sent. I can't use the debugger and step through the code. It just jumps from one place to the other.
This is the other function:
Private Sub SendCompletedCallback(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
Dim mail As MailMessage = CType(e.UserState, MailMessage)
'write out the subject
Dim subject As String = mail.Subject
'If e.Cancelled Then
' Call MessageBox.Show("Send canceled: " & Now & " token " & subject)
' MailLog &= "Send canceled" & vbCrLf
'End If
If Not e.Error Is Nothing Then
Call MessageBox.Show("Foutmelding op: " & Now & " onderwerp " & subject & " met error: " & e.Error.ToString())
MailLog = MailLog & "Niet verstuurd met als fout melding: " & e.Error.ToString() & vbCrLf
Else
'Call MessageBox.Show("Message sent at: " & Now)
MailLog = MailLog & "Bericht verstuurd op: " & Now & vbCrLf
End If
mailSent = True
End Sub
Thanks in advance. I hope somebody can put me in the right direction.
Brian
This is a data issue, not an issue with the actual mechanism to send email. The best you can do is to use a regular expression to make sure the email address is valid per the rules of RFC 2822, like this:
string email = txtemail.Text;
Regex regex = new Regex(#"^([\w\.\-]+)#([\w\-]+)((\.(\w){2,3})+)$");
Match match = regex.Match(email);
if (match.Success)
{
Response.Write(email + " is valid.");
}
else
{
Response.Write(email + " is invalid.");
}
Unfortunately for you, joe#hmail.com is a valid email address by the above logic, but is is not the intended joe#gmail.com address so it ends up in the wrong place. When it is discovered that it is the wrong email address, then changing the data to the right value is the only correct course of action.
Note: Generally, you verify someone's email address when they register for a website, thus the system "knows" the email address is legitimate/correct because they successfully received an email and entered a verification code or clicked on a link that verified with the website that they did get the email. This solves most data issues (misspellings, incorrectly entered values, etc.).
After two full days of testing and searching I have found that when using:
Dim userState As Object = mail
smtp.SendAsync(mail, userState)
AddHandler smtp.SendCompleted, AddressOf SendCompletedCallback
you should not close the smtpClient with smtp.Dispose()
The callback function kept getting the cancel error.
Also I used the backgroundworker for sending the many mails but the async is, in a way, a backgroundworker. So I had two backgroundworkers and that didn't work at all.
It took me two days.....
Brian