Arda Çetinkaya Yazılım ve arada kendim ile ilgili karaladıklarım…

Blog’u uzun süredir takip edenler ya da az biraz tanışma fırsatı bulduğum kişiler Managed Extensibility Framework(MEF) ile ilk çıktığı günden beri haşır neşir olduğumu bilir. 🙂 Artık direkt .NET Framework’ün bir parçası olması belki farkındalığını biraz azalttı ama açıkcası .NET ile uygulama geliştirirken belli ihtiyaçları kolayca geliştirmek için güzel bir yapı.

MEF ne işe yarıyor, neler yapılabilir bunlara tekrar girmeyeceğim ama özet olması için;

  • Plug-in kavramı gibi ek parçalar ile uygulamalara yeni fonksiyonlar eklemek için ortaya çıkan bir yapı.
  • WPF, WinForms, ASP.NET gibi farklı uygulama tiplerinde kullanılabilmekte.
  • .NET Framework 4.0 ile beraber, .NET Framework’ün içine dahil edildi.
  • Uygulamanın bütünlüğünü çok değiştirmeden sadece fonkisyonları kolayca ekleyip, çıkarmak mümkün olabiliyor.

Daha önceleri paylaştığım yazılara da göz atmanızı tavsiye ederim. [MEF Yazıları]

Gelelim bu yazımızın konusuna; .NET Core’da Managed Extensibility Framework(MEF) kullanımı…

MEF, .NET Core’a Microsoft.Composition ile port edildi. Ama ne yazık ki tüm API’ları ile beraber edilmedi. Önemli bir kaç API port edilemese de, MEF’in karakteristik özelliklerini ve temel fonksiyonlarını sorunsuz bir şekilde .NET Core’da kullanabiliyoruz.

Port edilen API’lar sadece System.Composition namespace’indeki API’lar. Dolayısıyla MEF’deki en sevdiğim özelliklerden, kataloglar; AggregateCatalog, ApplicationCatalog, AssemblyCatalog .NET Core tarafında kullanılır durumda değil. Dolayısıyla MEF kullandığınız bir uygulamanızı .NET Core’a port etme çalışması içerisindeyseniz, MEF kısımlarında bir kaç düzenleme yapmanız gerekebilir.

Ayrıca sağlıklı MEF’i sağlıklı kullanmak için Microsoft.Composition 1.0.31 min. versiyon olacak şekilde kullanmanızı tavsiye ederim. Bir kaç sinir bozucu hata giderilmiş durumda. NuGet‘den versiyonları takip edebilirsiniz…

Neyse kafanızı yeteri kadar karıştırdığıma göre örnek kodlara geçelim…

Öncelikle MEF bileşeni olarak “export” etmek istediğimiz sınıflar için .NET Framework’de temel olarak ne yapıyorsak, .NET Core’da da devam.

Önce bir interface oluşturup, bu interface ile sınıfımızı geliştirip, [Export] özelliği ile MEF için dışarıya aktarılacak bir sınıf olduğunu belirtmemiz gerekmekte.


namespace NotifyMe.Common
{
    public interface IBaseTemplate
    {
        string Create(string message, string from, string image);
    }
}

//Export edeceğimiz sınıfların ayrı bir assembly'de olması,
//MEF'in genişletilebilir özellikleri sunması açısından doğru olacaktır.
namespace NotifyMe.Templates
{
    [Export("ChatMessage",typeof(IBaseTemplate))]
    public class ChatMessageBaseTemplate : IBaseTemplate
    {
        public string Create(string message, string from, string image)
        {
            //Burada takla atalım, zıplayalım, uçalım...
        }
    }
}

MEF’in tak-çıkart yaklaşımı ile fonksiyonları kullanma yaklaşımdan dolayı, tüm [Export]’ların ayrı assembly’ler yani ayrı projeler olmasına dikkat edin.

Şimdi de export ettiğimiz bileşenleri ana uygulamamızda nasıl alırız buna bakalım. .NET Core tarafında tüm katalogları kullanamadığımız için ContainerConfiguration sınıfı ile MEF bileşenleri için bir katalog oluşturup, bunun üzerinden bileşenleri alıyoruz.

Aşağıdaki örnek bir web uygulamasından(ASP.NET Core); örnekteki uygulamada root dizininde Plugins diye bir klasör var. Bu klasöre konan *.dll’ler, ana web uygulaması tarafından kontrol edilip yüklenmekte. Daha sonra _templates şeklinde bir liste üzerinden erişilebilir duruma geçiyor.Bu sayede bir ASP.NET Core uygulamasında herhangi bir değişiklik yapmadan sadece belli bir dizine(Plugins) ABC.dll’ini koyup belli fonksiyonları çalıştırmak mümkün. Eğer bu fonksiyonu değiştirmek gerekiyorsa, ana uygulamada bir değişiklik yapmadan ABC.dll’i yerine XYZ.dll’ini koyarak fonksiyonu değiştirmek oldukça kolay.


        [ImportMany]
        private IEnumerable<IBaseTemplate> _templates { get; set; }
        public MessageService(IHostingEnvironment hosting, ILogger<MessageService> logger)
        {
            _logger = logger;

            try
            {

                
                var rootPath = hosting.ContentRootPath;
                var path = Path.Combine(rootPath, "Plugins");
                var assemblies = Directory.GetFiles(path, "*.dll", SearchOption.AllDirectories)
                            .Select(AssemblyLoadContext.Default.LoadFromAssemblyPath)
                            .ToList();
                var pluginContainer = new ContainerConfiguration().WithAssemblies(assemblies);
                using (var container = pluginContainer.CreateContainer())
                {
                    _templates = container.GetExports<IBaseTemplate>("ChatMessage");
                }
            }
            catch (System.IO.DirectoryNotFoundException dnfe)
            {
                _logger.LogError(dnfe.Message);
            }
            catch (System.Exception ex)
            {
                _logger.LogError($"Unable to find message templates. {ex.Message}");
            }
        }

Hızlıca ve kısa oldu ama .NET Core tarafında da MEF API’larının kullanılabilir olduğunun farkındalığı bir çok ihtiyacınızı çok rahat bir şekilde karşılamanız için yol gösterecektir. Özellikle .NET Core’un “dependency injection” desteği ile MEF’in gücü birleştiği zaman ortaya güzel şeyler çıkıyor.

Office 365 kullanımı arttıkça yeni ihtiyaçlar ve özellikler ortaya çıkıyor. Microsoft Graph da bu ihtiyaçların sağlanması için ortaya çıkmış bir API platformu olarak yaklaşık 2-3 sene önce karşımıza çıktı. İlk çıktığında fiziksel olarak Office 365 sınırları içinde lanse edilse de, geçen sene Microsoft 365 konsepti ile; Office 365, Windows 10 ve mobil-güvenlik servislerini bir arada geliştirebilmek için gelecek vaat etti. Geçen sene Build 2017’de belli oturumlarda gösterilen demolar ile güzel heyecanlar yaşatmıştı…

Graph API

Office 365 kullanıyorsanız, platformun bir çok özelliğine Microsoft Graph API’ları ile erişmek mümkün. Bu sayede geliştirdiğiniz çözümler ile Office 365 çözümlerini entegre etmek ya da Office 365 ürünlerine eklentiler geliştirmek mümkün. Office 365’deki e-mail, OneDrive, OneNote, kullanıcı grupları, Planner, takvim vs. ürünlerindeki çeşitli özellikleri Graph API’ları ile kullanmak bir çok kapı açıyor. Office 365 API’ları ile bunları yapmak mümkün olabiliyordu kısmen, ama artık tek bir platformda Microsoft Graph çatısı altında ihtiyaçları sağlamak daha doğru ve geçerli. Ben demiyorum, Microsoft diyor… 🙂

Microsoft Graph API’ları özetle;

  • Azure AD
  • Outlook(E-Mail, Takvim, Kişiler, vs.)
  • OneDrive
  • SharePoint
  • OneNote
  • Planner
  • Yammer
  • Excel
  • Microsoft Teams
  • Intune
  • Azure AD Identity Protection
  • Azure Security Center

ürünlerinin çeşitli özelliklerine API olarak erişmek için güzel bir platform olarak karşımıza. Bu platformdaki API’lar REST ile sunulan bir yapıya sahip. Dolayısıyla bir çok farklı geliştirme teknolojisi; .NET Framework, PHP, Python, Swift(iOS), Objective-C(iOS), Android, Ruby, Node.js gibi, bu API’ları kullanmak mümkün.

Graph API

Yukarda da dediğim gibi geliştirdiğimiz çözümlere bu ürünlerdeki özellikleri katmak ya da bu ürünlere eklentiler geliştirmek bir çok kapıyı açıyor.

Daha önce GitHubda bir PoC çalışması için bir şeyler karalamıştım. Bunun üzerinden kurcalayıp siz de farklı API’lar ile farklı ihtiyaçlarınızı karşılayabilirsiniz. Orda basit olarak bir Office 365’deki Outlook e-mail hesabına gelen e-mail’leri filtreleyip belli bir operasyon gerçekleştirmenin senaryosu vardı. Kurumsal bir çok firmada, belli süreçleri otomatikleştirmek için tercih edilen bir senaryo, tanıdık gelmiştir. 🙂 🙂

Microsoft Graph API’ları ile bunu gerçekleştirmek, gelen bir e-mail’i parse edip, içeriğine göre Planner’da bir görev yaratmak, daha sonra e-mail’in eklerini OneDrive’a kaydedip kendi geliştirdiğiniz ERP uygulamasında süreç başlatmak gibi senaryolar oldukça kolay. Diğer API’lar ile daha kompleks ihtiyaçları kolay bir şekilde gerçekleştirebiliyoruz.

Microsoft Graph farkındalığı ve API’lara giriş için umarım biraz olsun kıvılcım yakar. Şimdilik bu kadar. Merak ettiğiniz, takıldığınız noktalar olursa; biliyorsunuz… 😉

Bu arada daha derinlere girmek için aşağıdaki kaynakları şiddetle öneririm.

Şu dönem .NET Core 2.2-Preview-1 ile gelişmeye devam eden ASP.NET Core, direkt framework içinde yerleşik olarak gelen “Dependency Injection(DI)” yaklaşımı bir çok açıdan kolaylıklar sağlıyor. Belli yapıları, geliştirilen uygulamalara enjekte etmek, uygulama fonksiyonlarının bağımlılıklarını sağlıklı yönetmek ve “seperation of concerns” prensibine sadık kalabilmek için DI oldukça gerekli. ASP.NET Core ile bunun yerleşik olarak sunulması, bağımlılıkların karmaşaya dönüşmesine uygun olan web uygulamaları için oldukça faydalı ve kolay bir özellik.

Bu kısa girişten sonra asıl konumuza gelelim; View ya da PageModel’larda(Razor Pages) bir bağlılığı nasıl kullanırız? Yani bir başka değişle *.cshtml’lerde bağımlılıkları nasıl kullanırız…

Enjekte ediyoruz…

ASP.NET Core’da Razor sayfalarda ya da View’larda @inject yöntemi ile bağımlılıkları sayfaların ön yüz tarafında kolay bir şekilde enjekte edebiliyoruz. Bu şekilde bir sayfada gösterilecek “data”’yı ASP.NET Core uygulamasına bağladğımız bir servis ile gösterebiliyoruz. Ne amaçla kullanabileceğimizden bahsetmek daha anlaşılır olmasını sağlayacak sanırım. Razor sayfası ya da View’de bir HTML “unordered list(<ul>)” elemanının içeriğini doldurma ihtiyacımız olsun ya da bir <select> elemanını… Bunun için View’e enjekte ettiğimiz bir servisi kullanarak kolaylıkla doldurabiliriz. Bunu tabi ki “Model” ya da “ViewData” ile yapmak da mümkün.Ama şöyle bir senaryonuz olduğunu düşünün; “multi-culture” bir uygulamanız var ve dil özelliklerine göre Model ya da ViewData’dan gelen veriler dil seçeneğine göre göstermeniz gerekiyor. 1 şeklinde gelen veriyi; “Yes, Ja, Si, Evet” şeklinde göstermek gibi. Bunun için dil değerlerini ViewData’ya gömmek yerine enjekte edilen farklı bir servis ile UI tarafında göstermek daha doğru olacaktır. Bu şekilde “seperation of concerns” prensibi de sağlanmış olur.

Peki bunu nasıl yapıyoruz…

Öncelikle bir servis oluşturmamız gerekiyor. Servisi burada, public olarak metotları olan bir sınıfı olarak düşünün. Public bir sınıfın, public metotları ile veri sağlayan bir yapı…

    public class VisitorService
    {
        private IServiceProvider _provider;
        private NotifyDbContext _db;
        public VisitorService(IServiceProvider provider, IConfiguration configuration)
        {
            _provider = provider;
            _db = (NotifyDbContext)_provider.GetService(typeof(NotifyDbContext));
        }
        public int GetActiveVisitorCount() => _db.Connections.Where(c => c.Connected).Count();
    }

Bu servisi, ASP.NET Core uygulamasının servislerini tanımladığımız yerde uygulamaya eklememiz gerekiyor.

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<VisitorService>();
            ....
            ..
            .
        }

ASP.NET Core’un içinde yerleşik olarak gelen DI yaklaşımının kilit noktası burası. Burada önemli olan 3 farklı ekleme yöntemi var.

  • .AddTransient<TService>(): Her request için yeni bir instance yaratırılır ve kullanılır.
  • .AddSingleton<TService>(): İlk request ile yaratılan, tek bir instance kullanılır.
  • .AddScoped<TService>(): Belli bir kapsam(web request) içinde her request de yaratılan instance kullanılır.

Servis tanımlarını bu şekilde yaparak, TService tipinde oluşturulan servis nesnesinin yaşam süresini belirtiyoruz. Bunların ayrıntılarına çok girmeyeceğim şimdi. Bu şekilde yaşam süresini belirttiğimiz servis nasıl View’larımızda kullanırız buna hızlıca bakalım.

*.cshtml sayfamızda @inject anahtarını kullanarak önce servisi sayfamız için tanımlıyoruz.

Burada @inject {Servisin sınıf adı} {Instance adı} şeklinde bir kalıp kullanmak gerekiyor.

Daha sonra *.cshtml sayfasının içinde istenilen yerde @{Instance adı}.ServisMetodu şeklinde ilgili servisin metoduna ulaşmak mümkün.

@using Microsoft.AspNetCore.Identity

@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@inject NotifyMe.Services.VisitorService VisitorService
@if (SignInManager.IsSignedIn(User))
{
    <a title="Active users count" asp-page="/Users">@VisitorService.GetActiveVisitorCount()</a>
    .....
    ..
    .
}

Bu kısa yazı ile hızlıca ASP.NET Core’da @inject ile bağımlılıkların *.cshtml tarafında nasıl yapılabileceğini görmüş olduk. Oldukça kolay ve hızlı bir şekilde belli ihtiyaçları hızlıca karşılamak bu sayede mümkün. Bunun farkındalığında olmak ve gerektiği yerde, olması gerektiği gibi kullanmak uygulamanızın kalitesini arttıracaktır; kesin bilgi, yayalım… 🙂 🙂

Güncelleme: Bu yazıyı yayınladıktan sonra Visual Studio Team Services(VSTS), Azure DevOps olarak bir isim değişikliğine uğradı. Yazıdaki bahsetmeye çalıştığım her konu hala geçerli.

Açık kaynak projelerin kod deposu olarak hayatımıza giren GitHub, yazılım teknolojilerinin olmazsa olmaz bir parçası oldu artık. Benzer şekilde Visual Studio Team Services(VSTS)’de uçtan uça bir kod geliştirme alt yapısı sunduğu ve bulut servisi olmasından dolayı da oldukça tercih edilen bir platform. Özellikle Team Foundation Server(TFS) ile bir şekilde haşır neşir olanların bir sonraki basamağı…Neyse şimdi VSTS’in ayrıntılarına girmeyelim, çıkamayız.

Bu yazıda basit bir senaryo ile GitHub’da kodlarını barındırdığımız bir projeyi, VSTS üzerindeki bir “build-relase” adımları ile Azure’da ki bir App Service’e dağıtımını yapacağız. Bir nevi Continuous Deployment(CD) sürecini basitçe bir VSTS üzerinde oluşturacağımız bir pipeline ile gerçekleştireceğiz. Anahtar kelimeleri de paylaştığıma göre başlayabilirim 🙂

Bu arada bu bahsetmiş olduğum senaryoyu bu yazıyı yazdığımdan daha kısa sürede gerçekleştirdim. Artık kullanılan sistem ve platformların etkin ve kolay olması bir çok ihtiyacta kurtarıcı oluyor. DevOps yaklaşımı anlamak, farklı sistemler ile bir uygulamayı nasıl belli platformlara dağıtabiliriz ön ayak olması için umarım faydalı olur.

Build…

Bu senaryoda GitHub’daki Notify.Me projemdeki kod repository’sini kullanacağım.(Belli bir PoC çalışması için yapılan, ASP.NET Core ile SignalR projesi) Zaten bir kod kaynağımız olduğu için ilk iş olarak VSTS’de Build adımını gerçekleştirmek. VSTS’deki menüdeki “Build and release” başlığından “Builds” menüsüne giriyoruz.

Haaaa; bu arada, bu yazıdaki ekran görüntüleri sizin VSTS hesabınızdan farklı ise, sağ üst köşedeki kullanıcınız üzerinden “Preview features”dan yeni görsel özellikleri açabilirsiniz.

“Builds” kısmında kod deposu olarak seçebileceğimiz seçenekleri göreceksiniz. Burada oldukça geniş bir kümeye sahibiz; Team Foundation Version Control(TFVC), GitHub, Bitbucket, Subversion. Yok yok… 🙂

Biz GitHub’ı seçiyoruz tabi ki. GitHub’ı seçince, GitHub hesabımız ile VSTS’i ilişkilendirmek için Login olmamız ve sonrasında gerekli izinleri vermemiz gerekiyor. Ekranların yönlendirmesi ile kolayca bunları yaptıktan sonra, GitHub’daki hangi projemizi derlemek istiyorsak onu seçiyoruz. Projemizde farklı branch’ler varsa, derleme işlemi için onu da belirtiyoruz.

Daha sonra gerek duyduğumuz derleme şablonunu seçiyoruz. Bu şablonlar derleme aşaması için genel kullanımlarda gerek duyulan adımların oluşturulduğu hazır şablonlar. Bu şablonlardan birini seçmek yerine direkt olarak boş bir şablon oluşturup, adımları kendiniz ekleyebilirsiniz.

Notify.Me bir ASP.NET Core 2.x uygulaması olduğu için, burda ASP.NET Core şablonunu seçiyorum. Daha sonra seçtiğim derleme şablonuna göre belli adımlar karşımıza çıkıyor. Bu ekranda, bu adımların ayarlarını düzenlemek yetiyor. Hangi komutların çalışacağı, hangi projelerin build olacağını gibi ayarlar…

Açıkcası burada ekranlar oldukça kolay ve güzel oluşturulmuş diye düşünüyorum. Takıldığınız bir adım olursa onun çeşitli label’ları ile adım hakkında bilgi ve hatta ayrıntılı ek bilginin linki oluyor.

Bu senaryoda seçtiğim GitHub repository’sinde hangi projeyi build edeceğimi ayarlıyorum, ayrıca Restore ve Test görevlerini de disable ediyorum. İstediğimiz adımları enable/disable edip “Build Pipeline”nında çeşitli durumları deneyebiliriz. Komple de kaldırabiliriz tabi ki. Hatta isterseniz farklı görevler(task) ekleyerek build aksiyonlarını zenginleştirebilirsiniz. VSTS’in mevcut taskları dışında, Visual Studio Marketplace’den de farklı task’ları kullanabilirsiniz. Tek tek bunlardan bahsetmeyeceğim, -ki edemem zaten,zamanımız yetmez 🙂 Ama oldukça zengin bir görev kümesi mevcut.

“Build” işleminin nasıl tetikleneceğini de derleme ayarlarındaki “Triggers” altından ayarlamak mümkün. GitHub’daki projeye her bir commit‘de build adımını tetikleyebiliyorum bu şekilde. Sadece belli branch’lere yapılan commit’leri ya da GitHub’daki “Pull Request”lere göre de tetiklemek mümkün.

Devam…

İnsanoğlunun zaman ilerledikçe bazı aksiyonları almakta ve değişikliklere adapte olmakta zorlanması gibi, “yaşlanmak” dediğimiz gerçekler, yazılım uygulamaları ve çözümleri içinde geçerli. Yazılım çözümlerinin çok yaygın olduğu bir çağda yaşadığımız ve etrafımızın yazılımlar ile çevrili olmasından dolayı bu “yaşlanan” yazılım çözümleri ile daha sık karşılaşıyoruz.

Yaşlanan yazılım uygulamaları için genellikle yazılım dünyası insanları olarak “Legacy” tabirini kullanırız bildiğiniz gibi. Biraz daha net anlamı anlamak için; “Günün şartlarına uymayan uygulamalar ve çözümlere “Legacy” uygulamalar diyoruz” diyebilirim. 10 yıl önce, hatta daha da önce geliştirilen ve hala kullanılan, ama artık negatif yönlerinin ağır bastığı, belli ihtiyaçları karşılamasının zor olduğu “Legacy” uygulamalar eminim bir çok kişinin karşısına çıkıyordur.

“Legacy” uygulamalar günün şartlarına uymamaları, adaptasyon güçlükleri yaşatmalarından dolayı oldukça zorlayıcı uygulamalar olabilir. Günün şartlarına uymamaları derken, atıl kalan donanımlar, desteklenmeyen işletim sistemleri, veri saklama sistemleri, değişim ihtiyaçları, performans durumları, kullanım şekillerinin çeşitliliği vs. bir çok başlık sayabilirim ama şimdi bunlara girmiyoruz.

Windows NT’den nerelere geldik 🙂

Windows NT üzerinde çalışan, PHP 3.0 ile geliştirilmiş, WEB bileşenleri sadece Internet Explorer’da düzgün çalışan, RDBMS olarak SQL Server 7.0 kullanan bir uygulamanın günümüzde hala yoğun bir şekilde kullanıldığını düşünün…Günümüze gelene kadar alt yapı olarak geliştirmelerin ve upgrade’lerin riskleri, iş ihtiyaçlarının en önemli olduğu algısı ve en önemlisi de insan faktörü yüzünden uygulama bu zamanın şartlarına uymayan bir çözüm olarak karşımızda…

Çok uç bileşenler ile bir örnekleri oldu ama benzer senaryolar var.

Bu tarz uygulamalar ne kadar zorlayıcı da olsa, günün IT çalışanları tarafından “offf poff” şeklinde yaklaşılıp, ne kadar çok negatif şey söylense de (bu arada hepsi genelde doğrudur), unutulmaması gereken çok önemli bir nokta var; “Değer”.

“Legacy” dediğimiz uygulamalar, sistemler öyle önemli bir değer yaratmışlar ki hala günümüze denk yaşayabilmişlerdir demek ki. 10, 15, 20 yıldır ayakta olan uygulamaları düşünün; ilk kullanıldıkları dönemlerden beri insanlara ve belli iş modellerine o kadar çok değer katıyorlar ki, hala o değeri koruma mücadelesinde içindeler… Biraz daha toparlamak gerekirse “Legacy” diye adlandırdığımız uygulamalar, ne kadar çok negatif bileşenle günümüze gelse de sahip oldukları “değer” çok önemlidir… Ve bu değer uygulamaların en güçlü özelliğidir.

Bir uygulamanın “Legacy” olarak adlandırılmaması, 10 yıl sonra da günün ihtiyaçlarını kolaylıkla karşılayabilmesi, bir 10 yıl sonra kattığı değerin üstüne yeni değerleri koyabilmesi için neler yapılması lazım, nasıl geliştirilmesi lazım falan filan bunlardan şimdi bahsetmeyeceğim… Derya deniz konular çünkü…15-20 yıl önce bir uygulamayı değişime hazır bir şekilde geliştirmek, bulunduğu zamanın ihtiyaçlarına uygun geliştirmek pek mümkün değildi belki, ama günümüzdeki yaklaşımlar yazılımların ömürlerini biraz daha kaliteli kılmayı sağlıyor. Arada karalarım ama bir şeyler.

Devam…