Microsoft Graph API ile mail gönderim — ASP.NET

Kerem Alincak
Kodcular
Published in
8 min readJul 4, 2020

Selamlar, güzel bir yazımıza daha vakit ayırmış olduk.

GİRİŞ

Bu yazımda Microsoft Graph API (MGA) üzerinden mail göndereceğiz. Mail gönderirken bazı güçlüklere değineceğim. Örneğin, küçük veya büyük boyutlu dosyaların gönderimi nasıl olacak.

Bu işlemleri OAuth bazlı Token kullanarak yapacağız. Tabi Token alma işlemine girmeyeceğim. Bu aşamanın yapıldığını düşünerek devam edeceğim.

Birde bunun Google ayağı var ama sonra…

O zaman başlayalım :)

Peki Microsoft Graph API (MGA) Nedir?

Microsoft Graph, Microsoft Cloud hizmet kaynaklarına erişmenizi sağlayan RESTful bir web API’sıdır.

Microsoft Graph

İşte Microsoft Graph API ise bu hizmetlere erişim sağlamak için tek bir uç nokta sunar. OData protokolü kullanılır. GraphQL değildir.

Merak edenler için OData vs GraphQL.

Biz .NET SDK’sını kullanacağız. Diğer Microsoft Graph SDK’larına buradan bakabilirsiniz.

Çalıştığım Ortamlar

İşletim sistemi: Windows 10

IDE: Microsoft Visual Studio

Programalama dili: C#

Projeyi Hazırlama

Bu aşamada test etmemiz için bir WEB projesi oluşturacağız. Solution içinde iki projemiz olacak.

Web Projesi: Test Mail’i göndermek için UI ihtiyacımı buradan karşılayacağım.

Integrations Projesi: “Class Library” olarak oluşturacağım. Bütün entegrasyon işlemlerim bunun içinde olacak.

Sırasıyla;

  • Burada ilk öncelik olarak MicrosoftGraphAPIMessage diye WEB projesi oluşturdum.
  • Sonra Integrations diye Solution içerisine “Class Library” oluşturdum.
  • Bu kütüphaneyi(Integrations) WEB projesine Reference Project olarak ekleyin.
Solution

Paketleri yükleme

Test etmek için projelerimizi oluşturduk. Şimdi MGA entegrasyonunun ihtiyaç duyacağı bazı paketleri yükleyececeğiz.

  • Integrations projesine sağ tıklayın ve Manage NuGet Packages… diyin.
  • Gelen ekranda Browse kısmına gelin.
  • Arama kısmına “Microsoft Graph” yazın ve muhtemelen ilk sırada göreceksiniz. Gelen listede Microsoft.Graph paketini seçin ve yükleyin.
  • Yüklemeyi başarılı yaptıktan sonra artık kod kısmına geçelim.

Mail işlemi için Scope listesi

Kullanıcının, mail gönderimi için entegrasyonu aktif etmesi gerekmektedir. Gerekli bilgiler girildiğinde Mail işlemi için bazı izinlere ihtiyacınız olur. Bu izinleri Authorize url’in Scope parametresine veriyoruz. Temel işlemler için şu Scope listesini vermeniz yeterli olacaktır.

  • offline_access
  • User.Read
  • Mail.ReadWrite
  • Mail.Send

Tüm izinlerin listesine şuradan ulaşabilirsiniz.

Client Service’i oluşturmak

GraphServiceClient, Microsoft Graph’a çağrı yapmayı kolaylaştırmak için tasarlanmıştır. Yapacağınız işlemlerin ömrü boyunca aynı istemci’yi (GraphServiceClient) kullanabilirsiniz. Özetleyecek olursak, HTTP üzerinden kolay sorgular atmamıza olanak sağlayacak.

Burada entegre olurken aldığımız OAuth Token bilgisini tanımlayarak istemcimizi işlemler için hazır hale getirmiş olacağız.

Peki istemcimizi tanımlayalım…

MicrosoftGraph.cs sınıfının üst kısmında readonly olarak client nesnemi tanımladım. Burada, readonly yapmamdaki amacım, aynı istemci üzerinden işlemleri tamamlamak. Readonly, istemciyi sadece başlangıç metodu(Constructor) içinde yaratılmasına izin verecek. Dışarıdan bir müdahaleye kapalı olacak. Dolayısıyla istemcinin ömrü MicrosoftGraph.cs nesnesinin ömrüne bağlanmış oluyor.

private readonly GraphServiceClient graphClient;

Service’i oluşturan bir metot yazıyorum.

Sınıfın başlangıç metodunda(Constructor) bu metodu çağırıyorum.

public MicrosoftGraph(string token)
{
graphClient = CreateGraphClientAsync(token);
}

Şimdi burada istemci artık kullanıma hazır.

Not: Aslında bu işlemden önce Token süresinin bitip bitmediğini, eğer bitmiş ise RefreshToken ile yenileme işlemi yaptığımı söylemek isterim. Yalnız bugünki konumuzun dışına çıkmak istemediğimden bu konulara girmeyeceğim.

Kısaca access_token ve refresh_token kavramları

Belki başka bir yazımda bu kavramlara daha detaylı girebilirim. Ama burada ufak bir girişimiz olsun.

access_token (erişim belirteci)

Kullanıcının kimliğini temsil eder. Bununla istenilen kaynağa erişilir. Örneğin, yukarıda istemcimize erişim belirteci bilgisini verdik. Artık bu bilgi ile kullanıcıdan aldığımız izin(verdiğimiz scope listesi) çerçevesinde kaynaklarına erişebilirsiniz. Erişim belirtecinin kullanım süresi/ömrü vardır. Ömrünü dolduran erişim belitecini yenilemek gerekiyor. İşte burada refresh_token devreye giriyor.

refresh_token (yenileme belirteci)

Erirşim belirtecini yenilemek/güncellemek için bu belirteci kullanırız.

Yenilemeye ne gerek var?

Güvenli bir API’ya kimlik doğrulamalı çağrı yapmak için Erişim Jetonlarını kullanıyoruz. Yani, erişim belirtecinin ömrü dolduğunda yenileme belirteci ile kimlik doğruluyorsunuz.

Yenileme belirtecinin ömrü var mı?

Öncelikle, yenileme belirtecleri iyi saklanmalıdır. Çünkü, erişim belirtecinin ömrü bittiğinde yenileme belirteci ile yenilenir. Bu demek oluyor ki, sonsuz yenileme imkanı ile kullanıcının kaynağına erişmek demektir.

Sorumuza cevap verecek olursak, evet yenileme belirtecinin de ömrü var. Tabi bu uzun bir süre. Örneğin; erişim belirteci 180 dakika iken, yenileme belirtecinin 90 gün gibi bir ömrü olabiliyor.

Dosya işlemleri için bazı senaryolar

Bu aşamada MGA’yı projeye entegre ederken neler yaşadık onu anlatayım.

1 — Mail gönderirken 3mb üstünde bir dosyayı aynı anda gönderemezsiniz.

Öncelikle şunu bilelim, Message nesnesi bizim mail nesnemizdir. İki farklı yöntem ile mail gönderebilirsiniz.

  • İster direkt Message nesnesinin gerekli içeriğini doldurup gönderebilirsiniz.
  • Veya bir Taslak(Draft) Message nesnesi oluşturup, sonrasında gönderebilirsiniz.

Dosya göndermek için birinci seçenek, Message nesnesinin Attachments elemanını kullandırır. Birinci seçeneği kullanırsanız, 3mb limitinin üstünde dosya atamazsınız.

Message sınıfı ve Attachments elemanı

Tabi burada 3mb, toplam limittir. Yani Attachment List elemanına eklediğiniz her dosya için değil, bütün dosyaların toplam limitinin 3mb üstünde olmaması gerekiyor.

İşte burada şöyle bir sıkıntı oluyor. Örneğin, elinizde beş tane 1mb boyutunda dosyalar olsun. Bunları Message nesnesine eklediğinizde mail gönderemiyorsunuz. Çünkü Message’ın içindeki dosya boyutu 5mb olmuş ve 3mb limitini aşmış oluyor.

Bu bir sorun değil. Dosyaların neden gönderilemediği konusunun nedeni var. Aşağıda yazacağım.

Peki aklımızdaki soru ne?

  • Yukarıda beş tane 1mb dosya için gönderim mümkün değil. Bu dosyalar nasıl gidecek…

2 — Add Attachment ile 3mb altında istediğiniz kadar dosya gönderebilirsiniz.

Başlıkta olduğu gibi dosyanız 3mb limitinin altında ise bu yöntemi kullanacağız. Bu yöntem ile oluşturulan Draft Message nesnesine dosyaları ekleyeceğiz. Yani ikinci yöntem kullanılacak.

3— Mail gönderirken dosyanız 3mb-150mb arasında ise UploadSession kullanarak yükleyebilirsiniz.

Evet, dosyanız 3mb limitinden büyük ise bu yöntemi kullanabilirsiniz. Tabi bu boyutta ki bir dosyanın gönderimi konusunda aşağıda detayları yazıcam.

Bu işleme UploadSession deniyor. Bu işlem ile dosyanızı parça parça yüklüyorsunuz. Her bir parça için tavsiye edilen boyut 4mb’dır. İsterseniz farklı boyutlara bölerek yüklemeyi yapabilirsiniz.

Peki aklımızdaki soru ne?

  • UploadSession kullanacağız ama mail gittikten sonra dosyanın gitmesi uzun sürerse? Yani mail gitmiş ama içinde dosya olmayacak.

150mb dosya gerçekten gidebiliyor mu?

Evet gider. Yalnız burada dikkat etmeniz gereken bir nokta var. Bazen şirketlerin güvenlik politikaları gereği sınırlar olabiliyor.

Burada 3mb-150mb arasında demem tamamen UploadSession konusudur. Yalnız, bu boyutta ki dosyaları gönderebileceğiniz anlamına gelmiyor.

Burada iki madde olarak açıklama yazayım;

  • Office365 için Message toplam limiti varsayılan 35mb’dır. Bu yükseltilebilir veya düşürülebilir. Bu durumda limiti aşmanız durumunda hata alırsınız. Örnek hata:
Status Code: BadRequest
Microsoft.Graph.ServiceException: Code: ErrorMessageSizeExceeded
Message: The message exceeds the maximum supported size.
  • Dosya boyut limitini proje içinde kontrol edebilirsiniz. Ben yaparken 30mb ile sınırlamıştım. Dolayısıyla dosya zaten bizim sistemden geçememiş oluyor. Ön tarafın(UI) böyle bir kontrol yapması sisteminizi boşuna meşgul etmez.

Bu sanırım planlar ile ilgili. Detaylar için şurayı inceleyin isterim…

Neden dosyaları ayrı yüklüyoruz?

Buna biraz bakındım. Yazma ihtiyacı hissettim. Bu tür ayrı yüklemeyi destekleyen sistemler, genelde bütün dünya ülkelerine hizmet verir. Sistemin böyle tasarlanmasının nedeni tam budur. O zaman detaylandıralım.

AddAttachment ve UploadSession kullanılmasının nedeni, dosyaları sağlıklı bir şekilde yükleyebilmek. AddAttachment zaten maksimum 3mb olduğu için yüklemede çok sıkıntı olmayacaktır. Yalnız, büyük dosyalar için yüklemeler sıkıntı olabilir.

Dünya üzerinde bazı ülkenin Upload hızı henüz tatmin edici düzeyde değil. Örneğin, saniyede 56KB (375KB evet hızım şuan bu 😅) dosya yüklemeleri için sağlıklı çalışmayabilir. Dolayısıyla burada sağlıklı bir işlem için dosyaları bölerek yüklemeniz gerekir.

Peki ne yapacağız?

Burada kısaca nasıl ilerleyeceğimizi yazalım. Dediğim gibi MGA’da mail konusu Message olarak geçiyor. Dolayısıyla bizim bu adımları geçmemiz için şunları yapıcaz.

  • Draft message oluşturacağız. Gönderilmeye hazır bir Message.
  • Sonra dosyaları ekleyeceğiz. Eğer dosya 3mb limitinin altında ise Add Attachmet, değilse UploadSession yöntemini kullanacağız.
  • Hepsi tamamlandıktan sonra Message gönderim işini yapacağız.

Not: Burada kısaca özetleyelim;

1 — Eğer direkt Message ile Attachment gönderirseniz limit 3mb’tır. Dikkat edin, bu her dosya için bir limit değil. Dosyaların toplam limiti.

2 — AddAttachment yöntemi ile 3mb limitinin altında dosyaları ekleyebilirsiniz.

3 — UploadSession yöntemi ile 3mb-150mb arasında dosyaları ekleyebilirsiniz.

O zaman senaryodan sonra başlayalım tekrar yazmaya…

Not: Burada async-await yöntemini kullanacağız. Bu konuda bilgi almak isteyen olursa Türkçe kaynak önerebilirim.

Burak Selim Şenyurt — Asp.Net–Doğru async, await Kullanımı

Taslak (Draft) Message oluşturma

Çok temel düzeyde Taslak oluşturan kodu şöyle yazdım. Tabi sizler istediğiniz başka bilgiler varsa ekleyebilirsiniz.

Burada iki durum var. Amacım eğer messageid dolu ise bu cevaplamak istediğim bir mail, değilse yeni bir mail oluşturmak.

Message silme işlemi

Burada bir hata olması durumunda Message Taslak olarak kalacaktır. Bunun Taslak olarak kalmasını istemiyoruz. Eğer başarısız bir işlem var ise bu Taslağı silmeliyiz. Yoksa Taslak çöplüğü oluşur. 😅

AddAttachment — 3MB altı dosya ekleme

Taslağımızı oluşturduk. Artık dosyamız var ise ekleyebiliriz. Öncelikle tabi 3mb altında ise şu kodu yazdım; aşağıda örnek projeyi paylaşacağım.

UploadSession — 3MB-150MB arasında dosya ekleme

Eğer dosyanız bu aralıklarda ise bu yöntemi kullanmalısınız. Burada işlem parçalı olarak yapılmaktadır.

Bu yöntem ile dosyayı istenilen boyutta parçalıyorsunuz. Sonra bu parçaları tek tek gönderiryorsunuz. MGA dokümanları 4mb olarak parçalanmasını, daha performanslı olması adına tavsiye etmektedir.

Dediğim gibi bu işlem parçalı olarak devam etmektedir. Normalde parçalama ve yükleme kodlarını geliştirici yazıyordu. Artık uğraşmadan yapabilmemizi sağlayan bir yapı kurgulanmış. Bende o yapıyı kulladım.

Öncelikle kodum şöyle;

İşin özeti;

  • uploadSession oluşturuyorsunuz
  • LargeFileUploadTask ile yapıyı hazır hale getiriyorsunuz
  • largeFileUploadTask.UploadAsync ile gönderimi yapıyorsunuz
UploadAsync kaynak kodu

Yukarıda kaynak kodu görebilirsiniz. İşte bu kısmı öncesinde geliştiriciler yazıyordu. Artık bunu MGA kendisi yapıyor. Bazı geliştiricilerin, internette kendilerinin yazdığını görebilirsiniz.

Böyle kaynak kodlarına girip bakmayı, okumayı çok seven birisiyim. İyi bir deneyim sunuyor. Bu kaynakları görmek için Visual Studio’da sunulan ILSpy diye bir extension kullanıyorum. :)

Mail gönderimi

Dosyalar yüklendiğine göre artık gönderime hazır demektir. Gönderen kodumuz;

İşlemler bu kadar. Bir sıkıntı olacağını düşünmüyorum. Test ettiğim ve çalışan bir projeyi GitHub hesabıma atacağım. Orada bulabilirsiniz. Aşağıda kaynaklar kısmında olacaktır.

Bazı bilinmesi gerekenler

Denemelerimiz sırasında, asenkron kullanım sebebiyle HttpContext ile o anki web çalışma zamanına ait verilerin null geldiğine şahit olduk.

Sorun nasıl oluşuyordu?

Basit bir örnek üzerinden anlatayım. Bir projeniz olsun. Burada kişileriniz olsun. Bir kişi kaydına girdiniz ve bu kayda kayıtlı olan bir mail adresine gönderim yapıyorsunuz. Sonra gönderdiğiniz mail bilgisini sisteminize “gönderildi” diye kaydetmek istiyorsunuz. Sonra bu kaydı yapabilmeniz için HttpContext ile o anki kullanıcı (sisteme giriş yapan kullanıcı, yani CurrentUser) bilgisini almak istiyorsunuz. Bu bilginin mail gönderim sonrasında farklı çalışma zamanına ait olduğundan null olduğunu farketttik.

Nasıl çözdük?

1 — aspnet:UseTaskFriendlySynchronizationContext

Sistemi zaman uyumsuz yapan, ilk bulduğumuz yöntem olarak “aspnet:UseTaskFriendlySynchronizationContext” config ayarını ekledik. Aslında bu sorunumuzu çözdü. Tabi bunu kullanmaktan vazgeçtik. Çünkü başka sorunlar çıktı.

Öncelikle hata logumuz şöyle;

HandleError recovered from error: System.InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async=”true” %>. This exception may also indicate an attempt to call an “async void” method, which is generally unsupported within ASP.NET request processing. Instead, the asynchronous method should return a Task, and the caller should await it.

Bu config ayarından sonra bizden şu isteniyor; ASP.NET zaman uyumsuz sayfaları, Async özniteliği "true" olarak ayarlanmış sayfa yönergesini içermelidir.

Ama biz bunu yapmak istemedik. Çünkü, ikinci yöntem daha tercih edilebilir geldi.

2 — HttpContext kullanmaktan vazgeçtik

Evet, diğer yöntemi testler ve zaman açısından tercih etmedik. Çünkü HttpContext’i kullanmadan CurrentUser bilgisini alabildik. Yani işin özeti, mail gönderim işlemi sırasında HttpContext kullanımını kaldırdık. Sisteme yeni bir özellik ekleyip test etmek yerine, risk almadan böyle bir yolu tercih ettik.

Not: Burada bir kaç tane fikir olması için link paylaşacağım. Yalnız bunları kullanmadığımızı bilmenizi isterim. Burada “aspnet:UseTaskFriendlySynchronizationContextsorunumuzu çözsede yukarıdaki bahsettiğim sorunla karşılaştık. Bundan dolayı mail gönderimi içinde “HttpContext” kullanmaktan vazgeçtik. Tekrar söylemem gerekirse fikriniz olması adına bu linkleri paylaşmak istiyorum.

--

--