Send to mail, sql query result as csv file

19 באפריל 2013

3 תגובות

התקבשתי לפתח תוכנה קטה שתדע להריץ שאילתא בבסיס נתונים, לקבל את התוצאה ולשלוח אותה במייל כקובץ csv.

מייד נכתוב אותה יחד. (ניתן להוריד קבצי מקור – כאן לא נראה את כל הקוד, רק את החלקים החשובים).

 

למעשה הפתרון שלנו יהיה מורכב משלושה פרוייקטים.

  • SqlReportLib, יכיל את כל הקוד עצמו (הלוגיקה השליחה במייל, ההרצה ב – DB.
  • SqlReportApp, אפליקצייה מסוג Console הגדירה את כל ההגדרות בקונפיג, ופשוט מריצה את הקוד.
  • UI, יודעת גם להריץ את הקוד, אך נותנת UI עבור הפרמטרים.

 

נתחיל:

בפרוייקט SqlReportLib יהיו לנו שלושה אובייקטים מרכזיים:

  • Dal – אחראי על העבודה מול בסיס הנתונים.
  • MailHelper – אחראי על שליחת המייל.
  • Worker – הוא המקשר בין כל האובייקטים.

במחלקת Worker ישנם שני פונקציות מרכזיות בשם Execute, הנראות כך:

Code Snippet
public static void Execute()
{
    WorkerParameters parameters = new WorkerParameters()
    {
        DalParams = ReadDalParamsFromConfig(),
        MailParams = ReadMailParamsFromConfig()
    };

    Execute(parameters);
}

public static void Execute(WorkerParameters parameters)
{
    ReadParametersFromConfig(parameters);

    DataTable table = DAL.GetData(parameters.DalParams);
    MailHelper.Send(parameters.MailParams, table);
}

המתודה הראשונה, לא מקבלת שום פרמטר, ויוצאת מתוך הנחה שכל המידע נמצא בקונפיג, המתודה השנייה מקבלת את האובייקט (או מאותחלת מהקונפיג בעזרת המתודה הקודמת, או עם פרמטרים כפי שנעשה מה – UI)

נסתכל לרגע על קובץ הקונפיג (הנמצאת באפליקצית SqlReportApp – שזוהי אפליקציית ה – Console, שלדוגמא רוצים להריץ אותה בעזרת windows scheduler)

Code Snippet
<connectionStrings>
  <add name="SqlReportConnection" connectionString="Data Source=.;Initial Catalog=hours;Integrated Security=True" />
</connectionStrings>

<appSettings>
  <!– Text, StoredProcedure –>
  <add key="CommandType" value="Text"/>
  <add key="Query" value="SELECT * FROM Users"/>

  <add key="Body" value="Read the attach report, if you have any question, please mail back."/>
  <add key="CC" value="cc1@server.com"/>
  <add key="From" value="user@gmail.com"/>
  <!– Embedded, Csv –>
  <add key="ReportType" value="Embedded"/>
  <add key="Subject" value="User Report"/>
  <add key="To" value="to1@gmail.com;to2@gmail.com"/>
</appSettings>

<system.net>
  <mailSettings>
    <smtp deliveryMethod="Network" from="user@gmail.com">
      <network enableSsl="true" host="smtp.gmail.com" port="587" userName="user@gmail.com" password="PWD" />
    </smtp>
  </mailSettings>
</system.net>

 

ראשית יש ה – ConnectioString שמכיל את הכתובת לבסיס הנתונים, לאחר מכן ב – AppSettings יש הגדרות עבור ה – Dal (CommandType, Query) כלומר מה השאילתא להרצה וכיצד להריץ אותה – האפליקצייה משתמשת ב Ado.net בסיסי, להסברים נוספים.

לאחר מכן יש פרמטרים עבור המייל (הפרטרים הם פשוטים, למי לשלוח, ממי וכד’ – הפרמטר היחידי שדורש הסבר זה ה – ReportType, שמגדיר האם הדוח יגיע כחלק מהמייל או יצורף כקובץ)

בסוף יש הגדרות smtp בדוגמא כאן נשתמש ב – gmail (כמובן שצריך להכניס שם משתמש וסיסמא של gmail)

 

נחזור למחלקות הפרמטרים שנראות כך: (הסברים בהמשך)

Code Snippet
public class WorkerParameters
{
    public DalParameters DalParams { get; set; }
    public MailParameters MailParams { get; set; }
}

public class NotifyObject : INotifyPropertyChanged
{
    protected void OnPropertyChanged(string propName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }

    [field: NonSerialized]
    public event PropertyChangedEventHandler PropertyChanged;
}

public class DalParameters : NotifyObject
{
    private string connectionString;
    public string ConnectionString
    {
        get { return connectionString; }
        set
        {
            connectionString = value;
            OnPropertyChanged("ConnectionString");
        }
    }

    private string query;
    public string Query
    {
        get { return query; }
        set
        {
            query = value;
            OnPropertyChanged("Query");
        }
    }

    private CommandType commandType;
    public CommandType CommandType
    {
        get { return commandType; }
        set
        {
            commandType = value;
            OnPropertyChanged("CommandType");
        }
    }
}

public class MailParameters : NotifyObject
{
    private string from;
    public string From
    {
        get { return from; }
        set
        {
            from = value;
            OnPropertyChanged("From");
        }
    }

    private string to;
    public string To
    {
        get { return to; }
        set
        {
            to = value;
            OnPropertyChanged("To");
        }
    }

    private string cc;
    public string CC
    {
        get { return cc; }
        set
        {
            cc = value;
            OnPropertyChanged("CC");
        }
    }

    private string subject;
    public string Subject
    {
        get { return subject; }
        set
        {
            subject = value;
            OnPropertyChanged("Subject");
        }
    }

    private string body;
    public string Body
    {
        get { return body; }
        set
        {
            body = value;
            OnPropertyChanged("Body");
        }
    }

    private ReportType reportType;
    public ReportType ReportType
    {
        get { return reportType; }
        set
        {
            reportType = value;
            OnPropertyChanged("ReportType");
        }
    }

    public SMTPParameters SMTP { get; set; }
}

public class SMTPParameters : NotifyObject
{
    private string host;
    public string Host
    {
        get { return host; }
        set
        {
            host = value;
            OnPropertyChanged("Host");
        }
    }

    private int port;
    public int Port
    {
        get { return port; }
        set
        {
            port = value;
            OnPropertyChanged("Port");
        }
    }

    private string userName;
    public string UserName
    {
        get { return userName; }
        set
        {
            userName = value;
            OnPropertyChanged("UserName");
        }
    }

    private string password;
    public string Password
    {
        get { return password; }
        set
        {
            password = value;
            OnPropertyChanged("Password");
        }
    }
}

public enum ReportType
{
    Embedded,
    Csv
}

 

המחלקה הראשית (WorkerParameters) מכילה את שני המחלקות של הפרמטרים Mailparameters, DalParameters.

כל אחד מהם מכיל את ההגדרות שראינו בקונפיג – הסיבה שהם יורשות מ – NotifyObject וכל המאפיינים מרימים את האירוע בכל set של כל מאפיין, היא כדי לאפשר binding באפליקציית ה – ui (כפי שנראה בהמשך)

 

נחזור למתודת Execute

Code Snippet
public static void Execute(WorkerParameters parameters)
{
    ReadParametersFromConfig(parameters);

    DataTable table = DAL.GetData(parameters.DalParams);
    MailHelper.Send(parameters.MailParams, table);
}

 

דבר ראשון המתודה מקבלת DataTable מה – Dal, הקוד הוא מאוד פשוט (הגדרת הפרמטרים הפעלת המתודה והחזרת הטבלה).

Code Snippet
internal static class DAL
{
    private static SqlConnection connection;
    private static SqlCommand command = new SqlCommand();

    static DAL()
    {
        connection = new SqlConnection();
        command = new SqlCommand();

        command.Connection = connection;
    }

    internal static DataTable GetData(DalParameters parameters)
    {
        connection.ConnectionString = parameters.ConnectionString;
        command.CommandText = parameters.Query;
        command.CommandType = parameters.CommandType;

        connection.Open();
        DataTable table = new DataTable();
        table.Load(command.ExecuteReader());
        connection.Close();

        return table;
    }
}

 

בסוף מתודת GetData לאחר שיצרנו את הטבלה השתמשנו במתודת Load שיודעת לקבל כפרמטר DataReader ולבנות טבלה אוטומטית מכל המידע שנמצא בה.

 

לאחר שאנחנו מקבלים את הטבלה (במתודת Execute) אנחנו פונים ל – MailHelper ושולחים את הטבלה.

Code Snippet
internal static void Send(MailParameters parameters, DataTable table)
{
    MailMessage message = CreateMailMessage(parameters);
    AttachReport(message, table, parameters.ReportType);
    SmtpClient client = CreateSmtpClient(parameters);

    client.Send(message);
}

 

ראשית ניצור אובייקט מסוג MailMessage מתוך הפרמטרים, לאחר מכן נוסיף את הטבלה למייל (כקובץ או כחלק מהמיל) ולאחר מכן ניצור אובייקט מסוג SmtpClient ונשלח את המייל.

מתודת CreateMailMessage

Code Snippet
private static MailMessage CreateMailMessage(MailParameters parameters)
{
    MailMessage message = new MailMessage();
    message.From = new MailAddress(parameters.From);

    foreach (var item in parameters.To.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
    {
        message.To.Add(item);
    }

    if (parameters.CC != null)
    {
        foreach (var item in parameters.CC.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
        {
            message.CC.Add(item);
        }
    }

    message.Body = parameters.Body;
    message.Subject = parameters.Subject;
    message.IsBodyHtml = true;

    return message;
}

כדי לאפשר לשלוח ב – to, cc) ליותר מאחד, המחרוזת (בקונפיג) יכולה להכיל את התו ; ולכן יש כאן split למאפיינים הללו.

 

מתודת AttachReport נראית כך:

Code Snippet
private static void AttachReport(MailMessage message, DataTable table, ReportType reportType)
{
    if (reportType == ReportType.Csv)
        message.Attachments.Add(GetCsvFile(table));
    else
        message.Body += GetHtmlFromData(table);
}

private static string GetHtmlFromData(DataTable data)
{
    string tab = "\t";
    StringBuilder sb = new StringBuilder();

    sb.AppendLine("<html>");
    sb.AppendLine(tab + "<body>");
    sb.AppendLine(tab + tab + "<table>");

    // headers.
    sb.Append(tab + tab + tab + "<tr>");

    foreach (DataColumn dc in data.Columns)
    {
        sb.AppendFormat("<td>{0}</td>", dc.ColumnName);
    }

    sb.AppendLine("</tr>");

    // data rows
    foreach (DataRow dr in data.Rows)
    {
        sb.Append(tab + tab + tab + "<tr>");

        foreach (DataColumn dc in data.Columns)
        {
            string cellValue = dr[dc] != null ? dr[dc].ToString() : "";
            sb.AppendFormat("<td>{0}</td>", cellValue);
        }

        sb.AppendLine("</tr>");
    }

    sb.AppendLine(tab + tab + "</table>");
    sb.AppendLine(tab + "</body>");
    sb.AppendLine("</html>");

    return sb.ToString();
}

private static Attachment GetCsvFile(DataTable data)
{
    StringBuilder sb = new StringBuilder();

    foreach (DataColumn dc in data.Columns)
    {
        sb.AppendFormat("{0},", dc.ColumnName);
    }

    sb.AppendLine("\r\n");

    foreach (DataRow dr in data.Rows)
    {
        foreach (DataColumn dc in data.Columns)
        {
            string cellValue = dr[dc] != null ? dr[dc].ToString() : "";
            sb.AppendFormat("{0},", cellValue);
        }

        sb.AppendLine("\r\n");
    }

    MemoryStream ms = new MemoryStream();
    StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);
    sw.Write(sb.ToString());
    sw.Flush();
    ms.Seek(0, SeekOrigin.Begin);

    Attachment att = new Attachment(ms, "Report.csv");
    return att;

}

 

ראשית נוודא האם רוצים לצרף כקובץ או לשלוח כחלק מה – body, במידה ואכן רוצים לשלוח כחלק מה – body, נקרא למתודת GetHtmlFromData, שרצה על הטבלה ומייצרת טבלת html שמתווספת ל – body של המייל, אחרת (כלומר ורצים את הדוח כקובץ מצורף) נוסיף למאפיין Attachments את הקובץ המגיע ממתודת GetCsvFile, הקוד במתודה זו מאוד דומה לקודמת, אבל בסוף נכניס את כל המידע לאובייקט מסוג MemeoryStream ונייצר אובייקט מסוג Attachment שיקבל את השם Report.csv.

לאחר כל התהליך, נייצר את ה – SmtpClient שיקבל את המאפיינים מהאובייקט (או מהקונפיג) ונשלח את המייל.

כדי להפעיל את כל הקוד, כל מה שנצטרך לעשות ב – Console Application לכתוב את הקוד הבא:

Code Snippet
static void Main(string[] args)
{
    try
    {
        Worker.Execute();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}

כמובן שכדי שזה יעבוד הקונפיג צריך להיות מוגדר כמו שצריך.

 

כדי להיות נחמד יותר ללקוח, החלטתי גם לכתוב אפליקציית UI שתאפשר מילוי של הערכים (ואפילו טעינה ושמירה שלהם לקובץ).

לא נעבור כאן על הקוד (תוכלו כמובן להוריד את האפליקצייה) המסך נראה כך:

image

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

3 תגובות

  1. ישראל23 באפריל 2013 ב 6:40

    היי שלמה,
    מדוע לא השתמשת ברעיון המגניב מהפוסט הזה? http://blogs.microsoft.co.il/blogs/shlomo/archive/2012/11/13/inotifypropertychanged-and-callermembername.aspx

    הגב
  2. Shlomo23 באפריל 2013 ב 10:44

    בגלל שאתל הלקוח מותקן רק net4.0, ולא יכולתי לעדכן את הגרסה.

    הגב
    1. ישראל19 בפברואר 2014 ב 16:22

      שלמה,

      ניתן לעבוד עם הפתרון שלך באמצעות Attribute http://blogs.microsoft.co.il/shlomo/2012/11/13/inotifypropertychanged-and-callermembername/
      כל עוד אתה משתמש ב VS2012 ומעלה, למרות שאתה מקמפל לnet4.0
      עיין כאן:
      http://stackoverflow.com/questions/13381917/is-the-callermembername-attribute-in-4-5-able-to-be-faked

      הגב