Makale Özeti

Bu geliştirdiğimiz uygulamalarda çoğunlukla bilgilendirme amaçlı mailler veya günlük özet raporlar tarzı mailler atılmaktadır. Daha önceki makalemde template kullanarak bu maillerin nasıl kolayca oluşturulabileceğini incelemiştik. Şimdi karşımızda başka bir sorun var. Müşteri bizden gönderilen mailin kişi tarafından okunup okunamadığı bilgisini tutmamızı istedi. Bu bilgi kişinin kendisine atanmış bir görevi ben okumamıştım, bana mail gelmedi şeklinde bir bahane ile yapmadığının üzerini örtmesini engelleyecektir.

Makale

         Merhabalar,

         Bu geliştirdiğimiz uygulamalarda çoğunlukla bilgilendirme amaçlı mailler veya günlük özet raporlar tarzı mailler atılmaktadır. Daha önceki makalemde template kullanarak bu maillerin nasıl kolayca oluşturulabileceğini incelemiştik. Şimdi karşımızda başka bir sorun var. Müşteri bizden gönderilen mailin kişi tarafından okunup okunamadığı bilgisini tutmamızı istedi. Bu bilgi kişinin kendisine atanmış bir görevi ben okumamıştım, bana mail gelmedi şeklinde bir bahane ile yapmadığının üzerini örtmesini engelleyecektir.

         Bunun için öncelikle göndereceğimiz maillerin okundu bilgisiyle eşleştirilebilmesi için öncelikle veritabanına yazılması gerekmektedir. Zaten geliştirdiğimiz uygulamalarda maillerin direkt gönderilmesindense ilk önce bir veritabanına yazılması, daha sonra bu veritabanından bir listener yardımıyla okuyup gönderilmesi daha iyidir. Bunun sebebi uygulamamızın logic kodu içinden gönderilecek mailler sırasında;

         1- Mail sunucusuna erişilememesi durumunda fırlayan exception tüm transacationumuzu sonlandırabilir. Bu durumda mail gönderilemediği takdirde uygulamamız üzerinden işlem yapılamayacaktır. Uygulamamız standalone çalışmayacak ve çalışır durumda bir mail sunucusuna ihtiyaç duyacaktır.
         2- Mail sunucusuna erişilememesi durumunda fırlayan exception'u handle ettiğimiz durumlarda transactionlar rollback edilmeyecek ancak bu mail kullanıcılara bir daha hiçbir zaman gönderilememek üzere kaybolacaktır ve tüm bunun yanısıra oluşan exception'un fırlaması sırasında ciddi zaman kayıpları yaşanacaktır.
         3- Birden fazla mail gönderilecek durumlarda, örneğin birden fazla siparişin onaylanması durumunda her sipariş sahibine ayrı ayrı mail atılacaksa. Mail sunucusuyla erişim uzun sürecektir. Bu işlemler sırasında kullanıcı beklemek zorunda kalacak, o an için kilitlediği kaynaklar varsa o kaynaklara başka kullanıcılar erişemeyecektir.

         Bu sebeplerden dolayı gönderilecek tüm mailleri From,To,Cc,Bcc,Subject,Body alanlarını veritabanına yazarak saklamak ve daha sonra başarı ile gönderildiğinde ise bir alana gönderilme tarihini yazarak gönderildiğini belirlemek aynı mailin iki kere gönderilmesinin önüne geçmek adına kaçınılmazdır. Maillerimizi bir windows servisin bu tabloyu okuyarak göndermesi sırasında kaynaklarımızı kullanıcıyı etkileyecek şekilde tüketmiş olmayız ve kullanıcıyı bekletmeyiz.

         Tüm bu bahsettiğim kaynak tüketimini azaltacak ve uygulamayı hızlandıracak önlemlerden sonra maillerin okundu bilgisini nasıl alacağımızı yazmaya başlayalım.

         Öncelikle veritabanımızı bahsettiğimiz maddeler ışığında aşağıdaki gibi oluşturuyoruz.

        

         Oluşturduğumuz tablodaki alanların neler olduğu isimlerinden net bir şekilde anlaşılabilmektedir. Dikkat etmemiz gereken nokta MailId alanının integer değilde uniqueidentifier olarak tanımlanmış olduğudur. Bu alandaki veriyi mailimizin okunduğu bilgisini alırken kullanacağız.

         Şimdi gelelim kodlama tarafına, bu kısımda ilk önce geliştireceğimiz uygulamalardan ve uygulamanın çalışma mantığından bahsedelim. Sistemin işleyişi gönderilen maillere bir resim koyulması ve bu resmin url'înin her mail için MailId alanını kapsayacak şekilde farklı olarak oluşturulması temeline dayanmaktadır. Bu kullanım lokal sistemlerde sorunsuz çalışmasına rağmen bu tip resimler Hotmail,Gmail,Yahoo gibi web tabanlı mail servisi sağlayıcılar engellemektedir. Bunun yanısıra dış bir kaynaktan gelen mail bu şekilde bir resim içeriyorsa Outlook'da bunu spam olarak görebilmektedir. Belirttiğim özelliği sağlamak için 2 adet uygulama geliştirmemiz gerekmektedir.

         Bu uygulamalar;

         1 - Windows Service : Mail tablosundan verileri okuyacak ve bu mailleri kişilere gönderecek olan servistir. Maillerri kişilere gönderirken bahsettiğim gibi içeriğine resmi ekleyecektir.

         2 - Web uygulaması : Bu uygulama gönderilmiş olan mail içerisinde bulunan resmin host edileceği ve bu resme gelen isteklerin karşılanarak resmin gönderileceği uygulamadır.

         Bu uygulamaları geliştirmeye başlayalım.

         Öncelikle veritabanı kısmında ihtiyaç duyacağımız stored procedure'ları yazalım.

CREATE PROC SelectUnsentMails
AS
SELECT
* FROM Mails WHERE SentDate IS NULL
 
GO
 
CREATE PROC MarkMailSent
@pMailId uniqueidentifier
AS
UPDATE
Mails SET SentDate=getdate() WHERE MailId=@pMailId
 
GO
 
CREATE PROC MarkMailRead
@pMailId uniqueidentifier
AS
UPDATE Mails SET ReadDate=getdate() WHERE MailId=@pMailId
 
GO
 
CREATE PROC CheckMailRead
@pMailId uniqueidentifier
AS
SELECT ReadDate FROM Mails WHERE MailId=@pMailId


         Oluşturduğumuz bu sp'lerden ilki henüz gönderilmemiş bilgileri getirirken, ikincisi Id'sini verdiğimiz maili gönderildi update ediyor, üçüncüsü ise Id'sini verdiğimiz maili okuındu olarak update ediyor. Bu splerden sonuncusu ise bize Mail'Id sine göre okunma tarihini getiriyor.

         Artık Mail gönderecek olan windows servisimizi yazmaya başlayabiliriz. Öncelikle servisimizin application.config dosyasını yapılandıralım.
 

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <
appSettings>
    <
add key="sql" value="data Source=.;initial catalog=YGMail;integrated security=SSPI"/>
    <add key="smtp" value="localhost"/>
    <add key="imagePath" value="http://localhost:46651/ImageHandler.ashx"/>
  </appSettings>
</configuration>


      Gördüğünüz gibi config dosyamızda veritabanına bağlanabilmemiz için gerekli connection string, smtp server adresi, ve mailimizin sonuna ekleyeceğimiz resmin adresi bulunmaktadır. Burada dikkat ettiyseniz resmin uzantısı ashx olarak yazılmıştır. Bunun sebebi resim dosyamıza gelecek istekleri veritabanına kaydetmeyi HttpHandler kullanarak yapacak olmamızdır. Şimdi ise C# kodumuzu yazalım.

        private void SendMails()
        {
            SqlConnection insSqlConnection = new SqlConnection(System.Configuration.ConfigurationManager.AppSettings["sql"]);
            SqlCommand insSqlCommand = new SqlCommand("SelectUnsentMails", insSqlConnection);
            SqlDataAdapter insSqlDataAdapter = new SqlDataAdapter(insSqlCommand);
            DataTable insDataTableUnsentMails = null;
            insSqlDataAdapter.Fill(insDataTableUnsentMails);
            insSqlConnection.Open();
            foreach (DataRow dr in insDataTableUnsentMails.Rows)
            {
                MailMessage mm = new MailMessage();
                mm.To.Add(dr["MailTo"].ToString());
                mm.CC.Add(dr["MailCc"].ToString());
                mm.Bcc.Add(dr["MailBcc"].ToString());
                mm.Body = dr["MailBody"].ToString();
                mm.IsBodyHtml = true;
                mm.From = new MailAddress(dr["MailFrom"].ToString());
                mm.Subject = dr["MailSubject"].ToString();
                mm.Body = mm.Body + "<img src='" + System.Configuration.ConfigurationManager.AppSettings["imagePath"] + "?Id=" + dr["MailId"].ToString() + "'>";
                SmtpClient scli = new SmtpClient(System.Configuration.ConfigurationManager.AppSettings["smtp"]);
                try
                {
                    scli.Send(mm);
                    SqlCommand insSqlCommandUpdate = new SqlCommand("MarkMailSent", insSqlConnection);
                    insSqlCommandUpdate.CommandType = System.Data.CommandType.StoredProcedure;
                    insSqlCommandUpdate.Parameters.AddWithValue("@pMailId", dr["MailId"].ToString());
                    insSqlCommandUpdate.ExecuteNonQuery();
                }
                catch (Exception)
                {
 
                }
 
            }
            insSqlConnection.Close();
        }


         Yazdığımız kodu inceleyelim. Öncelikle veritabanından henüz gönderilmemiş maillerin bir listesini alıyoruz ve bir döngü içerisinde bu mailleri gönderiyoruz. Dikkat ettiyseniz mailleri göndermeden önce bir MailMessage nesnesi atıyor ve veritabanından okuduğumuz her alanı bu maildeki bir property ile eşleştiriyoruz. Burada Body propertysine dikkat ederseniz bu property'e değer atandıktan sonra SmtpClient nesnesini oluşturmadan hemen önce mail'in body'sine resmimizi html kodu olarak koyuyoruz ve resim yolunun sonuna QueryStrıng ile Mail'in Id sini ekliyoruz. Daha sonraki kısımda ise maili gönderiyor ve veritabanında gönderildi olarak işaretliyoruz.

         Şimdi maili kullanıcılara gönderdiğimizde resmin download edileceği adresteki web uygulamamızı geliştirmeye başlayabiliriz yine öncelikle web.config dosyasını inceleyelim.


  <appSettings>
    <
add key="sql" value="data Source=.;initial catalog=YGMail;integrated security=SSPI"/>
    <add key="imageName" value="yazgelistir_logo.gif"/>
  </appSettings>


         Gördüğünüz gibi veritabanına bağlanmak için gerekli olan connectionstring'i veresmin fiziksel adresini tanımlıyoruz. Şimdi artık HttpHandler dosyamızı yazmaya başlayabiliriz.

using System;
using System.Data;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
 
namespace Web
{
    /// <summary>
    /// Summary description for $codebehindclassname$
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class ImageHandler : IHttpHandler
    {
 
        public void ProcessRequest(HttpContext context)
        {
            try
            {
                string guid = context.Request.QueryString["Id"];
                Guid g = new Guid(guid);
                System.Data.SqlClient.SqlConnection insSqlConnection = new System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.AppSettings["sql"]);
                System.Data.SqlClient.SqlCommand insSqlCommand = new System.Data.SqlClient.SqlCommand("CheckMailRead", insSqlConnection);
                insSqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
                insSqlCommand.Parameters.AddWithValue("@pMailId", g);
                try
                {
                    insSqlConnection.Open();
                    object o = insSqlCommand.ExecuteScalar();
                    if (o == DBNull.Value)
                    {
                        insSqlCommand.CommandText="MarkMailRead";
                        insSqlCommand.ExecuteNonQuery();
                    }
                    insSqlConnection.Close();
                }
                catch (Exception)
                { 
 
                }
            }
            catch (Exception)
            {
 
            }
            System.Drawing.Image i = System.Drawing.Image.FromFile(context.Server.MapPath(System.Configuration.ConfigurationManager.AppSettings["imageName"]));
            i.Save(context.Response.OutputStream, System.Drawing.Imaging.ImageFormat.Gif);
        }
 
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}


         Şimdi yazdığımız handler'daki kodu inceleyelim. Öncelikle kodumuzu ProcessRequest metoduna yazıyoruz. Bu kısımda QueryString'den gelen bilgiyi alarak bir guid'e çeviriyoruz. Bu bilgi esasında kullanıcının MailId'si. Daha sonra ise kullanıcının bu maili daha öncedden okuyup okumadığı bilgisini kontrol ediyoruz ve okunmamış ise maili okundu olarak işaretliyoruz. En sonda ise her ne hata olursa olsun kullanıcıya resmi döndürmek zorundayız. Bunun için ilk önce resmi web.config dosyasında belirttiğimiz path'den okuyoruz ve Image nesnesi olarak oluşturuyoruz. Sonrasında ise Image nesnesinin Save metodunu kullanarak resmimizi responsestream'e yazıyoruz.

         Umarım faydalı olmuştur.
         oztamer@hotmail.com
         tamer.oz@yazgelistir.com
         oztamer@hotmail.comOrnek Uygulama