Bölüm 2, Razor ASP.NET Core'da bulunan EF Core sayfalar - CRUD

Tom Dykstra, Jeremy Likness ve Jon P Smith tarafından

Contoso University web uygulaması, ve Visual Studio kullanarak EF Core Sayfalar web uygulamalarının nasıl oluşturulacağını Razor gösterir. Öğretici serisi hakkında bilgi için ilk öğreticiye bakın.

Çözemediğiniz sorunlarla karşılaşırsanız, tamamlanmış uygulamayı indirin ve öğreticiyi izleyerek bu kodu oluşturduğunuz kodla karşılaştırın.

Bu öğreticide, iskelesi oluşturulmuş CRUD (oluşturma, okuma, güncelleştirme, silme) kodu gözden geçirilir ve özelleştirilir.

Depo yok

Bazı geliştiriciler, kullanıcı arabirimi (Razor Sayfalar) ile veri erişim katmanı arasında bir soyutlama katmanı oluşturmak için bir hizmet katmanı veya depo deseni kullanır. Bu öğretici bunu yapmaz. Karmaşıklığı en aza indirmek ve öğreticinin üzerinde EF CoreEF Core odaklanmasını sağlamak için kod doğrudan sayfa modeli sınıflarına eklenir.

Ayrıntılar sayfasını güncelleştirme

Öğrenciler sayfaları için yapı iskelesi oluşturulmuş kod kayıt verilerini içermez. Bu bölümde, kayıtlar sayfaya Details eklenir.

Kayıtları okuma

Öğrencinin kayıt verilerini sayfada görüntülemek için kayıt verilerinin okunması gerekir. içindeki iskelelenmiş kodPages/Students/Details.cshtml.cs, veriler olmadan Enrollment yalnızca Student verileri okur:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

OnGetAsync Seçilen öğrencinin kayıt verilerini okumak için yöntemini aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Include ve ThenInclude yöntemleri, bağlamın gezinti özelliğini yüklemesine Student.Enrollments ve her kayıtta gezinti özelliğinin Enrollment.Course içine neden olur. Bu yöntemler, İlgili verileri okuma öğreticisinde ayrıntılı olarak incelenir.

yöntemi, AsNoTracking döndürülen varlıkların geçerli bağlamda güncelleştirilmediği senaryolarda performansı artırır. AsNoTracking bu öğreticinin ilerleyen bölümlerinde ele alınmalıdır.

Kayıtları görüntüleme

Kayıtların listesini görüntülemek için içindeki Pages/Students/Details.cshtml kodu aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Yukarıdaki kod, gezinti özelliğindeki Enrollments varlıklar arasında döngü oluşturur. Her kayıt için kurs başlığını ve notu görüntüler. Kurs başlığı, Kayıtlar varlığının Course gezinti özelliğinde Course depolanan varlıktan alınır.

Uygulamayı çalıştırın, Öğrenciler sekmesini seçin ve bir öğrencinin Ayrıntılar bağlantısına tıklayın. Seçilen öğrencinin ders ve not listesi görüntülenir.

Bir varlığı okumanın yolları

Oluşturulan kod, bir varlığı okumak için FirstOrDefaultAsync kullanır. Hiçbir şey bulunmazsa bu yöntem null döndürür; aksi takdirde, sorgu filtresi ölçütlerini karşılayan ilk satırı döndürür. FirstOrDefaultAsync genellikle aşağıdaki alternatiflerden daha iyi bir seçimdir:

  • SingleOrDefaultAsync - Sorgu filtresini karşılayan birden fazla varlık varsa bir özel durum oluşturur. Sorgu tarafından birden fazla satır döndürülebilir olup olmadığını belirlemek için birden SingleOrDefaultAsync çok satır getirmeye çalışır. Sorgu benzersiz bir anahtarda arama yaparken olduğu gibi yalnızca bir varlık döndürebiliyorsa bu ek iş gereksizdir.
  • FindAsync - Birincil anahtara (PK) sahip bir varlık bulur. PK içeren bir varlık bağlam tarafından izleniyorsa, veritabanına bir istekte bulunmaksızın döndürülür. Bu yöntem, tek bir varlığı aramak için iyileştirilmiştir, ancak ile FindAsyncçağrı Include yapamazsınız. Bu nedenle, ilgili verilere ihtiyaç duyulduğu FirstOrDefaultAsync takdirde daha iyi bir seçimdir.

Verileri yönlendirme ve sorgu dizesi karşılaştırması

Ayrıntılar sayfasının URL'si şeklindedir https://localhost:<port>/Students/Details?id=1. Varlığın birincil anahtar değeri sorgu dizesindedir. Bazı geliştiriciler yol verilerinde anahtar değerini geçirmeyi tercih eder: https://localhost:<port>/Students/Details/1. Daha fazla bilgi için bkz . Oluşturulan kodu güncelleştirme.

Oluştur sayfasını güncelleştirme

Oluştur sayfasının yapı iskelesi oluşturulmuş OnPostAsync kodu, fazla paylaşıma karşı savunmasızdır. OnPostAsync içindeki Pages/Students/Create.cshtml.cs yöntemini aşağıdaki kodla değiştirin.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Yukarıdaki kod bir Student nesnesi oluşturur ve ardından Student nesnesinin özelliklerini güncelleştirmek için gönderilen form alanlarını kullanır. TryUpdateModelAsync yöntemi:

Uygulamayı çalıştırın ve Oluştur sayfasını test etmek için bir öğrenci varlığı oluşturun.

Üst Paylaşım

Alanları TryUpdateModel deftere nakledilmiş değerlerle güncelleştirmek için kullanmak, fazla paylaşım yapılmasını önlediği için en iyi güvenlik uygulamasıdır. Örneğin, Öğrenci varlığının bu web sayfasının güncelleştirmemesi veya eklememesi gereken bir Secret özellik içerdiğini varsayalım:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Uygulamanın oluşturma veya güncelleştirme Sayfasında bir Secret alanı olmasa bile, bir bilgisayar korsanı üst paylaşım yaparak değeri ayarlayabilirSecret.Razor Bir bilgisayar korsanı, form değeri göndermek için Fiddler gibi bir Secret araç kullanabilir veya javascript yazabilir. Özgün kod, model bağlayıcısının student örneği oluştururken kullandığı alanları sınırlamaz.

Hacker'ın form alanı için Secret belirttiği değer veritabanında güncelleştirilir. Aşağıdaki görüntüde, "OverPost" değeriyle alanı gönderilen form değerlerine ekleyen Secret Fiddler aracı gösterilmektedir.

Fiddler adding Secret field

"OverPost" değeri eklenen satırın Secret özelliğine başarıyla eklenir. Uygulama tasarımcısı özelliğin Oluştur sayfasıyla ayarlanmasını Secret hiçbir zaman amaçlamasa bile bu durum ortaya çıkar.

Modeli görüntüleme

Görünüm modelleri, fazla göndermeyi önlemek için alternatif bir yol sağlar.

Uygulama modeli genellikle etki alanı modeli olarak adlandırılır. Etki alanı modeli genellikle veritabanında karşılık gelen varlık için gereken tüm özellikleri içerir. Görünüm modeli yalnızca kullanıcı arabirimi sayfası için gereken özellikleri (örneğin Oluştur sayfası) içerir.

Görünüm modeline ek olarak, bazı uygulamalar Sayfalar sayfa modeli sınıfı ile tarayıcı arasında Razor veri geçirmek için bağlama modeli veya giriş modeli kullanır.

Aşağıdaki StudentVM görünüm modelini göz önünde bulundurun:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Aşağıdaki kod, yeni bir öğrenci oluşturmak için görünüm modelini kullanır StudentVM :

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

SetValues yöntemi, başka bir PropertyValues nesneden değerleri okuyarak bu nesnenin değerlerini ayarlar. SetValues özellik adı eşleştirmeyi kullanır. Görünüm modeli türü:

  • Model türüyle ilgili olması gerekmez.
  • Eşleşen özelliklere sahip olması gerekir.

kullanmak StudentVM için yerine Sayfa oluşturma kullanımı StudentVMStudentgerekir:

@page
@model CreateVMModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Student</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Düzenle sayfasını güncelleştirme

içinde Pages/Students/Edit.cshtml.csve OnPostAsync yöntemlerini aşağıdaki kodla değiştirinOnGetAsync.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Kod değişiklikleri oluştur sayfasına benzer ve birkaç özel durum vardır:

  • FirstOrDefaultAsync ile FindAsyncdeğiştirildi. İlgili verileri dahil etmek zorunda olmadığınızda daha FindAsync verimli olur.
  • OnPostAsync parametresine id sahiptir.
  • Geçerli öğrenci boş bir öğrenci oluşturmak yerine veritabanından getirilir.

Uygulamayı çalıştırın ve öğrenci oluşturup düzenleyerek test edin.

Varlık Durumları

Veritabanı bağlamı, bellekteki varlıkların veritabanındaki karşılık gelen satırlarla eşitlenip eşitlenmediğini izler. Bu izleme bilgileri SaveChangesAsync çağrıldığında ne olacağını belirler. Örneğin, yöntemine AddAsync yeni bir varlık geçirildiğinde, bu varlığın durumu olarak Addedayarlanır. Çağrıldığında SaveChangesAsync , veritabanı bağlamı bir SQL INSERT komutu döndürür.

Bir varlık aşağıdaki durumlardan birinde olabilir:

  • Added: Varlık henüz veritabanında yok. SaveChanges yöntemi bir INSERT deyimini oluşturur.

  • Unchanged: Bu varlıkla hiçbir değişikliğin kaydedilmesi gerekmez. Varlık, veritabanından okunduğunda bu duruma sahiptir.

  • Modified: Varlığın özellik değerlerinin bazıları veya tümü değiştirildi. SaveChanges yöntemi bir UPDATE deyimini oluşturur.

  • Deleted: Varlık silinmek üzere işaretlendi. SaveChanges yöntemi bir DELETE deyimini oluşturur.

  • Detached: Varlık veritabanı bağlamı tarafından izlenmiyor.

Bir masaüstü uygulamasında durum değişiklikleri genellikle otomatik olarak ayarlanır. Varlık okunur, değişiklikler yapılır ve varlık durumu otomatik olarak olarak Modifieddeğiştirilir. Çağrısı SaveChanges , yalnızca değiştirilen özellikleri güncelleştiren bir SQL UPDATE deyimi oluşturur.

Bir web uygulamasında, DbContext bir varlığı okuyan ve verileri görüntüleyen, bir sayfa işlendikten sonra atılır. Bir sayfanın OnPostAsync yöntemi çağrıldığında, yeni bir web isteği yapılır ve yeni bir örneğiyle DbContext. Bu yeni bağlamda varlığın yeniden okunarak masaüstü işleme benzetimi gerçekleştirilir.

Sil sayfasını güncelleştirme

Bu bölümde, çağrısı SaveChanges başarısız olduğunda özel bir hata iletisi uygulanır.

içindeki Pages/Students/Delete.cshtml.cs kodu aşağıdaki kodla değiştirin:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<DeleteModel> _logger;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context,
                           ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, ErrorMessage);

                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Önceki kod:

  • Günlüğe Kaydetme ekler.
  • İsteğe bağlı parametresini saveChangesError yöntem imzasına OnGetAsync ekler. saveChangesError , öğrenci nesnesini silme hatasından sonra yönteminin çağrılıp çağrılmadığını gösterir.

Geçici ağ sorunları nedeniyle silme işlemi başarısız olabilir. Veritabanı bulutta olduğunda geçici ağ hataları daha olasıdır. saveChangesError Parametresi, false Kullanıcı arabiriminden Delete sayfası OnGetAsync çağrıldığında yapılır. Silme işlemi başarısız olduğundan tarafından çağrıldığında OnGetAsync parametresidir saveChangesErrortrue.OnPostAsync

OnPostAsync yöntemi seçili varlığı alır, ardından varlığın durumunu Deletedolarak ayarlamak için Remove yöntemini çağırır. Çağrıldığında SaveChanges bir SQL DELETE komutu oluşturulur. Başarısız olursa Remove :

  • Veritabanı özel durumu yakalanmış.
  • Delete pages OnGetAsync yöntemi ile saveChangesError=trueçağrılır.

içine Pages/Students/Delete.cshtmlbir hata iletisi ekleyin:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Sil sayfasını test etmek için uygulamayı çalıştırın ve bir öğrenciyi silin.

Sonraki adımlar

Bu öğreticide, iskelesi oluşturulmuş CRUD (oluşturma, okuma, güncelleştirme, silme) kodu gözden geçirilir ve özelleştirilir.

Depo yok

Bazı geliştiriciler, kullanıcı arabirimi (Razor Sayfalar) ile veri erişim katmanı arasında bir soyutlama katmanı oluşturmak için bir hizmet katmanı veya depo deseni kullanır. Bu öğretici bunu yapmaz. Karmaşıklığı en aza indirmek ve öğreticinin üzerinde EF CoreEF Core odaklanmasını sağlamak için kod doğrudan sayfa modeli sınıflarına eklenir.

Ayrıntılar sayfasını güncelleştirme

Öğrenciler sayfaları için yapı iskelesi oluşturulmuş kod kayıt verilerini içermez. Bu bölümde, kayıtlar sayfaya Details eklenir.

Kayıtları okuma

Öğrencinin kayıt verilerini sayfada görüntülemek için kayıt verilerinin okunması gerekir. içindeki iskelelenmiş kodPages/Students/Details.cshtml.cs, veriler olmadan Enrollment yalnızca Student verileri okur:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

OnGetAsync Seçilen öğrencinin kayıt verilerini okumak için yöntemini aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Include ve ThenInclude yöntemleri, bağlamın gezinti özelliğini yüklemesine Student.Enrollments ve her kayıtta gezinti özelliğinin Enrollment.Course içine neden olur. Bu yöntemler, İlgili verileri okuma öğreticisinde ayrıntılı olarak incelenir.

yöntemi, AsNoTracking döndürülen varlıkların geçerli bağlamda güncelleştirilmediği senaryolarda performansı artırır. AsNoTracking bu öğreticinin ilerleyen bölümlerinde ele alınmalıdır.

Kayıtları görüntüleme

Kayıtların listesini görüntülemek için içindeki Pages/Students/Details.cshtml kodu aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Yukarıdaki kod, gezinti özelliğindeki Enrollments varlıklar arasında döngü oluşturur. Her kayıt için kurs başlığını ve notu görüntüler. Kurs başlığı, Kayıtlar varlığının Course gezinti özelliğinde Course depolanan varlıktan alınır.

Uygulamayı çalıştırın, Öğrenciler sekmesini seçin ve bir öğrencinin Ayrıntılar bağlantısına tıklayın. Seçilen öğrencinin ders ve not listesi görüntülenir.

Bir varlığı okumanın yolları

Oluşturulan kod, bir varlığı okumak için FirstOrDefaultAsync kullanır. Hiçbir şey bulunmazsa bu yöntem null döndürür; aksi takdirde, sorgu filtresi ölçütlerini karşılayan ilk satırı döndürür. FirstOrDefaultAsync genellikle aşağıdaki alternatiflerden daha iyi bir seçimdir:

  • SingleOrDefaultAsync - Sorgu filtresini karşılayan birden fazla varlık varsa bir özel durum oluşturur. Sorgu tarafından birden fazla satır döndürülebilir olup olmadığını belirlemek için birden SingleOrDefaultAsync çok satır getirmeye çalışır. Sorgu benzersiz bir anahtarda arama yaparken olduğu gibi yalnızca bir varlık döndürebiliyorsa bu ek iş gereksizdir.
  • FindAsync - Birincil anahtara (PK) sahip bir varlık bulur. PK içeren bir varlık bağlam tarafından izleniyorsa, veritabanına bir istekte bulunmaksızın döndürülür. Bu yöntem, tek bir varlığı aramak için iyileştirilmiştir, ancak ile FindAsyncçağrı Include yapamazsınız. Bu nedenle, ilgili verilere ihtiyaç duyulduğu FirstOrDefaultAsync takdirde daha iyi bir seçimdir.

Verileri yönlendirme ve sorgu dizesi karşılaştırması

Ayrıntılar sayfasının URL'si şeklindedir https://localhost:<port>/Students/Details?id=1. Varlığın birincil anahtar değeri sorgu dizesindedir. Bazı geliştiriciler yol verilerinde anahtar değerini geçirmeyi tercih eder: https://localhost:<port>/Students/Details/1. Daha fazla bilgi için bkz . Oluşturulan kodu güncelleştirme.

Oluştur sayfasını güncelleştirme

Oluştur sayfasının yapı iskelesi oluşturulmuş OnPostAsync kodu, fazla paylaşıma karşı savunmasızdır. OnPostAsync içindeki Pages/Students/Create.cshtml.cs yöntemini aşağıdaki kodla değiştirin.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Yukarıdaki kod bir Student nesnesi oluşturur ve ardından Student nesnesinin özelliklerini güncelleştirmek için gönderilen form alanlarını kullanır. TryUpdateModelAsync yöntemi:

Uygulamayı çalıştırın ve Oluştur sayfasını test etmek için bir öğrenci varlığı oluşturun.

Üst Paylaşım

Alanları TryUpdateModel deftere nakledilmiş değerlerle güncelleştirmek için kullanmak, fazla paylaşım yapılmasını önlediği için en iyi güvenlik uygulamasıdır. Örneğin, Öğrenci varlığının bu web sayfasının güncelleştirmemesi veya eklememesi gereken bir Secret özellik içerdiğini varsayalım:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Uygulamanın oluşturma veya güncelleştirme Sayfasında bir Secret alanı olmasa bile, bir bilgisayar korsanı üst paylaşım yaparak değeri ayarlayabilirSecret.Razor Bir bilgisayar korsanı, form değeri göndermek için Fiddler gibi bir Secret araç kullanabilir veya javascript yazabilir. Özgün kod, model bağlayıcısının student örneği oluştururken kullandığı alanları sınırlamaz.

Hacker'ın form alanı için Secret belirttiği değer veritabanında güncelleştirilir. Aşağıdaki görüntüde, "OverPost" değeriyle alanı gönderilen form değerlerine ekleyen Secret Fiddler aracı gösterilmektedir.

Fiddler adding Secret field

"OverPost" değeri eklenen satırın Secret özelliğine başarıyla eklenir. Uygulama tasarımcısı özelliğin Oluştur sayfasıyla ayarlanmasını Secret hiçbir zaman amaçlamasa bile bu durum ortaya çıkar.

Modeli görüntüleme

Görünüm modelleri, fazla göndermeyi önlemek için alternatif bir yol sağlar.

Uygulama modeli genellikle etki alanı modeli olarak adlandırılır. Etki alanı modeli genellikle veritabanında karşılık gelen varlık için gereken tüm özellikleri içerir. Görünüm modeli yalnızca kullanıcı arabirimi sayfası için gereken özellikleri (örneğin Oluştur sayfası) içerir.

Görünüm modeline ek olarak, bazı uygulamalar Sayfalar sayfa modeli sınıfı ile tarayıcı arasında Razor veri geçirmek için bağlama modeli veya giriş modeli kullanır.

Aşağıdaki StudentVM görünüm modelini göz önünde bulundurun:

public class StudentVM
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
}

Aşağıdaki kod, yeni bir öğrenci oluşturmak için görünüm modelini kullanır StudentVM :

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

SetValues yöntemi, başka bir PropertyValues nesneden değerleri okuyarak bu nesnenin değerlerini ayarlar. SetValues özellik adı eşleştirmeyi kullanır. Görünüm modeli türü:

  • Model türüyle ilgili olması gerekmez.
  • Eşleşen özelliklere sahip olması gerekir.

kullanmak StudentVM için yerine Sayfa oluşturma kullanımı StudentVMStudentgerekir:

@page
@model CreateVMModel

@{
    ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Student</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="StudentVM.LastName" class="control-label"></label>
                <input asp-for="StudentVM.LastName" class="form-control" />
                <span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.FirstMidName" class="control-label"></label>
                <input asp-for="StudentVM.FirstMidName" class="form-control" />
                <span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
                <input asp-for="StudentVM.EnrollmentDate" class="form-control" />
                <span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-page="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Düzenle sayfasını güncelleştirme

içinde Pages/Students/Edit.cshtml.csve OnPostAsync yöntemlerini aşağıdaki kodla değiştirinOnGetAsync.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Kod değişiklikleri oluştur sayfasına benzer ve birkaç özel durum vardır:

  • FirstOrDefaultAsync ile FindAsyncdeğiştirildi. İlgili verileri dahil etmek zorunda olmadığınızda daha FindAsync verimli olur.
  • OnPostAsync parametresine id sahiptir.
  • Geçerli öğrenci boş bir öğrenci oluşturmak yerine veritabanından getirilir.

Uygulamayı çalıştırın ve öğrenci oluşturup düzenleyerek test edin.

Varlık Durumları

Veritabanı bağlamı, bellekteki varlıkların veritabanındaki karşılık gelen satırlarla eşitlenip eşitlenmediğini izler. Bu izleme bilgileri SaveChangesAsync çağrıldığında ne olacağını belirler. Örneğin, yöntemine AddAsync yeni bir varlık geçirildiğinde, bu varlığın durumu olarak Addedayarlanır. Çağrıldığında SaveChangesAsync , veritabanı bağlamı bir SQL INSERT komutu döndürür.

Bir varlık aşağıdaki durumlardan birinde olabilir:

  • Added: Varlık henüz veritabanında yok. SaveChanges yöntemi bir INSERT deyimini oluşturur.

  • Unchanged: Bu varlıkla hiçbir değişikliğin kaydedilmesi gerekmez. Varlık, veritabanından okunduğunda bu duruma sahiptir.

  • Modified: Varlığın özellik değerlerinin bazıları veya tümü değiştirildi. SaveChanges yöntemi bir UPDATE deyimini oluşturur.

  • Deleted: Varlık silinmek üzere işaretlendi. SaveChanges yöntemi bir DELETE deyimini oluşturur.

  • Detached: Varlık veritabanı bağlamı tarafından izlenmiyor.

Bir masaüstü uygulamasında durum değişiklikleri genellikle otomatik olarak ayarlanır. Varlık okunur, değişiklikler yapılır ve varlık durumu otomatik olarak olarak Modifieddeğiştirilir. Çağrısı SaveChanges , yalnızca değiştirilen özellikleri güncelleştiren bir SQL UPDATE deyimi oluşturur.

Bir web uygulamasında, DbContext bir varlığı okuyan ve verileri görüntüleyen, bir sayfa işlendikten sonra atılır. Bir sayfanın OnPostAsync yöntemi çağrıldığında, yeni bir web isteği yapılır ve yeni bir örneğiyle DbContext. Bu yeni bağlamda varlığın yeniden okunarak masaüstü işleme benzetimi gerçekleştirilir.

Sil sayfasını güncelleştirme

Bu bölümde, çağrısı SaveChanges başarısız olduğunda özel bir hata iletisi uygulanır.

içindeki Pages/Students/Delete.cshtml.cs kodu aşağıdaki kodla değiştirin:

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;
        private readonly ILogger<DeleteModel> _logger;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context,
                           ILogger<DeleteModel> logger)
        {
            _context = context;
            _logger = logger;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = String.Format("Delete {ID} failed. Try again", id);
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException ex)
            {
                _logger.LogError(ex, ErrorMessage);

                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Önceki kod:

  • Günlüğe Kaydetme ekler.
  • İsteğe bağlı parametresini saveChangesError yöntem imzasına OnGetAsync ekler. saveChangesError , öğrenci nesnesini silme hatasından sonra yönteminin çağrılıp çağrılmadığını gösterir.

Geçici ağ sorunları nedeniyle silme işlemi başarısız olabilir. Veritabanı bulutta olduğunda geçici ağ hataları daha olasıdır. saveChangesError Parametresi, false Kullanıcı arabiriminden Delete sayfası OnGetAsync çağrıldığında yapılır. Silme işlemi başarısız olduğundan tarafından çağrıldığında OnGetAsync parametresidir saveChangesErrortrue.OnPostAsync

OnPostAsync yöntemi seçili varlığı alır, ardından varlığın durumunu Deletedolarak ayarlamak için Remove yöntemini çağırır. Çağrıldığında SaveChanges bir SQL DELETE komutu oluşturulur. Başarısız olursa Remove :

  • Veritabanı özel durumu yakalanmış.
  • Delete pages OnGetAsync yöntemi ile saveChangesError=trueçağrılır.

içine Pages/Students/Delete.cshtmlbir hata iletisi ekleyin:

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Sil sayfasını test etmek için uygulamayı çalıştırın ve bir öğrenciyi silin.

Sonraki adımlar

Bu öğreticide, iskelesi oluşturulmuş CRUD (oluşturma, okuma, güncelleştirme, silme) kodu gözden geçirilir ve özelleştirilir.

Depo yok

Bazı geliştiriciler, kullanıcı arabirimi (Razor Sayfalar) ile veri erişim katmanı arasında bir soyutlama katmanı oluşturmak için bir hizmet katmanı veya depo deseni kullanır. Bu öğretici bunu yapmaz. Karmaşıklığı en aza indirmek ve öğreticinin üzerinde EF CoreEF Core odaklanmasını sağlamak için kod doğrudan sayfa modeli sınıflarına eklenir.

Ayrıntılar sayfasını güncelleştirme

Öğrenciler sayfaları için yapı iskelesi oluşturulmuş kod kayıt verilerini içermez. Bu bölümde kayıtlar Ayrıntılar sayfasına eklenir.

Kayıtları okuma

Öğrencinin kayıt verilerini sayfada görüntülemek için kayıt verilerinin okunması gerekir. içindeki yapı iskelesi oluşturulmuş kod Pages/Students/Details.cshtml.cs , Kayıt verileri olmadan yalnızca Öğrenci verilerini okur:

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

OnGetAsync Seçilen öğrencinin kayıt verilerini okumak için yöntemini aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students
        .Include(s => s.Enrollments)
        .ThenInclude(e => e.Course)
        .AsNoTracking()
        .FirstOrDefaultAsync(m => m.ID == id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

Include ve ThenInclude yöntemleri, bağlamın gezinti özelliğini yüklemesine Student.Enrollments ve her kayıtta gezinti özelliğinin Enrollment.Course içine neden olur. Bu yöntemler, İlgili verileri okuma öğreticisinde ayrıntılı olarak incelenir.

yöntemi, AsNoTracking döndürülen varlıkların geçerli bağlamda güncelleştirilmediği senaryolarda performansı artırır. AsNoTracking bu öğreticinin ilerleyen bölümlerinde ele alınmalıdır.

Kayıtları görüntüleme

Kayıtların listesini görüntülemek için içindeki Pages/Students/Details.cshtml kodu aşağıdaki kodla değiştirin. Değişiklikler vurgulanır.

@page
@model ContosoUniversity.Pages.Students.DetailsModel

@{
    ViewData["Title"] = "Details";
}

<h1>Details</h1>

<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.Enrollments)
        </dt>
        <dd class="col-sm-10">
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Student.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<div>
    <a asp-page="./Edit" asp-route-id="@Model.Student.ID">Edit</a> |
    <a asp-page="./Index">Back to List</a>
</div>

Yukarıdaki kod, gezinti özelliğindeki Enrollments varlıklar arasında döngü oluşturur. Her kayıt için kurs başlığını ve notu görüntüler. Kurs başlığı, Kayıtlar varlığının gezinti özelliğinde Course depolanan Kurs varlığından alınır.

Uygulamayı çalıştırın, Öğrenciler sekmesini seçin ve bir öğrencinin Ayrıntılar bağlantısına tıklayın. Seçilen öğrencinin ders ve not listesi görüntülenir.

Bir varlığı okumanın yolları

Oluşturulan kod, bir varlığı okumak için FirstOrDefaultAsync kullanır. Hiçbir şey bulunmazsa bu yöntem null döndürür; aksi takdirde, sorgu filtresi ölçütlerini karşılayan ilk satırı döndürür. FirstOrDefaultAsync genellikle aşağıdaki alternatiflerden daha iyi bir seçimdir:

  • SingleOrDefaultAsync - Sorgu filtresini karşılayan birden fazla varlık varsa bir özel durum oluşturur. Sorgu tarafından birden fazla satır döndürülebilir olup olmadığını belirlemek için birden SingleOrDefaultAsync çok satır getirmeye çalışır. Sorgu benzersiz bir anahtarda arama yaparken olduğu gibi yalnızca bir varlık döndürebiliyorsa bu ek iş gereksizdir.
  • FindAsync - Birincil anahtara (PK) sahip bir varlık bulur. PK içeren bir varlık bağlam tarafından izleniyorsa, veritabanına bir istekte bulunmaksızın döndürülür. Bu yöntem, tek bir varlığı aramak için iyileştirilmiştir, ancak ile FindAsyncçağrı Include yapamazsınız. Bu nedenle, ilgili verilere ihtiyaç duyulduğu FirstOrDefaultAsync takdirde daha iyi bir seçimdir.

Verileri yönlendirme ve sorgu dizesi karşılaştırması

Ayrıntılar sayfasının URL'si şeklindedir https://localhost:<port>/Students/Details?id=1. Varlığın birincil anahtar değeri sorgu dizesindedir. Bazı geliştiriciler yol verilerinde anahtar değerini geçirmeyi tercih eder: https://localhost:<port>/Students/Details/1. Daha fazla bilgi için bkz . Oluşturulan kodu güncelleştirme.

Oluştur sayfasını güncelleştirme

Oluştur sayfasının yapı iskelesi oluşturulmuş OnPostAsync kodu, fazla paylaşıma karşı savunmasızdır. OnPostAsync içindeki Pages/Students/Create.cshtml.cs yöntemini aşağıdaki kodla değiştirin.

public async Task<IActionResult> OnPostAsync()
{
    var emptyStudent = new Student();

    if (await TryUpdateModelAsync<Student>(
        emptyStudent,
        "student",   // Prefix for form value.
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        _context.Students.Add(emptyStudent);
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

TryUpdateModelAsync

Yukarıdaki kod bir Student nesnesi oluşturur ve ardından Student nesnesinin özelliklerini güncelleştirmek için gönderilen form alanlarını kullanır. TryUpdateModelAsync yöntemi:

Uygulamayı çalıştırın ve Oluştur sayfasını test etmek için bir öğrenci varlığı oluşturun.

Üst Paylaşım

Alanları TryUpdateModel deftere nakledilmiş değerlerle güncelleştirmek için kullanmak, fazla paylaşım yapılmasını önlediği için en iyi güvenlik uygulamasıdır. Örneğin, Öğrenci varlığının bu web sayfasının güncelleştirmemesi veya eklememesi gereken bir Secret özellik içerdiğini varsayalım:

public class Student
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstMidName { get; set; }
    public DateTime EnrollmentDate { get; set; }
    public string Secret { get; set; }
}

Uygulamanın oluşturma veya güncelleştirme Sayfasında bir Secret alanı olmasa bile, bir bilgisayar korsanı üst paylaşım yaparak değeri ayarlayabilirSecret.Razor Bir bilgisayar korsanı, form değeri göndermek için Fiddler gibi bir Secret araç kullanabilir veya javascript yazabilir. Özgün kod, model bağlayıcısının student örneği oluştururken kullandığı alanları sınırlamaz.

Hacker'ın form alanı için Secret belirttiği değer veritabanında güncelleştirilir. Aşağıdaki görüntüde, fiddler aracının gönderilen form değerlerine Secret alanı ("OverPost" değeriyle) eklemesi gösterilmektedir.

Fiddler adding Secret field

"OverPost" değeri eklenen satırın Secret özelliğine başarıyla eklenir. Uygulama tasarımcısı özelliğin Oluştur sayfasıyla ayarlanmasını Secret hiçbir zaman amaçlamasa bile bu durum ortaya çıkar.

Modeli görüntüleme

Görünüm modelleri, fazla göndermeyi önlemek için alternatif bir yol sağlar.

Uygulama modeli genellikle etki alanı modeli olarak adlandırılır. Etki alanı modeli genellikle veritabanında karşılık gelen varlık için gereken tüm özellikleri içerir. Görünüm modeli yalnızca kullanıcı arabirimi için gerekli olan özellikleri içerir (örneğin, Oluştur sayfası).

Görünüm modeline ek olarak, bazı uygulamalar Sayfalar sayfa modeli sınıfı ile tarayıcı arasında Razor veri geçirmek için bağlama modeli veya giriş modeli kullanır.

Aşağıdaki Student görünüm modelini göz önünde bulundurun:

using System;

namespace ContosoUniversity.Models
{
    public class StudentVM
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
    }
}

Aşağıdaki kod, yeni bir öğrenci oluşturmak için görünüm modelini kullanır StudentVM :

[BindProperty]
public StudentVM StudentVM { get; set; }

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    var entry = _context.Add(new Student());
    entry.CurrentValues.SetValues(StudentVM);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}

SetValues yöntemi, başka bir PropertyValues nesneden değerleri okuyarak bu nesnenin değerlerini ayarlar. SetValues özellik adı eşleştirmeyi kullanır. Görünüm modeli türünün model türüyle ilişkili olması gerekmez, yalnızca eşleşen özelliklere sahip olması gerekir.

kullanmak StudentVM için Create.cshtml dosyasının yerine Studentkullanılacak StudentVM şekilde güncelleştirilmiş olması gerekir.

Düzenle sayfasını güncelleştirme

içinde Pages/Students/Edit.cshtml.csve OnPostAsync yöntemlerini aşağıdaki kodla değiştirinOnGetAsync.

public async Task<IActionResult> OnGetAsync(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    Student = await _context.Students.FindAsync(id);

    if (Student == null)
    {
        return NotFound();
    }
    return Page();
}

public async Task<IActionResult> OnPostAsync(int id)
{
    var studentToUpdate = await _context.Students.FindAsync(id);

    if (studentToUpdate == null)
    {
        return NotFound();
    }

    if (await TryUpdateModelAsync<Student>(
        studentToUpdate,
        "student",
        s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
    {
        await _context.SaveChangesAsync();
        return RedirectToPage("./Index");
    }

    return Page();
}

Kod değişiklikleri oluştur sayfasına benzer ve birkaç özel durum vardır:

  • FirstOrDefaultAsync ile FindAsyncdeğiştirildi. dahil edildiğinde ilgili veriler gerekli FindAsync olmadığında daha verimli olur.
  • OnPostAsync parametresine id sahiptir.
  • Geçerli öğrenci boş bir öğrenci oluşturmak yerine veritabanından getirilir.

Uygulamayı çalıştırın ve öğrenci oluşturup düzenleyerek test edin.

Varlık Durumları

Veritabanı bağlamı, bellekteki varlıkların veritabanındaki karşılık gelen satırlarla eşitlenip eşitlenmediğini izler. Bu izleme bilgileri SaveChangesAsync çağrıldığında ne olacağını belirler. Örneğin, yöntemine AddAsync yeni bir varlık geçirildiğinde, bu varlığın durumu olarak Addedayarlanır. Çağrıldığında SaveChangesAsync , veritabanı bağlamı bir SQL INSERT komutu döndürür.

Bir varlık aşağıdaki durumlardan birinde olabilir:

  • Added: Varlık henüz veritabanında yok. SaveChanges yöntemi bir INSERT deyimi oluşturur.

  • Unchanged: Bu varlıkla hiçbir değişikliğin kaydedilmesi gerekmez. Varlık, veritabanından okunduğunda bu duruma sahiptir.

  • Modified: Varlığın özellik değerlerinin bazıları veya tümü değiştirildi. SaveChanges yöntemi bir UPDATE deyimi oluşturur.

  • Deleted: Varlık silinmek üzere işaretlendi. SaveChanges yöntemi bir DELETE deyimi oluşturur.

  • Detached: Varlık veritabanı bağlamı tarafından izlenmiyor.

Bir masaüstü uygulamasında durum değişiklikleri genellikle otomatik olarak ayarlanır. Varlık okunur, değişiklikler yapılır ve varlık durumu otomatik olarak olarak Modifieddeğiştirilir. Çağrısı SaveChanges , yalnızca değiştirilen özellikleri güncelleştiren bir SQL UPDATE deyimi oluşturur.

Bir web uygulamasında, DbContext bir varlığı okuyan ve verileri görüntüleyen, bir sayfa işlendikten sonra atılır. Bir sayfanın OnPostAsync yöntemi çağrıldığında, yeni bir web isteği yapılır ve yeni bir örneğiyle DbContext. Bu yeni bağlamda varlığın yeniden okunarak masaüstü işleme benzetimi gerçekleştirilir.

Sil sayfasını güncelleştirme

Bu bölümde, çağrısı SaveChanges başarısız olduğunda özel bir hata iletisi uygulayacaksınız.

Pages/Students/Delete.cshtml.cs içindeki kodu aşağıdaki kodla değiştirin. Değişiklikler vurgulanır (deyimlerin using temizlenmesi dışında).

using ContosoUniversity.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;

namespace ContosoUniversity.Pages.Students
{
    public class DeleteModel : PageModel
    {
        private readonly ContosoUniversity.Data.SchoolContext _context;

        public DeleteModel(ContosoUniversity.Data.SchoolContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Student Student { get; set; }
        public string ErrorMessage { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id, bool? saveChangesError = false)
        {
            if (id == null)
            {
                return NotFound();
            }

            Student = await _context.Students
                .AsNoTracking()
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Student == null)
            {
                return NotFound();
            }

            if (saveChangesError.GetValueOrDefault())
            {
                ErrorMessage = "Delete failed. Try again";
            }

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var student = await _context.Students.FindAsync(id);

            if (student == null)
            {
                return NotFound();
            }

            try
            {
                _context.Students.Remove(student);
                await _context.SaveChangesAsync();
                return RedirectToPage("./Index");
            }
            catch (DbUpdateException /* ex */)
            {
                //Log the error (uncomment ex variable name and write a log.)
                return RedirectToAction("./Delete",
                                     new { id, saveChangesError = true });
            }
        }
    }
}

Yukarıdaki kod, isteğe bağlı parametresini saveChangesError yöntem imzasına OnGetAsync ekler. saveChangesError , öğrenci nesnesini silme hatasından sonra yönteminin çağrılıp çağrılmadığını gösterir. Geçici ağ sorunları nedeniyle silme işlemi başarısız olabilir. Veritabanı bulutta olduğunda geçici ağ hataları daha olasıdır. saveChangesError Delete sayfası OnGetAsync kullanıcı arabiriminden çağrıldığında parametresi false değeridir. tarafından OnPostAsync çağrıldığında OnGetAsync (silme işlemi başarısız olduğundan), saveChangesError parametresi true değeridir.

OnPostAsync yöntemi seçili varlığı alır, ardından varlığın durumunu Deletedolarak ayarlamak için Remove yöntemini çağırır. Çağrıldığında SaveChanges bir SQL DELETE komutu oluşturulur. Başarısız olursa Remove :

  • Veritabanı özel durumu yakalanmış.
  • Delete sayfasının OnGetAsync yöntemi ile saveChangesError=trueçağrılır.

Sayfayı Sil'e Razor (Pages/Students/Delete.cshtml):

@page
@model ContosoUniversity.Pages.Students.DeleteModel

@{
    ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<p class="text-danger">@Model.ErrorMessage</p>

<h3>Are you sure you want to delete this?</h3>
<div>
    <h4>Student</h4>
    <hr />
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.LastName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.LastName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.FirstMidName)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.FirstMidName)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Student.EnrollmentDate)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Student.EnrollmentDate)
        </dd>
    </dl>

    <form method="post">
        <input type="hidden" asp-for="Student.ID" />
        <input type="submit" value="Delete" class="btn btn-danger" /> |
        <a asp-page="./Index">Back to List</a>
    </form>
</div>

Sil sayfasını test etmek için uygulamayı çalıştırın ve bir öğrenciyi silin.

Sonraki adımlar