- ASP.NET Core; cross-platform, yüksek performanslı, açık kaynak kodlu bir framework'tür.
- .NET Core ve .NET Framework üzerinde çalıştırılabilir.
- ASP.NET Framework, yalnızca Windows işletim sisteminde, .NET Framework üzerinde çalışmaktadır.
- ASP.NET Core uygulaması,
Main
metodu içinde web server yaratıp kullanan bir console uygulamasıdır.
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace aspnetcoreapp
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}
WebHost.CreateDefaultBuilder()
metodu, uygulamamızı üzerinde çalıştıracağımız bir host yaratır ve varsa ayarlamalar yapmamızı sağlar.- Bu ayarlamalar,
UseStartup()
,UseKestrel()
gibi metotlarla yapılandırılır. - Default builder ayarlamsında Kestel web server otomatik olarak seçilmiş olur.
Build()
veRun()
metotlarıylaIWebHost
türünde bir host oluşturulup, HTTP istekleri dinlenmeye başlanır.
- Bu ayarlamalar,
- Dotnet Core ile yazılmış bir programı çalıştırdığımızda, çalışma sırası aşağıdaki şemadaki gibidir.
- NOT : Middleware'ler, program çalıştığında çalışmaz. Request geldiğinde ve response döndüğünde çalışır.
-
Main metot içinde, Host oluştururken (WebHostBuilder) kullandığımız
UseStartup()
metodu,Request Handling Pipeline
düzenlemeleri yapmamızı sağlayan, external bir class oluşturmamızı sağlar. -
Startup class'ı public olmalı ve aşağıdaki iki metodu içermelidir.
public class Startup
{
// This method gets called by the runtime. Use this method
// to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
}
// This method gets called by the runtime. Use this method
// to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
}
}
- ConfigureServices : Uygulamanın kullandığı servislerin (örn; ASP.NET Core MVC, Entity Framework Core, Identity) tanımlandığı yerdir.
- Tanımlanması opsiyoneldir.
- Configure : Request pipeline için kullanılacak ara katmanların (middleware) belirtildiği yerdir.
- Tanımlanması zorunludur.
- Bu metotlar, uygulama çalıştığı zaman
runtime
anında çalışırlar.
...
- Startup sınıfı kullanılacağı zaman, host tarafından sağlanan bazı bağımlılıklar enjenkte edilebilir. Bunlardan en çok kullanılanları:
IHostingEnvironment
: Servisleri geliştirme ortamlarına göre özelleştirmeyi sağlar.IConfiguration
: Startup boyunca uygulama ayarları yapmamızı sağlar.
public class Startup
{
public Startup(IHostingEnvironment env, IConfiguration config)
{
HostingEnvironment = env;
Configuration = config;
}
public IHostingEnvironment HostingEnvironment { get; }
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
if (HostingEnvironment.IsDevelopment())
{
// Development configuration
}
else
{
// Staging/Production configuration
}
// Configuration is available during startup. Examples:
// Configuration["key"]
// Configuration["subsection:suboption1"]
}
}
IHostingEnvironment
veILoggerFactory
, alternatif olarak Startup metotlarına parametre olarak da verilebilir(conventions-based approach).
public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment hosting)
{
// ...
}
}
- Opsiyoneldir.
- Web host tarafından,
Configure
motodu çağırılmadan önce çağırılır ve uygulamanın kullanıcı tanımlı servis ayalarının yapılmasını sağlar. ConfigureServices
metodu içine yeni bir servis eklemek, bu servisi uygulama veConfigure
için kullanılabilir yapar.- Bu servisler uygulama içinde
dependency injection
veyaIApplicationBuilder.ApplicationServices
aracılığıyla kullanılabilir.
- Bu servisler uygulama içinde
- Web Host, bazı servisleri Startup metodu çağırılmadan önce düzenleyebilir. (Ayrıntı)
- Servisleri ekleme :
IServiceCollection
sınıfı içindekiAdd[Service_Name]
metotları çağırılıp servisler eklenebilir.- Servisler eklendiği zaman, varsa ayarları da yapılabilir.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
// Add application services.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
}
- Bu metot, gelen HTTP isteğine nasıl cevap verileceğini ayarlamamızı sağlar.
- Gelen isteğe cevap verilme sürecine (Request => Response)
Request Handling Pipeline
denir.
- Gelen isteğe cevap verilme sürecine (Request => Response)
- Request pipeline yönetimi,
IApplicationBuilder
nesnesi üzerineAra katman (Middleware)
eklenmesiyle düzenlenir.IApplicationBuilder
web host tarafından üretilir ve direk olarakConfigure
metoduna yönlendirilir.
- Request pipeline, hazır gelen Core middleware'leri (developer exception page, BrowserLink, error pages, static files, and ASP.NET MVC) ile düzenlenebileceği gibi, custom middleware de yazabiliriz.
IApplicationBuilder
üzerine eklenmişUse[middleware]
extension metotları middleware eklenmesi için kullanılabilir.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
}
- Her eklenen middleware, kendisinden sonra gelen middleware'i çalıştırmak ya da kısa devre (short-circuiting) yaparak başka bir yere yönlendirmekle sorumludur.
- Short-circuiting ile yönlendirilen yer, tekrar middleware'leri en baştan çağırılmasını sağlayabileceği gibi, sonrakileri atlayarak direk response da gönderebilir.
ConfigureServices
veConfigure
metotları,Startup
sınıfı içinde tanımlanabileceği gibi, web host build edilirken de oluşturulabilir.
public class Program
{
public static IHostingEnvironment HostingEnvironment { get; set; }
public static IConfiguration Configuration { get; set; }
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
HostingEnvironment = hostingContext.HostingEnvironment;
Configuration = config.Build();
})
.ConfigureServices(services =>
{
services.AddMvc();
})
.Configure(app =>
{
if (HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
// Configuration is available during startup. Examples:
// Configuration["key"]
// Configuration["subsection:suboption1"]
app.UseMvcWithDefaultRoute();
app.UseStaticFiles();
})
.Build();
}
IStartupFilter
interface'inden implament edilen bir sınıf ile, herhangi bir middleware'i, tüm middleware pipeline'ından önce veya sonra çalıştırılmasını sağlayabiliriz.- Bu sayede bir middleware'in,
Configure
metodu içinde belirttiğimiz middleware'ler dışında, sistemde default gelen middleware'lerden de önce veya sonra çalıştığından emin olabiliriz. IStartupFilter
interface'i, bir taneAction<IApplicationBuilder>
döndürenConfigure
adlı metot implament eder.- Her bir sınıf, bir ya da daha fazla middleware kullanabilir. Kullanılan middleware'ler filtre olarak ekleneceğinden, ayrıca
Startup > Configure
metodu içine eklenmesine gerek yoktur. - Oluşturulan filtre,
ConfigureServices
içinde aktifleştirilir.- Birden fazla filtre veya aynı filtre birden fazla defa kullanılabilir.
- Yazıldığı yere göre, servisler sırayla aktifleştirilir.
public class RequestSetOptionsStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return builder =>
{
builder.UseMiddleware<RequestSetOptionsMiddleware>();
next(builder);
};
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IStartupFilter, RequestSetOptionsStartupFilter>();
services.AddMvc();
}
- Dependency Injection Nedir?
- Dotnet Core içinde, DI işlemleri default olarak gelmektedir.
- DI işlemleri,
Startup.cs
içindekiConfigureServices
metoduna servislerin tanımlanması ve tanımlanan servislerinRequest Pipeline
boyunca ilgili yerlere enjekte edilmesiyle sağlanır. - Bu işlemler, dotnet core içinde gömülü halde gelen ve default olarak
constructor injection
(Yapıcı Metot ile DI) DI metodunu destekleyen,IServiceProvider
arayüzünü kullanarak gerçekleştirilir.ConfigureServices
metodunda bu arayüze eklemeler yapılır ve ilgili yerlerde bu arayüzden servisler çekilerek (GetRequeiredService
) kullanılır.- Aşağıdaki örnekte, basit olarak bir database ve örnek servisin eklenmesi, database servisinin aynı zamanda örnek sınıf içinde çekilerek kullanımı gösterilmiştir.
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ExampleClass>();
services.AddScoped<IDatabaseService, EfDatabaseService>();
services.AddMvc();
}
namespace Project.WebUI.Library.Functions
{
public class ExampleClass
{
private readonly IServiceProvider provider;
public ExampleClass(IDatabaseService _db, IServiceProvider _provider)
{
provider = _provider;
}
public void AddPerson(string name)
{
IDatabaseService database = provider.GetRequiredService<IDatabaseService>();
database.People.Add(new Person { Name = name });
database.SaveChanges();
}
}
}
- Uygulama içinde kullanılacak her servisin öncelikle
ConfigureServices
metodu içinde tanıtılması gerekir. Bununla birlikte bazı servisler default olarak tanımlanmış halde gelirler. Bu servisler ayrıca tanımlanmadan direk olarak kullanılacağı yerde enjekte edilebilirler.
- Servisler basit olarak aşağıdaki gibi tanıtılabilir.
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
- Burada;
Add<xxx>
olarak yazılan metot, belirli bir ömre göre servisi ekleyen metottur.- Metodun ilk generic tipi, konteynır yapılarının isteyeceği türü belirtir.
- Bu yapı genellikle
interface
yapısındadır.
- Bu yapı genellikle
- Metodun ikinci generic tipi, ilk tip istendiğinde oluşturulacak ve servis olarak sunulup kullanılacak
concrete
tipi belirtir.- Bu yapı
class
yapısındadır.
- Bu yapı
- Bu yapılar dışında tek bir generic tip olarak da servisler tanıtılabilir.
services.AddTransient<AuthMessageSender>();
- Burada;
- Yapı sadece generic olarak
concrete
class yapısında olduğundan, konteynır yapılarının direk olarak bu yapıyı istemesi gerekmektedir. - DI mantığına aykırı olduğundan bu yapı fazla kullanılmaz.
- Yapı sadece generic olarak
- Bunlar dışında custom servisler extension metot olarak da eklenip kullanılabilir.
// Extension method
public static class ExampleServiceExtension
{
public static IServiceCollection AddFunctionService(this IServiceCollection service)
{
return service.AddScoped<ExampleService>();
}
}
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddFunctionService();
services.AddMvc();
}
- Servisler tanımlanırken belirli bir ömre göre tanıtılırlar. Bu ömürler, ilgili container servisi çağırdığında, servis nesnelerinin ne zaman yaratılacağını belirler.
- Servislerin
Constructor
metotlarının, nesne yaratıldığında oluştuğu unutulmamalıdır.
- Servislerin
- Bu ömürler üçe ayrılır:
Transient
- Servis her istendiğinde, yeni bir nesne yaratılır.
- Özellikle stateless servisler için best-practice olarak kullanılır.
Scoped
- Her bir scope (request) için bir tane nesne yaratır ve request sonuna kadar o nesneyi kullanır.
- Request lifecycle boyunca hep aynı nesne kullanılır.
- Request boyunca stateful bir yapı sağlanması istenirse, bu yöntem kullanılır.
-
NOT : Eğer scoped servisler middleware içinde kullanılacaksa, bu servisler
Invoke
veInvokeAsync
metotlarına parametre olarak enjekte edilmelidir. Contructor metot ile enjekte edilirse, bu durum servisin singleton gibi davranmasını isteyeceğinden kullanılmaz. -
NOT : Entity Framework kullanan servislerin
Scoped
ile eklenmesi gerekmektedir.
Singleton
- İsminden de anlaşılacağı üzere singleton yani uygulama ilk çalıştığında tek bir nesne yaratır ve sonrasında uygulama stop olana kadar bu instance kullanılır.
- Servis içinde başka bir servis kullanırken ömürlere dikkat etmek gerekir:
- Singleton servis içinde;
- Singleton servis kullanılabilir.
- Scoped servis kullanılamaz!
- Transient servis kullanılabilir.
- Scoped servis içinde;
- Singleton servis kullanılabilir.
- Scoped servis kullanılabilir.
- Transient servis kullanılabilir.
- Transient servis içinde;
- Singleton servis kullanılabilir.
- Scoped servis kullanılabilir.
- Transient servis kullanılabilir.
- Singleton servis içinde;
- Bunun için,
CreateScope()
metodu ile scope servislerine ulaşılır ve oluşan nesneden türetilenServiceProvider
ile servisler kullanılabilir. - Program başlangıcında scoped servislere ulaşıp kullanmak için kullanılan bir yöntemdir.
public static void Main(string[] args)
{
var host = BuildWebHost(args);
using (var serviceScope = host.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var serviceContext = services.GetRequiredService<MyScopedService>();
// Use the context here
}
host.Run();
}
- Tanıtılan servisler,
HttpContext
içinde bulunanRequestServices
koleksiyonu içinde de bulunur. - İstenilirse bu koleksiyon içinden de servisler çekilip kullanılabilir.
- Fakat bu yöntem ile direk servislerin çekilmesi pek sağlıklı değildir. Bunun yerine constructor metotlar kullanılarak enjekte edilip, framework yapısının kendisinin enjekte etmesine izin vermek daha doğrudur.
- Bununla birlikte hızlı test yapılması istenildiği durumlarda bu yöntem kullanılabilir.
public class HomeController : Controller
{
// Constructor Method Injection
private IDatabaseService database;
public HomeController(IDatabaseService _database)
=> database = _database;
public IActionResult Index()
{
// Using services directly
IServiceProvider provider = this.HttpContext.RequestServices;
IDatabaseService db = provider.GetRequiredService<IDatabaseService>();
return View();
}
}
- Container yapıları, kendi ürettikleri servisleri, eğer bu servisler dispose edilebiliyorsa (
IDisposable
'dan türemişse) yine kendileri dispose ederler. - Kendi üretmedikleri servisleri ise dispose edemezler.
- Örnek olarak;
// Services implement IDisposable:
public class Service1 : IDisposable {}
public class Service2 : IDisposable {}
public class Service3 : IDisposable {}
public interface ISomeService {}
public class SomeServiceImplementation : ISomeService, IDisposable {}
public void ConfigureServices(IServiceCollection services)
{
// container will create the instance(s) of these types and will dispose them
services.AddScoped <ISomeService, SomeServiceImplementation>();
services.AddSingleton <ISomeService, SomeServiceImplementation>();
services.AddTransient <ISomeService, SomeServiceImplementation>();
services.AddSingleton <ISomeService>(sp => new SomeServiceImplementation());
services.AddScoped <ISomeService>(sp => new SomeServiceImplementation());
services.AddTransient <ISomeService>(sp => new SomeServiceImplementation());
services.AddSingleton <SomeServiceImplementation>(sp => new SomeServiceImplementation());
services.AddScoped <SomeServiceImplementation>(sp => new SomeServiceImplementation());
services.AddTransient <SomeServiceImplementation>(sp => new SomeServiceImplementation());
services.AddScoped <Service1>();
services.AddSingleton <Service2>();
// container didn't create instance so it will NOT dispose it
services.AddSingleton <ISomeService>(new SomeServiceImplementation());
services.AddSingleton <SomeServiceImplementation>(new SomeServiceImplementation());
services.AddSingleton <Service3>(new Service3());
services.AddSingleton (new Service3());
}
- Statik dosyalar; HTML, CSS, resim dosyaları ve JS dosyalarına verilen genel bir addır.
- Bu dosyalar dinamik değil de statik olduğundan, web programı üzerinden geçmeden direk response olarak döndürülebilir (Short-circuting).
- Statik dosyalar, default olarak
<content_root>/wwwroot
dizini içinde barındırılır veUseWebRoot
metodu kullanılarak istenilirse dizin yolu değiştirilebilir. - Content root yolu, program başlangıcında
WebHost
yaratılırken belirtilmelidir.WebHost.CreateDefaultBuilder
metodu kullanıldığında (default olarak bu metot çalışır), content root olarak o anki bulunan ana dizin ayarlanır.
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
- Statik dosyaları kullanmak için, bu servisi aktifleştirmek gerekir.
- Bunun için
Configure
metodu içindeUseStaticFiles()
metodu ile aktifleştirme yapılmalıdır.
- Bunun için
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
}
wwwroot
altında bulunan dosyalara ulaşırken belirtilecek dosya yolları, wwwroot ismi dikkate alınmadan kullanılır.- Örnek vermek gerekirse
Folders
-------
wwwroot
images
example.jpg
styles
example.css
scripts
Using
-----
<img src="~/images/example.jpg" />
<link rel="stylesheet" href="~/styles/example.css">
NOT :
.NET Framework
altyapısı kullanılacaksa,Microsoft.AspNetCore.StaticFiles
;.NET Core
altyapısı kullanılacaksaMicrosoft.AspNetCore.All
paketlerinin projeye dahil olduğundan emin olmamız gerekmektedir.