Labels

Wednesday, June 22, 2011

Using .NET COM object for sending e-mails or SMS in NAV

As we all know standard MAPI interface, provided in NAV, works only when mail client (like Outlook Express or Outlook) is installed. There are several issues why this approach is not suitable. I decided to develop my own COM object to work directly with SMTP server.

My .NET COM wrapper for System::Net::Mail::SmtpClient consist of 3 main files:

  • SMTPClient.idl - interface definition;
  • SMTPMessage.h - header file;
  • SMTPMessage.c - source code;
New .NET COM class should provide methods to define parameters:
  • Sender e-mail address & sender name (shown in message, field From);
  • Receiver e-mail address & receiver name (shown in message, field To);
  • Server name, port and flag for using SSL protocol;
  • User name & password for authentication.
Total source project directory you can download here: https://sites.google.com/site/9ri331y/SMTPClient.sources.zip


Please, post a feedback if you find this project useful for you.

SMTPClient.idl list:

// SMTPClient.idl : IDL source for SMTPClient
import "oaidl.idl";
import "ocidl.idl";



[
object,
uuid(174A5DE1-84AA-4707-9FF7-DFFCAEED360B),
dual,
nonextensible,
helpstring("ISMTPMessage Interface"),
pointer_default(unique)
]
interface ISMTPMessage : IDispatch{
[id(10), helpstring("method SetFromNameAddress")] HRESULT SetFromNameAddress([in] BSTR _FromName, [in] BSTR _FromAddress);
[id(11), helpstring("method SetToNameAddress")] HRESULT SetToNameAddress([in] BSTR _ToName, [in] BSTR _ToAddress);
[id(4), helpstring("method SetBodySubject")] HRESULT SetBodySubject([in] BSTR _Body, [in] BSTR _Subject);
[id(12), helpstring("method SetServerCredentials")] HRESULT SetServerCredentials([in] BSTR _ServerName, [in] VARIANT _ServerPort, 
[in] BSTR _UserName, [in] BSTR _UserPassword, [in] VARIANT _SSL);
[id(2), helpstring("method Send")] HRESULT  Send([out, retval] long* ErrorCode);
};
[
uuid(31611138-DDA1-4E29-B35B-B9B7B3DA218D),
version(1.0),
helpstring("SMTPClient 1.0 Type Library")
]
library SMTPClientLib
{
importlib("stdole2.tlb");
[
uuid(E2C4030F-94AD-48EC-965D-FEC5567AC02C),
helpstring("SMTPMessage Class")
]
coclass SMTPMessage
{
[default] interface ISMTPMessage;
};
};
This file is created automaticly and it provides method definition of ISMTPMessage interface.

SMTPMessage.h list:
// SMTPMessage.h : Declaration of the CSMTPMessage
#pragma once
#include "resource.h"       // main symbols

#include "SMTPClient_i.h"

#using <mscorlib.dll>
#using <System.dll>

#if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
#error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
#endif

// CSMTPMessage

class ATL_NO_VTABLE CSMTPMessage :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CSMTPMessage, &CLSID_SMTPMessage>,
public ISupportErrorInfo,
public IDispatchImpl<ISMTPMessage, &IID_ISMTPMessage, &LIBID_SMTPClientLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CSMTPMessage()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_SMTPMESSAGE)

BEGIN_COM_MAP(CSMTPMessage)
COM_INTERFACE_ENTRY(ISMTPMessage)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()

DECLARE_PROTECT_FINAL_CONSTRUCT()

HRESULT FinalConstruct()
{
return S_OK;
}

void FinalRelease()
{
}
public:
STDMETHOD(SetFromNameAddress)(BSTR _FromName, BSTR _FromAddress);
STDMETHOD(SetToNameAddress)(BSTR _ToName, BSTR _ToAddress);
STDMETHOD(SetBodySubject)(BSTR _Body, BSTR _Subject);
STDMETHOD(SetServerCredentials)(BSTR _ServerName, VARIANT _ServerPort, BSTR _UserName, BSTR _UserPassword, VARIANT _SSL);
STDMETHOD(Send)(long* ErrorCode);
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);
public:
BSTR FromName;
BSTR FromAddress;
BSTR ToName;
BSTR ToAddress;
BSTR BodyContainer;
BSTR Subject;
BSTR ServerName;
UINT ServerPort;
BSTR UserName;
BSTR UserPassword;
BOOL SSL;
};

OBJECT_ENTRY_AUTO(__uuidof(SMTPMessage), CSMTPMessage)

SMTPMessage class should implement interface ISupportInfo to provide NAV error message.

SMTPMessage.C List:
// SMTPMessage.cpp : Implementation of CSMTPMessage

#include "stdafx.h"
#include "SMTPMessage.h"
#include "atlcomcli.h"
#include <msclr\marshal_cppstd.h>

using namespace System;
using namespace System::Net;
using namespace System::Net::Mail;
using namespace System::Runtime::InteropServices;

// CSMTPMessage
STDMETHODIMP CSMTPMessage::InterfaceSupportsErrorInfo(REFIID riid)
{
  static const IID* arr[] = 
  {
    &IID_ISMTPMessage
  };
  for (int i=0; i < sizeof(arr) / sizeof(arr[0]); i++)
  {
    if (InlineIsEqualGUID(*arr[i],riid))
      return S_OK;
  }
  return S_FALSE;
}
#pragma unmanaged

void NativeTakesAString(BSTR bstr) {
   printf_s("%S", bstr);
}

#pragma managed

BSTR toss( System::String ^ s )
 {
   IntPtr ip = Marshal::StringToBSTR(s);
   BSTR bs = static_cast<BSTR>(ip.ToPointer());
   pin_ptr<BSTR> b = &bs;

   NativeTakesAString( bs );
   Marshal::FreeBSTR(ip);
   return bs;
}

STDMETHODIMP CSMTPMessage::SetFromNameAddress(BSTR _FromName, BSTR _FromAddress)
{
FromName = SysAllocString(_FromName);
FromAddress = SysAllocString(_FromAddress);
return S_OK;
}

STDMETHODIMP CSMTPMessage::SetToNameAddress(BSTR _ToName, BSTR _ToAddress)
{
ToName = SysAllocString(_ToName);
ToAddress = SysAllocString(_ToAddress);
return S_OK;
}

STDMETHODIMP CSMTPMessage::SetBodySubject(BSTR _Body, BSTR _Subject)
{
BodyContainer = SysAllocString(_Body);
Subject = SysAllocString(_Subject);
return S_OK;
}

STDMETHODIMP CSMTPMessage::SetServerCredentials(BSTR _ServerName, VARIANT _ServerPort, BSTR _UserName, BSTR _UserPassword, VARIANT _SSL)
{
ServerName = SysAllocString(_ServerName);
ServerPort = _ServerPort.intVal;
UserName = SysAllocString(_UserName);
UserPassword = SysAllocString(_UserPassword);
SSL = _SSL.boolVal;
return S_OK;
}

STDMETHODIMP CSMTPMessage::Send(long* ErrorCode)
{
String^ SFromAddress = gcnew String(FromAddress);
String^ SFromName = gcnew String(FromName);
String^ SToAddress = gcnew String(ToAddress);
String^ SToName = gcnew String(ToName);
String^ SBody = gcnew String(BodyContainer);
String^ SSubject = gcnew String(Subject);
String^ SUser = gcnew String(UserName);
String^ SPassword = gcnew String(UserPassword);
String^ SServer = gcnew String(ServerName);

MailMessage^ msg;

try {
MailAddress^ from = gcnew MailAddress(SFromAddress,SFromName);
MailAddress^ to = gcnew MailAddress(SToAddress,SToName);
msg = gcnew MailMessage(from,to);

msg->Subject = SSubject;
msg->SubjectEncoding = System::Text::Encoding::UTF8;
msg->Body = SBody;
msg->BodyEncoding = System::Text::Encoding::UTF8;
msg->IsBodyHtml = True;

SmtpClient^ client = gcnew SmtpClient();
client->Credentials = gcnew NetworkCredential(SUser, SPassword);
client->Port = ServerPort;
client->Host = SServer;
client->EnableSsl = True;
client->Send(msg);
catch(Exception^ ex) {
return Error(toss(ex->ToString()), IID_ISMTPMessage); 
};
  return S_OK;
}


In NAV component SMTPMessage used this way:
IF ISCLEAR(SMTPClient3) THEN
  CREATE(SMTPClient3);
SMTPClient3.SetFromNameAddress('*********','*********');
SMTPClient3.SetToNameAddress('*********','*********');
SMTPClient3.SetBodySubject('<br/><br/>Hello, its just a test!<hr/><br/>','Notify');
SMTPClient3.SetServerCredentials('smtp.gmail.com',25,'***user***','***password***',1);
Result := SMTPClient3.Send();
MESSAGE(FORMAT(Result));


You could use this code also for SMS notification, because most cell operators having some kind of e-mail-to-sms gateways. For example mailing to 1234567890@sms.vodafone.com should post an SMS to +1234567890 subscriber. You only need to know what сell operator the subscriber is using. You could also set matches between phone mask and the cell operator for automatic recognition of cell provider. 

No comments: