VB.NET - Mit Lotus Notes E-Mails versenden

Vor etwas mehr als zwei Jahren (wie die Zeit vergeht :-() habe ich hier einen Artikel namens "VB .NET - Mit Lotus Notes E-Mails und Dateianhängen arbeiten" veröffentlicht, der beschreibt, wie man mit Lotus Notes E-Mails aus der Inbox arbeiten kann.

Heute musste ich nun etwas ähnliches bewerkstellen, und zwar E-Mails über unseren Domino Server versenden. Im Prinzip benötigt man dazu keinen COM-Verweis auf das Domino Mail Objekt, da man (sofern SMTP auf dem Domino Server freigegegeben ist) das ganze auch schön mit .NET Bordmitteln, sprich Net.Mail.MailMessage usw. lösen kann.

Doch wir wollten auch Faxe über Lotus Notes versenden, und dazu benötigt der Domino Server eine Authentifizierung, was man theoretisch über die .NET Credentials lösen könnte. Leider bekam ich beim Faxversenden immer wieder den Fehler, dass die Benutzerrechte nicht korrekt seien.

Laut Rechereche im Internet hat das auch noch keiner hinbekommen, also blieb mir nichts anderes übrig, als wieder auf das Domino COM-Objekt zu verweisen und damit zu arbeiten.

Herausgekommen dabei sind einige Klassen, mit denen man relativ einfach E-Mails und Faxe über den Server verschicken kann.

Zuerst einmal die Helfer-Klassen:

Hier die Anhangs-Klasse für Dateianhänge:

Public Class LotusNotesMailAttachment
 
  Public FullFilename As String
  Public DisplayName As String
 
  Public Sub New()
  End Sub
 
  Public Sub New(ByVal aFullFilename As String)
    Me.FullFilename = aFullFilename
  End Sub
 
  Public Sub New(ByVal aFullFilename As String, ByVal aDisplayName As String)
    Me.FullFilename = aFullFilename
    Me.Displayname = aDisplayName
  End Sub
 
End Class

Die Liste der Anhänge:

Public Class LotusNotesMailAttachmentList
 
  Inherits List(Of LotusNotesMailAttachment)
 
End Class

Die Klase LotusNotesMailData enthält einfach nur Absender, Empfänger, Betreff usw.
Diese Daten werden später an die Factory zum Versenden weitergereicht.

Public Class LotusNotesMailData
 
  Public Subject As String
 
  Private _To As List(Of String)
  Public Property [To]() As List(Of String)
    Get
      If _To Is Nothing Then
        _To = New List(Of String)
      End If
      Return _To
    End Get
    Set(ByVal value As List(Of String))
      _To = value
    End Set
  End Property
 
  Public From As String
 
  Private _CC As List(Of String)
  Public Property CC() As List(Of String)
    Get
      If _CC Is Nothing Then
        _CC = New List(Of String)
      End If
      Return _CC
    End Get
    Set(ByVal value As List(Of String))
      _CC = value
    End Set
  End Property
  Public Body As String
 
  Private _Attachments As LotusNotesMailAttachmentList
  Public Property Attachments() As LotusNotesMailAttachmentList
    Get
      If _Attachments Is Nothing Then
        _Attachments = New LotusNotesMailAttachmentList
      End If
      Return _Attachments
    End Get
    Set(ByVal value As LotusNotesMailAttachmentList)
      _Attachments = value
    End Set
  End Property
 
End Class

Die folgende Klasse "MailAccount" beinhaltet einfach die "Login-Daten" für den Domino-Server und Infos über das Postfach, das zum Senden verwendet werden soll.

Klasse Mailaccount:

Public Class MailAccount
 
  ''' <summary>
  ''' Hostname of the Domino Server
  ''' </summary>
  ''' <remarks></remarks>
  Public Host As String
 
  ''' <summary>
  ''' Filename with Path of the Notes Mail File
  ''' </summary>
  ''' <remarks>e.g. 'mail\user.nsf'</remarks>
  Public DatabaseFilename As String
 
  Public Password As String
 
  Public Sub New()
  End Sub
 
  Public Sub New(ByVal aHost As String, ByVal aDatabaseFilename As String, ByVal aPassword As String)
    Me.Host = aHost
    Me.DatabaseFilename = aDatabaseFilename
    Me.Password = aPassword
  End Sub
 
End Class

Hier nun meine Notes Factory, die das eigentliche Versenden der E-Mails und Faxe bewerkstelligt. Faxe werden dabei wie E-Mails gehandhabt. Bei unserer Fax-Software für Domino (Ferrari-Fax (übrigens sehr zu empfehlen)) wird statt der E-Mail Adresse einfach die Faxnummer mit dem Zusatz "@Fax" angegeben. Die Faxsoftware wandelt daraufhin alle Anhänge in TIFF um und sendet diese als Fax an den/die Empfänger.

Man erhält zudem das versendete Fax in Form einer E-Mail im "Gesendet" Ordner von Lotus Notes.

Public Class LotusNotesMailFactory
 
  Public Shared Function SendMail(ByVal aMailAccount As MailAccount, ByVal aMaildata As LotusNotesMailData) As Boolean
 
    ' Die Session starten
    Dim Session As New Domino.NotesSession
    Session.Initialize(aMailAccount.Password)
 
    ' Datenbank öffnen
    Dim MailDB As Domino.NotesDatabase
    Dim MailServer As String = Session.GetEnvironmentString("MailServer", True)
    MailDB = Session.GetDatabase(MailServer, aMailAccount.DatabaseFilename)
 
    If Not MailDB.IsOpen Then
      MailDB.Open()
    End If
 
    Dim MailDoc As Domino.NotesDocument = MailDB.CreateDocument
    Dim Body As Domino.NotesRichTextItem = MailDoc.CreateRichTextItem("Body")
 
    MailDoc.ReplaceItemValue("Form", "Memo")
 
    For Each toadr As String In aMaildata.To
      MailDoc.ReplaceItemValue("SendTo", toadr)
      'MailDoc.AppendItemValue("SendTo", aMaildata.To)
    Next
 
    MailDoc.ReplaceItemValue("Subject", aMaildata.Subject)
    'MailDoc.AppendItemValue("Subject", aMaildata.Subject)

    ' CC (Carbon Copy)
    For Each ccadr As String In aMaildata.CC
      MailDoc.AppendItemValue("CopyTo", ccadr)
    Next
 
    ' Create and set the Body content
    Body.AppendText(String.Empty)
    Body.AppendText(aMaildata.Body)
 
    ' Attachment
    For Each lnma As LotusNotesMailAttachment In aMaildata.Attachments
      Body.AddNewLine(2)
      Body.EmbedObject(1454, String.Empty, lnma.FullFilename, lnma.DisplayName)
    Next
 
    ' Example to save the message (optional)
    MailDoc.SaveMessageOnSend = True
 
    ' Send the document
    ' Gets the mail to appear in the Sent items folder
    MailDoc.ReplaceItemValue("PostedDate", Now)
    MailDoc.Send(False)
 
    ' Clean Up
    MailDB = Nothing
    MailDoc = Nothing
    Body = Nothing
    Session = Nothing
 
    Return True
 
  End Function
 
End Class

Wer Verbesserungen hat, oder gar weiß, wie man über die .NET Bordmittel das ganze bewerkstellingen kann, ist herzlich eingeladen, zu kommentieren.

Sollte ich dazukommen, werde ich das ganze noch etwas besser dokumentieren, aufräumen und verbessern. Read More

HowTo: Wie man User Eingaben validiert

Notice: Undefined index: name in system/classes/format.php line 422
Notice: Undefined index: name in system/classes/format.php line 422
Notice: Undefined index: name in system/classes/htmltokenset.php line 66
Notice: Undefined index: name in system/classes/htmltokenset.php line 66

Quelle

VCard / VCF Dateien in VB.NET erstellen

Hier eine kleine Klasse, die VCard-Dateien erstellen kann.
Wie man die Klasse aufruft, zeige ich am Ende dieses Eintrags.

Klasse VCard.vb:

Public Class VCard
 
  Private _VCardData As List(Of String)
  Public Property VCardData() As List(Of String)
    Get
      If _VCardData Is Nothing Then
        _VCardData = New List(Of String)
      End If
      Return _VCardData
    End Get
    Set(ByVal value As List(Of String))
      _VCardData = value
    End Set
  End Property
 
  Public InternalName As String
 
  Public VORNAME As String
  Public NACHNAME As String
  Public STRASSE As String
  Public ORT As String
  Public PLZ As String
  Public LAND As String
  Public TELEFONNUMMER As String
  Public TELEFAXNUMMER As String
  Public MOBILNUMMER As String
  Public MAILADRESSE As String
 
  Public Sub New(ByVal aInternalName As String)
 
    Me.InternalName = aInternalName
 
    Me.VCardData.Add("BEGIN:VCARD")
    Me.VCardData.Add("VERSION:2.1")
    Me.VCardData.Add("FN:%VORNAME% %NACHNAME%")
    Me.VCardData.Add("N:%NACHNAME%;%VORNAME%")
    Me.VCardData.Add("ADR;HOME:;;%STRASSE%;%ORT%;;%PLZ%;%LAND%")
    Me.VCardData.Add("TEL;HOME;VOICE:%TELEFONNUMMER%")
    Me.VCardData.Add("TEL;CELL:%MOBILNUMMER%")
    Me.VCardData.Add("TEL;HOME;FAX:%TELEFAXNUMMER%")
    Me.VCardData.Add("EMAIL;WORK;PREF;INTERNET:%MAILADRESSE%")
    Me.VCardData.Add("END:VCARD")
 
  End Sub
 
  ''' <summary>
  ''' Anhand der Felder werden sämtliche Informationen in VCardData durch die jeweiligen Feldwerte ersetzt
  ''' </summary>
  ''' <remarks></remarks>
  Public Sub Fill()
 
    Dim fis As System.Reflection.FieldInfo() = Me.GetType().GetFields()
 
    Dim strReplace As String = String.Empty
    Dim strSearch As String = String.Empty
 
    ' Durch jede Zeile
    For i As Integer = 0 To Me.VCardData.Count - 1
 
      ' Zeile merken
      Dim line As String = Me.VCardData(i)
      If Not line.Contains("%") Then GoTo NextLine
 
      ' Durch alle Felder
      For Each fi As System.Reflection.FieldInfo In fis
 
        strSearch = "%" & fi.Name.ToUpper & "%"
        strReplace = fi.GetValue(Me)
 
        ' Debug.Print("Replacing: " & line & " with: " & strSearch & " - " & strReplace)

        line = Replace(line, strSearch, strReplace)
 
      Next
 
      Me.VCardData(i) = line
 
NextLine:
    Next
 
  End Sub
 
  Public Sub ExportToPath(ByVal aDirectory As String)
 
    ' Mich mit Daten füllen
    Me.Fill()
 
    ' Text für VCard Datei generieren
    Dim strVCardData As String = String.Empty
    For Each line As String In Me.VCardData
      If strVCardData = String.Empty Then
        strVCardData = line
      Else
        strVCardData += vbNewLine & line
      End If
 
    Next
 
    ' Datei schreiben
    My.Computer.FileSystem.WriteAllText(aDirectory & "\" & Me.InternalName & ".vcf", strVCardData, False, System.Text.Encoding.ASCII)
 
  End Sub
 
End Class

Klasse VCardList.vb:

Public Class VCardList
 
  Inherits List(Of VCard)
 
End Class

Aufruf:

Public Class SCVCardCreator
 
  Public Sub New()
  End Sub
 
  Private Function GetList() As VCardList
 
    Dim Result As New VCardList
 
    Using con As New SqlClient.SqlConnection(My.Settings.SQLConnectionString)
 
      Try
        con.Open()
      Catch ex As Exception
        Return Nothing
      End Try
 
      Dim strSQL As String = "Select * From Datenquelle"
 
      Using cmd As New SqlClient.SqlCommand(strSQL, con)
 
        Using dr As SqlClient.SqlDataReader = cmd.ExecuteReader
 
          Do While dr.Read
 
            Try
 
              Dim vcard As New VCard(dr.Item("MAB_NACHNAME"))
              With vcard
 
                Try
                  .VORNAME = dr.Item("MAB_VORNAME")
                Catch ex As Exception
                End Try
 
                Try
                  .NACHNAME = dr.Item("MAB_NACHNAME")
                Catch ex As Exception
                End Try
 
             End With
 
              Result.Add(vcard)
 
            Catch ex As Exception
 
            End Try
 
          Loop
 
        End Using
 
      End Using
 
      con.Close()
    End Using
 
    Return Result
 
  End Function
 
  Public Sub ExportToPath(ByVal aDirectory As String)
 
    Dim lst As VCardList = Me.GetList
 
    For Each vcard As VCard In lst
      vcard.ExportToPath(aDirectory)
    Next
 
  End Sub
 
End Class Read More

Crystal Report und die Formeln

Da ich immer wieder die genaue Syntax von Crystal Report Formeln vergesse, hier eine kleine Erinnerung für mich:

Wichtig: Zuweisung von Variablen immer mit :=, nicht nur mit =. Und: am Ende jeder Zeile muss ein Semikolon stehen!

local numbervar U1;
local numbervar U2;

U1 := Sum ({dtVT1Detail.Umsatz1}, {dtVT1Detail.MetaADM});
U2 := Sum ({dtVT1Detail.Umsatz2}, {dtVT1Detail.MetaADM});

if U1 = 0 or U2 = 0 then "0" else CSTR(U1 / U2 * 100 - 100);

Um einen Detailbereich abwechselnd einzufärben, kann man den Bereichassistent öffnen, auf das Register "Farbe" wechseln und dort folgenden Code hinterlegen:

Global numbervar moddy;

moddy := moddy + 1;

if moddy mod 2 = 0 then
NoColor
else
RGB(224,238,238)

Deutlich einfacher ist allerdings folgende Methode:

If RecordNumber mod 2 = 0 then RGB(224,238,238) else NoColor;

Mit NLog unter .NET protokollieren

Wer kennt das nicht? Mal eben ein kleines Programm geschrieben, an die Anwender verteilt und auf irgendeinem Rechner läuft das Programm nicht wie es soll.

Nun ist guter Rat teuer, denn welcher Programmierer gibt sich schon die Mühe in kleinere Tools eine ausgefeilte Protokoll-Funktion zu implementieren um dem Fehler gegebenenfalls schnell auf die Schliche zu kommen?

Mit NLog ist das anders. NLog ist ein Framework fürs Protokollieren unter .NET. Ist der Verweis zu NLog im Projekt erst einmal eingebunden, kann man mit einer Zeile wie

Me.Logger.Warn(ex.Message)

eine Exception inklusive aufrufender Methode und kompletter Exception-Fehlerausgabe protokollieren.

Auf der NLog Webseite ist zwar relativ einfach beschrieben, wie man das Framework in das eigene Projekt einbaut, was ich hier im folgenden allerdings beschreibe, ist noch etwas kürzer, da ausschließlich für VB .NET.

Verweis einbinden

Bevor man das NLog Framework verwenden kann, muss man als Verweis die NLog.dll zum Projekt hinzufügen. Hierzu lädt man auf der Codeplex Projekteseite von NLog am einfachsten die vorkompilierte DLL herunter (man kann sich natürlich auch den Quellcode schnappen und selbst kompilieren :-).

Konfigurationsdatei erstellen

Es gibt zwar viele Varianten, wie NLog nach einer Konfigurationsdatei sucht, aber die einfachste ist, eine Datei namens "nlog.config" im Programmverzeichnis zu erstellen.

Als Inhalt dieser Datei empfehle ich für Anfänger folgendes:

<geshi lang="xml"><nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 
  <targets> 
    <target name="file" xsi:type="File" 
            layout="${longdate} ${logger} ${callsite} ${message} ${exception:Format=tostring} ${newline}" 
            fileName="${basedir}/${windows-identity:domain=false}.log"/> 
  </targets> 
  <rules> 
    <logger name="*" minlevel="Debug" writeTo="file"/> 
  </rules> 
</nlog>

In der Konfigurationsdatei wird mit dem Tag "Target" bestimmt, wohin der Protokolleintrag wandern soll.

Das Target vom Typ "file" beispielsweise schreibt alle Einträge in eine Datei. Außer der Möglichkeit Protokolleinträge in Dateien zu schreiben, bietet NLog dem Benutzer auch eine Mail, ein Textcontrol, ein Netzwerk, das Windows Ereignisprotokoll einen Webservice und eine per ADO.NET ansprechbare Datenbank als Ziel an. Darüberhinaus ist es auch noch möglich, sich eigene Targets, wie beispielsweise einen FTP-Server, zu schreiben. Und natürlich kann man auch mehrere Targets gleichzeitig befüllen.

Im obigen Beispiel wir als Ziel eine Datei verwendet, deren Dateiname aus dem Windows-Benutzername besteht. Die Datei wird dabei automatisch in Unterordnern gelagert, deren Name aus dem aktuellen Datum generiert wird.

Der Tag "Layout" beschreibt dabei, wie die Protokolleinträge formatiert bzw. ergänzt werden.

Der Platzhalter "${longdate}" wird durch das Datum, der Platzhalter "${logger}" durch den Namen der Logging-Klasse, "${callsite}" durch die aufrufende Methode und schließlich "${message}" als die eigentliche Protokollmeldung ersetzt.

Der Zusatz "${exception:Format=tostring}" regelt, dass Exceptions inklusive der Detailmeldungen erfasst werden. Der Platzhalter "${newline}" schließlich, fügt einen Zeilenumbruch am Ende der Meldung ein.

Verwendung

Im Programm selbst, greift man auf NLog beispielsweise so zu:

Public Logger as NLog.Logger = NLog.LogManager.GetCurrentClassLogger
...
Me.Logger.Info("Dies ist ein Test")
Me.Logger.Warn(ex.Message)

Ergänzung vom 21.01.2011:

Hier noch ein Beispiel für eine ziemlich umfassende Protokollausgabe, inklusiv Ausgabe auf die Konsolenanwendung:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
	<targets>
		<target name="file" xsi:type="File" layout="${longdate} ${logger} ${callsite} ${message} ${exception:Format=tostring}" fileName="${basedir}/${windows-identity:domain=false}.log"/>
		<target name="console" xsi:type="Console" layout="${longdate} ${logger} ${callsite} ${message} ${exception:Format=tostring}"/>
	</targets>
 
	<rules>
		<logger name="*" minlevel="Debug" writeTo="file"/>
		<logger name="*" minlevel="Info" writeTo="console"/>	
	</rules>
 
</nlog>

Ergänzung vom 17.02.2011:

Noch ein Beispiel mit einem Formular-Target:

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 
	<targets>
		<target xsi:type="FormControl" name="FormControlTarget" layout="${longdate} ${logger} ${message} ${exception:Format=tostring}" append="Boolean" controlName="txtLog" formName="frmMain" />
	</targets>
 
	<rules>
		<logger name="*" minlevel="Debug" writeTo="FormControlTarget"/>	
	</rules>
 
</nlog>

VB .NET: MSI Pakete automatisch installieren

Dieses kleine Modul installiert alle MSI-Pakete, die im aktuellen Programmverzeichnis gefunden werden im "Quiet"-Modus, d. h. ohne irgendwelche Nachfragen.

Module modMain
 
  Sub Main()
    Console.WriteLine("Installiere MSI Pakete...")
    Try
      RunAll()
    Catch ex As Exception
      Console.WriteLine("Fehler: " & ex.Message)
      Console.WriteLine("Bitte drücken Sie eine beliebige Taste zum Beenden.")
      Console.ReadLine()
    End Try
    Console.WriteLine("Installation abgeschlossen.")
  End Sub
 
  ''' <summary>
  ''' Nach MSI-Paketen im aktuellen Verzeichnis (dieser EXE) suchen und alle MSI-Pakete
  ''' nacheinander ausführen.
  ''' </summary>
  ''' <remarks></remarks>
  Public Sub RunAll()
 
    Dim lstFiles As Collections.ObjectModel.ReadOnlyCollection(Of String) = _
    My.Computer.FileSystem.GetFiles(Environment.CurrentDirectory, FileIO.SearchOption.SearchTopLevelOnly, "*.MSI")
 
    For Each fn As String In lstFiles
      ExecuteAndWait(fn, "/quiet")
    Next
 
  End Sub
 
  ''' <summary>
  ''' Einen Prozess starten und auf dessen Ende warten
  ''' </summary>
  ''' <param name="ProcessPath">Name der Datei die ausgeführt werden soll</param>
  ''' <param name="Arguments">Optionale Parameter</param>
  ''' <remarks></remarks>
  Public Sub ExecuteAndWait(ByVal ProcessPath As String, Optional ByVal Arguments As String = "")
 
    Dim proc As System.Diagnostics.Process
 
    Try
      proc = New System.Diagnostics.Process()
      proc.StartInfo.FileName = ProcessPath
      proc.StartInfo.Arguments = Arguments
      proc.StartInfo.WindowStyle = ProcessWindowStyle.Normal
      proc.Start()
      proc.WaitForExit()
      proc.Close()
    Catch ex As Exception
      MsgBox(String.Format("Prozess {0} konnte nicht gestartet werden. Fehler: {1}.", ProcessPath, ex.Message))
    End Try
 
  End Sub
 
End Module Read More

Typische Programmierfehler?

Manchmal sieht man den Wald vor lauter Bäumen nicht.

Gestern wunderte ich mich, warum das von mir erstellte FTP-Verzeichnis nicht im FTP Client (FileZilla) sichtbar war, obwohl es erfolgreich erstellt wurde.

Wie sich dann später herausgestellt hat, lösche ich es gleich danach wieder. Ein typischer Copy & Paste Fehler...