Anasayfa » Teknoloji » ASP.NET Web Form Legacy ortamlarda Dependency Injection kullanımı

ASP.NET Web Form Legacy ortamlarda Dependency Injection kullanımı

Dependency Injection yazılım dünyasında epey tanınan bir kavramdır. Bağımlılıkların injection prosedürüyle bir IoC container’dan alınıp ilgili ortamlara aktarılmasını temel alır. Örneğin bir Aspx sayfada gereksiniminiz olan şey HttpClient yada SqlConnection olabilir. Bunu her kezinde new etmek ve sonrasında Dispose davetini yapmak ekstra kod demektir. Kimi durumlarda da ismi üstünde legacy dediğimiz eski bir uygulamanın bakımı veyahut güzelleştirmesini yapıyor olabilirsiniz. Bu üzere durumlarda dependency injection yapısının kurgulanması projenin toplanması ve kaynakların aktif kullanımını sağlayabilir.

Dependency Injection legacy web form projelerde 2 kısımda inceleyeceğiz. property injection (service locator pattern) ve constructor injection.

Her iki yapımızda da .Net (örneğin 5.0,6.0) Microsoft.Extensions.DependencyInjection paketini kullanacağız. Çalışmayı yaptığımız .Net Framework 4.7.2 versiyonudur.

Öncelikle Nuget marifetiyle ilgili paketi kuralım.

Kurulum sonrasında projemizde Küresel.asax.cs içerisinde Application_Start metodunda servislerimizi ekleyelim.

public class Küresel : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            // Code that runs on application startup
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            ConfigureServices(); //dependency düzenlemesinin etkinleştirilmesi
            //Constructor injection için
            //HttpRuntime.WebObjectActivator = new ServiceLocator();
        }

        public static IServiceProvider ServiceProvider { get; set; }

        private void ConfigureServices()
        {
            IServiceCollection collection = new ServiceCollection();
            collection.AddTransient<ILogService, LogService>(); //servisleriniz örneğin ILogService

            //http client için "Microsoft.Extensions.Http" paketi eklenmelidir
            collection.AddTransient<ApiHttpClientMessageHandler>();
            collection.AddHttpClient("ApiHttpclient", options =>
            {
                options.BaseAddress = new Uri(ConfigurationManager.AppSettings["ApiBaseUrl"]);
            }).AddHttpMessageHandler<ApiHttpClientMessageHandler>();

            ServiceProvider = collection.BuildServiceProvider();
        }
    }

Eğer burada olduğu üzere HttpClient eklemeyi ve kullanmayı düşünüyorsanız Microsoft.Extensions.Http paketini de kurmalısınız. Nuget ekran manzarası:

Örnekteki BaseAddress konfigurasyonu da web.config de şu formdadır.

<appSettings>
    <add key="ApiBaseUrl" value="http://localhost/api"/>
</appSettings>

Unutmadan HttpClient için eklediğim MessageHandler ise şu halde:

public class ApiHttpClientMessageHandler : DelegatingHandler
   {
       protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
       {
           //belki headere token ekliyoruzdur.
           request.Headers.Authorization = new AuthenticationHeaderValue(scheme: "Bearer", parameter: "token-from-session");
           return base.SendAsync(request, cancellationToken);
       }
   }

Kaldığımız yerden devam edecek olursak, Küresel.asax.cs içerisinde Application_Start metodumuzda configure ettiğimiz service provider ‘ımızı kullanmak için neler yapabiliriz buna gelelim.

Öncelikle Küresel altında static tarifli ServiceProvider isimli provider’e kodlarımızdan rastgele bir yerden erişebiliriz. Lakin eriştiğimizde uygulamanın genelinde kullanılan singleton, transient ve scoped yapılar oluşturulacaktır ve uygulama kapanana kadar Dispose edilmezler. Garbage Collector onları temizleyene kadar hafızada bulunacaklardır ve kaynakları gereksiz meşgul edeceklerdir.

Bunun önüne geçmek için Request boyunca oluşan ve resolve süreci yapan bir service scope oluşturmalıyız. Bunun için ServiceLocator isimli bir class hazırlayacağız. Kod örneğinde sunuyorum.

public class ServiceLocator : IServiceProvider
   {
       public static T GetService<T>()
       {
           var scope = GetLifeTimeScop();
           return scope.ServiceProvider.GetService<T>();
       }

       public static T GetRequiredService<T>()
       {
           var scope = GetLifeTimeScop();
           return scope.ServiceProvider.GetRequiredService<T>();
       }
       public object GetService(Type serviceType)
       {
           try
           {
               IServiceScope lifetimeScope = GetLifeTimeScop();         
               //önce service provider yahut public create instance denenir
               return ActivatorUtilities.GetServiceOrCreateInstance(lifetimeScope == null ? Küresel.ServiceProvider : lifetimeScope.ServiceProvider, serviceType);
           }
           catch (InvalidOperationException)
           {
               //yoksa rastgele bir constructor ile oluşturma denenir.
               return Activator.CreateInstance(serviceType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, null, null);
           }
       }

       private static IServiceScope GetLifeTimeScop()
       {
           var currentHttpContext = HttpContext.Current;
           if (currentHttpContext != null)
           {
               var lifetimeScope = (IServiceScope)currentHttpContext.Items[typeof(IServiceScope)];
               if (lifetimeScope == null)
               {
                   void CleanScope(object sender, EventArgs args)
                   {
                       if (sender is HttpApplication application)
                       {
                           application.RequestCompleted -= CleanScope;
                           lifetimeScope.Dispose();
                           Debug.WriteLine("Clean");
                       }
                   }

                   lifetimeScope = Küresel.ServiceProvider.CreateScope();
                   currentHttpContext.Items.Add(typeof(IServiceScope), lifetimeScope);
                   currentHttpContext.ApplicationInstance.RequestCompleted += CleanScope;
                   Debug.WriteLine("Create");
               }

               return lifetimeScope;
           }

           return null;
       }
   }

ServiceLocator’de GetService ve GetRequiredService metodlarımız static dikkat ederseniz. Bunun nedeni bu metodları direkt olarak kullanacak olmamızdandır. Property injection sürecini bu metodlarla yapacağız. Öbür object dönüşü yapan GetService metodumuzu ise constructor injection için kullanılacaktır.

Gelelim Aspx sayfamızda hazırlığımızın meyvelerini toplamaya..

public partial class _Default : Page
   {
       protected void Page_Load(object sender, EventArgs e)
       {
           var s1 = ServiceLocator.GetService<ILogService>();
           var s2 = ServiceLocator.GetService<ILogService>();
           s1.Log("test1");
           s2.Log("test2");
       }

       [WebMethod(enableSession: true)]
       public static List<User> GetUsers()
       {
           var factory = ServiceLocator.GetRequiredService<IHttpClientFactory>();
           using (var client = factory.CreateClient("ApiHttpclient"))
           {
               var result = client.GetAsync("/ListUsers").GetAwaiter().GetResult();
               var veri = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
               return JsonConvert.DeserializeObject<List<User>>(data);
           }
       }
   }

Sayfamıza giriş yapıldığında Page_Load’da s1 ve s2 isminde 2 ILogService üretilmektedir. Bu servislerin oluşturulması, kullanımı ve dispose sürecini Debug Output ekranında kendilerine guid tanımlayarak yazdırdım. Create edilen servisler, log olarak test sözünü yazdıktan sonra request sonunda dispose olmaktadır.

Sayfa kodunda gördüğünüz WebMethod attribute ile verilmiş GetUsers metodu ise örneğin bir jquery istemcisi ile Default.aspx/GetUsers formunda çağrılabilir. Bu kullanımın ayrıntısına bahsimiz olmadığı için girmiyorum.

Property injection yahut Service Locator pattern kullanımını gerçekleştirdik.

Constructor injection için ise Küresel.asax.cs belgemizde Application_Start güncelleyerek WebObjectActivator instance vermemiz gerekiyor. Bu instance ServiceLocator’dan diğeri değildir. Küresel.asax.cs evrakımızı şu halde güncelleyelim.

void Application_Start(object sender, EventArgs e)
 {
     // Code that runs on application startup
     RouteConfig.RegisterRoutes(RouteTable.Routes);
     BundleConfig.RegisterBundles(BundleTable.Bundles);
     ConfigureServices(); //dependency düzenlemesinin etkinleştirilmesi
     //Constructor injection için
     HttpRuntime.WebObjectActivator = new ServiceLocator();
 }

Artık Aspx sayfamıza Constructor ekleme vakti gelmiştir. Aşağıdaki kod bloğunda görebilirsiniz.

public partial class _Default : Page
 {
     private readonly ILogService logService;
     
     //Dependency injection yoluyla alması sağlandı
     public _Default(ILogService logService)
     {
         this.logService = logService;
     }
     protected void Page_Load(object sender, EventArgs e)
     {
         var s1 = ServiceLocator.GetService<ILogService>();
         var s2 = ServiceLocator.GetService<ILogService>();
         s1.Log("test1");
         s2.Log("test2");
         logService.Log("constructor injection test");
     }

     [WebMethod(enableSession: true)]
     public static List<User> GetUsers()
     {
         var factory = ServiceLocator.GetRequiredService<IHttpClientFactory>();
         using (var client = factory.CreateClient("ApiHttpclient"))
         {
             var result = client.GetAsync("/ListUsers").GetAwaiter().GetResult();
             var veri = result.Content.ReadAsStringAsync().GetAwaiter().GetResult();
             return JsonConvert.DeserializeObject<List<User>>(data);
         }
     }
 }

Sayfayı yüklediğimizde Debug çıktısı şu biçimdedir.

Gördüğünüz üzere constructor injection ile gelen servisin test iletisi görünmektedir. Öbür servisler de kodda durduğu için onların bildirisi da görünüyor.

.NET 4.7.2 Web Form, Mvc ve Web Api (Legacy) Projelerinde başka ayrı implemente ettiğim Dependency Injection (Microsoft DI) örneklerine github repo‘dan ulaşabilirsiniz.

Hayırlı Ramazanlar..

İçeriği Oyla

Yorum yapın