cub-e.net

just coding...

Hadi Plug-in Yazalim

Plug-in’ler hakkında temel bilgileri öğrendiğimize gore artik plug-in yazabiliriz. Asagida vereceğim orneklerde bir plug-in içerisinde yapabileceğiniz temel işlemleri anlatmaya calisacagim. Bu kodlara CRM SDK\SampleCode\CS\Plug-ins içerisinden ulaşabilirsiniz.

Veritabanina gitmeden kayitlari değiştirmek

Daha once de ifade ettiğim gibi CRM içerisinde bir kayit veritabanina gitmeden Pre-Operation(Pre-Event) adiminda kaydettiğiniz bir plug-in ile kullanicinin oluşturmak istediği kayda ulaşabilirsiniz.

Asagidaki kod oluşturulan bir account(firma) nesnesinin içerisine eger yok ise bir numara oluşturarak bunu accountnumber(müşteri numarasi) alanina vermekte böylece kayitta olmayan bir alan veritabanina bu alan eklenmiş bir sekilde gidecek.

Kodda da görebileceğiniz uzere ilk once Execute metodumuzu oluşturuyoruz. Biliyorsunuz ki bu metod parametre olarak içine aldigi ServiceProvider ile bize ihtiyacimiz olan butun verileri sunacak.

Ilk once Context’i ServiceProvider’dan türetiyoruz. Daha sonra da Context içerisindeki Target’in bir Entity mi olup olmadigina bakıyoruz. Tam bu noktada gelin Context nesnesinin içerisine bir bakalim.

Asagidaki ekran goruntusunu bu plug-in’i debug ettiğim anda aldim. Ilerleyen bölümlerde bir plug-in’in nasil debug edileceğini anlatacagim. Simdilik Context’e odaklanalim.

Gorebileceginiz uzere Context UserId, BusinessUnitId, MessageName, PrimaryEntityName, CreatedOn gibi o anda isimize yarayacak birçok veri yiginini içermekte. Iste Plug-in içerisinde ihtiyacimiz olanlari buradan alip kullanacagiz.

Entity ise onu entity sinifindan bir nesne haline getiriyoruz.

Bu sefer bu oluşturduğumuz yeni entity nesnesi account turunden bir nesne midir diye bakıyoruz.

Daha sonra Icinde accountnumber diye bir alan var mi diye bakıyoruz. Yoksa iste tam bu noktada veritabanina doğru yolculuğa cikmis olan kullanicinin bu kaydına müdahale edip içerisine bizim urettigimiz numara ile accountnumber nesnesini doldurarak entity mize veriyoruz. Artik içerisinde accountnumber alani da var.

             /// <summary>

        /// A plug-in that auto generates an account number when an

        /// account is created.

             /// </summary>

        /// <remarks>Register this plug-in on the Create message, account entity,

        /// and pre-operation stage.

        /// </remarks>

        //<snippetAccountNumberPlugin2>

        public void Execute(IServiceProvider serviceProvider)

             {

            // Obtain the execution context from the service provider.

            Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)

                serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));

 

            // The InputParameters collection contains all the data passed in the message request.

                    if (context.InputParameters.Contains("Target") &&

                           context.InputParameters["Target"] is Entity)

            {

                // Obtain the target entity from the input parameters.

                           Entity entity = (Entity)context.InputParameters["Target"];

                //</snippetAccountNumberPlugin2>

 

                // Verify that the target entity represents an account.

                // If not, this plug-in was not registered correctly.

                if (entity.LogicalName == "account")

                {

                    // An accountnumber attribute should not already exist because

                    // it is system generated.

                                  if (entity.Attributes.Contains("accountnumber") == false)

                                  {

                        // Create a new accountnumber attribute, set its value, and add

                        // the attribute to the entity's attribute collection.

                                        Random rndgen = new Random();

                        entity.Attributes.Add("accountnumber", rndgen.Next().ToString());

                                  }

                                  else

                                  {

                                        // Throw an error, because account numbers must be system generated.

                        // Throwing an InvalidPluginExecutionException will cause the error message

                        // to be displayed in a dialog of the Web application.

                        throw new InvalidPluginExecutionException("The account number can only be set by the system.");

                                  }

                           }

                    }

             }

Umarim birsey dikkatinizi çekmiştir. Entity içerisine direkt alani Attributes.Add metodu ile ekliyoruz. Yani bir update işlemi yapmıyoruz zaten kayit daha veritabanina gitmedi doğal olarak CRM Service nesnesinin bir instance’ini olusturmamiza da gerek kalmadi.

Update aninda update edilmemiş değerlere ulaşmak

Baslik biraz karisik gelebilir ama aslinda tam olarak da durum bu update aninda update edilmemiş alanlara ulaşmak istiyorsaniz birazdan bahsedecegim yöntemi uygulamniz gerekmekte. Peki biz neye neden ulasamiyoruz diye soracak olursaniz aciklayayim. Dynamics CRM’in Create aninda kayit ile ilgili elde ettiği butun bilgileri bize Target’tan türettiğimiz entity içerisinde verir. Asagidaki ekran goruntusunde de bu durumu görebilirsiniz.

Update aninda durum bundan farkli sistem bize sadece (doğal olarak) update edilmiş alanlari vermektedir. Asagidaki ekran goruntusune bakabilirsiniz.

Ben bu contact üzerinde sadece jobtitle alanini güncelledim. Sistem jobtitle ve yaninda ihtiyaç duyulacak birkaç bilgiyi daha Target’a vermekte o kadar.

Simdi konu basligina dönecek olursak iste tam bu update aninda ben update edilmemiş bir alanin değerine ulaşmak istersem ne yaparim? Sistemde bunun için Image yani o kaydin o anki snapshot’ini almamizi sağlayan bir ozellik var.

Herhangi bir plug-in step’i üzerinde sag tuşa basarak “create new image” seçeneğini seçtiğimizde asagidaki ekran karsimiza gelecektir.

Bu ekranda Pre ve Post olarak istediğimiz alanlari parametre olarak seçebilir ve bunlara genel bir isim verebiliriz. Ben Target dedim.

Iste bu ayarlamayi yapdiginizda asagidaki ekran goruntusundeki gibi update aninda değişmeyen ama sizin erişmek istediğiniz alan/alanlar PreEntityImages içerisinde hazir olacaktır.

 

Peki kod tarafında buna nasil ulasacagiz derseniz o da su sekilde olacak;

Once image nesnesine ulaşıyoruz:

Entity image = (Entity)context.PreEntityImages["Target"];

Sonra image içerisinden istediğimiz alana erişiyoruz:

String descriptionMessage = "Old full name: " + image["fullname"];

 

Uzerinde calistigim nesnenin id’si nerede?

Update edilmis ya da post-operation durumdaki bir nesnenin id’sine ihtiyaç duyarsaniz su sekilde elde edebilirsiniz:

Guid id = new Guid(context.OutputParameters["id"].ToString());

Ya da

Guid id = context.PrimaryEntityId;

 

Plug-in’ler arasinda bilgi paylasimi

Eger bir plug-in içinde oluşturduğumuz bir veriyi diğer plug-in’lerin de erişmesini istiyorsak “SharedVariables” yapisini kullanmamiz gerekmekte. SharedVarabiles aslinda bir parametre kolleksiyonu ve içerisinde paylaşmak istediğiniz nesneleri saklayabilirsiniz.

 

Asagida 2 tane plug-in yer almakta. Ilk plug-in (PreEventPlugin)

context.SharedVariables.Add("PrimaryContact", (Object)contact.ToString());


Kodu ile Contact isimli Guid nesnesini “PrimaryContact” adinda SharedVariables içerisinde saklamakta.

 

Ikinci plug-in ise Post-event aninda calismakta ve SharedVariables’den istediği değeri örnekteki gibi almaktadır.

 

Boylece plug-inler arasi veri transferi ve yapilmis olmakta.

 

Guid contact = new Guid((string)context.SharedVariables["PrimaryContact"]);

 

 

    public class PreEventPlugin : IPlugin

    {

        public void Execute(IServiceProvider serviceProvider)

        {

            // Obtain the execution context from the service provider.

            Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)

                serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));

 

            // Create or retrieve some data that will be needed by the post event

            // plug-in. You could run a query, create an entity, or perform a calculation.

            //In this sample, the data to be passed to the post plug-in is

            // represented by a GUID.

            Guid contact = new Guid("{74882D5C-381A-4863-A5B9-B8604615C2D0}");

 

            // Pass the data to the post event plug-in in an execution context shared

            // variable named PrimaryContact.

            context.SharedVariables.Add("PrimaryContact", (Object)contact.ToString());

        }

    }

 

    public class PostEventPlugin : IPlugin

    {

        public void Execute(IServiceProvider serviceProvider)

        {

            // Obtain the execution context from the service provider.

            Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)

                serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));

 

            // Obtain the contact from the execution context shared variables.

            if (context.SharedVariables.Contains("PrimaryContact"))

            {

                Guid contact =

                    new Guid((string)context.SharedVariables["PrimaryContact"]);

 

                // Do something with the contact.

            }

        }

    }

Dynamics CRM 2015 Plug-in Mimarisi

Sizlere bugun Dynamics CRM icerisindeki Plug-in Mimarisinden soz etmek isitiyorum. Plug-in’ler IPlugin arayuzunden turetilmis kod parcaciklaridir ve CRM’in icerisinde belli bir sira icerisinde calisirlar. Kabaca tariff edersek bunlar birer .dll dosyalaridir ve CRM’e bu dosyaya ne zaman bakmasi gerektigini biz soyleriz. Plug-in’ler olay bazli olarak calisirlar. Yani herhangi bir kayit olusturuldugunda, guncellendiginde, silindiginde vb.. olaylar oldukca biz ilgili ayari yapmissak calisirlar.

Plug-in’lerin en güzel yani pre ve post olarak calismaya ayarlanabilmeleridir. Plug-in’ler olay bazli calisirlar demiştim iste bu olay olmadan önceki kaydin son hali üzerinden ve olay olduktan sonraki hali üzerinden işlem yapmaniza olanak sağlarlar.

Plug-in’lerin calismasi icin Microsoft.Xrm.Sdk.dll ve Microsoft.Crm.Sdk.Proxy.dll dosyalarinin referanslara eklenmis olmasi gerekmektedir. Tam yeri gelmisken bahsedeyim eger siz ucuncu parti bir .dll kullaniyorsaniz (yani kendi yazdiginiz siniflarin oldugu ya da diger uygulamalardan aldiginiz) bu .dll’lerin ilgili serverin assembly klasöründe olduğundan emin olun yoksa plug-in calismayabilir.

public class MyPlugin: IPlugin

{

    public void Execute(IServiceProvider serviceProvider)

    {}

}

 

Detaya inecek olursak IPlugin arayuzunden turerilmis bir sinif içerisinde Execute metodu yer almalidir. Bu metod parametre olarak IServiceProvider arayuzunden türetilmiş bir bilgi yiginini içerir. Yani CRM kod içerisinde yapacagimiz işlemlerde bize CRM içerisinde olan olaylardan bize bilgi tasir ki biz de bu bilgileri kodun içerisinde kullanalim. Ne gibi veriler içinde tasimakta derseniz cok fazla detay verebilirim mesela su anda hangi kullanicinin işlem yaptigi, tasidigi nesnenin turunu, eger pre-plugin ise değerlerin değişmeden önceki halini vb… bir cok veri içermekte.

Simdi sirasiyla gelen veri yiginlarini inceleyelim.

Plug-in Execution Context

Calisma zamaninda oluşan veriler bu yapi içerisinde yer almaktadır. Bunlara kodun calisma hiyerarşisi ve entity bilgileri de dahildir.

IPluginExecutionContext context = (IPluginExecutionContext)

    serviceProvider.GetService(typeof(IPluginExecutionContext));

Bir olay olduğunda kayit edilmiş bir plug-in’e bu veriler aktarılır aslinda o anda calisan butun plug-in’lere bu veriler aktarılır ama execution pipeline denen sralamaya uyarak aktarılır once pre sonra post pluginlere veri aktarılır. Hatta siz pre-plugin ile bir veriyi değiştirirseniz post-plugin’e o veri aktarılır.

Tabii burada yeri gelmişken bahsedeyim burada sozu edilen kodlarin sonsuz döngüye girmemeleri için sistem içerisinde Depth denen bir anahtar yer almaktadır. Varsayilanda bu bir plugin’i arda arda 8 kere calistirir ve durdurur. Boylece sistemin bir kod yanlisigi ile çökmesi engellenmiştir. Bu değer değiştirebilir bir değerdir.

Sistemin calismasi da aslinda su mantiga dayanmakta;



Yani Event Execution Pipeline’a bir mesaj girdiginde Pre-Event -> Platform Islemleri (Yani CRM’in kendi ic isleyisi) -> Post-Event seklinde islenmekte. Bu dongu senkron ve asenkron yapilar için böyle ilerlemekte.

Organization Servise Erisme

CRM içerisinde işlem yapabilmek her zaman bir servis nesnesine ihtiyaç duymaktayız. Iste kullanicinin hareketi neticesinde acilmiş bu servisi bize kullanmamiz için geçirmekte serviceProvider nesnesi.

IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

Notification Servise Erisme

Senkron olarak işaretlenmiş plug-in’ler Microsoft Azure Service Bus’a veri mesaj gondebilirler. IServiceEndpointNotificationService turunden olan bu bilgi Azure Service Bus’a gönderilir. Bu sayede Azure Service Bus içerisinde endpoint oluşturulmuş ve endpoint’i dinleyen 3. Parti bir servis ile iletişime geçebilmektedir.

Input ve Output Parametreleri

InputParameters nesnesi su anda yapilan hareketin yani su anda tetiklenmiş olayin bilgisini ve su anda üzerinde işlem görülen entity’nin bilgisini içerir. Bu bilgiye erişmek için “Target” nesnesine bakmamiz gerekmektedir ve bu nesneyi alip Entity class’ina çevirebiliriz. Input nesneleri Request message yapisindadir.

if (context.InputParameters.Contains("Target") &&

    context.InputParameters["Target"] is Entity)

{

    // Obtain the target entity from the input parameters.

    Entity entity = (Entity)context.InputParameters["Target"];

}

Fakat unutmaniz gereken bir nokta var her mesaj Entity nesnesini içermeyebilir. Ornegin DeleteRequest; Entity değil EntityReference dondurur.

if (context.InputParameters.Contains("Target") &&    context.InputParameters["Target"] is EntityReference)

{

    // Obtain the target entity from the input parameters.

    EntityReference entity = (EntityReference)context.InputParameters["Target"];

}

Benzer sekilde OutputParameters da Response message içerir. Ama sunu unutmayin ki senkron post-event ve asenkron plug-in’ler OutputParameters turunden nesneler içerirler.

Pre ve Post Entity Imajlari

Bu konuyu okurken sakin Ingilizce Images kelimesinin resim anlamiyla karistirmayin buradaki anlami verinin  o anki goruntusu seklinde ifade etmek daha doğru olur. Aslinda tam Ingilizce tabiriyle snapshot. PreEntityImages ve PostEntityImages verileri sistem tarafından size gönderilir ama siz ozellikle beklediğiniz alanlari plug-in’in kayit işlemi sirasinda sisteme soyleyebilrsiniz.

Burada tabii ki bir mantik çerçevesi olduğunu da unutmayin Create aninda bir nesnenin preImage’i olamayacagi gibi Delete işleminden sonra da bir postImage beklemeyin.

Simdi bu bilgileri verdikten sonra butun bunlari birleştirerek bir plug-in temel goruntusune bakalim.

Aciklamalar kodun içinde.

using System;

using System.ServiceModel;

using Microsoft.Xrm.Sdk;

 

public class MyPlugin: IPlugin

{

    public void Execute(IServiceProvider serviceProvider)

    {

        // Sandbox içerisinde calisan plug-in’ler TracingService’den yararlanabilirler.

        ITracingService tracingService =

            (ITracingService)serviceProvider.GetService(typeof(ITracingService));

 

        //Context’i elde ediyoruz.

        IPluginExecutionContext context = (IPluginExecutionContext)

            serviceProvider.GetService(typeof(IPluginExecutionContext));

 

        // InputParameters’dan gelen verileri aliyoruz

        if (context.InputParameters.Contains("Target") &&

            context.InputParameters["Target"] is Entity)

        {

            // Target ile entity’e erisiyoruz.

            Entity entity = (Entity)context.InputParameters["Target"];

 

            // Beklediginiz entity geldi mi diye kontrol ediyoruz.

            if (entity.LogicalName != "account")

                return;

 

            // CRM Servisi elde ediyoruz

            IOrganizationServiceFactory serviceFactory =

                (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

 

            try

            {

                / Iste buradan sonrasi size kalmis istediğiniz kodu yazabilirsiniz.

            }

 

            catch (FaultException<OrganizationServiceFault> ex)

            {

                throw new InvalidPluginExecutionException("An error occurred in MyPlug-in.", ex);

            }

 

            catch (Exception ex)

            {

                tracingService.Trace("MyPlugin: {0}", ex.ToString());

                throw;

            }

        }

    }

}