Bu yazımda sizlere Aspect Oriented Programming'dan bahsedeceğim. Aspect Oriented Programming Türkçeye Cephe Yönelimli Programlama olarak çeviriliyor. Neden ? Çünkü bu programlama mantığının temelinde Seperation of Concerns var yani Konuların Ayrımı var.
Okurken ne dinlemeli ?
Öyleyse bir terimi daha açıklayalım. Çoğu zaman yazdığımız kodlarda Loglama, Authorization , Exception Handling gibi işler gerekir ve kod içerisinde birbirine bağlı olarak yazarız bunları örneğin aşağıdaki kod parçasında Exception Handling, Logging, Authorization içi içe girmiş durumda :
public List<NewsItem> GetNews()
{
if(Utiliy.AuthorizedUser(UserID))
{
try
{
Logger.Log("Success");
return newsRepository.GetAllNews();
}
catch(Exception ex)
{
Logger.Log("Error",ex.Message);
throw BusinessException();
}
}
else
{
return null;
}
}
İşte biz bu duruma Cross-Cutting Concerns diyoruz. Yani birbirleri ile kesişen işlerimiz oluyorlar. Aşağıdaki görsel konuyu anlatıyor. Peki bunları nasıl ayıracağız? Ayırmak bize gerçekten kolaylık sağlar mı ?
İşte bağımlılıkları ayırma konusunda AOP (Aspect Oriented Programming) bize yardımcı oluyor ve Seperation of Concerns işlevini gerçekleyebiliyoruz. Bize sağladığı faydaları sayarsak : En başta kod tekrarından bizi kurtarıyor - Reusability , Projeye yeni başlayan geliştiriciler neyin nerede olduğunu daha kolay anlıyor ve müdahale edebiliyor. Readability.
AOP 'yi .Net ortamında üç şekilde gerçekleyebiliriz. İlk ve en kolayı ActionFilterAttribute özelliğini kullanmak, bu bize bazı noktalarda esneklik sağlamadığı için proje büyüklüğünüze göre Interceptor 'ı tercih etmenizi öneririm ama dediğim gibi projeye göre değişir. .Net Core ortamında ise artık Middleware var. ActionFilterAttribute 'nın kısıtları konusunu bu yazıdan okuyabilirsiniz.
Biz senaryomuzda bir .NET WebAPI oluşturacağız ve Castle Windsor yardımı ile Intreceptorlar yaratacağız. Örnek projemizi oluşturmaya başlayalım. Interceptor oluşturmak için başka paketlerde kullanabilirsiniz örneğin Unity gibi.
Web API projemizi aşağıdaki ekranlardaki gibi oluşturalım:
Şimdi projeyi API ,Bussiness ,Infastructure ve Logging olarak katmanlara ayıracağım bunun için belirttiğim isimlerde Solution 'a sağ tıklayarak Solution Folder Ekliyorum.
API projesinde yeni yarattığımız projemiz olsun bunun dışında Business klasörümüze aşağıdaki üç Class Library projemizi oluşturalım.
GothamNews.Contracts
GothamNews.Repositories
GothamNews.Services
Bunun için Klasöre sağ tıklayıp - Add -> New Project ve aşağıdaki seçimi yapalım.
Benzer şekilde Infrastructure klasörümüze:
GothamNews.DependencyResolvers
Logging klasörüne :
GothamNews.Logging projelerini oluşturalım.
Contracts projemizde API'nin gerek duyacağı modeller ve Interface'ler var, Repository bize istenen haberleri getirip Listeleyen proje , NewsService ise API Controller 'larının kullanacağı iş akşınının bulunduğu proje. Aşağıdaki gibi sınıf ve arayüzlerimizi oluşturalım.
namespace GothamNews.Repositories
{
public class NewsRepository : INewsRepository
{
public List<NewsItem> GetAllNews()
{
List<NewsItem> response = new List<NewsItem>();
response.Add(new NewsItem { Id=1, Title="News 1", Content="News 1 Content ", Thumbnail="www.something.com/1_5674.png",CreatedDate=DateTime.Now });
response.Add(new NewsItem { Id = 2, Title = "News 2", Content = "News 2 Content ", Thumbnail = "www.something.com/2_5478.png", CreatedDate = DateTime.Now });
return response;
}
}
}
namespace GothamNews.Services
{
public class NewsService :INewsService
{
private readonly INewsRepository newsRepository;
public NewsService()
{
this.newsRepository = new NewsRepository();
}
public List<NewsItem> GetNews()
{
return newsRepository.GetAllNews();
}
}
}
API 'de Controllers'a sağ tıklayıp yeni boş bir Controller Oluşturalım:
namespace GothamNews.Controllers
{
public class NewsController : ApiController
{
private readonly INewsService newsService;
public NewsController(INewsService _newsService)
{
newsService = _newsService;
}
// GET: News
[HttpGet]
public List<NewsItem> Get()
{
return newsService.GetNews();
}
}
}
Gördüğünüz gibi NewsController 'a bir Dependency Injection uyguluyoruz. Yani Controller 'lar genelde non parameters constructor'a sahiptirler ama bize Castle Windsor kütüphanesi sayesinde Constructor'a injection yapmış olacağız. CW kütüphanesi de aslında isteklerin arasına giren bir Proxy oluşturur bu sayede istek yapıldığında ve isteğe cevap dönüldüğünde araya girip istediğimiz Loglama ,Caching vb. cross-cutting concern'leri yapabiliyor olacağız. Ve işte bu iş için özelleşmiş olan Logging Interceptor 'u aşağıdaki gibi olacak.
namespace GothamNews.Logging
{
public class LoggingInterceptor:IInterceptor
{
public void
Intercept(IInvocation invocation)
{
var serializer = new JavaScriptSerializer();
var parametersJson = serializer.Serialize(invocation.Arguments);
string filename = "logfile_" + DateTime.Now.ToString().Replace(":", "_").Replace(".", "") + ".txt";
string path = AppDomain.CurrentDomain.BaseDirectory + filename;
// create a
writer and open the file
TextWriter tw = new StreamWriter(path);
tw.WriteLine("Request of " +
invocation.Method.Name + " is " + parametersJson,filename);
invocation.Proceed();
var returnValueJson = serializer.Serialize(invocation.ReturnValue);
tw.WriteLine("Response of " +
invocation.Method.Name + " is: " + invocation.ReturnValue,filename);
// close the
stream
tw.Close();
}
}
}
Yukarıda biz log için bir txt doyasına yazmaktayız faklı şekillerde Loglama işlemlerini bu Interceptor 'da yazabilirsiniz. Interceptor 'u invocation.Proceed ()' den öncesi istek aşaması sonrası için isteğe verilen cevap aşaması olarak düşünebilirsiniz.
Şimdi gelelim biraz daha karışık olan kısımlara API'nin istek ve response 'lerinin arasına girdiğimiz için API 'nin hangi controlleri çağırdığımızı anlamasına yardımcı olacak DependencyResolver sınıflarını oluşturmamız gerek, Web API 'deki HttpControllerActivator sınıfını devreden çıkarıp bizim yazacağımız sınıfı devreye sokarak API çözümlemesini ve bağımlılık inject edebilmeyi sağlayacağız. Bu işler için aşağıdaki iki sınıf önemli :
namespace GothamNews.Installers
{
public class WindsorDependencyScope : IDependencyScope
{
private readonly IWindsorContainer _container;
private readonly IDisposable _scope;
public WindsorDependencyScope(IWindsorContainer container)
{
if (container == null)
throw new
ArgumentNullException("container");
_container = container;
_scope = container.BeginScope();
}
public object
GetService(Type t)
{
return _container.Kernel.HasComponent(t)
? _container.Resolve(t) : null;
}
public IEnumerable<object> GetServices(Type t)
{
return _container.ResolveAll(t)
.Cast<object>().ToArray();
}
public void
Dispose()
{
_scope.Dispose();
}
}
}
namespace GothamNews.Installers
{
public class WindsorHttpDependencyResolver :
IDependencyResolver
{
private readonly IWindsorContainer _container;
public WindsorHttpDependencyResolver(IWindsorContainer container)
{
if (container == null)
throw new
ArgumentNullException("container");
_container = container;
}
public object
GetService(Type t)
{
return _container.Kernel.HasComponent(t)
? _container.Resolve(t) : null;
}
public IEnumerable<object> GetServices(Type t)
{
return _container.ResolveAll(t)
.Cast<object>().ToArray();
}
public IDependencyScope BeginScope()
{
return new
WindsorDependencyScope(_container);
}
public void
Dispose()
{
}
}
}
Bu iki sınıf GothamNews.DependencyResolvers projesi içerisinde Castle Windsor ve CasteCore nuget paketlerini bu projeye API projesine dahil etmek için aşağıdaki yol izlenebilir.
Ayrıca bu sınıflar direk API projemizin altında yer alamadığı için aşağıdaki gibi yükleyerek System.Web.Http kütüphanesini de bu projeye referans almamız gerekir.
Referans aldığımız Web.Http nuget paketi ve API'de yer alan Web.Http paketinin versiyonları da aynı olmalıdır.
Oluşturduğumuz interceptor'ları ve WebApiControllerInstaller 'i API 'nin Global.asax 'ında install etmemiz gerekir. Bunun için hazırlık olarak aşağıdaki sınıfları oluşturalım :
namespace GothamNews.Installers
{
public static class CastleHelper
{
public static
WindsorContainer Container { get; private set; }
private static
WindsorHttpDependencyResolver _resolver;
private static bool _initialized;
static CastleHelper()
{
Container = new WindsorContainer();
_initialized = false;
}
public static
WindsorHttpDependencyResolver GetDependencyResolver()
{
if (_initialized)
return _resolver;
_initialized = true;
Container.Install(FromAssembly.This());
_resolver = new
WindsorHttpDependencyResolver(Container);
return _resolver;
}
}
}
namespace GothamNews.Installers
{
public class WebApiControllerInstaller :
IWindsorInstaller
{
public void
Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(Classes.FromThisAssembly()
.BasedOn<ApiController>()
.LifestylePerWebRequest());
}
}
}
namespace GothamNews.Installers
{
public class ServiceInstaller : IWindsorInstaller
{
public void
Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<LoggingInterceptor>(),
Component.For<INewsService>()
.ImplementedBy<NewsService>()
.Interceptors<LoggingInterceptor>());
}
}
}
WebApıConfig. cs dosyasında da aşağıdaki kod satırını ekleyelim :
config.DependencyResolver =
CastleHelper.GetDependencyResolver();
Son olarak Global.asax dosyasına Castle Windsor container 'ine Install kodlarımızı yazıp aşağıdaki configurasyonları yenilediğimiz için onları en sonda register edelim.
// Configure
WebApi to use the newly configured GlobalConfiguration complete with Castle
dependency resolver
WebApiConfig.Register(GlobalConfiguration.Configuration);
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configuration.EnsureInitialized();
public class WebApiApplication :
System.Web.HttpApplication
{
protected void
Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// Initialize
Castle & install application components
var container = new
WindsorContainer();
container.Install(new WebApiControllerInstaller());
container.Install(new ServiceInstaller());
// Configure
WebApi to use the newly configured GlobalConfiguration complete with Castle
dependency resolver
WebApiConfig.Register(GlobalConfiguration.Configuration);
RouteConfig.RegisterRoutes(RouteTable.Routes);
GlobalConfiguration.Configuration.EnsureInitialized();
}
}
Projemizi çalıştıralım ve http://localhost:26824/api/news gibi bir endpoint 'i çağıralım 26824 kısmı değişik olabilir. İki haberinde xml olarak çıktısını görelim :
Ve gelelim Loglama işlemine API projemizin ana dizninde logfile_11012019 13_11_12.txt gibi dosyalar oluştuğunu göreceğiz.
Umarım faydalı olmuştur. Projenin tam halini burada bulabilirsiniz.
Kaynak :
https://richardniemand.wordpress.com/2016/07/22/adding-castle-windsor-to-your-webapi-project/
Okuduğunuz için teşekkürler,
Sağlıkla Kalın,
Huzurla Kalın.
Hiç yorum yok:
Yorum Gönder