İçeriğe atla

ASP.NET Core 6/Razor Sayfaları/Razor Sayfasının Kod Kısmı

Vikikitap, özgür kütüphane

Daha önce bir Razor sayfasının bir kod, bir de görünüm kısmı olduğunu söylemiştik. İşte bu bölümde Razor sayfalarının kod kısımlarını inceleyeceğiz. Razor sayfalarının kod sınıfları PageModel sınıfından türer. PageModel sınıfının önemli üyeleri aşağıda verilmiştir:

HttpContext: Geçerli request'in HttpContext nesnesini verir.
ModelState: Forma girilen verilerin geçerli olup olmadığını anlayacağımız ModelState nesnesini verir.
PageContext: Geçerli sayfa hakkında bilgi verir.
HttpRequest: Geçerli request'in HttpRequest nesnesini verir.
HttpResponse: Geçerli request'in response'unu verir.
RouteData: Geçerli rota segmentlerine erişim sağlar.
TempData: TempData verisine erişim sağlar.
User: Geçerli request'i yapan kullanıcıyı döndürür.

Sayfanın kod kısmının ayrı dosyada saklanması

[düzenle]

Önceki derslerdeki örneğimizde sayfanın kod kısmını görünümle aynı dosyada tutmuştuk. Sayfanın kod kısmını belirtmek için @functions direktifini kullanmıştık. Sayfanın kod kısmını görünümle aynı dosyada saklamak pratik olmasına karşın yönetilebilirlik bakımından dezavantajlıdır. Şimdi Page klasöründeki Index.cshtml dosyasını şöyle değiştirelim:

@page "{id:long?}"
@model WebApp.Pages.IndexModel
<!DOCTYPE html>
<html>
<head>
</head>
<body>
	<div>@Model.Product.Name</div>
</body>
</html>

Burada bazı değişikliklikler var. Öncelikle @functions direktifi ile belirtilen kod kısmını kaldırdık. Artık gerek olmadığı için @using direktiflerini kaldırdık. Artık model sınıfımız aynı dosyada olmadığı için @model direktifinde sınıfın isim alanıyla beraber tam yolunu kullanmalıyız.

Sayfanın kod kısmını barındıracak dosyanın ismi geleneksel olarak görünüm kısmını temsil eden dosyaya .cs takısı eklenmesiyle elde edilir. Örneğimizde görünüm kısmını temsil eden dosya Index.cshtml olduğu için kod kısmını temsil edecek dosyanın ismi Index.cshtml.cs olacaktır. Kod dosyaları da görünüm dosyalarında olduğu gibi Pages klasörüne konulur. Eğer Visual Studio'da Razor sayfası eklemek için "Razor Page" şablonunu kullandıysanız sayfanın kod kısmı görünüm kısmının altına otomatik eklenecektir. View dosyasının solundaki küçük oka tıklayarak kod dosyasını görebilirsiniz. Şimdi bu kod dosyasını açın ve içeriğini şöyle değiştirin:

using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Models;
namespace WebApp.Pages
{
    public class IndexModel : PageModel
    {
        private DataContext context;
        public Product Product { get; set; }
        public IndexModel(DataContext ctx)
        {
            context = ctx;
        }
        public async Task OnGetAsync(long id = 1)
        {
            Product = await context.Products.FindAsync(id);
        }
    }
}

Bu dosya önceki derslerde gördüğümüz, görünümle aynı dosyada tanımlanan ve @functions direktifi içinde belirtilen sınıfın aynısını içermektedir.

_ViewImports.cshtml dosyasının eklenmesi

[düzenle]

MVC'dekiyle aynı mantıkla _ViewImports.cshtml dosyası eklenebilir ve view'da model olarak model sınıfını belirtirken sınıfın isim alanıyla beraber tam yolunu belirtme zorunluluğundan kurtarır. Şimdi Pages klasörüne _ViewImports.cshtml isminde yeni bir view ekleyelim ve içeriği şöyle olsun:

@namespace WebApp.Pages
@using WebApp.Models

Burada Pages klasöründeki bütün view'ları WebApp.Pages isim alanına koyuyoruz. Sayfaların kod kısımları da aynı isim alanında olduğu için artık sayfaların görünüm kısımları kod kısımlarına direkt erişebilecek. Ayrıca WebApp.Models isim alanındaki sınıflara da direkt erişim hakkı kazanıyoruz. Artık Index.cshtml dosyasını aşağıdaki gibi değiştirebiliriz:

@page "{id:long?}"
@model IndexModel
<!DOCTYPE html>
<html>
<head>
</head>
<body>
	<div>@Model.Product.Name</div>
</body>
</html>

Razor sayfalarındaki action'ların geri dönüş tipleri

[düzenle]

Oluşturduğumuz model sınıfını incelerseniz OnGetAsync() metodunun Task döndürdüğünü görürsünüz. OnGetAsync()'in senkron versiyonu ise void geri dönüş tipine sahiptir. Bunun anlamı Razor sayfalarındaki action'ların bir şey döndürmediği anlamına gelir. Bu mantıklıdır, çünkü handler metotların yapacağı iş kendi bünyesindeki özellikleri ayarlayıp view kısmının bu özelliklere erişmesini sağlamasıdır. Ancak esasında işler bir tık farklıdır ve MVC'ye daha yakındır. Yukarıdaki model sınıfını (Index.cshtml.cs) şöyle değiştirebiliriz:

using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Models;
using Microsoft.AspNetCore.Mvc;
namespace WebApp.Pages
{
    public class IndexModel : PageModel
    {
        private DataContext context;
        public Product Product { get; set; }
        public IndexModel(DataContext ctx)
        {
            context = ctx;
        }
        public async Task<IActionResult> OnGetAsync(long id = 1)
        {
            Product = await context.Products.FindAsync(id);
            return Page();
        }
    }
}

Bu kod da tamamen geçerlidir ve Razor Pages'ın arkaplanda nasıl uygulandığına dair ipucu vermektedir. PageModel sınıfından devralınan Page() metodu geriye IActionResult arayüzünü uygulamış bir PageResult nesnesi döndürür ve ASP.NET Core'a ilgili model sınıfının bağlı olduğu view'ı renderlamasını söyler. Ayrıca Page() metodu parametre almaz. Bu mantıklıdır. Çünkü başka bir view seçme imkanı veya kendinden başka bir model nesnesi gönderme imkanı yoktur. PageModel sınıfı, değişik sonuçlar döndürmek için Page() metoduna benzer ve geriye IActionResult arayüzünü uygulamış bir sınıf nesnesi döndüren metotlar da içermektedir. Bu metotlar aşağıdaki listede verilmiştir:

Page(): HTTP 200 koduyla beraber sayfanın görünüm kısmı renderlanır.
NotFound(): Geriye HTTP 404 kodunu döndürür.
BadRequest(state): Geriye HTTP 400 kodunu döndürür. Opsiyonel bir model state nesnesi alabilir, model state nesnesi request'in neden neden kabul edilmediğine dair istemciye bilgi verebilir.
File(name, type): Geriye HTTP 200 koduyla beraber belirtilen isimli ve belirtilen tipli dosyayı döndürür. Response'ın Content-Type header'ı type parametresine göre belirlenir.
Redirect(path), RedirectPermanent(path): Sırasıyla HTTP 302 ve HTTP 301 kodlarını döndürürler. Belirtilen path'a yönlendirme yaparlar.
RedirectToAction(name), RedirectToActionPermanent(name): Sırasıyla HTTP 302 ve HTTP 301 kodlarını döndürürler. Belirtilen action'a yönlendirme yaparlar.
RedirectToPage(name), RedirectToPagePermanent(name): Sırasıyla HTTP 302 ve HTTP 301 kodlarını döndürürler. Belirtilen Razor sayfasına yönlendirme yaparlar. Eğer parametre verilmezse geçerli sayfaya yönlendirme yapılır.
StatusCode(code): Belirtilen durum kodunu döndürmeye yarar.

Bu metotlar burada yalnızca tamlık amacıyla verilmiştir. Gerçekte Razor sayfaları servis tüketicileri tarafından değil, gerçek insanlar tarafından tüketildiği için salt bir durum kodu döndürmektense istenen mesajı kullanıcıya verecek bir sayfaya yönlendirme yapmak daha doğrudur.

Model sınıfındaki handler metotlar

[düzenle]

Şimdiye kadar model sınıfına OnGetAsync() isimli GET request'lerine karşılık veren bir handler metot ekledik. Başka HTTP metotlarına karşılık gelen başka metotlar da ekleyebiliriz. Şimdi projenizin Pages klasörüne Editor isimli bir Razor sayfası ekleyin. Razor sayfasının view dosyası şöyle olsun:

@page "{id:long}"
@model EditorModel
<!DOCTYPE html>
<html>
<head>
</head>
<body>
	<div>Editor</div>
	<div>
		<table>
			<tbody>
				<tr><th>Name</th><td>@Model.Product.Name</td></tr>
				<tr><th>Price</th><td>@Model.Product.Price</td></tr>
			</tbody>
		</table>
		<form method="post">
			@Html.AntiForgeryToken()
			<div>
				<label>Price</label>
				<input name="price" value="@Model.Product.Price" />
			</div>
			<button type="submit">Submit</button>
		</form>
	</div>
</body>
</html>

Bu view'ı inceleyerek EditorModel sınıfında bir Product özelliği olduğunu ve Product nesnesi içerdiği çıkarımında bulunabiliriz. View'ımız ilgili Product'ın Name ve Price özelliklerini ekranda görüntülemekte ve Price özelliğinin değiştirilme imkanını sağlamaktadır. Formun action'ı olmadığına göre formdaki veriler "Submit" butonuna tıklandığında view'ı sunan sayfanın OnPostAsync() metoduna gidecektir. Bu metodun price isimli bir parametre tanımlayarak bu fiyat bilgisini alması gerekmektedir. @Html.AntiForgeryToken() metot çağrısı Cross-Site Request Forgery (CSRF) saldırısı için alınmış bir önlemdir ve forma gizli bir kontrol eklemektedir. Sunucu bu gizli kontrolün verileri olmayan POST isteklerini kabul etmemektedir. Dolayısıyla teknik olarak OnPostAsync() metoduna gönderilen POST request'lerinin başarılı olması ve veritabanına yazım yapabilmesi için form verilerini, daha önce sunucunun bu view'ı gönderdiği istemcinin göndermesi gerekir.

Şimdi bu view dosyasını genişletelim ve Editor.cshtml.cs dosyasını görelim. Bu dosyayı açalım ve içeriğini şöyle değiştirelim:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Models;
namespace WebApp.Pages
{
    public class EditorModel : PageModel
    {
        private DataContext context;
        public Product? Product { get; set; }
        public EditorModel(DataContext ctx)
        {
            context = ctx;
        }
        public async Task OnGetAsync(long id)
        {
            Product = await context.Products.FindAsync(id);
        }
        public async Task<IActionResult> OnPostAsync(long id, decimal price)
        {
            Product p = await context.Products.FindAsync(id);
            if (p != null)
            {
                p.Price = price;
            }
            await context.SaveChangesAsync();
            return RedirectToPage();
        }
    }
}

Kodda da gördüğünüz üzere model sınıfına OnPostAsync() metodu eklenmiştir. Kod oldukça açık gözüküyor. Ancak burada OnPostAsync() metodunun id parametresini nereden aldığını sorabilirsiniz. Cevap: rota segmentinden. Sayfa en başında görüntülendiğinde id isimli bir rota parametresi verilmesi zorunluydu. Dolayısıyla ilk formu sunan URL http://localhost:5000/editor/1 gibi bir şeydi. Daha sonra aynı URL'e POST request'i yapıldığında yine aynı http://localhost:5000/editor/1 URL'sine POST request'i yapıldı. Dolayısıyla OnPostAsync() metodu id parametresini alabildi. Ayrıca burada RedirectToPage() metot çağrısının kritik bir önemi vardır. İstemciye aynı sayfaya bir yönlendirme cevabı gönderir. İstemci bunun üzerine OnGetAsync() metoduna yeniden talepte bulunur. Burada direkt return Page(); satırını kullanmıyoruz. Eğer yönlendirme yapmadan direkt sayfayı render'lasaydık istemci bu sayfayı aldıktan sonra sayfayı yenilemesi durumunda form verilerini tekrar gönderecekti.

Handler metodun manuel olarak seçilmesi

[düzenle]

Şimdiye kadar bir handler metot gelen HTTP request'inin metoduna göre seçiliyordu. Örneğin gelen request POST request'i ise OnPostAsync() metodu çalışıyordu. Şimdi bu avrsayılan durumu değiştirebiliriz. BÖrneğin IndexModel model sınıfımız aşağıdaki gibi iki handler içersin:

public async Task<IActionResult> OnGetAsync(long id = 1)
{
    // ...
}
public async Task<IActionResult> OnGetRelatedAsync(long id = 1)
{
    // ...
}

Bu sayfanın URL'sine yapılan GET request'leri varsayılan durumda OnGetAsync() metodu tarafından karşılanır. OnGetRelatedAsync() metodu tarafından karşılanmasını istiyorsak URL'yi /Index?handler=related path'ıyla oluşturmalıyız. Query string parametresi handler olmak zorundadır. Query string değeri ise On[Get|Post|...] ön ekleri ve Async son eki olmadan metot ismidir. Rota parametresi kullanarak da benzer işlevsellik sağlanabilir. Ancak bu sefer ilgili Razor sayfasının başında @page "{id:long?}/{handler?}" direktifiyle handler isimli bir rota değişkeni tanımlamamız gerekirdi.