Pengikatan Model di ASP.NET Core

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Artikel ini menjelaskan apa itu pengikatan model, cara kerjanya, dan cara menyesuaikan perilakunya.

Apa itu Pengikatan Model

Pengontrol dan Razor halaman bekerja dengan data yang berasal dari permintaan HTTP. Misalnya, data rute dapat menyediakan kunci rekaman, dan bidang formulir yang diposting dapat memberikan nilai untuk properti model. Menulis kode untuk mengambil masing-masing nilai ini dan mengonversinya dari string ke jenis .NET akan melelahkan dan rawan kesalahan. Pengikatan model mengotomatiskan proses ini. Sistem pengikatan model:

  • Mengambil data dari berbagai sumber seperti data rute, bidang formulir, dan string kueri.
  • Menyediakan data ke pengontrol dan Razor halaman dalam parameter metode dan properti publik.
  • Mengonversi data string ke jenis .NET.
  • Memperbarui properti jenis kompleks.

Contoh

Misalkan Anda memiliki metode tindakan berikut:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Dan aplikasi menerima permintaan dengan URL ini:

https://contoso.com/api/pets/2?DogsOnly=true

Pengikatan model melalui langkah-langkah berikut setelah sistem perutean memilih metode tindakan:

  • Menemukan parameter pertama dari , bilangan GetByIdbulat bernama id.
  • Lihat sumber yang tersedia dalam permintaan HTTP dan temukan id = "2" dalam data rute.
  • Mengonversi string "2" menjadi bilangan bulat 2.
  • Menemukan parameter berikutnya dari GetById, boolean bernama dogsOnly.
  • Telusuri sumber dan temukan "DogsOnly=true" dalam string kueri. Pencocokan nama tidak peka huruf besar/kecil.
  • Mengonversi string "true" menjadi boolean true.

Kerangka kerja kemudian memanggil GetById metode , meneruskan 2 untuk id parameter , dan true untuk dogsOnly parameter .

Dalam contoh sebelumnya, target pengikatan model adalah parameter metode yang merupakan jenis sederhana . Target mungkin juga merupakan properti dari jenis kompleks. Setelah setiap properti berhasil diikat, validasi model terjadi untuk properti tersebut. Catatan data apa yang terikat ke model, dan kesalahan pengikatan atau validasi apa pun, disimpan di ControllerBase.ModelState atau PageModel.ModelState. Untuk mengetahui apakah proses ini berhasil, aplikasi memeriksa bendera ModelState.IsValid .

Target

Pengikatan model mencoba menemukan nilai untuk jenis target berikut:

  • Parameter metode tindakan pengontrol tempat permintaan dirutekan.
  • Razor Parameter metode handler Pages tempat permintaan dirutekan.
  • Properti publik pengontrol atau PageModel kelas, jika ditentukan oleh atribut.

Atribut [BindProperty]

Dapat diterapkan ke properti publik pengontrol atau PageModel kelas untuk menyebabkan pengikatan model ke menargetkan properti tersebut:

public class EditModel : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

Atribut [BindProperties]

Dapat diterapkan ke pengontrol atau PageModel kelas untuk memberi tahu pengikatan model untuk menargetkan semua properti publik kelas:

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Pengikatan model untuk permintaan HTTP GET

Secara default, properti tidak terikat untuk permintaan HTTP GET. Biasanya, yang Anda butuhkan untuk permintaan GET adalah parameter ID rekaman. ID rekaman digunakan untuk mencari item dalam database. Oleh karena itu, tidak perlu mengikat properti yang menyimpan instans model. Dalam skenario di mana Anda ingin properti terikat ke data dari permintaan GET, atur properti ke SupportsGettrue:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }

Pengikatan model jenis sederhana dan kompleks

Pengikatan model menggunakan definisi tertentu untuk jenis yang dioperasikannya. Jenis sederhana dikonversi dari satu string menggunakan TypeConverter atau TryParse metode . Jenis kompleks dikonversi dari beberapa nilai input. Kerangka kerja menentukan perbedaan berdasarkan keberadaan TypeConverter atau TryParse. Sebaiknya buat pengonversi jenis atau gunakan TryParse untuk string konversi ke SomeType yang tidak memerlukan sumber daya eksternal atau beberapa input.

Sumber

Secara default, pengikatan model mendapatkan data dalam bentuk pasangan kunci-nilai dari sumber berikut dalam permintaan HTTP:

  1. Bidang formulir
  2. Isi permintaan (Untuk pengontrol yang memiliki atribut [ApiController].)
  3. Merutekan data
  4. Parameter untai kueri
  5. File yang diunggah

Untuk setiap parameter atau properti target, sumber dipindai dalam urutan yang ditunjukkan dalam daftar sebelumnya. Ada beberapa pengecualian:

  • Data rute dan nilai string kueri hanya digunakan untuk jenis sederhana .
  • File yang diunggah hanya terikat ke jenis target yang mengimplementasikan IFormFile atau IEnumerable<IFormFile>.

Jika sumber default tidak benar, gunakan salah satu atribut berikut untuk menentukan sumber:

  • [FromQuery] - Mendapatkan nilai dari string kueri.
  • [FromRoute] - Mendapatkan nilai dari data rute.
  • [FromForm] - Mendapatkan nilai dari bidang formulir yang diposting.
  • [FromBody] - Mendapatkan nilai dari isi permintaan.
  • [FromHeader] - Mendapatkan nilai dari header HTTP.

Atribut ini:

  • Ditambahkan ke properti model satu per satu dan bukan ke kelas model, seperti dalam contoh berikut:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Secara opsional menerima nilai nama model di konstruktor. Opsi ini disediakan jika nama properti tidak cocok dengan nilai dalam permintaan. Misalnya, nilai dalam permintaan mungkin berupa header dengan tanda hubung atas namanya, seperti dalam contoh berikut:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atribut [FromBody]

Terapkan [FromBody] atribut ke parameter untuk mengisi propertinya dari isi permintaan HTTP. Runtime ASP.NET Core mendelegasikan tanggung jawab membaca isi ke formatter input. Pemformat input dijelaskan nanti dalam artikel ini.

Ketika [FromBody] diterapkan ke parameter jenis kompleks, atribut sumber pengikatan apa pun yang diterapkan ke propertinya diabaikan. Misalnya, tindakan berikut Create menentukan bahwa parameternya pet diisi dari isi:

public ActionResult<Pet> Create([FromBody] Pet pet)

Kelas Pet menentukan bahwa propertinya Breed diisi dari parameter string kueri:

public class Pet
{
    public string Name { get; set; } = null!;

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; } = null!;
}

Dalam contoh sebelumnya:

  • Atribut [FromQuery] diabaikan.
  • Properti Breed tidak diisi dari parameter string kueri.

Pemformat input hanya membaca isi dan tidak memahami atribut sumber pengikatan. Jika nilai yang sesuai ditemukan dalam isi, nilai tersebut digunakan untuk mengisi Breed properti.

Jangan berlaku [FromBody] untuk lebih dari satu parameter per metode tindakan. Setelah aliran permintaan dibaca oleh pemformat input, aliran permintaan tidak lagi tersedia untuk dibaca lagi untuk mengikat parameter lain [FromBody] .

Sumber tambahan

Data sumber disediakan untuk sistem pengikatan model oleh penyedia nilai. Anda dapat menulis dan mendaftarkan penyedia nilai kustom yang mendapatkan data untuk pengikatan model dari sumber lain. Misalnya, Anda mungkin menginginkan data dari cookiestatus s atau sesi. Untuk mendapatkan data dari sumber baru:

  • Buat kelas yang mengimplementasikan IValueProvider.
  • Buat kelas yang mengimplementasikan IValueProviderFactory.
  • Daftarkan kelas pabrik di Program.cs.

Sampel mencakup penyedia nilai dan contoh pabrik yang mendapatkan nilai dari cookie s. Daftarkan pabrik penyedia nilai kustom di Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

Kode sebelumnya menempatkan penyedia nilai kustom setelah semua penyedia nilai bawaan. Untuk menjadikannya yang pertama dalam daftar, panggil Insert(0, new CookieValueProviderFactory()) alih-alih Add.

Tidak ada sumber untuk properti model

Secara default, kesalahan status model tidak dibuat jika tidak ada nilai yang ditemukan untuk properti model. Properti diatur ke null atau nilai default:

  • Jenis sederhana yang dapat diubah ke null diatur ke null.
  • Jenis nilai yang tidak dapat diubah ke null diatur ke default(T). Misalnya, parameter int id diatur ke 0.
  • Untuk Jenis kompleks, pengikatan model membuat instans dengan menggunakan konstruktor default, tanpa mengatur properti.
  • Array diatur ke Array.Empty<T>(), kecuali bahwa byte[] array diatur ke null.

Jika status model harus divalidasi ketika tidak ada yang ditemukan di bidang formulir untuk properti model, gunakan [BindRequired] atribut .

Perhatikan bahwa perilaku ini [BindRequired] berlaku untuk pengikatan model dari data formulir yang diposting, bukan ke JSdata ON atau XML dalam isi permintaan. Data isi permintaan ditangani oleh pemformat input.

Kesalahan konversi jenis

Jika sumber ditemukan tetapi tidak dapat dikonversi menjadi jenis target, status model ditandai sebagai tidak valid. Parameter target atau properti diatur ke null atau nilai default, seperti yang disebutkan di bagian sebelumnya.

Dalam pengontrol API yang memiliki [ApiController] atribut , status model yang tidak valid menghasilkan respons HTTP 400 otomatis.

Razor Di halaman, putar ulang halaman dengan pesan kesalahan:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    // ...

    return RedirectToPage("./Index");
}

Ketika halaman diputar ulang oleh kode sebelumnya, input yang tidak valid tidak ditampilkan di bidang formulir. Ini karena properti model telah diatur ke null atau nilai default. Input yang tidak valid memang muncul dalam pesan kesalahan. Jika Anda ingin memutar ulang data buruk di bidang formulir, pertimbangkan untuk menjadikan properti model sebagai string dan melakukan konversi data secara manual.

Strategi yang sama direkomendasikan jika Anda tidak ingin kesalahan konversi jenis mengakibatkan kesalahan status model. Dalam hal ini, jadikan properti model sebagai string.

Jenis sederhana

Lihat Model yang mengikat jenis sederhana dan kompleks untuk penjelasan tentang jenis sederhana dan kompleks.

Jenis sederhana pengikat model dapat mengonversi string sumber menjadi termasuk yang berikut:

Ikat dengan IParsable<T>.TryParse

IParsable<TSelf>.TryParse API mendukung nilai parameter tindakan pengikatan pengontrol:

public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);

Kelas berikut DateRange mengimplementasikan IParsable<TSelf> untuk mendukung pengikatan rentang tanggal:

public class DateRange : IParsable<DateRange>
{
    public DateOnly? From { get; init; }
    public DateOnly? To { get; init; }

    public static DateRange Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse(string? value,
                                IFormatProvider? provider, out DateRange dateRange)
    {
        var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries 
                                       | StringSplitOptions.TrimEntries);

        if (segments?.Length == 2
            && DateOnly.TryParse(segments[0], provider, out var fromDate)
            && DateOnly.TryParse(segments[1], provider, out var toDate))
        {
            dateRange = new DateRange { From = fromDate, To = toDate };
            return true;
        }

        dateRange = new DateRange { From = default, To = default };
        return false;
    }
}

Kode sebelumnya:

  • Mengonversi string yang mewakili dua tanggal menjadi DateRange objek
  • Pengikat IParsable<TSelf>.TryParse model menggunakan metode untuk mengikat DateRange.

Tindakan pengontrol berikut menggunakan DateRange kelas untuk mengikat rentang tanggal:

// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
                     && DateOnly.FromDateTime(wf.Date) <= range.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d"),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Kelas berikut Locale mengimplementasikan IParsable<TSelf> untuk mendukung pengikatan ke CultureInfo:

public class Locale : CultureInfo, IParsable<Locale>
{
    public Locale(string culture) : base(culture)
    {
    }

    public static Locale Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider, out Locale locale)
    {
        if (value is null)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
        
        try
        {
            locale = new Locale(value);
            return true;
        }
        catch (CultureNotFoundException)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
    }
}

Tindakan pengontrol berikut menggunakan Locale kelas untuk mengikat CultureInfo string:

// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View(weatherForecasts);
}

Tindakan pengontrol berikut menggunakan DateRange kelas dan Locale untuk mengikat rentang tanggal dengan CultureInfo:

// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
    {
        ModelState.TryAddModelError(nameof(range),
            $"Invalid date range: {range} for locale {locale.DisplayName}");

        return View("Error", ModelState.Values.SelectMany(v => v.Errors));
    }

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
                     && DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Aplikasi sampel API di GitHub menunjukkan sampel sebelumnya untuk pengontrol API.

Ikat dengan TryParse

TryParse API mendukung nilai parameter tindakan pengikatan pengontrol:

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

IParsable<T>.TryParse adalah pendekatan yang direkomendasikan untuk pengikatan parameter karena tidak seperti TryParse, itu tidak bergantung pada refleksi.

Kelas berikut DateRangeTP mengimplementasikan TryParse:

public class DateRangeTP
{
    public DateOnly? From { get; }
    public DateOnly? To { get; }

    public DateRangeTP(string from, string to)
    {
        if (string.IsNullOrEmpty(from))
            throw new ArgumentNullException(nameof(from));
        if (string.IsNullOrEmpty(to))
            throw new ArgumentNullException(nameof(to));

        From = DateOnly.Parse(from);
        To = DateOnly.Parse(to);
    }

    public static bool TryParse(string? value, out DateRangeTP? result)
    {
        var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (range?.Length != 2)
        {
            result = default;
            return false;
        }

        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

Tindakan pengontrol berikut menggunakan DateRangeTP kelas untuk mengikat rentang tanggal:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
                     && DateOnly.FromDateTime(wf.Date) <= range.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d"),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Jenis kompleks

Jenis kompleks harus memiliki konstruktor default publik dan properti bisa-tulis publik untuk diikat. Saat pengikatan model terjadi, kelas dibuat menggunakan konstruktor default publik.

Untuk setiap properti dari jenis kompleks, pengikatan model melihat melalui sumber untuk polanama prefix.property_name. Jika tidak ada yang ditemukan, ia mencari hanya property_name tanpa awalan. Keputusan untuk menggunakan awalan tidak dibuat per properti. Misalnya, dengan kueri yang berisi ?Instructor.Id=100&Name=foo, terikat ke metode OnGet(Instructor instructor), objek jenis Instructor yang dihasilkan berisi:

  • Id atur ke 100.
  • Name atur ke null. Pengikatan model mengharapkan Instructor.Name karena Instructor.Id digunakan dalam parameter kueri sebelumnya.

Untuk mengikat parameter, awalannya adalah nama parameter. Untuk mengikat ke PageModel properti publik, awalannya adalah nama properti publik. Beberapa atribut memiliki Prefix properti yang memungkinkan Anda mengambil alih penggunaan default parameter atau nama properti.

Misalnya, misalkan jenis kompleks adalah kelas berikut Instructor :

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Awalan = nama parameter

Jika model yang akan diikat adalah parameter bernama instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci instructorToUpdate.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan = nama properti

Jika model yang akan diikat adalah properti bernama Instructor pengontrol atau PageModel kelas:

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

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan kustom

Jika model yang akan diikat adalah parameter bernama instructorToUpdate dan Bind atribut menentukan Instructor sebagai awalan:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Atribut untuk target jenis kompleks

Beberapa atribut bawaan tersedia untuk mengontrol pengikatan model dari jenis kompleks:

Peringatan

Atribut ini memengaruhi pengikatan model saat data formulir yang diposting adalah sumber nilai. Mereka tidak memengaruhi pemformat input, proses mana yang memposting JSbadan permintaan ON dan XML. Pemformat input dijelaskan nanti dalam artikel ini.

Atribut [Ikat]

Dapat diterapkan ke kelas atau parameter metode. Menentukan properti model mana yang harus disertakan dalam pengikatan model. [Bind]tidak memengaruhi pemformat input.

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat ketika penangan atau metode tindakan apa pun dipanggil:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat saat OnPost metode dipanggil:

[HttpPost]
public IActionResult OnPost(
    [Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

Atribut [Bind] dapat digunakan untuk melindungi dari overposting dalam membuat skenario. Ini tidak berfungsi dengan baik dalam skenario edit karena properti yang dikecualikan diatur ke null atau nilai default alih-alih dibiarkan tidak berubah. Untuk pertahanan terhadap overposting, lihat model direkomendasikan daripada [Bind] atribut . Untuk informasi selengkapnya, lihat Catatan keamanan tentang overposting.

Atribut [ModelBinder]

ModelBinderAttribute dapat diterapkan ke jenis, properti, atau parameter. Ini memungkinkan menentukan jenis pengikat model yang digunakan untuk mengikat instans atau jenis tertentu. Contohnya:

[HttpPost]
public IActionResult OnPost(
    [ModelBinder<MyInstructorModelBinder>] Instructor instructor)

Atribut [ModelBinder] juga dapat digunakan untuk mengubah nama properti atau parameter saat sedang terikat model:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    // ...
}

Atribut [BindRequired]

Menyebabkan pengikatan model untuk menambahkan kesalahan status model jika pengikatan tidak dapat terjadi untuk properti model. Berikut contohnya:

public class InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

Lihat juga diskusi [Required] atribut dalam validasi Model.

Atribut [BindNever]

Dapat diterapkan ke properti atau jenis. Mencegah pengikatan model dari pengaturan properti model. Saat diterapkan ke jenis, sistem pengikatan model mengecualikan semua properti yang ditentukan jenis. Berikut contohnya:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Koleksi

Untuk target yang merupakan kumpulan jenis sederhana, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter yang akan diikat adalah array bernama selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Data string formulir atau kueri bisa dalam salah satu format berikut:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

    Hindari mengikat parameter atau properti bernama index atau Index jika berdekatan dengan nilai koleksi. Pengikatan model mencoba digunakan index sebagai indeks untuk koleksi yang mungkin mengakibatkan pengikatan yang salah. Misalnya, pertimbangkan tindakan berikut:

    public IActionResult Post(string index, List<Product> products)
    

    Dalam kode sebelumnya, index parameter string kueri mengikat parameter index metode dan juga digunakan untuk mengikat koleksi produk. Mengganti nama index parameter atau menggunakan atribut pengikatan model untuk mengonfigurasi pengikatan menghindari masalah ini:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Format berikut ini hanya didukung dalam data formulir:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan array dua item ke selectedCourses parameter :

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Format data yang menggunakan nomor subskrip (... [0] ... [1] ...) harus memastikan bahwa mereka diberi nomor secara berurutan mulai dari nol. Jika ada celah dalam penomoran subskrip, semua item setelah celah diabaikan. Misalnya, jika subskrip adalah 0 dan 2, bukan 0 dan 1, item kedua diabaikan.

Kamus

Untuk Dictionary target, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter target bernama Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Formulir yang diposting atau data string kueri bisa terlihat seperti salah satu contoh berikut:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan kamus dua item ke selectedCourses parameter :

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Pengikatan konstruktor dan jenis rekaman

Pengikatan model mengharuskan jenis kompleks memiliki konstruktor tanpa parameter. Pemformat System.Text.Json input berbasis dan Newtonsoft.Json mendukung deserialisasi kelas yang tidak memiliki konstruktor tanpa parameter.

Jenis rekaman adalah cara yang bagus untuk mewakili data melalui jaringan. ASP.NET Core mendukung pengikatan model dan memvalidasi jenis rekaman dengan satu konstruktor:

public record Person(
    [Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
<br />
Age: <input asp-for="Age" />

Saat memvalidasi jenis rekaman, runtime mencari metadata pengikatan dan validasi khusus pada parameter daripada pada properti.

Kerangka kerja memungkinkan pengikatan ke dan memvalidasi jenis rekaman:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Agar sebelumnya berfungsi, jenisnya harus:

  • Jadilah jenis catatan.
  • Memiliki tepat satu konstruktor publik.
  • Berisi parameter yang memiliki properti dengan nama dan jenis yang sama. Nama tidak boleh berbeda menurut kasus.

POCO tanpa konstruktor tanpa parameter

POCO yang tidak memiliki konstruktor tanpa parameter tidak dapat terikat.

Kode berikut menghasilkan pengecualian yang mengatakan bahwa jenis harus memiliki konstruktor tanpa parameter:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
    public Person(string Name) : this (Name, 0);
}

Jenis rekaman dengan konstruktor yang ditulis secara manual

Jenis rekaman dengan konstruktor yang ditulis secara manual yang terlihat seperti pekerjaan konstruktor utama

public record Person
{
    public Person([Required] string Name, [Range(0, 100)] int Age)
        => (this.Name, this.Age) = (Name, Age);

    public string Name { get; set; }
    public int Age { get; set; }
}

Jenis rekaman, validasi, dan metadata pengikatan

Untuk jenis rekaman, metadata validasi dan pengikatan pada parameter digunakan. Metadata apa pun pada properti diabaikan

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validasi dan metadata

Validasi menggunakan metadata pada parameter tetapi menggunakan properti untuk membaca nilai. Dalam kasus biasa dengan konstruktor utama, keduanya akan identik. Namun, ada cara untuk mengalahkannya:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel tidak memperbarui parameter pada jenis catatan

public record Person(string Name)
{
    public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Dalam hal ini, MVC tidak akan mencoba mengikat Name lagi. Namun, Age diizinkan untuk diperbarui

Perilaku globalisasi data rute pengikatan model dan string kueri

Penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri:

  • Perlakukan nilai sebagai budaya invarian.
  • Harapkan bahwa URL invarian budaya.

Sebaliknya, nilai yang berasal dari data formulir mengalami konversi sensitif terhadap budaya. Ini dirancang agar URL dapat dibagikan di seluruh lokal.

Untuk membuat penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri menjalani konversi yang sensitif terhadap budaya:

public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query?.Count > 0)
        {
            context.ValueProviders.Add(
                new QueryStringValueProvider(
                    BindingSource.Query,
                    query,
                    CultureInfo.CurrentCulture));
        }

        return Task.CompletedTask;
    }
}
builder.Services.AddControllers(options =>
{
    var index = options.ValueProviderFactories.IndexOf(
        options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
            .Single());

    options.ValueProviderFactories[index] =
        new CultureQueryStringValueProviderFactory();
});

Tipe data khusus

Ada beberapa jenis data khusus yang dapat ditangani oleh pengikatan model.

IFormFile dan IFormFileCollection

File yang diunggah disertakan dalam permintaan HTTP. Juga didukung adalah IEnumerable<IFormFile> untuk beberapa file.

TokenPembatalan

Tindakan dapat secara opsional mengikat CancellationToken sebagai parameter. Ini mengikat yang memberi sinyal ketika koneksi yang mendasar RequestAborted permintaan HTTP dibatalkan. Tindakan dapat menggunakan parameter ini untuk membatalkan operasi asinkron jangka panjang yang dijalankan sebagai bagian dari tindakan pengontrol.

FormCollection

Digunakan untuk mengambil semua nilai dari data formulir yang diposting.

Pemformat input

Data dalam isi permintaan dapat berada di JSON, XML, atau beberapa format lainnya. Untuk mengurai data ini, pengikatan model menggunakan pemformat input yang dikonfigurasi untuk menangani jenis konten tertentu. Secara default, ASP.NET Core menyertakan JSpemformat input berbasis ON untuk menangani JSdata ON. Anda bisa menambahkan formatter lain untuk tipe isi lainnya.

ASP.NET Core memilih pemformat input berdasarkan atribut Konsumsi . Jika tidak ada atribut, atribut menggunakan header Content-Type.

Untuk menggunakan pemformat input XML bawaan:

Menyesuaikan pengikatan model dengan pemformat input

Pemformat input bertanggung jawab penuh untuk membaca data dari isi permintaan. Untuk menyesuaikan proses ini, konfigurasikan API yang digunakan oleh pemformat input. Bagian ini menjelaskan cara mengkustomisasi System.Text.Jsonpemformat input berbasis untuk memahami jenis kustom bernama ObjectId.

Pertimbangkan model berikut, yang berisi properti kustom ObjectId :

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

Untuk menyesuaikan proses pengikatan model saat menggunakan System.Text.Json, buat kelas yang berasal dari JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => new(JsonSerializer.Deserialize<int>(ref reader, options));

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value.Id);
}

Untuk menggunakan pengonversi kustom, terapkan JsonConverterAttribute atribut ke jenis . Dalam contoh berikut, jenis dikonfigurasi ObjectId dengan ObjectIdConverter sebagai pengonversi kustomnya:

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Untuk informasi selengkapnya, lihat Cara menulis pengonversi kustom.

Mengecualikan jenis yang ditentukan dari pengikatan model

Perilaku sistem pengikatan dan validasi model didorong oleh ModelMetadata. Anda dapat menyesuaikan ModelMetadata dengan menambahkan penyedia detail ke MvcOptions.ModelMetadataDetailsProviders. Penyedia detail bawaan tersedia untuk menonaktifkan pengikatan atau validasi model untuk jenis tertentu.

Untuk menonaktifkan pengikatan model pada semua model dari jenis tertentu, tambahkan ExcludeBindingMetadataProvider di Program.cs. Misalnya, untuk menonaktifkan pengikatan model pada semua model jenis System.Version:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Untuk menonaktifkan validasi pada properti dari jenis tertentu, tambahkan SuppressChildValidationMetadataProvider di Program.cs. Misalnya, untuk menonaktifkan validasi pada properti jenis System.Guid:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Pengikat model kustom

Anda dapat memperluas pengikatan model dengan menulis pengikat model kustom dan menggunakan [ModelBinder] atribut untuk memilihnya untuk target tertentu. Pelajari selengkapnya tentang pengikatan model kustom.

Pengikatan model manual

Pengikatan model dapat dipanggil secara manual dengan menggunakan TryUpdateModelAsync metode . Metode ini didefinisikan pada kedua ControllerBase kelas dan PageModel . Kelebihan metode memungkinkan Anda menentukan awalan dan penyedia nilai yang akan digunakan. Metode mengembalikan false jika pengikatan model gagal. Berikut contohnya:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync menggunakan penyedia nilai untuk mendapatkan data dari isi formulir, string kueri, dan data rute. TryUpdateModelAsync biasanya:

  • Digunakan dengan Razor aplikasi Pages dan MVC menggunakan pengontrol dan tampilan untuk mencegah pengeposan berlebihan.
  • Tidak digunakan dengan API web kecuali dikonsumsi dari data formulir, string kueri, dan data rute. Titik akhir API Web yang menggunakan JSON menggunakan Pemformat input untuk mendeserialisasi isi permintaan ke dalam objek.

Untuk informasi selengkapnya, lihat TryUpdateModelAsync.

Atribut [FromServices]

Nama atribut ini mengikuti pola atribut pengikatan model yang menentukan sumber data. Tetapi ini bukan tentang mengikat data dari penyedia nilai. Ini mendapatkan instans jenis dari kontainer injeksi dependensi. Tujuannya adalah untuk memberikan alternatif untuk injeksi konstruktor ketika Anda membutuhkan layanan hanya jika metode tertentu dipanggil.

Jika instans jenis tidak terdaftar dalam kontainer injeksi dependensi, aplikasi akan memberikan pengecualian saat mencoba mengikat parameter. Untuk membuat parameter opsional, gunakan salah satu pendekatan berikut:

  • Buat parameter nullable.
  • Tetapkan nilai default untuk parameter .

Untuk parameter nullable, pastikan parameter tidak null sebelum mengaksesnya.

Sumber Daya Tambahan:

Artikel ini menjelaskan apa itu pengikatan model, cara kerjanya, dan cara menyesuaikan perilakunya.

Apa itu Pengikatan Model

Pengontrol dan Razor halaman bekerja dengan data yang berasal dari permintaan HTTP. Misalnya, data rute dapat menyediakan kunci rekaman, dan bidang formulir yang diposting dapat memberikan nilai untuk properti model. Menulis kode untuk mengambil masing-masing nilai ini dan mengonversinya dari string ke jenis .NET akan melelahkan dan rawan kesalahan. Pengikatan model mengotomatiskan proses ini. Sistem pengikatan model:

  • Mengambil data dari berbagai sumber seperti data rute, bidang formulir, dan string kueri.
  • Menyediakan data ke pengontrol dan Razor halaman dalam parameter metode dan properti publik.
  • Mengonversi data string ke jenis .NET.
  • Memperbarui properti jenis kompleks.

Contoh

Misalkan Anda memiliki metode tindakan berikut:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Dan aplikasi menerima permintaan dengan URL ini:

https://contoso.com/api/pets/2?DogsOnly=true

Pengikatan model melalui langkah-langkah berikut setelah sistem perutean memilih metode tindakan:

  • Menemukan parameter pertama dari , bilangan GetByIdbulat bernama id.
  • Lihat sumber yang tersedia dalam permintaan HTTP dan temukan id = "2" dalam data rute.
  • Mengonversi string "2" menjadi bilangan bulat 2.
  • Menemukan parameter berikutnya dari GetById, boolean bernama dogsOnly.
  • Telusuri sumber dan temukan "DogsOnly=true" dalam string kueri. Pencocokan nama tidak peka huruf besar/kecil.
  • Mengonversi string "true" menjadi boolean true.

Kerangka kerja kemudian memanggil GetById metode , meneruskan 2 untuk id parameter , dan true untuk dogsOnly parameter .

Dalam contoh sebelumnya, target pengikatan model adalah parameter metode yang merupakan jenis sederhana . Target mungkin juga merupakan properti dari jenis kompleks. Setelah setiap properti berhasil diikat, validasi model terjadi untuk properti tersebut. Catatan data apa yang terikat ke model, dan kesalahan pengikatan atau validasi apa pun, disimpan di ControllerBase.ModelState atau PageModel.ModelState. Untuk mengetahui apakah proses ini berhasil, aplikasi memeriksa bendera ModelState.IsValid .

Target

Pengikatan model mencoba menemukan nilai untuk jenis target berikut:

  • Parameter metode tindakan pengontrol tempat permintaan dirutekan.
  • Razor Parameter metode handler Pages tempat permintaan dirutekan.
  • Properti publik pengontrol atau PageModel kelas, jika ditentukan oleh atribut.

Atribut [BindProperty]

Dapat diterapkan ke properti publik pengontrol atau PageModel kelas untuk menyebabkan pengikatan model ke menargetkan properti tersebut:

public class EditModel : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

Atribut [BindProperties]

Dapat diterapkan ke pengontrol atau PageModel kelas untuk memberi tahu pengikatan model untuk menargetkan semua properti publik kelas:

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Pengikatan model untuk permintaan HTTP GET

Secara default, properti tidak terikat untuk permintaan HTTP GET. Biasanya, yang Anda butuhkan untuk permintaan GET adalah parameter ID rekaman. ID rekaman digunakan untuk mencari item dalam database. Oleh karena itu, tidak perlu mengikat properti yang menyimpan instans model. Dalam skenario di mana Anda ingin properti terikat ke data dari permintaan GET, atur properti ke SupportsGettrue:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }

Pengikatan model jenis sederhana dan kompleks

Pengikatan model menggunakan definisi tertentu untuk jenis yang dioperasikannya. Jenis sederhana dikonversi dari satu string menggunakan TypeConverter atau TryParse metode . Jenis kompleks dikonversi dari beberapa nilai input. Kerangka kerja menentukan perbedaan berdasarkan keberadaan TypeConverter atau TryParse. Sebaiknya buat pengonversi jenis atau gunakan TryParse untuk string konversi ke SomeType yang tidak memerlukan sumber daya eksternal atau beberapa input.

Sumber

Secara default, pengikatan model mendapatkan data dalam bentuk pasangan kunci-nilai dari sumber berikut dalam permintaan HTTP:

  1. Bidang formulir
  2. Isi permintaan (Untuk pengontrol yang memiliki atribut [ApiController].)
  3. Merutekan data
  4. Parameter untai kueri
  5. File yang diunggah

Untuk setiap parameter atau properti target, sumber dipindai dalam urutan yang ditunjukkan dalam daftar sebelumnya. Ada beberapa pengecualian:

  • Data rute dan nilai string kueri hanya digunakan untuk jenis sederhana .
  • File yang diunggah hanya terikat ke jenis target yang mengimplementasikan IFormFile atau IEnumerable<IFormFile>.

Jika sumber default tidak benar, gunakan salah satu atribut berikut untuk menentukan sumber:

  • [FromQuery] - Mendapatkan nilai dari string kueri.
  • [FromRoute] - Mendapatkan nilai dari data rute.
  • [FromForm] - Mendapatkan nilai dari bidang formulir yang diposting.
  • [FromBody] - Mendapatkan nilai dari isi permintaan.
  • [FromHeader] - Mendapatkan nilai dari header HTTP.

Atribut ini:

  • Ditambahkan ke properti model satu per satu dan bukan ke kelas model, seperti dalam contoh berikut:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Secara opsional menerima nilai nama model di konstruktor. Opsi ini disediakan jika nama properti tidak cocok dengan nilai dalam permintaan. Misalnya, nilai dalam permintaan mungkin berupa header dengan tanda hubung atas namanya, seperti dalam contoh berikut:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atribut [FromBody]

Terapkan [FromBody] atribut ke parameter untuk mengisi propertinya dari isi permintaan HTTP. Runtime ASP.NET Core mendelegasikan tanggung jawab membaca isi ke formatter input. Pemformat input dijelaskan nanti dalam artikel ini.

Ketika [FromBody] diterapkan ke parameter jenis kompleks, atribut sumber pengikatan apa pun yang diterapkan ke propertinya diabaikan. Misalnya, tindakan berikut Create menentukan bahwa parameternya pet diisi dari isi:

public ActionResult<Pet> Create([FromBody] Pet pet)

Kelas Pet menentukan bahwa propertinya Breed diisi dari parameter string kueri:

public class Pet
{
    public string Name { get; set; } = null!;

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; } = null!;
}

Dalam contoh sebelumnya:

  • Atribut [FromQuery] diabaikan.
  • Properti Breed tidak diisi dari parameter string kueri.

Pemformat input hanya membaca isi dan tidak memahami atribut sumber pengikatan. Jika nilai yang sesuai ditemukan dalam isi, nilai tersebut digunakan untuk mengisi Breed properti.

Jangan berlaku [FromBody] untuk lebih dari satu parameter per metode tindakan. Setelah aliran permintaan dibaca oleh pemformat input, aliran permintaan tidak lagi tersedia untuk dibaca lagi untuk mengikat parameter lain [FromBody] .

Sumber tambahan

Data sumber disediakan untuk sistem pengikatan model oleh penyedia nilai. Anda dapat menulis dan mendaftarkan penyedia nilai kustom yang mendapatkan data untuk pengikatan model dari sumber lain. Misalnya, Anda mungkin menginginkan data dari cookiestatus s atau sesi. Untuk mendapatkan data dari sumber baru:

  • Buat kelas yang mengimplementasikan IValueProvider.
  • Buat kelas yang mengimplementasikan IValueProviderFactory.
  • Daftarkan kelas pabrik di Program.cs.

Sampel mencakup penyedia nilai dan contoh pabrik yang mendapatkan nilai dari cookie s. Daftarkan pabrik penyedia nilai kustom di Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

Kode sebelumnya menempatkan penyedia nilai kustom setelah semua penyedia nilai bawaan. Untuk menjadikannya yang pertama dalam daftar, panggil Insert(0, new CookieValueProviderFactory()) alih-alih Add.

Tidak ada sumber untuk properti model

Secara default, kesalahan status model tidak dibuat jika tidak ada nilai yang ditemukan untuk properti model. Properti diatur ke null atau nilai default:

  • Jenis sederhana yang dapat diubah ke null diatur ke null.
  • Jenis nilai yang tidak dapat diubah ke null diatur ke default(T). Misalnya, parameter int id diatur ke 0.
  • Untuk Jenis kompleks, pengikatan model membuat instans dengan menggunakan konstruktor default, tanpa mengatur properti.
  • Array diatur ke Array.Empty<T>(), kecuali bahwa byte[] array diatur ke null.

Jika status model harus divalidasi ketika tidak ada yang ditemukan di bidang formulir untuk properti model, gunakan [BindRequired] atribut .

Perhatikan bahwa perilaku ini [BindRequired] berlaku untuk pengikatan model dari data formulir yang diposting, bukan ke JSdata ON atau XML dalam isi permintaan. Data isi permintaan ditangani oleh pemformat input.

Kesalahan konversi jenis

Jika sumber ditemukan tetapi tidak dapat dikonversi menjadi jenis target, status model ditandai sebagai tidak valid. Parameter target atau properti diatur ke null atau nilai default, seperti yang disebutkan di bagian sebelumnya.

Dalam pengontrol API yang memiliki [ApiController] atribut , status model yang tidak valid menghasilkan respons HTTP 400 otomatis.

Razor Di halaman, putar ulang halaman dengan pesan kesalahan:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    // ...

    return RedirectToPage("./Index");
}

Ketika halaman diputar ulang oleh kode sebelumnya, input yang tidak valid tidak ditampilkan di bidang formulir. Ini karena properti model telah diatur ke null atau nilai default. Input yang tidak valid memang muncul dalam pesan kesalahan. Jika Anda ingin memutar ulang data buruk di bidang formulir, pertimbangkan untuk menjadikan properti model sebagai string dan melakukan konversi data secara manual.

Strategi yang sama direkomendasikan jika Anda tidak ingin kesalahan konversi jenis mengakibatkan kesalahan status model. Dalam hal ini, jadikan properti model sebagai string.

Jenis sederhana

Lihat Model yang mengikat jenis sederhana dan kompleks untuk penjelasan tentang jenis sederhana dan kompleks.

Jenis sederhana pengikat model dapat mengonversi string sumber menjadi termasuk yang berikut:

Ikat dengan IParsable<T>.TryParse

IParsable<TSelf>.TryParse API mendukung nilai parameter tindakan pengikatan pengontrol:

public static bool TryParse (string? s, IFormatProvider? provider, out TSelf result);

Kelas berikut DateRange mengimplementasikan IParsable<TSelf> untuk mendukung pengikatan rentang tanggal:

public class DateRange : IParsable<DateRange>
{
    public DateOnly? From { get; init; }
    public DateOnly? To { get; init; }

    public static DateRange Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse(string? value,
                                IFormatProvider? provider, out DateRange dateRange)
    {
        var segments = value?.Split(',', StringSplitOptions.RemoveEmptyEntries 
                                       | StringSplitOptions.TrimEntries);

        if (segments?.Length == 2
            && DateOnly.TryParse(segments[0], provider, out var fromDate)
            && DateOnly.TryParse(segments[1], provider, out var toDate))
        {
            dateRange = new DateRange { From = fromDate, To = toDate };
            return true;
        }

        dateRange = new DateRange { From = default, To = default };
        return false;
    }
}

Kode sebelumnya:

  • Mengonversi string yang mewakili dua tanggal menjadi DateRange objek
  • Pengikat IParsable<TSelf>.TryParse model menggunakan metode untuk mengikat DateRange.

Tindakan pengontrol berikut menggunakan DateRange kelas untuk mengikat rentang tanggal:

// GET /WeatherForecast/ByRange?range=7/24/2022,07/26/2022
public IActionResult ByRange([FromQuery] DateRange range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
                     && DateOnly.FromDateTime(wf.Date) <= range.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d"),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Kelas berikut Locale mengimplementasikan IParsable<TSelf> untuk mendukung pengikatan ke CultureInfo:

public class Locale : CultureInfo, IParsable<Locale>
{
    public Locale(string culture) : base(culture)
    {
    }

    public static Locale Parse(string value, IFormatProvider? provider)
    {
        if (!TryParse(value, provider, out var result))
        {
           throw new ArgumentException("Could not parse supplied value.", nameof(value));
        }

        return result;
    }

    public static bool TryParse([NotNullWhen(true)] string? value,
                                IFormatProvider? provider, out Locale locale)
    {
        if (value is null)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
        
        try
        {
            locale = new Locale(value);
            return true;
        }
        catch (CultureNotFoundException)
        {
            locale = new Locale(CurrentCulture.Name);
            return false;
        }
    }
}

Tindakan pengontrol berikut menggunakan Locale kelas untuk mengikat CultureInfo string:

// GET /en-GB/WeatherForecast
public IActionResult Index([FromRoute] Locale locale)
{
    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View(weatherForecasts);
}

Tindakan pengontrol berikut menggunakan DateRange kelas dan Locale untuk mengikat rentang tanggal dengan CultureInfo:

// GET /af-ZA/WeatherForecast/RangeByLocale?range=2022-07-24,2022-07-29
public IActionResult RangeByLocale([FromRoute] Locale locale, [FromQuery] string range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    if (!DateRange.TryParse(range, locale, out DateRange rangeResult))
    {
        ModelState.TryAddModelError(nameof(range),
            $"Invalid date range: {range} for locale {locale.DisplayName}");

        return View("Error", ModelState.Values.SelectMany(v => v.Errors));
    }

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= rangeResult.From
                     && DateOnly.FromDateTime(wf.Date) <= rangeResult.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d", locale),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int) (wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Aplikasi sampel API di GitHub menunjukkan sampel sebelumnya untuk pengontrol API.

Ikat dengan TryParse

TryParse API mendukung nilai parameter tindakan pengikatan pengontrol:

public static bool TryParse(string value, T out result);
public static bool TryParse(string value, IFormatProvider provider, T out result);

IParsable<T>.TryParse adalah pendekatan yang direkomendasikan untuk pengikatan parameter karena tidak seperti TryParse, itu tidak bergantung pada refleksi.

Kelas berikut DateRangeTP mengimplementasikan TryParse:

public class DateRangeTP
{
    public DateOnly? From { get; }
    public DateOnly? To { get; }

    public DateRangeTP(string from, string to)
    {
        if (string.IsNullOrEmpty(from))
            throw new ArgumentNullException(nameof(from));
        if (string.IsNullOrEmpty(to))
            throw new ArgumentNullException(nameof(to));

        From = DateOnly.Parse(from);
        To = DateOnly.Parse(to);
    }

    public static bool TryParse(string? value, out DateRangeTP? result)
    {
        var range = value?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (range?.Length != 2)
        {
            result = default;
            return false;
        }

        result = new DateRangeTP(range[0], range[1]);
        return true;
    }
}

Tindakan pengontrol berikut menggunakan DateRangeTP kelas untuk mengikat rentang tanggal:

// GET /WeatherForecast/ByRangeTP?range=7/24/2022,07/26/2022
public IActionResult ByRangeTP([FromQuery] DateRangeTP range)
{
    if (!ModelState.IsValid)
        return View("Error", ModelState.Values.SelectMany(v => v.Errors));

    var weatherForecasts = Enumerable
        .Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = Summaries[Random.Shared.Next(Summaries.Length)]
        })
        .Where(wf => DateOnly.FromDateTime(wf.Date) >= range.From
                     && DateOnly.FromDateTime(wf.Date) <= range.To)
        .Select(wf => new WeatherForecastViewModel
        {
            Date = wf.Date.ToString("d"),
            TemperatureC = wf.TemperatureC,
            TemperatureF = 32 + (int)(wf.TemperatureC / 0.5556),
            Summary = wf.Summary
        });

    return View("Index", weatherForecasts);
}

Jenis kompleks

Jenis kompleks harus memiliki konstruktor default publik dan properti bisa-tulis publik untuk diikat. Saat pengikatan model terjadi, kelas dibuat menggunakan konstruktor default publik.

Untuk setiap properti dari jenis kompleks, pengikatan model melihat melalui sumber untuk polanama prefix.property_name. Jika tidak ada yang ditemukan, ia mencari hanya property_name tanpa awalan. Keputusan untuk menggunakan awalan tidak dibuat per properti. Misalnya, dengan kueri yang berisi ?Instructor.Id=100&Name=foo, terikat ke metode OnGet(Instructor instructor), objek jenis Instructor yang dihasilkan berisi:

  • Id atur ke 100.
  • Name atur ke null. Pengikatan model mengharapkan Instructor.Name karena Instructor.Id digunakan dalam parameter kueri sebelumnya.

Untuk mengikat parameter, awalannya adalah nama parameter. Untuk mengikat ke PageModel properti publik, awalannya adalah nama properti publik. Beberapa atribut memiliki Prefix properti yang memungkinkan Anda mengambil alih penggunaan default parameter atau nama properti.

Misalnya, misalkan jenis kompleks adalah kelas berikut Instructor :

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Awalan = nama parameter

Jika model yang akan diikat adalah parameter bernama instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci instructorToUpdate.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan = nama properti

Jika model yang akan diikat adalah properti bernama Instructor pengontrol atau PageModel kelas:

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

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan kustom

Jika model yang akan diikat adalah parameter bernama instructorToUpdate dan Bind atribut menentukan Instructor sebagai awalan:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Atribut untuk target jenis kompleks

Beberapa atribut bawaan tersedia untuk mengontrol pengikatan model dari jenis kompleks:

Peringatan

Atribut ini memengaruhi pengikatan model saat data formulir yang diposting adalah sumber nilai. Mereka tidak memengaruhi pemformat input, proses mana yang memposting JSbadan permintaan ON dan XML. Pemformat input dijelaskan nanti dalam artikel ini.

Atribut [Ikat]

Dapat diterapkan ke kelas atau parameter metode. Menentukan properti model mana yang harus disertakan dalam pengikatan model. [Bind]tidak memengaruhi pemformat input.

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat ketika penangan atau metode tindakan apa pun dipanggil:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat saat OnPost metode dipanggil:

[HttpPost]
public IActionResult OnPost(
    [Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

Atribut [Bind] dapat digunakan untuk melindungi dari overposting dalam membuat skenario. Ini tidak berfungsi dengan baik dalam skenario edit karena properti yang dikecualikan diatur ke null atau nilai default alih-alih dibiarkan tidak berubah. Untuk pertahanan terhadap overposting, lihat model direkomendasikan daripada [Bind] atribut . Untuk informasi selengkapnya, lihat Catatan keamanan tentang overposting.

Atribut [ModelBinder]

ModelBinderAttribute dapat diterapkan ke jenis, properti, atau parameter. Ini memungkinkan menentukan jenis pengikat model yang digunakan untuk mengikat instans atau jenis tertentu. Contohnya:

[HttpPost]
public IActionResult OnPost(
    [ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

Atribut [ModelBinder] juga dapat digunakan untuk mengubah nama properti atau parameter saat sedang terikat model:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    // ...
}

Atribut [BindRequired]

Menyebabkan pengikatan model untuk menambahkan kesalahan status model jika pengikatan tidak dapat terjadi untuk properti model. Berikut contohnya:

public class InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

Lihat juga diskusi [Required] atribut dalam validasi Model.

Atribut [BindNever]

Dapat diterapkan ke properti atau jenis. Mencegah pengikatan model dari pengaturan properti model. Saat diterapkan ke jenis, sistem pengikatan model mengecualikan semua properti yang ditentukan jenis. Berikut contohnya:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Koleksi

Untuk target yang merupakan kumpulan jenis sederhana, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter yang akan diikat adalah array bernama selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Data string formulir atau kueri bisa dalam salah satu format berikut:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

    Hindari mengikat parameter atau properti bernama index atau Index jika berdekatan dengan nilai koleksi. Pengikatan model mencoba digunakan index sebagai indeks untuk koleksi yang mungkin mengakibatkan pengikatan yang salah. Misalnya, pertimbangkan tindakan berikut:

    public IActionResult Post(string index, List<Product> products)
    

    Dalam kode sebelumnya, index parameter string kueri mengikat parameter index metode dan juga digunakan untuk mengikat koleksi produk. Mengganti nama index parameter atau menggunakan atribut pengikatan model untuk mengonfigurasi pengikatan menghindari masalah ini:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Format berikut ini hanya didukung dalam data formulir:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan array dua item ke selectedCourses parameter :

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Format data yang menggunakan nomor subskrip (... [0] ... [1] ...) harus memastikan bahwa mereka diberi nomor secara berurutan mulai dari nol. Jika ada celah dalam penomoran subskrip, semua item setelah celah diabaikan. Misalnya, jika subskrip adalah 0 dan 2, bukan 0 dan 1, item kedua diabaikan.

Kamus

Untuk Dictionary target, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter target bernama Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Formulir yang diposting atau data string kueri bisa terlihat seperti salah satu contoh berikut:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan kamus dua item ke selectedCourses parameter :

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Pengikatan konstruktor dan jenis rekaman

Pengikatan model mengharuskan jenis kompleks memiliki konstruktor tanpa parameter. Pemformat System.Text.Json input berbasis dan Newtonsoft.Json mendukung deserialisasi kelas yang tidak memiliki konstruktor tanpa parameter.

Jenis rekaman adalah cara yang bagus untuk mewakili data melalui jaringan. ASP.NET Core mendukung pengikatan model dan memvalidasi jenis rekaman dengan satu konstruktor:

public record Person(
    [Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
<br />
Age: <input asp-for="Age" />

Saat memvalidasi jenis rekaman, runtime mencari metadata pengikatan dan validasi khusus pada parameter daripada pada properti.

Kerangka kerja memungkinkan pengikatan ke dan memvalidasi jenis rekaman:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Agar sebelumnya berfungsi, jenisnya harus:

  • Jadilah jenis catatan.
  • Memiliki tepat satu konstruktor publik.
  • Berisi parameter yang memiliki properti dengan nama dan jenis yang sama. Nama tidak boleh berbeda menurut kasus.

POCO tanpa konstruktor tanpa parameter

POCO yang tidak memiliki konstruktor tanpa parameter tidak dapat terikat.

Kode berikut menghasilkan pengecualian yang mengatakan bahwa jenis harus memiliki konstruktor tanpa parameter:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
    public Person(string Name) : this (Name, 0);
}

Jenis rekaman dengan konstruktor yang ditulis secara manual

Jenis rekaman dengan konstruktor yang ditulis secara manual yang terlihat seperti pekerjaan konstruktor utama

public record Person
{
    public Person([Required] string Name, [Range(0, 100)] int Age)
        => (this.Name, this.Age) = (Name, Age);

    public string Name { get; set; }
    public int Age { get; set; }
}

Jenis rekaman, validasi, dan metadata pengikatan

Untuk jenis rekaman, metadata validasi dan pengikatan pada parameter digunakan. Metadata apa pun pada properti diabaikan

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validasi dan metadata

Validasi menggunakan metadata pada parameter tetapi menggunakan properti untuk membaca nilai. Dalam kasus biasa dengan konstruktor utama, keduanya akan identik. Namun, ada cara untuk mengalahkannya:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel tidak memperbarui parameter pada jenis catatan

public record Person(string Name)
{
    public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Dalam hal ini, MVC tidak akan mencoba mengikat Name lagi. Namun, Age diizinkan untuk diperbarui

Perilaku globalisasi data rute pengikatan model dan string kueri

Penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri:

  • Perlakukan nilai sebagai budaya invarian.
  • Harapkan bahwa URL invarian budaya.

Sebaliknya, nilai yang berasal dari data formulir mengalami konversi sensitif terhadap budaya. Ini dirancang agar URL dapat dibagikan di seluruh lokal.

Untuk membuat penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri menjalani konversi yang sensitif terhadap budaya:

public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query?.Count > 0)
        {
            context.ValueProviders.Add(
                new QueryStringValueProvider(
                    BindingSource.Query,
                    query,
                    CultureInfo.CurrentCulture));
        }

        return Task.CompletedTask;
    }
}
builder.Services.AddControllers(options =>
{
    var index = options.ValueProviderFactories.IndexOf(
        options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
            .Single());

    options.ValueProviderFactories[index] =
        new CultureQueryStringValueProviderFactory();
});

Tipe data khusus

Ada beberapa jenis data khusus yang dapat ditangani oleh pengikatan model.

IFormFile dan IFormFileCollection

File yang diunggah disertakan dalam permintaan HTTP. Juga didukung adalah IEnumerable<IFormFile> untuk beberapa file.

TokenPembatalan

Tindakan dapat secara opsional mengikat CancellationToken sebagai parameter. Ini mengikat yang memberi sinyal ketika koneksi yang mendasar RequestAborted permintaan HTTP dibatalkan. Tindakan dapat menggunakan parameter ini untuk membatalkan operasi asinkron jangka panjang yang dijalankan sebagai bagian dari tindakan pengontrol.

FormCollection

Digunakan untuk mengambil semua nilai dari data formulir yang diposting.

Pemformat input

Data dalam isi permintaan dapat berada di JSON, XML, atau beberapa format lainnya. Untuk mengurai data ini, pengikatan model menggunakan pemformat input yang dikonfigurasi untuk menangani jenis konten tertentu. Secara default, ASP.NET Core menyertakan JSpemformat input berbasis ON untuk menangani JSdata ON. Anda bisa menambahkan formatter lain untuk tipe isi lainnya.

ASP.NET Core memilih pemformat input berdasarkan atribut Konsumsi . Jika tidak ada atribut, atribut menggunakan header Content-Type.

Untuk menggunakan pemformat input XML bawaan:

Menyesuaikan pengikatan model dengan pemformat input

Pemformat input bertanggung jawab penuh untuk membaca data dari isi permintaan. Untuk menyesuaikan proses ini, konfigurasikan API yang digunakan oleh pemformat input. Bagian ini menjelaskan cara mengkustomisasi System.Text.Jsonpemformat input berbasis untuk memahami jenis kustom bernama ObjectId.

Pertimbangkan model berikut, yang berisi properti kustom ObjectId :

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

Untuk menyesuaikan proses pengikatan model saat menggunakan System.Text.Json, buat kelas yang berasal dari JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => new(JsonSerializer.Deserialize<int>(ref reader, options));

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value.Id);
}

Untuk menggunakan pengonversi kustom, terapkan JsonConverterAttribute atribut ke jenis . Dalam contoh berikut, jenis dikonfigurasi ObjectId dengan ObjectIdConverter sebagai pengonversi kustomnya:

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Untuk informasi selengkapnya, lihat Cara menulis pengonversi kustom.

Mengecualikan jenis yang ditentukan dari pengikatan model

Perilaku sistem pengikatan dan validasi model didorong oleh ModelMetadata. Anda dapat menyesuaikan ModelMetadata dengan menambahkan penyedia detail ke MvcOptions.ModelMetadataDetailsProviders. Penyedia detail bawaan tersedia untuk menonaktifkan pengikatan atau validasi model untuk jenis tertentu.

Untuk menonaktifkan pengikatan model pada semua model dari jenis tertentu, tambahkan ExcludeBindingMetadataProvider di Program.cs. Misalnya, untuk menonaktifkan pengikatan model pada semua model jenis System.Version:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Untuk menonaktifkan validasi pada properti dari jenis tertentu, tambahkan SuppressChildValidationMetadataProvider di Program.cs. Misalnya, untuk menonaktifkan validasi pada properti jenis System.Guid:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Pengikat model kustom

Anda dapat memperluas pengikatan model dengan menulis pengikat model kustom dan menggunakan [ModelBinder] atribut untuk memilihnya untuk target tertentu. Pelajari selengkapnya tentang pengikatan model kustom.

Pengikatan model manual

Pengikatan model dapat dipanggil secara manual dengan menggunakan TryUpdateModelAsync metode . Metode ini didefinisikan pada kedua ControllerBase kelas dan PageModel . Kelebihan metode memungkinkan Anda menentukan awalan dan penyedia nilai yang akan digunakan. Metode mengembalikan false jika pengikatan model gagal. Berikut contohnya:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync menggunakan penyedia nilai untuk mendapatkan data dari isi formulir, string kueri, dan data rute. TryUpdateModelAsync biasanya:

  • Digunakan dengan Razor aplikasi Pages dan MVC menggunakan pengontrol dan tampilan untuk mencegah pengeposan berlebihan.
  • Tidak digunakan dengan API web kecuali dikonsumsi dari data formulir, string kueri, dan data rute. Titik akhir API Web yang menggunakan JSON menggunakan Pemformat input untuk mendeserialisasi isi permintaan ke dalam objek.

Untuk informasi selengkapnya, lihat TryUpdateModelAsync.

Atribut [FromServices]

Nama atribut ini mengikuti pola atribut pengikatan model yang menentukan sumber data. Tetapi ini bukan tentang mengikat data dari penyedia nilai. Ini mendapatkan instans jenis dari kontainer injeksi dependensi. Tujuannya adalah untuk memberikan alternatif untuk injeksi konstruktor ketika Anda membutuhkan layanan hanya jika metode tertentu dipanggil.

Jika instans jenis tidak terdaftar dalam kontainer injeksi dependensi, aplikasi akan memberikan pengecualian saat mencoba mengikat parameter. Untuk membuat parameter opsional, gunakan salah satu pendekatan berikut:

  • Buat parameter nullable.
  • Tetapkan nilai default untuk parameter .

Untuk parameter nullable, pastikan parameter tidak null sebelum mengaksesnya.

Sumber Daya Tambahan:

Artikel ini menjelaskan apa itu pengikatan model, cara kerjanya, dan cara menyesuaikan perilakunya.

Apa itu Pengikatan Model

Pengontrol dan Razor halaman bekerja dengan data yang berasal dari permintaan HTTP. Misalnya, data rute dapat menyediakan kunci rekaman, dan bidang formulir yang diposting dapat memberikan nilai untuk properti model. Menulis kode untuk mengambil masing-masing nilai ini dan mengonversinya dari string ke jenis .NET akan melelahkan dan rawan kesalahan. Pengikatan model mengotomatiskan proses ini. Sistem pengikatan model:

  • Mengambil data dari berbagai sumber seperti data rute, bidang formulir, dan string kueri.
  • Menyediakan data ke pengontrol dan Razor halaman dalam parameter metode dan properti publik.
  • Mengonversi data string ke jenis .NET.
  • Memperbarui properti jenis kompleks.

Contoh

Misalkan Anda memiliki metode tindakan berikut:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Dan aplikasi menerima permintaan dengan URL ini:

https://contoso.com/api/pets/2?DogsOnly=true

Pengikatan model melalui langkah-langkah berikut setelah sistem perutean memilih metode tindakan:

  • Menemukan parameter pertama dari , bilangan GetByIdbulat bernama id.
  • Lihat sumber yang tersedia dalam permintaan HTTP dan temukan id = "2" dalam data rute.
  • Mengonversi string "2" menjadi bilangan bulat 2.
  • Menemukan parameter berikutnya dari GetById, boolean bernama dogsOnly.
  • Telusuri sumber dan temukan "DogsOnly=true" dalam string kueri. Pencocokan nama tidak peka huruf besar/kecil.
  • Mengonversi string "true" menjadi boolean true.

Kerangka kerja kemudian memanggil GetById metode , meneruskan 2 untuk id parameter , dan true untuk dogsOnly parameter .

Dalam contoh sebelumnya, target pengikatan model adalah parameter metode yang merupakan jenis sederhana. Target mungkin juga merupakan properti dari jenis kompleks. Setelah setiap properti berhasil diikat, validasi model terjadi untuk properti tersebut. Catatan data apa yang terikat ke model, dan kesalahan pengikatan atau validasi apa pun, disimpan di ControllerBase.ModelState atau PageModel.ModelState. Untuk mengetahui apakah proses ini berhasil, aplikasi memeriksa bendera ModelState.IsValid .

Target

Pengikatan model mencoba menemukan nilai untuk jenis target berikut:

  • Parameter metode tindakan pengontrol tempat permintaan dirutekan.
  • Razor Parameter metode handler Pages tempat permintaan dirutekan.
  • Properti publik pengontrol atau PageModel kelas, jika ditentukan oleh atribut.

Atribut [BindProperty]

Dapat diterapkan ke properti publik pengontrol atau PageModel kelas untuk menyebabkan pengikatan model ke menargetkan properti tersebut:

public class EditModel : PageModel
{
    [BindProperty]
    public Instructor? Instructor { get; set; }

    // ...
}

Atribut [BindProperties]

Dapat diterapkan ke pengontrol atau PageModel kelas untuk memberi tahu pengikatan model untuk menargetkan semua properti publik kelas:

[BindProperties]
public class CreateModel : PageModel
{
    public Instructor? Instructor { get; set; }

    // ...
}

Pengikatan model untuk permintaan HTTP GET

Secara default, properti tidak terikat untuk permintaan HTTP GET. Biasanya, yang Anda butuhkan untuk permintaan GET adalah parameter ID rekaman. ID rekaman digunakan untuk mencari item dalam database. Oleh karena itu, tidak perlu mengikat properti yang menyimpan instans model. Dalam skenario di mana Anda ingin properti terikat ke data dari permintaan GET, atur properti ke SupportsGettrue:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string? ApplicationInsightsCookie { get; set; }

Sumber

Secara default, pengikatan model mendapatkan data dalam bentuk pasangan kunci-nilai dari sumber berikut dalam permintaan HTTP:

  1. Bidang formulir
  2. Isi permintaan (Untuk pengontrol yang memiliki atribut [ApiController].)
  3. Merutekan data
  4. Parameter untai kueri
  5. File yang diunggah

Untuk setiap parameter atau properti target, sumber dipindai dalam urutan yang ditunjukkan dalam daftar sebelumnya. Ada beberapa pengecualian:

  • Data rute dan nilai string kueri hanya digunakan untuk jenis sederhana.
  • File yang diunggah hanya terikat ke jenis target yang mengimplementasikan IFormFile atau IEnumerable<IFormFile>.

Jika sumber default tidak benar, gunakan salah satu atribut berikut untuk menentukan sumber:

  • [FromQuery] - Mendapatkan nilai dari string kueri.
  • [FromRoute] - Mendapatkan nilai dari data rute.
  • [FromForm] - Mendapatkan nilai dari bidang formulir yang diposting.
  • [FromBody] - Mendapatkan nilai dari isi permintaan.
  • [FromHeader] - Mendapatkan nilai dari header HTTP.

Atribut ini:

  • Ditambahkan ke properti model satu per satu dan bukan ke kelas model, seperti dalam contoh berikut:

    public class Instructor
    {
        public int Id { get; set; }
    
        [FromQuery(Name = "Note")]
        public string? NoteFromQueryString { get; set; }
    
        // ...
    }
    
  • Secara opsional menerima nilai nama model di konstruktor. Opsi ini disediakan jika nama properti tidak cocok dengan nilai dalam permintaan. Misalnya, nilai dalam permintaan mungkin berupa header dengan tanda hubung atas namanya, seperti dalam contoh berikut:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atribut [FromBody]

Terapkan [FromBody] atribut ke parameter untuk mengisi propertinya dari isi permintaan HTTP. Runtime ASP.NET Core mendelegasikan tanggung jawab membaca isi ke formatter input. Pemformat input dijelaskan nanti dalam artikel ini.

Ketika [FromBody] diterapkan ke parameter jenis kompleks, atribut sumber pengikatan apa pun yang diterapkan ke propertinya diabaikan. Misalnya, tindakan berikut Create menentukan bahwa parameternya pet diisi dari isi:

public ActionResult<Pet> Create([FromBody] Pet pet)

Kelas Pet menentukan bahwa propertinya Breed diisi dari parameter string kueri:

public class Pet
{
    public string Name { get; set; } = null!;

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; } = null!;
}

Dalam contoh sebelumnya:

  • Atribut [FromQuery] diabaikan.
  • Properti Breed tidak diisi dari parameter string kueri.

Pemformat input hanya membaca isi dan tidak memahami atribut sumber pengikatan. Jika nilai yang sesuai ditemukan dalam isi, nilai tersebut digunakan untuk mengisi Breed properti.

Jangan berlaku [FromBody] untuk lebih dari satu parameter per metode tindakan. Setelah aliran permintaan dibaca oleh pemformat input, aliran permintaan tidak lagi tersedia untuk dibaca lagi untuk mengikat parameter lain [FromBody] .

Sumber tambahan

Data sumber disediakan untuk sistem pengikatan model oleh penyedia nilai. Anda dapat menulis dan mendaftarkan penyedia nilai kustom yang mendapatkan data untuk pengikatan model dari sumber lain. Misalnya, Anda mungkin menginginkan data dari cookiestatus s atau sesi. Untuk mendapatkan data dari sumber baru:

  • Buat kelas yang mengimplementasikan IValueProvider.
  • Buat kelas yang mengimplementasikan IValueProviderFactory.
  • Daftarkan kelas pabrik di Program.cs.

Sampel mencakup penyedia nilai dan contoh pabrik yang mendapatkan nilai dari cookie s. Daftarkan pabrik penyedia nilai kustom di Program.cs:

builder.Services.AddControllers(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
});

Kode sebelumnya menempatkan penyedia nilai kustom setelah semua penyedia nilai bawaan. Untuk menjadikannya yang pertama dalam daftar, panggil Insert(0, new CookieValueProviderFactory()) alih-alih Add.

Tidak ada sumber untuk properti model

Secara default, kesalahan status model tidak dibuat jika tidak ada nilai yang ditemukan untuk properti model. Properti diatur ke null atau nilai default:

  • Jenis sederhana yang dapat diubah ke null diatur ke null.
  • Jenis nilai yang tidak dapat diubah ke null diatur ke default(T). Misalnya, parameter int id diatur ke 0.
  • Untuk Jenis kompleks, pengikatan model membuat instans dengan menggunakan konstruktor default, tanpa mengatur properti.
  • Array diatur ke Array.Empty<T>(), kecuali bahwa byte[] array diatur ke null.

Jika status model harus divalidasi ketika tidak ada yang ditemukan di bidang formulir untuk properti model, gunakan [BindRequired] atribut .

Perhatikan bahwa perilaku ini [BindRequired] berlaku untuk pengikatan model dari data formulir yang diposting, bukan ke JSdata ON atau XML dalam isi permintaan. Data isi permintaan ditangani oleh pemformat input.

Kesalahan konversi jenis

Jika sumber ditemukan tetapi tidak dapat dikonversi menjadi jenis target, status model ditandai sebagai tidak valid. Parameter target atau properti diatur ke null atau nilai default, seperti yang disebutkan di bagian sebelumnya.

Dalam pengontrol API yang memiliki [ApiController] atribut , status model yang tidak valid menghasilkan respons HTTP 400 otomatis.

Razor Di halaman, putar ulang halaman dengan pesan kesalahan:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    // ...

    return RedirectToPage("./Index");
}

Ketika halaman diputar ulang oleh kode sebelumnya, input yang tidak valid tidak ditampilkan di bidang formulir. Ini karena properti model telah diatur ke null atau nilai default. Input yang tidak valid memang muncul dalam pesan kesalahan. Jika Anda ingin memutar ulang data buruk di bidang formulir, pertimbangkan untuk menjadikan properti model sebagai string dan melakukan konversi data secara manual.

Strategi yang sama direkomendasikan jika Anda tidak ingin kesalahan konversi jenis mengakibatkan kesalahan status model. Dalam hal ini, jadikan properti model sebagai string.

Jenis sederhana

Jenis sederhana pengikat model dapat mengonversi string sumber menjadi termasuk yang berikut:

Jenis kompleks

Jenis kompleks harus memiliki konstruktor default publik dan properti bisa-tulis publik untuk diikat. Saat pengikatan model terjadi, kelas dibuat menggunakan konstruktor default publik.

Untuk setiap properti dari jenis kompleks, pengikatan model melihat melalui sumber untuk polanama prefix.property_name. Jika tidak ada yang ditemukan, ia mencari hanya property_name tanpa awalan. Keputusan untuk menggunakan awalan tidak dibuat per properti. Misalnya, dengan kueri yang berisi ?Instructor.Id=100&Name=foo, terikat ke metode OnGet(Instructor instructor), objek jenis Instructor yang dihasilkan berisi:

  • Id atur ke 100.
  • Name atur ke null. Pengikatan model mengharapkan Instructor.Name karena Instructor.Id digunakan dalam parameter kueri sebelumnya.

Untuk mengikat parameter, awalannya adalah nama parameter. Untuk mengikat ke PageModel properti publik, awalannya adalah nama properti publik. Beberapa atribut memiliki Prefix properti yang memungkinkan Anda mengambil alih penggunaan default parameter atau nama properti.

Misalnya, misalkan jenis kompleks adalah kelas berikut Instructor :

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Awalan = nama parameter

Jika model yang akan diikat adalah parameter bernama instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci instructorToUpdate.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan = nama properti

Jika model yang akan diikat adalah properti bernama Instructor pengontrol atau PageModel kelas:

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

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan kustom

Jika model yang akan diikat adalah parameter bernama instructorToUpdate dan Bind atribut menentukan Instructor sebagai awalan:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Atribut untuk target jenis kompleks

Beberapa atribut bawaan tersedia untuk mengontrol pengikatan model dari jenis kompleks:

Peringatan

Atribut ini memengaruhi pengikatan model saat data formulir yang diposting adalah sumber nilai. Mereka tidak memengaruhi pemformat input, proses mana yang memposting JSbadan permintaan ON dan XML. Pemformat input dijelaskan nanti dalam artikel ini.

Atribut [Ikat]

Dapat diterapkan ke kelas atau parameter metode. Menentukan properti model mana yang harus disertakan dalam pengikatan model. [Bind]tidak memengaruhi pemformat input.

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat ketika penangan atau metode tindakan apa pun dipanggil:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat saat OnPost metode dipanggil:

[HttpPost]
public IActionResult OnPost(
    [Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

Atribut [Bind] dapat digunakan untuk melindungi dari overposting dalam membuat skenario. Ini tidak berfungsi dengan baik dalam skenario edit karena properti yang dikecualikan diatur ke null atau nilai default alih-alih dibiarkan tidak berubah. Untuk pertahanan terhadap overposting, lihat model direkomendasikan daripada [Bind] atribut . Untuk informasi selengkapnya, lihat Catatan keamanan tentang overposting.

Atribut [ModelBinder]

ModelBinderAttribute dapat diterapkan ke jenis, properti, atau parameter. Ini memungkinkan menentukan jenis pengikat model yang digunakan untuk mengikat instans atau jenis tertentu. Contohnya:

[HttpPost]
public IActionResult OnPost(
    [ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

Atribut [ModelBinder] juga dapat digunakan untuk mengubah nama properti atau parameter saat sedang terikat model:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    // ...
}

Atribut [BindRequired]

Menyebabkan pengikatan model untuk menambahkan kesalahan status model jika pengikatan tidak dapat terjadi untuk properti model. Berikut contohnya:

public class InstructorBindRequired
{
    // ...

    [BindRequired]
    public DateTime HireDate { get; set; }
}

Lihat juga diskusi [Required] atribut dalam validasi Model.

Atribut [BindNever]

Dapat diterapkan ke properti atau jenis. Mencegah pengikatan model dari pengaturan properti model. Saat diterapkan ke jenis, sistem pengikatan model mengecualikan semua properti yang ditentukan jenis. Berikut contohnya:

public class InstructorBindNever
{
    [BindNever]
    public int Id { get; set; }

    // ...
}

Koleksi

Untuk target yang merupakan kumpulan jenis sederhana, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter yang akan diikat adalah array bernama selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Data string formulir atau kueri bisa dalam salah satu format berikut:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

    Hindari mengikat parameter atau properti bernama index atau Index jika berdekatan dengan nilai koleksi. Pengikatan model mencoba digunakan index sebagai indeks untuk koleksi yang mungkin mengakibatkan pengikatan yang salah. Misalnya, pertimbangkan tindakan berikut:

    public IActionResult Post(string index, List<Product> products)
    

    Dalam kode sebelumnya, index parameter string kueri mengikat parameter index metode dan juga digunakan untuk mengikat koleksi produk. Mengganti nama index parameter atau menggunakan atribut pengikatan model untuk mengonfigurasi pengikatan menghindari masalah ini:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Format berikut ini hanya didukung dalam data formulir:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan array dua item ke selectedCourses parameter :

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Format data yang menggunakan nomor subskrip (... [0] ... [1] ...) harus memastikan bahwa mereka diberi nomor secara berurutan mulai dari nol. Jika ada celah dalam penomoran subskrip, semua item setelah celah diabaikan. Misalnya, jika subskrip adalah 0 dan 2, bukan 0 dan 1, item kedua diabaikan.

Kamus

Untuk Dictionary target, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter target bernama Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Formulir yang diposting atau data string kueri bisa terlihat seperti salah satu contoh berikut:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan kamus dua item ke selectedCourses parameter :

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Pengikatan konstruktor dan jenis rekaman

Pengikatan model mengharuskan jenis kompleks memiliki konstruktor tanpa parameter. Pemformat System.Text.Json input berbasis dan Newtonsoft.Json mendukung deserialisasi kelas yang tidak memiliki konstruktor tanpa parameter.

Jenis rekaman adalah cara yang bagus untuk mewakili data melalui jaringan. ASP.NET Core mendukung pengikatan model dan memvalidasi jenis rekaman dengan satu konstruktor:

public record Person(
    [Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
    public IActionResult Index() => View();

    [HttpPost]
    public IActionResult Index(Person person)
    {
        // ...
    }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
<br />
Age: <input asp-for="Age" />

Saat memvalidasi jenis rekaman, runtime mencari metadata pengikatan dan validasi khusus pada parameter daripada pada properti.

Kerangka kerja memungkinkan pengikatan ke dan memvalidasi jenis rekaman:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Agar sebelumnya berfungsi, jenisnya harus:

  • Jadilah jenis catatan.
  • Memiliki tepat satu konstruktor publik.
  • Berisi parameter yang memiliki properti dengan nama dan jenis yang sama. Nama tidak boleh berbeda menurut kasus.

POCO tanpa konstruktor tanpa parameter

POCO yang tidak memiliki konstruktor tanpa parameter tidak dapat terikat.

Kode berikut menghasilkan pengecualian yang mengatakan bahwa jenis harus memiliki konstruktor tanpa parameter:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
    public Person(string Name) : this (Name, 0);
}

Jenis rekaman dengan konstruktor yang ditulis secara manual

Jenis rekaman dengan konstruktor yang ditulis secara manual yang terlihat seperti pekerjaan konstruktor utama

public record Person
{
    public Person([Required] string Name, [Range(0, 100)] int Age)
        => (this.Name, this.Age) = (Name, Age);

    public string Name { get; set; }
    public int Age { get; set; }
}

Jenis rekaman, validasi, dan metadata pengikatan

Untuk jenis rekaman, metadata validasi dan pengikatan pada parameter digunakan. Metadata apa pun pada properti diabaikan

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validasi dan metadata

Validasi menggunakan metadata pada parameter tetapi menggunakan properti untuk membaca nilai. Dalam kasus biasa dengan konstruktor utama, keduanya akan identik. Namun, ada cara untuk mengalahkannya:

public record Person([Required] string Name)
{
    private readonly string _name;

    // The following property is never null.
    // However this object could have been constructed as "new Person(null)".
    public string Name { get; init => _name = value ?? string.Empty; }
}

TryUpdateModel tidak memperbarui parameter pada jenis catatan

public record Person(string Name)
{
    public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Dalam hal ini, MVC tidak akan mencoba mengikat Name lagi. Namun, Age diizinkan untuk diperbarui

Perilaku globalisasi data rute pengikatan model dan string kueri

Penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri:

  • Perlakukan nilai sebagai budaya invarian.
  • Harapkan bahwa URL invarian budaya.

Sebaliknya, nilai yang berasal dari data formulir mengalami konversi sensitif terhadap budaya. Ini dirancang agar URL dapat dibagikan di seluruh lokal.

Untuk membuat penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri menjalani konversi yang sensitif terhadap budaya:

public class CultureQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        _ = context ?? throw new ArgumentNullException(nameof(context));

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query?.Count > 0)
        {
            context.ValueProviders.Add(
                new QueryStringValueProvider(
                    BindingSource.Query,
                    query,
                    CultureInfo.CurrentCulture));
        }

        return Task.CompletedTask;
    }
}
builder.Services.AddControllers(options =>
{
    var index = options.ValueProviderFactories.IndexOf(
        options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>()
            .Single());

    options.ValueProviderFactories[index] =
        new CultureQueryStringValueProviderFactory();
});

Tipe data khusus

Ada beberapa jenis data khusus yang dapat ditangani oleh pengikatan model.

IFormFile dan IFormFileCollection

File yang diunggah disertakan dalam permintaan HTTP. Juga didukung adalah IEnumerable<IFormFile> untuk beberapa file.

TokenPembatalan

Tindakan dapat secara opsional mengikat CancellationToken sebagai parameter. Ini mengikat yang memberi sinyal ketika koneksi yang mendasar RequestAborted permintaan HTTP dibatalkan. Tindakan dapat menggunakan parameter ini untuk membatalkan operasi asinkron jangka panjang yang dijalankan sebagai bagian dari tindakan pengontrol.

FormCollection

Digunakan untuk mengambil semua nilai dari data formulir yang diposting.

Pemformat input

Data dalam isi permintaan dapat berada di JSON, XML, atau beberapa format lainnya. Untuk mengurai data ini, pengikatan model menggunakan pemformat input yang dikonfigurasi untuk menangani jenis konten tertentu. Secara default, ASP.NET Core menyertakan JSpemformat input berbasis ON untuk menangani JSdata ON. Anda bisa menambahkan formatter lain untuk tipe isi lainnya.

ASP.NET Core memilih pemformat input berdasarkan atribut Konsumsi . Jika tidak ada atribut, atribut menggunakan header Content-Type.

Untuk menggunakan pemformat input XML bawaan:

Menyesuaikan pengikatan model dengan pemformat input

Pemformat input bertanggung jawab penuh untuk membaca data dari isi permintaan. Untuk menyesuaikan proses ini, konfigurasikan API yang digunakan oleh pemformat input. Bagian ini menjelaskan cara mengkustomisasi System.Text.Jsonpemformat input berbasis untuk memahami jenis kustom bernama ObjectId.

Pertimbangkan model berikut, yang berisi properti kustom ObjectId :

public class InstructorObjectId
{
    [Required]
    public ObjectId ObjectId { get; set; } = null!;
}

Untuk menyesuaikan proses pengikatan model saat menggunakan System.Text.Json, buat kelas yang berasal dari JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => new(JsonSerializer.Deserialize<int>(ref reader, options));

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value.Id);
}

Untuk menggunakan pengonversi kustom, terapkan JsonConverterAttribute atribut ke jenis . Dalam contoh berikut, jenis dikonfigurasi ObjectId dengan ObjectIdConverter sebagai pengonversi kustomnya:

[JsonConverter(typeof(ObjectIdConverter))]
public record ObjectId(int Id);

Untuk informasi selengkapnya, lihat Cara menulis pengonversi kustom.

Mengecualikan jenis yang ditentukan dari pengikatan model

Perilaku sistem pengikatan dan validasi model didorong oleh ModelMetadata. Anda dapat menyesuaikan ModelMetadata dengan menambahkan penyedia detail ke MvcOptions.ModelMetadataDetailsProviders. Penyedia detail bawaan tersedia untuk menonaktifkan pengikatan atau validasi model untuk jenis tertentu.

Untuk menonaktifkan pengikatan model pada semua model dari jenis tertentu, tambahkan ExcludeBindingMetadataProvider di Program.cs. Misalnya, untuk menonaktifkan pengikatan model pada semua model jenis System.Version:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Untuk menonaktifkan validasi pada properti dari jenis tertentu, tambahkan SuppressChildValidationMetadataProvider di Program.cs. Misalnya, untuk menonaktifkan validasi pada properti jenis System.Guid:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(Guid)));
    });

Pengikat model kustom

Anda dapat memperluas pengikatan model dengan menulis pengikat model kustom dan menggunakan [ModelBinder] atribut untuk memilihnya untuk target tertentu. Pelajari selengkapnya tentang pengikatan model kustom.

Pengikatan model manual

Pengikatan model dapat dipanggil secara manual dengan menggunakan TryUpdateModelAsync metode . Metode ini didefinisikan pada kedua ControllerBase kelas dan PageModel . Kelebihan metode memungkinkan Anda menentukan awalan dan penyedia nilai yang akan digunakan. Metode mengembalikan false jika pengikatan model gagal. Berikut contohnya:

if (await TryUpdateModelAsync(
    newInstructor,
    "Instructor",
    x => x.Name, x => x.HireDate!))
{
    _instructorStore.Add(newInstructor);
    return RedirectToPage("./Index");
}

return Page();

TryUpdateModelAsync menggunakan penyedia nilai untuk mendapatkan data dari isi formulir, string kueri, dan data rute. TryUpdateModelAsync biasanya:

  • Digunakan dengan Razor aplikasi Pages dan MVC menggunakan pengontrol dan tampilan untuk mencegah pengeposan berlebihan.
  • Tidak digunakan dengan API web kecuali dikonsumsi dari data formulir, string kueri, dan data rute. Titik akhir API Web yang menggunakan JSON menggunakan Pemformat input untuk mendeserialisasi isi permintaan ke dalam objek.

Untuk informasi selengkapnya, lihat TryUpdateModelAsync.

Atribut [FromServices]

Nama atribut ini mengikuti pola atribut pengikatan model yang menentukan sumber data. Tetapi ini bukan tentang mengikat data dari penyedia nilai. Ini mendapatkan instans jenis dari kontainer injeksi dependensi. Tujuannya adalah untuk memberikan alternatif untuk injeksi konstruktor ketika Anda membutuhkan layanan hanya jika metode tertentu dipanggil.

Jika instans jenis tidak terdaftar dalam kontainer injeksi dependensi, aplikasi akan memberikan pengecualian saat mencoba mengikat parameter. Untuk membuat parameter opsional, gunakan salah satu pendekatan berikut:

  • Buat parameter nullable.
  • Tetapkan nilai default untuk parameter .

Untuk parameter nullable, pastikan parameter tidak null sebelum mengaksesnya.

Sumber Daya Tambahan:

Artikel ini menjelaskan apa itu pengikatan model, cara kerjanya, dan cara menyesuaikan perilakunya.

Lihat atau unduh sampel kode (cara mengunduh).

Apa itu Pengikatan Model

Pengontrol dan Razor halaman bekerja dengan data yang berasal dari permintaan HTTP. Misalnya, data rute dapat menyediakan kunci rekaman, dan bidang formulir yang diposting dapat memberikan nilai untuk properti model. Menulis kode untuk mengambil masing-masing nilai ini dan mengonversinya dari string ke jenis .NET akan melelahkan dan rawan kesalahan. Pengikatan model mengotomatiskan proses ini. Sistem pengikatan model:

  • Mengambil data dari berbagai sumber seperti data rute, bidang formulir, dan string kueri.
  • Menyediakan data ke pengontrol dan Razor halaman dalam parameter metode dan properti publik.
  • Mengonversi data string ke jenis .NET.
  • Memperbarui properti jenis kompleks.

Contoh

Misalkan Anda memiliki metode tindakan berikut:

[HttpGet("{id}")]
public ActionResult<Pet> GetById(int id, bool dogsOnly)

Dan aplikasi menerima permintaan dengan URL ini:

http://contoso.com/api/pets/2?DogsOnly=true

Pengikatan model melalui langkah-langkah berikut setelah sistem perutean memilih metode tindakan:

  • Menemukan parameter pertama dari , bilangan GetByIdbulat bernama id.
  • Lihat sumber yang tersedia dalam permintaan HTTP dan temukan id = "2" dalam data rute.
  • Mengonversi string "2" menjadi bilangan bulat 2.
  • Menemukan parameter berikutnya dari GetById, boolean bernama dogsOnly.
  • Telusuri sumber dan temukan "DogsOnly=true" dalam string kueri. Pencocokan nama tidak peka huruf besar/kecil.
  • Mengonversi string "true" menjadi boolean true.

Kerangka kerja kemudian memanggil GetById metode , meneruskan 2 untuk id parameter , dan true untuk dogsOnly parameter .

Dalam contoh sebelumnya, target pengikatan model adalah parameter metode yang merupakan jenis sederhana. Target mungkin juga merupakan properti dari jenis kompleks. Setelah setiap properti berhasil diikat, validasi model terjadi untuk properti tersebut. Catatan data apa yang terikat ke model, dan kesalahan pengikatan atau validasi apa pun, disimpan di ControllerBase.ModelState atau PageModel.ModelState. Untuk mengetahui apakah proses ini berhasil, aplikasi memeriksa bendera ModelState.IsValid .

Target

Pengikatan model mencoba menemukan nilai untuk jenis target berikut:

  • Parameter metode tindakan pengontrol tempat permintaan dirutekan.
  • Razor Parameter metode handler Pages tempat permintaan dirutekan.
  • Properti publik pengontrol atau PageModel kelas, jika ditentukan oleh atribut.

Atribut [BindProperty]

Dapat diterapkan ke properti publik pengontrol atau PageModel kelas untuk menyebabkan pengikatan model ke menargetkan properti tersebut:

public class EditModel : InstructorsPageModel
{
    [BindProperty]
    public Instructor Instructor { get; set; }

Atribut [BindProperties]

Tersedia di ASP.NET Core 2.1 dan yang lebih baru. Dapat diterapkan ke pengontrol atau PageModel kelas untuk memberi tahu pengikatan model untuk menargetkan semua properti publik kelas:

[BindProperties(SupportsGet = true)]
public class CreateModel : InstructorsPageModel
{
    public Instructor Instructor { get; set; }

Pengikatan model untuk permintaan HTTP GET

Secara default, properti tidak terikat untuk permintaan HTTP GET. Biasanya, yang Anda butuhkan untuk permintaan GET adalah parameter ID rekaman. ID rekaman digunakan untuk mencari item dalam database. Oleh karena itu, tidak perlu mengikat properti yang menyimpan instans model. Dalam skenario di mana Anda ingin properti terikat ke data dari permintaan GET, atur properti ke SupportsGettrue:

[BindProperty(Name = "ai_user", SupportsGet = true)]
public string ApplicationInsightsCookie { get; set; }

Sumber

Secara default, pengikatan model mendapatkan data dalam bentuk pasangan kunci-nilai dari sumber berikut dalam permintaan HTTP:

  1. Bidang formulir
  2. Isi permintaan (Untuk pengontrol yang memiliki atribut [ApiController].)
  3. Merutekan data
  4. Parameter untai kueri
  5. File yang diunggah

Untuk setiap parameter atau properti target, sumber dipindai dalam urutan yang ditunjukkan dalam daftar sebelumnya. Ada beberapa pengecualian:

  • Data rute dan nilai string kueri hanya digunakan untuk jenis sederhana.
  • File yang diunggah hanya terikat ke jenis target yang mengimplementasikan IFormFile atau IEnumerable<IFormFile>.

Jika sumber default tidak benar, gunakan salah satu atribut berikut untuk menentukan sumber:

  • [FromQuery] - Mendapatkan nilai dari string kueri.
  • [FromRoute] - Mendapatkan nilai dari data rute.
  • [FromForm] - Mendapatkan nilai dari bidang formulir yang diposting.
  • [FromBody] - Mendapatkan nilai dari isi permintaan.
  • [FromHeader] - Mendapatkan nilai dari header HTTP.

Atribut ini:

  • Ditambahkan ke properti model satu per satu (bukan ke kelas model), seperti dalam contoh berikut:

    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    
  • Secara opsional menerima nilai nama model di konstruktor. Opsi ini disediakan jika nama properti tidak cocok dengan nilai dalam permintaan. Misalnya, nilai dalam permintaan mungkin berupa header dengan tanda hubung atas namanya, seperti dalam contoh berikut:

    public void OnGet([FromHeader(Name = "Accept-Language")] string language)
    

Atribut [FromBody]

Terapkan [FromBody] atribut ke parameter untuk mengisi propertinya dari isi permintaan HTTP. Runtime ASP.NET Core mendelegasikan tanggung jawab membaca isi ke formatter input. Pemformat input dijelaskan nanti dalam artikel ini.

Ketika [FromBody] diterapkan ke parameter jenis kompleks, atribut sumber pengikatan apa pun yang diterapkan ke propertinya diabaikan. Misalnya, tindakan berikut Create menentukan bahwa parameternya pet diisi dari isi:

public ActionResult<Pet> Create([FromBody] Pet pet)

Kelas Pet menentukan bahwa propertinya Breed diisi dari parameter string kueri:

public class Pet
{
    public string Name { get; set; }

    [FromQuery] // Attribute is ignored.
    public string Breed { get; set; }
}

Dalam contoh sebelumnya:

  • Atribut [FromQuery] diabaikan.
  • Properti Breed tidak diisi dari parameter string kueri.

Pemformat input hanya membaca isi dan tidak memahami atribut sumber pengikatan. Jika nilai yang sesuai ditemukan dalam isi, nilai tersebut digunakan untuk mengisi Breed properti.

Jangan berlaku [FromBody] untuk lebih dari satu parameter per metode tindakan. Setelah aliran permintaan dibaca oleh pemformat input, aliran permintaan tidak lagi tersedia untuk dibaca lagi untuk mengikat parameter lain [FromBody] .

Sumber tambahan

Data sumber disediakan untuk sistem pengikatan model oleh penyedia nilai. Anda dapat menulis dan mendaftarkan penyedia nilai kustom yang mendapatkan data untuk pengikatan model dari sumber lain. Misalnya, Anda mungkin menginginkan data dari cookiestatus s atau sesi. Untuk mendapatkan data dari sumber baru:

  • Buat kelas yang mengimplementasikan IValueProvider.
  • Buat kelas yang mengimplementasikan IValueProviderFactory.
  • Daftarkan kelas pabrik di Startup.ConfigureServices.

Aplikasi sampel menyertakan penyedia nilai dan contoh pabrik yang mendapatkan nilai dari cookie s. Berikut adalah kode pendaftaran di Startup.ConfigureServices:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Kode yang ditampilkan menempatkan penyedia nilai kustom setelah semua penyedia nilai bawaan. Untuk menjadikannya yang pertama dalam daftar, panggil Insert(0, new CookieValueProviderFactory()) alih-alih Add.

Tidak ada sumber untuk properti model

Secara default, kesalahan status model tidak dibuat jika tidak ada nilai yang ditemukan untuk properti model. Properti diatur ke null atau nilai default:

  • Jenis sederhana yang dapat diubah ke null diatur ke null.
  • Jenis nilai yang tidak dapat diubah ke null diatur ke default(T). Misalnya, parameter int id diatur ke 0.
  • Untuk Jenis kompleks, pengikatan model membuat instans dengan menggunakan konstruktor default, tanpa mengatur properti.
  • Array diatur ke Array.Empty<T>(), kecuali bahwa byte[] array diatur ke null.

Jika status model harus divalidasi ketika tidak ada yang ditemukan di bidang formulir untuk properti model, gunakan [BindRequired] atribut .

Perhatikan bahwa perilaku ini [BindRequired] berlaku untuk pengikatan model dari data formulir yang diposting, bukan ke JSdata ON atau XML dalam isi permintaan. Data isi permintaan ditangani oleh pemformat input.

Kesalahan konversi jenis

Jika sumber ditemukan tetapi tidak dapat dikonversi menjadi jenis target, status model ditandai sebagai tidak valid. Parameter target atau properti diatur ke null atau nilai default, seperti yang disebutkan di bagian sebelumnya.

Dalam pengontrol API yang memiliki [ApiController] atribut , status model yang tidak valid menghasilkan respons HTTP 400 otomatis.

Razor Di halaman, putar ulang halaman dengan pesan kesalahan:

public IActionResult OnPost()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _instructorsInMemoryStore.Add(Instructor);
    return RedirectToPage("./Index");
}

Validasi sisi klien menangkap data paling buruk yang akan dikirimkan ke Razor formulir Pages. Validasi ini membuatnya sulit untuk memicu kode yang disorot sebelumnya. Aplikasi sampel menyertakan tombol Kirim dengan Tanggal Tidak Valid yang menempatkan data buruk di bidang Tanggal Perekrutan dan mengirimkan formulir. Tombol ini menunjukkan cara kerja kode untuk memutar ulang halaman saat terjadi kesalahan konversi data.

Ketika halaman diputar ulang oleh kode sebelumnya, input yang tidak valid tidak ditampilkan di bidang formulir. Ini karena properti model telah diatur ke null atau nilai default. Input yang tidak valid memang muncul dalam pesan kesalahan. Tetapi jika Anda ingin memutar ulang data buruk di bidang formulir, pertimbangkan untuk menjadikan properti model sebagai string dan melakukan konversi data secara manual.

Strategi yang sama direkomendasikan jika Anda tidak ingin kesalahan konversi jenis mengakibatkan kesalahan status model. Dalam hal ini, jadikan properti model sebagai string.

Jenis sederhana

Jenis sederhana pengikat model dapat mengonversi string sumber menjadi termasuk yang berikut:

Jenis kompleks

Jenis kompleks harus memiliki konstruktor default publik dan properti bisa-tulis publik untuk diikat. Saat pengikatan model terjadi, kelas dibuat menggunakan konstruktor default publik.

Untuk setiap properti dari jenis kompleks, pengikatan model melihat melalui sumber untuk pola nama prefix.property_name. Jika tidak ada yang ditemukan, ia mencari hanya property_name tanpa awalan.

Untuk mengikat parameter, awalannya adalah nama parameter. Untuk mengikat ke PageModel properti publik, awalannya adalah nama properti publik. Beberapa atribut memiliki Prefix properti yang memungkinkan Anda mengambil alih penggunaan default parameter atau nama properti.

Misalnya, misalkan jenis kompleks adalah kelas berikut Instructor :

public class Instructor
{
    public int ID { get; set; }
    public string LastName { get; set; }
    public string FirstName { get; set; }
}

Awalan = nama parameter

Jika model yang akan diikat adalah parameter bernama instructorToUpdate:

public IActionResult OnPost(int? id, Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci instructorToUpdate.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan = nama properti

Jika model yang akan diikat adalah properti bernama Instructor pengontrol atau PageModel kelas:

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

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Awalan kustom

Jika model yang akan diikat adalah parameter bernama instructorToUpdate dan Bind atribut menentukan Instructor sebagai awalan:

public IActionResult OnPost(
    int? id, [Bind(Prefix = "Instructor")] Instructor instructorToUpdate)

Pengikatan model dimulai dengan melihat melalui sumber untuk kunci Instructor.ID. Jika itu tidak ditemukan, itu mencari ID tanpa awalan.

Atribut untuk target jenis kompleks

Beberapa atribut bawaan tersedia untuk mengontrol pengikatan model dari jenis kompleks:

  • [Bind]
  • [BindRequired]
  • [BindNever]

Peringatan

Atribut ini memengaruhi pengikatan model saat data formulir yang diposting adalah sumber nilai. Mereka tidak memengaruhi pemformat input, proses mana yang memposting JSbadan permintaan ON dan XML. Pemformat input dijelaskan nanti dalam artikel ini.

Atribut [Ikat]

Dapat diterapkan ke kelas atau parameter metode. Menentukan properti model mana yang harus disertakan dalam pengikatan model. [Bind]tidak memengaruhi pemformat input.

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat ketika penangan atau metode tindakan apa pun dipanggil:

[Bind("LastName,FirstMidName,HireDate")]
public class Instructor

Dalam contoh berikut, hanya properti Instructor model yang ditentukan yang terikat saat OnPost metode dipanggil:

[HttpPost]
public IActionResult OnPost([Bind("LastName,FirstMidName,HireDate")] Instructor instructor)

Atribut [Bind] dapat digunakan untuk melindungi dari overposting dalam membuat skenario. Ini tidak berfungsi dengan baik dalam skenario edit karena properti yang dikecualikan diatur ke null atau nilai default alih-alih dibiarkan tidak berubah. Untuk pertahanan terhadap overposting, lihat model direkomendasikan daripada [Bind] atribut . Untuk informasi selengkapnya, lihat Catatan keamanan tentang overposting.

Atribut [ModelBinder]

ModelBinderAttribute dapat diterapkan ke jenis, properti, atau parameter. Ini memungkinkan menentukan jenis pengikat model yang digunakan untuk mengikat instans atau jenis tertentu. Contohnya:

[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyInstructorModelBinder))] Instructor instructor)

Atribut [ModelBinder] juga dapat digunakan untuk mengubah nama properti atau parameter saat sedang terikat model:

public class Instructor
{
    [ModelBinder(Name = "instructor_id")]
    public string Id { get; set; }

    public string Name { get; set; }
}

Atribut [BindRequired]

Hanya dapat diterapkan ke properti model, bukan ke parameter metode. Menyebabkan pengikatan model untuk menambahkan kesalahan status model jika pengikatan tidak dapat terjadi untuk properti model. Berikut contohnya:

public class InstructorWithCollection
{
    public int ID { get; set; }

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    [Display(Name = "Hire Date")]
    [BindRequired]
    public DateTime HireDate { get; set; }

Lihat juga diskusi [Required] atribut dalam validasi Model.

Atribut [BindNever]

Hanya dapat diterapkan ke properti model, bukan ke parameter metode. Mencegah pengikatan model dari pengaturan properti model. Berikut contohnya:

public class InstructorWithDictionary
{
    [BindNever]
    public int ID { get; set; }

Koleksi

Untuk target yang merupakan kumpulan jenis sederhana, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter yang akan diikat adalah array bernama selectedCourses:

    public IActionResult OnPost(int? id, int[] selectedCourses)
    
  • Data string formulir atau kueri bisa dalam salah satu format berikut:

    selectedCourses=1050&selectedCourses=2000 
    
    selectedCourses[0]=1050&selectedCourses[1]=2000
    
    [0]=1050&[1]=2000
    
    selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b
    
    [a]=1050&[b]=2000&index=a&index=b
    

    Hindari mengikat parameter atau properti bernama index atau Index jika berdekatan dengan nilai koleksi. Pengikatan model mencoba digunakan index sebagai indeks untuk koleksi yang mungkin mengakibatkan pengikatan yang salah. Misalnya, pertimbangkan tindakan berikut:

    public IActionResult Post(string index, List<Product> products)
    

    Dalam kode sebelumnya, index parameter string kueri mengikat parameter index metode dan juga digunakan untuk mengikat koleksi produk. Mengganti nama index parameter atau menggunakan atribut pengikatan model untuk mengonfigurasi pengikatan menghindari masalah ini:

    public IActionResult Post(string productIndex, List<Product> products)
    
  • Format berikut ini hanya didukung dalam data formulir:

    selectedCourses[]=1050&selectedCourses[]=2000
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan array dua item ke selectedCourses parameter :

    • selectedCourses[0]=1050
    • selectedCourses[1]=2000

    Format data yang menggunakan nomor subskrip (... [0] ... [1] ...) harus memastikan bahwa mereka diberi nomor secara berurutan mulai dari nol. Jika ada celah dalam penomoran subskrip, semua item setelah celah diabaikan. Misalnya, jika subskrip adalah 0 dan 2, bukan 0 dan 1, item kedua diabaikan.

Kamus

Untuk Dictionary target, pengikatan model mencari kecocokan untuk parameter_name atau property_name. Jika tidak ada kecocokan yang ditemukan, ia mencari salah satu format yang didukung tanpa awalan. Contohnya:

  • Misalkan parameter target bernama Dictionary<int, string>selectedCourses:

    public IActionResult OnPost(int? id, Dictionary<int, string> selectedCourses)
    
  • Formulir yang diposting atau data string kueri bisa terlihat seperti salah satu contoh berikut:

    selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics
    
    [1050]=Chemistry&selectedCourses[2000]=Economics
    
    selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
    selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics
    
    [0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics
    
  • Untuk semua format contoh sebelumnya, pengikatan model meneruskan kamus dua item ke selectedCourses parameter :

    • selectedCourses["1050"]="Chemistry"
    • selectedCourses["2000"]="Economics"

Pengikatan konstruktor dan jenis rekaman

Pengikatan model mengharuskan jenis kompleks memiliki konstruktor tanpa parameter. Pemformat System.Text.Json input berbasis dan Newtonsoft.Json mendukung deserialisasi kelas yang tidak memiliki konstruktor tanpa parameter.

C# 9 memperkenalkan jenis rekaman, yang merupakan cara yang bagus untuk mewakili data melalui jaringan. ASP.NET Core menambahkan dukungan untuk pengikatan model dan memvalidasi jenis rekaman dengan satu konstruktor:

public record Person([Required] string Name, [Range(0, 150)] int Age, [BindNever] int Id);

public class PersonController
{
   public IActionResult Index() => View();

   [HttpPost]
   public IActionResult Index(Person person)
   {
       ...
   }
}

Person/Index.cshtml:

@model Person

Name: <input asp-for="Name" />
...
Age: <input asp-for="Age" />

Saat memvalidasi jenis rekaman, runtime mencari metadata pengikatan dan validasi khusus pada parameter daripada pada properti.

Kerangka kerja memungkinkan pengikatan ke dan memvalidasi jenis rekaman:

public record Person([Required] string Name, [Range(0, 100)] int Age);

Agar sebelumnya berfungsi, jenisnya harus:

  • Jadilah jenis catatan.
  • Memiliki tepat satu konstruktor publik.
  • Berisi parameter yang memiliki properti dengan nama dan jenis yang sama. Nama tidak boleh berbeda menurut kasus.

POCO tanpa konstruktor tanpa parameter

POCO yang tidak memiliki konstruktor tanpa parameter tidak dapat terikat.

Kode berikut menghasilkan pengecualian yang mengatakan bahwa jenis harus memiliki konstruktor tanpa parameter:

public class Person(string Name)

public record Person([Required] string Name, [Range(0, 100)] int Age)
{
   public Person(string Name) : this (Name, 0);
}

Jenis rekaman dengan konstruktor yang ditulis secara manual

Jenis rekaman dengan konstruktor yang ditulis secara manual yang terlihat seperti pekerjaan konstruktor utama

public record Person
{
   public Person([Required] string Name, [Range(0, 100)] int Age) => (this.Name, this.Age) = (Name, Age);

   public string Name { get; set; }
   public int Age { get; set; }
}

Jenis rekaman, validasi, dan metadata pengikatan

Untuk jenis rekaman, metadata validasi dan pengikatan pada parameter digunakan. Metadata apa pun pada properti diabaikan

public record Person (string Name, int Age)
{
   [BindProperty(Name = "SomeName")] // This does not get used
   [Required] // This does not get used
   public string Name { get; init; }
}

Validasi dan metadata

Validasi menggunakan metadata pada parameter tetapi menggunakan properti untuk membaca nilai. Dalam kasus biasa dengan konstruktor utama, keduanya akan identik. Namun, ada cara untuk mengalahkannya:

public record Person([Required] string Name)
{
   private readonly string _name;
   public Name { get; init => _name = value ?? string.Empty; } // Now this property is never null. However this object could have been constructed as `new Person(null);`
}

TryUpdateModel tidak memperbarui parameter pada jenis catatan

public record Person(string Name)
{
   public int Age { get; set; }
}

var person = new Person("initial-name");
TryUpdateModel(person, ...);

Dalam hal ini, MVC tidak akan mencoba mengikat Name lagi. Namun, Age diizinkan untuk diperbarui

Perilaku globalisasi data rute pengikatan model dan string kueri

Penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri:

  • Perlakukan nilai sebagai budaya invarian.
  • Harapkan bahwa URL invarian budaya.

Sebaliknya, nilai yang berasal dari data formulir mengalami konversi sensitif terhadap budaya. Ini dirancang agar URL dapat dibagikan di seluruh lokal.

Untuk membuat penyedia nilai rute inti ASP.NET dan penyedia nilai string kueri menjalani konversi yang sensitif terhadap budaya:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        var index = options.ValueProviderFactories.IndexOf(
            options.ValueProviderFactories.OfType<QueryStringValueProviderFactory>().Single());
        options.ValueProviderFactories[index] = new CulturedQueryStringValueProviderFactory();
    });
}
public class CulturedQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var query = context.ActionContext.HttpContext.Request.Query;
        if (query != null && query.Count > 0)
        {
            var valueProvider = new QueryStringValueProvider(
                BindingSource.Query,
                query,
                CultureInfo.CurrentCulture);

            context.ValueProviders.Add(valueProvider);
        }

        return Task.CompletedTask;
    }
}

Tipe data khusus

Ada beberapa jenis data khusus yang dapat ditangani oleh pengikatan model.

IFormFile dan IFormFileCollection

File yang diunggah disertakan dalam permintaan HTTP. Juga didukung adalah IEnumerable<IFormFile> untuk beberapa file.

TokenPembatalan

Tindakan dapat secara opsional mengikat CancellationToken sebagai parameter. Ini mengikat yang memberi sinyal ketika koneksi yang mendasar RequestAborted permintaan HTTP dibatalkan. Tindakan dapat menggunakan parameter ini untuk membatalkan operasi asinkron jangka panjang yang dijalankan sebagai bagian dari tindakan pengontrol.

FormCollection

Digunakan untuk mengambil semua nilai dari data formulir yang diposting.

Pemformat input

Data dalam isi permintaan dapat berada di JSON, XML, atau beberapa format lainnya. Untuk mengurai data ini, pengikatan model menggunakan pemformat input yang dikonfigurasi untuk menangani jenis konten tertentu. Secara default, ASP.NET Core menyertakan JSpemformat input berbasis ON untuk menangani JSdata ON. Anda bisa menambahkan formatter lain untuk tipe isi lainnya.

ASP.NET Core memilih pemformat input berdasarkan atribut Konsumsi . Jika tidak ada atribut, atribut menggunakan header Content-Type.

Untuk menggunakan pemformat input XML bawaan:

  • Microsoft.AspNetCore.Mvc.Formatters.Xml Instal paket NuGet.

  • Di Startup.ConfigureServices, panggil AddXmlSerializerFormatters atau AddXmlDataContractSerializerFormatters.

    services.AddRazorPages()
        .AddMvcOptions(options =>
    {
        options.ValueProviderFactories.Add(new CookieValueProviderFactory());
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(System.Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
    })
    .AddXmlSerializerFormatters();
    
  • Terapkan Consumes atribut ke kelas pengontrol atau metode tindakan yang seharusnya mengharapkan XML dalam isi permintaan.

    [HttpPost]
    [Consumes("application/xml")]
    public ActionResult<Pet> Create(Pet pet)
    

    Untuk informasi selengkapnya, lihat Memperkenalkan Serialisasi XML.

Menyesuaikan pengikatan model dengan pemformat input

Pemformat input bertanggung jawab penuh untuk membaca data dari isi permintaan. Untuk menyesuaikan proses ini, konfigurasikan API yang digunakan oleh pemformat input. Bagian ini menjelaskan cara mengkustomisasi System.Text.Jsonpemformat input berbasis untuk memahami jenis kustom bernama ObjectId.

Pertimbangkan model berikut, yang berisi properti kustom ObjectId bernama Id:

public class ModelWithObjectId
{
    public ObjectId Id { get; set; }
}

Untuk menyesuaikan proses pengikatan model saat menggunakan System.Text.Json, buat kelas yang berasal dari JsonConverter<T>:

internal class ObjectIdConverter : JsonConverter<ObjectId>
{
    public override ObjectId Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return new ObjectId(JsonSerializer.Deserialize<int>(ref reader, options));
    }

    public override void Write(
        Utf8JsonWriter writer, ObjectId value, JsonSerializerOptions options)
    {
        writer.WriteNumberValue(value.Id);
    }
}

Untuk menggunakan pengonversi kustom, terapkan JsonConverterAttribute atribut ke jenis . Dalam contoh berikut, jenis dikonfigurasi ObjectId dengan ObjectIdConverter sebagai pengonversi kustomnya:

[JsonConverter(typeof(ObjectIdConverter))]
public struct ObjectId
{
    public ObjectId(int id) =>
        Id = id;

    public int Id { get; }
}

Untuk informasi selengkapnya, lihat Cara menulis pengonversi kustom.

Mengecualikan jenis yang ditentukan dari pengikatan model

Perilaku sistem pengikatan dan validasi model didorong oleh ModelMetadata. Anda dapat menyesuaikan ModelMetadata dengan menambahkan penyedia detail ke MvcOptions.ModelMetadataDetailsProviders. Penyedia detail bawaan tersedia untuk menonaktifkan pengikatan atau validasi model untuk jenis tertentu.

Untuk menonaktifkan pengikatan model pada semua model dari jenis tertentu, tambahkan ExcludeBindingMetadataProvider di Startup.ConfigureServices. Misalnya, untuk menonaktifkan pengikatan model pada semua model jenis System.Version:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Untuk menonaktifkan validasi pada properti dari jenis tertentu, tambahkan SuppressChildValidationMetadataProvider di Startup.ConfigureServices. Misalnya, untuk menonaktifkan validasi pada properti jenis System.Guid:

services.AddRazorPages()
    .AddMvcOptions(options =>
{
    options.ValueProviderFactories.Add(new CookieValueProviderFactory());
    options.ModelMetadataDetailsProviders.Add(
        new ExcludeBindingMetadataProvider(typeof(System.Version)));
    options.ModelMetadataDetailsProviders.Add(
        new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
})
.AddXmlSerializerFormatters();

Pengikat model kustom

Anda dapat memperluas pengikatan model dengan menulis pengikat model kustom dan menggunakan [ModelBinder] atribut untuk memilihnya untuk target tertentu. Pelajari selengkapnya tentang pengikatan model kustom.

Pengikatan model manual

Pengikatan model dapat dipanggil secara manual dengan menggunakan TryUpdateModelAsync metode . Metode ini didefinisikan pada kedua ControllerBase kelas dan PageModel . Kelebihan metode memungkinkan Anda menentukan awalan dan penyedia nilai yang akan digunakan. Metode mengembalikan false jika pengikatan model gagal. Berikut contohnya:

if (await TryUpdateModelAsync<InstructorWithCollection>(
    newInstructor,
    "Instructor",
    i => i.FirstMidName, i => i.LastName, i => i.HireDate))
{
    _instructorsInMemoryStore.Add(newInstructor);
    return RedirectToPage("./Index");
}
PopulateAssignedCourseData(newInstructor);
return Page();

TryUpdateModelAsync menggunakan penyedia nilai untuk mendapatkan data dari isi formulir, string kueri, dan data rute. TryUpdateModelAsync biasanya:

  • Digunakan dengan Razor aplikasi Pages dan MVC menggunakan pengontrol dan tampilan untuk mencegah pengeposan berlebihan.
  • Tidak digunakan dengan API web kecuali dikonsumsi dari data formulir, string kueri, dan data rute. Titik akhir API Web yang menggunakan JSON menggunakan Pemformat input untuk mendeserialisasi isi permintaan ke dalam objek.

Untuk informasi selengkapnya, lihat TryUpdateModelAsync.

Atribut [FromServices]

Nama atribut ini mengikuti pola atribut pengikatan model yang menentukan sumber data. Tetapi ini bukan tentang mengikat data dari penyedia nilai. Ini mendapatkan instans jenis dari kontainer injeksi dependensi. Tujuannya adalah untuk memberikan alternatif untuk injeksi konstruktor ketika Anda membutuhkan layanan hanya jika metode tertentu dipanggil.

Sumber Daya Tambahan: