I have a powershell script using WinScp that allows me to authenticate the server cert against a fingerprint stored as a string "a1:ec:72:18:8b:c3:dc:12:9b:77:b0:6d:f4:c1:a6:cf:db:47:8f:66:66:15:14:39:c4:62:85:a7:b2:73:f7:93"
I am attempting to implement FluentFtp inside the main data processing app so we no longer need the script. _ftpsOptions is an object containing various ftps relevant settings from Configuration. I've read that I need to implement a callback handler to ValidateCertificate so, in my ftps method:
client.Config.ValidateAnyCertificate = _ftpsOptions.IgnoreCertificateErrors;
client.ValidateCertificate += new FtpSslValidation(OnValidateCertificate); //this is ignored if ValidateAnyCertificate is true
and then.
/// <summary>
/// Callback handler for FtpSslValidation
/// </summary>
/// <param name="control"><see cref="BaseFtpClient"/> A base Ftp Client</param>
/// <param name="e"><see cref="FtpSslValidationEventArgs"/> options</param>
private void OnValidateCertificate(BaseFtpClient control, FtpSslValidationEventArgs e)
{
X509Certificate2 serverCert = (X509Certificate2)e.Certificate;
if (serverCert.Thumbprint == _ftpsOptions.FingerPrint)
{
e.PolicyErrors = SslPolicyErrors.None;
}
else
{
e.PolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch;
}
if (e.PolicyErrors != System.Net.Security.SslPolicyErrors.None)
{
// invalid cert, do you want to accept it?
e.Accept = false;
}
else
{
e.Accept = true;
}
}
To access the Thumbprint property I have to cast to X509Certificate2 as mentioned in several blog posts. There is no 'bad server cert' option in the SslPolicyErrors enum, so picking the closest.
Problem is: check is failing. I can't see the Thumbprint value in VS because its being optimized away. Tried adding [MethodImpl(MethodImplOptions.NoOptimization] to the method, but it didn't help. I think it would need adding to the X509Certificate2 class. Added a Log.Debug and that worked.
[10:50:49 DBG] [FtpsFluent.OnValidateCertificate] Server Thumbprint: 679B8B251C541FA0CCF3734B25A7C523635B9360
[10:50:49 DBG] [FtpsFluent.OnValidateCertificate] Validate Against: a1:ec:72:18:8b:c3:dc:12:9b:77:b0:6d:f4:c1:a6:cf:db:47:8f:66:66:15:14:39:c4:62:85:a7:b2:73:f7:93
These look different enough that I don't think I'm comparing the same thing. Should I change my config to store the 679B... value? Should I be going about this differently?
Related
In SSIS (using VS 2013 with latest SSDT) I'm returning a SQL result set to the package and iterating through it with a Foreach ADO Enumerator. In the loop I'd like to have a control flow Script Task call a WCF service.
I have read and understand the tutorial found here but, as referenced here, this tutorial is using the data flow Script Component so I can't use its PreExecute() method.
How do I override the app.config setting programmatically to avoid the problem stated in the tutorial?
using a WCF client, the normal method of configuring the WCF client from the application configuration file doesn't work well.
Edited after answer:
I ended up structuring my code like this.
public ChannelFactory<IMyService> ChannelFactory;
public IMyService Client;
public void PreExecute()
{
//create the binding
var binding = new BasicHttpBinding
{
Security =
{
Mode = BasicHttpSecurityMode.Message,
Transport = {ClientCredentialType = HttpClientCredentialType.Windows}
}
};
//configure the binding
Uri myUri = new Uri(Dts.Variables["myUri"].Value.ToString());
var endpointAddress = new EndpointAddress(myUri);
ChannelFactory = new ChannelFactory<IMyService>(binding, endpointAddress);
//create the channel
Client = ChannelFactory.CreateChannel();
}
public void PostExecute()
{
//close the channel
IClientChannel channel = (IClientChannel)Client;
channel.Close();
//close the ChannelFactory
ChannelFactory.Close();
}
/// <summary>
/// This method is called when this script task executes in the control flow.
/// Before returning from this method, set the value of Dts.TaskResult to indicate success or failure.
/// To open Help, press F1.
/// </summary>
public void Main()
{
PreExecute();
//TODO: code
PostExecute();
Dts.TaskResult = (int)ScriptResults.Success;
}
A ScriptTask does not have a PreExecute method. You'll have to do all the instantiation and binding stuff per iteration of your loop. It's similar to what's happening in the script component example in that the setup happens once and then all the rows stream out. If you were looping over your data flow, it'd have to redo the preexecute methods per loop.
Based on the comments at the end of the article, it sounds like the code controls the configuration and there's no need to modify the app.config. WCF stuff isn't my strong suit so I can't comment on that.
HttpWebRequest doesn't support SOCKS proxies, so after a long research I've determined, that the best way to add SOCKS support for WebRequest and WebClient (without reinventing the wheel, like SocksHttpWebRequest does) was to create a temporary HTTP proxy which forwards any incoming request to the SOCKS proxy.
Forwarding an HTTP request is easy, and works like charm.
Forwarding an HTTPS request should be theoretically easy too:
The HttpWebRequest connects to our HTTP proxy, and sends the following:
CONNECT google.com:443 HTTP/1.0
Host: google.com
We connect to google.com:443 through the SOCKS proxy, and then send back the following to the client:
HTTP/1.0 200 Tunnel established
According to this document, at this point, we copy all the bytes between the streams, and when one closes the connection, we close the other one too.
However, copying from one NetworkStream to another doesn't seem to work like I expected. At one point, Read() seems to hang for no apparent reason. If I set timeouts, regardless how big ones, I get certificate errors.
The stripped down version of the code I'm using right now:
/// <summary>
/// In theory, forwards any incoming bytes from one stream to another.
/// </summary>
/// <param name="httpProxyStream">The HTTP proxy, which we opened for <code>HttpWebRequest</code>.</param>
/// <param name="socksProxyStream">The SOCKS proxy, which we connect to and forward the traffic from the HTTP proxy.</param>
private void TunnelRequest(NetworkStream httpProxyStream, NetworkStream socksProxyStream)
{
while (true)
{
try
{
if (httpProxyStream.DataAvailable)
{
CopyStreamToStream(httpProxyStream, socksProxyStream);
}
if (socksProxyStream.DataAvailable)
{
CopyStreamToStream(socksProxyStream, httpProxyStream);
}
}
catch
{
break;
}
}
}
/// <summary>
/// Copies the first stream's content from the current position to the second stream.
/// </summary>
/// <param name="source">The source stream.</param>
/// <param name="destionation">The destionation stream.</param>
/// <param name="flush">if set to <c>true</c>, <c>Flush()</c> will be called on the destination stream after finish.</param>
/// <param name="bufferLength">Length of the buffer.</param>
public static void CopyStreamToStream(Stream source, Stream destionation, bool flush = true, int bufferLength = 4096)
{
var buffer = new byte[bufferLength];
while (true)
{
int i;
try
{
i = source.Read(buffer, 0, bufferLength);
}
catch
{
break;
}
if (i == 0)
{
break;
}
destionation.Write(buffer, 0, i);
}
if (flush)
{
destionation.Flush();
}
}
The full class is available here: HttpToSocks.cs
I'm stuck on this piece of code for days now. The task is so simple, yet it just won't work... Please help me regain my sanity.
EDIT: I am aware that the while(true) is not my greatest line, but after lots of variations, this remained in the code to make sure it won't exit prematurely, and that's why I'm getting certificate errors.
EDIT 2: The problem has been fixed. The full class is available in the git repository, if anyone's interested.
How can I check for an internet connection constantly in my application and respond if the connection is not available?
Currently I am using:
while(true) {
if(HasConnection()) {
//doSomething..
}
//stop app by 1sec
}
but it seems rather inelegant.
The accepted answer to this question on superuser describes the way the Windows determines if it has network access. You could use a similar method, but I would spawn a separate thread when your application starts that is responsible for doing the check. Have the separate thread perform the check in whatever manner you feel is the best and raise an event if the connection status changes.
You're looking for the NetworkAvailabilityChanged event.
To check for internet connectivity, you can ping a reliable website, such as Google.com.
Note that it is not possible to be notified of every change in internet connectivity (such as an ISP outage).
Use the following code:
public static class LocalSystemConnection
{
[DllImport("wininet.dll", SetLastError=true, CallingConvention = CallingConvention.ThisCall)]
extern static bool InternetGetConnectedState(out ConnectionStates lpdwFlags, long dwReserved);
/// <summary>
/// Retrieves the connected state of the local system.
/// </summary>
/// <param name="connectionStates">A <see cref="ConnectionStates"/> value that receives the connection description.</param>
/// <returns>
/// A return value of true indicates that either the modem connection is active, or a LAN connection is active and a proxy is properly configured for the LAN.
/// A return value of false indicates that neither the modem nor the LAN is connected.
/// If false is returned, the <see cref="ConnectionStates.Configured"/> flag may be set to indicate that autodial is configured to "always dial" but is not currently active.
/// If autodial is not configured, the function returns false.
/// </returns>
public static bool IsConnectedToInternet(out ConnectionStates connectionStates)
{
connectionStates = ConnectionStates.Unknown;
return InternetGetConnectedState(out connectionStates, 0);
}
/// <summary>
/// Retrieves the connected state of the local system.
/// </summary>
/// <returns>
/// A return value of true indicates that either the modem connection is active, or a LAN connection is active and a proxy is properly configured for the LAN.
/// A return value of false indicates that neither the modem nor the LAN is connected.
/// If false is returned, the <see cref="ConnectionStates.Configured"/> flag may be set to indicate that autodial is configured to "always dial" but is not currently active.
/// If autodial is not configured, the function returns false.
/// </returns>
public static bool IsConnectedToInternet()
{
ConnectionStates state = ConnectionStates.Unknown;
return IsConnectedToInternet(out state);
}
}
[Flags]
public enum ConnectionStates
{
/// <summary>
/// Unknown state.
/// </summary>
Unknown = 0,
/// <summary>
/// Local system uses a modem to connect to the Internet.
/// </summary>
Modem = 0x1,
/// <summary>
/// Local system uses a local area network to connect to the Internet.
/// </summary>
LAN = 0x2,
/// <summary>
/// Local system uses a proxy server to connect to the Internet.
/// </summary>
Proxy = 0x4,
/// <summary>
/// Local system has RAS (Remote Access Services) installed.
/// </summary>
RasInstalled = 0x10,
/// <summary>
/// Local system is in offline mode.
/// </summary>
Offline = 0x20,
/// <summary>
/// Local system has a valid connection to the Internet, but it might or might not be currently connected.
/// </summary>
Configured = 0x40,
}
if you only need to know if at least one connection is available you can try this:
InternetGetConnectedStateEx()
http://msdn.microsoft.com/en-us/library/aa384705%28v=vs.85%29.aspx
if you want to check continuously then use timer
private Timer timer1;
public void InitTimer()
{
timer1 = new Timer();
timer1.Tick += new EventHandler(timerEvent);
timer1.Interval = 2000; // in miliseconds
timer1.Start();
}
private void timerEvent(object sender, EventArgs e)
{
DoSomeThingWithInternet();
}
private void DoSomeThingWithInternet()
{
if (isConnected())
{
// inform user that "you're connected to internet"
}
else
{
// inform user that "you're not connected to internet"
}
}
public static bool isConnected()
{
try
{
using (var client = new WebClient())
using (client.OpenRead("http://clients3.google.com/generate_204"))
{
return true;
}
}
catch
{
return false;
}
}
I know this is an old question but this works great for me.
System.Net.NetworkInformation.NetworkChange.NetworkAvailabilityChanged += NetworkChange_NetworkAvailabilityChanged;
private async void NetworkChange_NetworkAvailabilityChanged(object sender, System.Net.NetworkInformation.NetworkAvailabilityEventArgs e)
{
//code to execute...
}
I subscribe to the event to a listener and it constantly checks for connection. this you can add a If statement such as:
if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
{
//Send Ping...
}
else
{
//other code....
}
How will you know if you have an Internet Connection? Is it enough that you can route packets to a nearby router? Maybe the machine has only a single NIC, a single gateway, and perhaps that Gateway's connection goes down but the machine can still route to the gateway and local network?
Maybe the machine has a single NIC and a dozen gateways; maybe they come and go all the time, but one of them is always up?
What if the machine has multiple NICs, but only a single gateway? Perhaps it can route to some subset of the Internet, but still has an excellent connection to a local network not connected to the Internet?
What if the machine has muliple NICs, multiple gateways, but for administrative policy reasons, still only portions of the Internet are routeble?
Do you really only care if clients have connectivity to your servers?
What kind of latency between packets is acceptable? (30ms is good, 300ms is pushing the limits of human endurance, 3000ms is intolerable long time, 960000ms is what would be required for a connection to a solar probe.) What kind of packet loss is acceptable?
What are you really trying to measure?
This would be a start but as sarnold has mentioned there are a lot of things you need to consider
You can test internet connectivity by pinging to some website like:
public bool IsConnectedToInternet
{
try
{
using (System.Net.NetworkInformation.Ping ping = new System.Net.NetworkInformation.Ping())
{
string address = #"http://www.google.com";// System.Net.NetworkInformation.PingReply pingReplay = ping.Send(address);//you can specify timeout.
if (pingReplay.Status == System.Net.NetworkInformation.IPStatus.Success)
{
return true;
}
}
}
catch
{
#if DEBUG
System.Diagnostics.Debugger.Break();
#endif//DEBUG
}
return false;
}
This code will life-saving for you.. it not only checks real internet connection but also handle the exception with indication on the console window...
after every 2 seconds
using System;
using System.Net;
using System.Threading;
using System.Net.Http;
bool check() //Checking for Internet Connection
{
while (true)
{
try
{ var i = new Ping().Send("www.google.com").Status;
if (i == IPStatus.Success)
{ Console.WriteLine("connected");
return true;
}
else { return false; }
}
catch (Exception)
{
Console.WriteLine("Not Connected");
Thread.Sleep(2000);
continue;
}
}
};
check();
using the NetworkChange.NetworkAvailabilityChanged is the most misleading answer. It check the network availablity change not the internet connection change.
we can monitor the internet connection using Windows NLM API.
using System;
using System.Runtime.InteropServices.ComTypes;
using NETWORKLIST;
namespace Components.Network.Helpers
{
public class InternetConnectionChecker : INetworkListManagerEvents, IDisposable
{
private int _cookie;
private IConnectionPoint _connectionPoint;
private readonly INetworkListManager _networkListManager;
public InternetConnectionChecker()
{
_networkListManager = new NetworkListManager();
}
public bool IsConnected()
{
return _networkListManager.IsConnectedToInternet;
}
public void StartMonitoringConnection()
{
try
{
var container = _networkListManager as IConnectionPointContainer;
if (container == null)
throw new Exception("connection container is null");
var riid = typeof(INetworkListManagerEvents).GUID;
container.FindConnectionPoint(ref riid, out _connectionPoint);
_connectionPoint.Advise(this, out _cookie);
}
catch (Exception e)
{
}
}
public void ConnectivityChanged(NLM_CONNECTIVITY newConnectivity)
{
if (_networkListManager.IsConnectedToInternet)
{
// do something based on internet connectivity
}
}
public void Dispose()
{
_connectionPoint.Unadvise(_cookie);
}
}
}
I am writing an application to consume SOAP service. The work flow is like this:
Pull in order from SOAP server (Request Order)
POST/Response with changes to SOAP server (Response to Order)
Request PO changes from server (Request order changes)
The third party server already set up a testing environment for us. The first thing
I am trying to do is request and download the order from server. But seems the problem is that I can only download/pull the order per application once. After the first time I requested the order, I will get the exception: "System.Web.Services.Protocols.SoapException: No order updates available". If I create another application do the same thing, it will be able to request the order again, but only for once. Is there any place I can set/config so that my application could pull order regardless it is an newer order or not, or it's something to do with the server side?
The test code I am pulling order:
/// <summary>
/// Used by supplier to check Order
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRequestPO_Click(object sender, EventArgs e)
{
string t_return;
SupplierServices supplierServices = new SupplierServices();
LoginDetails lgDetails = new LoginDetails();
lgDetails.UserName = "user";
lgDetails.Password = "password";
supplierServices.LoginDetailsValue = lgDetails;
RequestPurchaseOrder requestPO = new RequestPurchaseOrder();
requestPO.SupplierCode = "1234";
try
{
Order returned_order = supplierServices.SupplierRequestPO(requestPO);
if (returned_order != null)
{
t_return = returned_order.ToString();
MessageBox.Show(t_return);
}
else
MessageBox.Show("Returned order is empty!");
}
catch (Exception ex)
{
String responseFromServer = ex.Message.ToString() + " ";
if (ex!= null)
{
MessageBox.Show(responseFromServer);
}
}
It appears from your code that the response "No order updates available" is being returned from the third party. You are going to have to ask them why you are getting that error.
As the author of a C# application, I found that troubleshooting issues reported by users would be much easier if I had access to the exception or debug logs.
I have included a home-grown logging mechanism that the user can turn on or off. I want the user to be able to submit the logs via the internet so I can review the logs for the error.
I have thought of using either SMTPClient or a web service to send the information. SMTPClient might not work because firewalls may block outgoing SMTP access. Would a web service has issue with sending a large amount of data (potentially 1+ MB)?
What would you recommend as the best way to have an application transmit error reports directly to developers for review?
EDIT: Clarification: This is a Windows application and when an error occurs I want to bring up a dialog asking to submit the error. My question is about the mechanism to transmit the error log from the application to me (developer) via the internet.
Some way that let's the user know you mean to do it. Ask them for a proxy, ask them for an email server, something like that.
The security minded will get real nervous if they discover that you're opening a socket or something like it and sending data out without notification.
And rightly so.
I would suggest NOT to send everything (the whole audit of your application).
But just if the user wants it ("Feedback" button) or if there is an explicit exception, fatal error, problem state in the application.
We used both Web services and email (SMTPClient). My thoughts on these
Web service
GOOD
No special configuration per user
Size limit, more than 5Mb-8MB of email are possible
BAD
Public visible (hacker like to play
with these things)
Additional developing to create the web service with db back end
Creating additional fields later is bad
Changes in web service are NOT GOOD!
SMTPClient
GOOD
No special configuration per user
Logging to public folder makes search/filter easy (grouping, ...)
All data possible to send, screenshots, stacktrace, user settings, ...
--> HTML
Changes in logging format and info is easy, because we used HTML emails
BAD
Special configuration per user (smtp server, email user, ...)
Size limit of email (5MB-8MB ??)
Logging to db of emails requires lot of development
We use 3 methods where I work
SMTP to a dedicated mailbox. This requires a lot of configuration and dancing around with "big corporate" IT departments to work out what their mail server is and how to authenticate against it and send through it. Then there's programs like Norton Internet Security that can block outbound SMTP traffic on the client machine which throw extra spanners in the works.
Submission to an asmx on our server. This is our preferred method, but lots of things can get in the way. It's mainly proxies, but Norton can also step in and swat you down. If there's a proxy involved, run away run away :-)
HTTP POST using HttpWebRequest and mime typ of multipart/form-encoded. It also has proxy and firewall issues, but can sometimes work where asmx submission fails.
Good luck. You're right in that it's much easier to debug if you've got the stack trace and perhaps even a screenie of what the poor old user was doing.
You can write it yourself, or you can use something like log4net, it takes care of the exception logging for you...
I like to receive stuff like this as email to a dedicated mailbox. That way I can easily archive it, search it, or ignore it.
On the client/sender side, I think a pop-up offering to send the logs is a good idea. If windows, you can use MAPI to send the email. On a unix system "mail" problem works on most systems.
You can prompt the user for an email address in the confirmation message, and maybe offer a few options on how to send it (including copy/paste into the mail client of their choice).
One thing you should NOT do is send the information without the user's permission.
If you aren't expecting that many reports to be sent in a single day... you could create a gmail account and use that to send the emails to get around having to force the user to configure an SMTP server. Not sure what gmail's terms and conditions are for doing this.
Here is a class which I wrote which sends an email using a gmail account...
Obviously there are some security issues here like someone could potentially get access to your gmail account. So, take that into consideration.
There are methods in this class to send the email synchronously or asynchronously.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Collections;
using System.Text;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
//Mime is Not necerrary if you dont change the msgview and
//if you dont add custom/extra headers
using System.Threading;
using System.IO;
using System.Windows.Forms; // needed for MessageBox only.
namespace BR.Util
{
public class Gmailer
{
SmtpClient client = new SmtpClient();
static String mDefaultToAddress = "yourToAddress#yourdomain.com";
static String mDefaultFromAddress = "anonymous#gmail.com";
static String mDefaultFromDisplayName = "Anonymous";
String mGmailLogin = "someaccount#gmail.com";
String mGmailPassword = "yourpassword";
public Gmailer()
{
client.Credentials = new System.Net.NetworkCredential(mGmailLogin, mGmailPassword);
client.Port = 587;
client.Host = "smtp.gmail.com";
client.EnableSsl = true;
client.SendCompleted += new SendCompletedEventHandler(Gmailer_DefaultAsyncSendCompletedHandler);
}
public void setSendCompletedHandler(SendCompletedEventHandler pHandler)
{
client.SendCompleted -= Gmailer_DefaultAsyncSendCompletedHandler;
client.SendCompleted += pHandler;
}
/// <summary>
/// Static method which sends an email synchronously.
/// It uses a hardcoded from email.
/// </summary>
/// <returns></returns>
public static bool quickSend(String toEmailAddress, String subject, String body)
{
return Gmailer.quickSend(toEmailAddress, mDefaultFromAddress, mDefaultFromDisplayName, subject, body);
}
/// <summary>
/// Static method which sends an email synchronously.
/// It uses the hardcoded email address.
/// </summary>
/// <returns>true if successful, false if an error occurred.</returns>
public static bool quickSend(String toEmailAddress, String fromEmailAddress,
String fromDisplayName, String subject, String body)
{
try
{
Gmailer gmailer = new Gmailer();
System.Net.Mail.MailMessage mailMsg = gmailer.createMailMessage(toEmailAddress, fromEmailAddress, fromDisplayName, subject, body);
gmailer.send(mailMsg);
}
catch (Exception ex)
{
return false;
}
return true;
}
// <summary> creates a MailMessage object initialized with the default values.</summary>
public System.Net.Mail.MailMessage createMailMessage()
{
return createMailMessage(mDefaultToAddress, mDefaultFromAddress, mDefaultFromDisplayName, mDefaultEmailSubject, mDefaultEmailBody);
}
public System.Net.Mail.MailMessage createMailMessage(String toEmailAddress, String fromEmailAddress,
String fromDisplayName, String subject, String body)
{
//Build The MSG
System.Net.Mail.MailMessage msg = new System.Net.Mail.MailMessage();
msg.To.Add(toEmailAddress);
msg.From = new MailAddress(fromEmailAddress, fromDisplayName, System.Text.Encoding.UTF8);
msg.Subject = subject;
msg.SubjectEncoding = System.Text.Encoding.UTF8;
msg.Body = body;
msg.BodyEncoding = System.Text.Encoding.UTF8;
msg.IsBodyHtml = false;
msg.Priority = MailPriority.High;
return msg;
}
public System.Net.Mail.MailMessage addAttachmentToMailMessage(System.Net.Mail.MailMessage msg, String attachmentPath)
{
msg.Attachments.Add(new Attachment(attachmentPath));
return msg;
}
// <summary> method which blocks until the MailMessage has been sent. Throws
// System.Net.Mail.SmtpException if error occurs.</summary>
public void send(System.Net.Mail.MailMessage pMailMessage)
{
//try {
client.Send(pMailMessage);
//}
//catch (System.Net.Mail.SmtpException ex)
//{
// MessageBox.Show(ex.Message, "Send Mail Error");
//}
}
//
public void sendAsync(System.Net.Mail.MailMessage pMailMessage)
{
object userState = pMailMessage;
try
{
MailSent = false;
client.SendAsync(pMailMessage, userState);
}
catch (System.Net.Mail.SmtpException ex)
{
MessageBox.Show(ex.Message, "Send Mail Error");
}
}
// <summary>
// Provides a default SendComplete handler which is activated when an AsyncCompletedEvent
// is triggered by the private client variable. This is useful for debugging etc.
// Use the method setSendCompletedHandler to define your own application specific handler.
// That method also turns this handler off.
// </summary>
public void Gmailer_DefaultAsyncSendCompletedHandler(object sender, AsyncCompletedEventArgs e)
{
MailMessage mail = (MailMessage)e.UserState;
string subject = mail.Subject;
if (e.Cancelled)
{
string cancelled = string.Format("[{0}] Send canceled.", subject);
MessageBox.Show(cancelled);
}
if (e.Error != null)
{
string error = String.Format("[{0}] {1}", subject, e.Error.ToString());
MessageBox.Show(error);
}
else
{
MessageBox.Show("Message sent.");
}
MailSent = true;
}
private bool _MailSent = false;
/// <summary>
/// Set to false when an async send operation is started and is set to true when finished.
/// </summary>
public bool MailSent
{
set
{
_MailSent = value;
}
get
{
return _MailSent;
}
}
}
}