Bagaimana saya dapat mengambil lebih banyak kendali di ASP.NET?

124

Saya mencoba untuk membangun sebuah "mikro-webapp" yang sangat, sangat sederhana yang saya curigai akan menarik bagi beberapa Stack Overflow jika saya bisa menyelesaikannya. Saya menghostingnya di situs C # in Depth saya, yaitu vanilla ASP.NET 3.5 (bukan MVC).

Alurnya sangat sederhana:

  • Jika pengguna memasuki aplikasi dengan URL yang tidak menentukan semua parameter (atau jika ada yang tidak valid) saya hanya ingin menampilkan kontrol masukan pengguna. (Hanya ada dua.)
  • Jika pengguna memasukkan app dengan URL yang tidak memiliki semua parameter yang diperlukan, saya ingin menampilkan hasil dan kontrol input (sehingga mereka dapat mengubah parameter)

Berikut adalah persyaratan yang saya tetapkan sendiri (campuran desain dan implementasi):

  • Saya ingin kiriman menggunakan GET daripada POST, sebagian besar agar pengguna dapat menandai halaman dengan mudah.
  • Saya tidak ingin URL-nya terlihat konyol setelah dikirimkan, dengan potongan dan potongan yang tidak relevan. Tolong hanya URL utama dan parameter aslinya.
  • Idealnya saya sama sekali tidak ingin meminta JavaScript. Tidak ada alasan bagus untuk itu di aplikasi ini.
  • Saya ingin dapat mengakses kontrol selama waktu render dan mengatur nilai, dll. Secara khusus, saya ingin dapat mengatur nilai default kontrol ke nilai parameter yang diteruskan, jika ASP.NET tidak dapat melakukan ini secara otomatis untuk saya (dalam batasan lain).
  • Saya senang bisa melakukan sendiri semua validasi parameter, dan saya tidak membutuhkan banyak hal terkait peristiwa sisi server. Sangat mudah untuk mengatur semuanya pada pemuatan halaman daripada melampirkan acara ke tombol dll.

Sebagian besar tidak apa-apa, tetapi saya belum menemukan cara apa pun untuk sepenuhnya menghapus kondisi tampilan dan menjaga fungsionalitas berguna lainnya. Dengan menggunakan postingan dari postingan blog ini, saya berhasil menghindari mendapatkan nilai aktual apa pun untuk kondisi tampilan - tetapi itu masih berakhir sebagai parameter pada URL, yang terlihat sangat jelek.

Jika saya membuatnya menjadi bentuk HTML biasa daripada bentuk ASP.NET (yaitu mengambil runat="server") maka saya tidak mendapatkan kondisi tampilan ajaib - tetapi kemudian saya tidak dapat mengakses kontrol secara terprogram.

Saya bisa melakukan semua ini dengan mengabaikan sebagian besar ASP.NET dan membangun dokumen XML dengan LINQ ke XML, dan menerapkannya IHttpHandler. Itu terasa level yang agak rendah.

Saya menyadari bahwa masalah saya dapat diselesaikan dengan merilekskan kendala saya (misalnya menggunakan POST dan tidak peduli dengan parameter surplus) atau dengan menggunakan ASP.NET MVC, tetapi apakah persyaratan saya benar-benar tidak masuk akal?

Mungkin ASP.NET tidak menurunkan skala ke aplikasi semacam ini? Namun ada alternatif yang sangat mungkin: Saya hanya bersikap bodoh, dan ada cara yang sangat sederhana untuk melakukannya yang belum saya temukan.

Ada pemikiran, siapa? (Isyarat komentar tentang bagaimana yang perkasa jatuh, dll. Tidak apa-apa - Saya harap saya tidak pernah mengklaim sebagai pakar ASP.NET, karena kenyataannya justru sebaliknya ...)

Jon Skeet
sumber
16
"Isyarat komentar tentang bagaimana yang perkasa jatuh" - kita semua tidak peduli, hanya tentang hal yang berbeda. Saya baru saja mulai berpartisipasi di sini, tetapi saya mengagumi pertanyaan itu lebih dari semua poinnya. Anda jelas masih berpikir dan belajar. Kudos to you.
duffymo
15
Saya tidak berpikir saya akan pernah memperhatikan seseorang yang menyerah belajar :)
Jon Skeet
1
Benar dalam kasus umum. Benar sekali dalam ilmu komputer.
Mehrdad Afshari
3
Dan akankah buku Anda berikutnya berjudul "ASP.NET in Depth"? :-P
chakrit
20
Ya, itu akan keluar pada 2025;)
Jon Skeet

Jawaban:

76

Solusi ini akan memberi Anda akses terprogram ke kontrol secara keseluruhan termasuk semua atribut di kontrol. Selain itu, hanya nilai kotak teks yang akan muncul di URL setelah pengiriman sehingga URL permintaan GET Anda akan lebih "bermakna"

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="JonSkeetForm.aspx.cs" Inherits="JonSkeetForm" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Jon Skeet's Form Page</title>
</head>
<body>
    <form action="JonSkeetForm.aspx" method="get">
    <div>
        <input type="text" ID="text1" runat="server" />
        <input type="text" ID="text2" runat="server" />
        <button type="submit">Submit</button>
        <asp:Repeater ID="Repeater1" runat="server">
            <ItemTemplate>
                <div>Some text</div>
            </ItemTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>

Kemudian di belakang kode Anda, Anda dapat melakukan semua yang Anda butuhkan di PageLoad

public partial class JonSkeetForm : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        text1.Value = Request.QueryString[text1.ClientID];
        text2.Value = Request.QueryString[text2.ClientID];
    }
}

Jika Anda tidak menginginkan formulir yang memiliki runat="server", maka Anda harus menggunakan kontrol HTML. Lebih mudah untuk bekerja dengan tujuan Anda. Cukup gunakan tag HTML biasa dan letakkan runat="server"dan beri mereka ID. Kemudian Anda dapat mengaksesnya secara terprogram dan kode tanpa ViewState.

Satu-satunya downside adalah bahwa Anda tidak akan memiliki akses ke banyak kontrol server ASP.NET yang "membantu" seperti GridViews. Saya menyertakan a Repeaterdalam contoh saya karena saya berasumsi bahwa Anda ingin memiliki bidang pada halaman yang sama dengan hasil dan (sepengetahuan saya) a Repeateradalah satu-satunya kontrol DataBound yang akan berjalan tanpa runat="server"atribut di tag Formulir.

Dan Herbert
sumber
1
Saya memiliki begitu sedikit bidang sehingga melakukannya secara manual sangat mudah :) Kuncinya adalah saya tidak tahu bahwa saya dapat menggunakan runat = server dengan kontrol HTML normal. Saya belum menerapkan hasilnya, tapi itu bagian yang mudah. Hampir sampai!
Jon Skeet
Memang, <form runat = "server"> akan menambahkan bidang tersembunyi __VIEWSTATE (dan beberapa lainnya) bahkan ketika Anda menyetel EnableViewState = "False" di tingkat halaman. Ini adalah cara yang harus dilakukan jika Anda ingin kehilangan Kondisi Tampilan pada halaman. Untuk keramahan Url, penulisan url mungkin bisa menjadi pilihan.
Sergiu Damian
1
Tidak perlu menulis ulang. Jawaban ini berfungsi dengan baik (meskipun ini berarti memiliki kontrol dengan ID "pengguna" - untuk beberapa alasan saya tidak dapat mengubah nama kontrol kotak teks secara terpisah dari ID-nya).
Jon Skeet
1
Sekadar konfirmasi, ini memang bekerja dengan sangat baik. Terima kasih banyak!
Jon Skeet
14
Sepertinya Anda seharusnya baru saja menulisnya di asp klasik!
ScottE
12

Anda pasti (IMHO) di jalur yang benar dengan tidak menggunakan runat = "server" di tag FORM Anda. Ini hanya berarti Anda harus mengekstrak nilai dari Request.QueryString secara langsung, seperti dalam contoh ini:

Di halaman .aspx itu sendiri:

<%@ Page Language="C#" AutoEventWireup="true" 
     CodeFile="FormPage.aspx.cs" Inherits="FormPage" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>ASP.NET with GET requests and no viewstate</title>
</head>
<body>
    <asp:Panel ID="ResultsPanel" runat="server">
      <h1>Results:</h1>
      <asp:Literal ID="ResultLiteral" runat="server" />
      <hr />
    </asp:Panel>
    <h1>Parameters</h1>
    <form action="FormPage.aspx" method="get">
    <label for="parameter1TextBox">
      Parameter 1:</label>
    <input type="text" name="param1" id="param1TextBox" value='<asp:Literal id="Param1ValueLiteral" runat="server" />'/>
    <label for="parameter1TextBox">
      Parameter 2:</label>
    <input type="text" name="param2" id="param2TextBox"  value='<asp:Literal id="Param2ValueLiteral" runat="server" />'/>
    <input type="submit" name="verb" value="Submit" />
    </form>
</body>
</html>

dan di belakang kode:

using System;

public partial class FormPage : System.Web.UI.Page {

        private string param1;
        private string param2;

        protected void Page_Load(object sender, EventArgs e) {

            param1 = Request.QueryString["param1"];
            param2 = Request.QueryString["param2"];

            string result = GetResult(param1, param2);
            ResultsPanel.Visible = (!String.IsNullOrEmpty(result));

            Param1ValueLiteral.Text = Server.HtmlEncode(param1);
            Param2ValueLiteral.Text = Server.HtmlEncode(param2);
            ResultLiteral.Text = Server.HtmlEncode(result);
        }

        // Do something with parameters and return some result.
        private string GetResult(string param1, string param2) {
            if (String.IsNullOrEmpty(param1) && String.IsNullOrEmpty(param2)) return(String.Empty);
            return (String.Format("You supplied {0} and {1}", param1, param2));
        }
    }

Triknya di sini adalah kita menggunakan ASP.NET Literals di dalam atribut value = "" dari input teks, sehingga kotak teks itu sendiri tidak harus runat = "server". Hasilnya kemudian dibungkus di dalam ASP: Panel, dan properti Terlihat diatur pada pemuatan halaman tergantung apakah Anda ingin menampilkan hasil apa pun atau tidak.

Dylan Beattie
sumber
Ini berfungsi cukup baik, tetapi URL tidak akan ramah seperti, katakanlah, StackOverflow.
Mehrdad Afshari
1
URL-nya akan sangat bersahabat, saya pikir ... Ini terlihat seperti solusi yang sangat bagus.
Jon Skeet
Argh, saya membaca tweet Anda sebelumnya, telah menelitinya, dan sekarang saya melewatkan pertanyaan Anda saat mempersiapkan anak-anak saya untuk bak mandi ... :-)
splattne
2

Oke Jon, masalah viewstate dulu:

Saya belum memeriksa apakah ada jenis perubahan kode internal sejak 2.0 tetapi inilah cara saya menangani penghapusan kondisi tampilan beberapa tahun yang lalu. Sebenarnya bidang tersembunyi itu di-hardcode di dalam HtmlForm sehingga Anda harus mendapatkan yang baru dan melangkah ke renderingnya untuk membuat panggilan sendiri. Perhatikan bahwa Anda juga dapat membiarkan __eventtarget dan __eventtarget tidak aktif jika Anda tetap menggunakan kontrol input lama biasa (yang menurut saya Anda ingin karena itu juga membantu tidak memerlukan JS pada klien):

protected override void RenderChildren(System.Web.UI.HtmlTextWriter writer)
{
    System.Web.UI.Page page = this.Page;
    if (page != null)
    {
        onFormRender.Invoke(page, null);
        writer.Write("<div><input type=\"hidden\" name=\"__eventtarget\" id=\"__eventtarget\" value=\"\" /><input type=\"hidden\" name=\"__eventargument\" id=\"__eventargument\" value=\"\" /></div>");
    }

    ICollection controls = (this.Controls as ICollection);
    renderChildrenInternal.Invoke(this, new object[] {writer, controls});

    if (page != null)
        onFormPostRender.Invoke(page, null);
}

Jadi Anda mendapatkan 3 MethodInfo statis itu dan memanggil mereka melewatkan bagian kondisi tampilan itu;)

static MethodInfo onFormRender;
static MethodInfo renderChildrenInternal;
static MethodInfo onFormPostRender;

dan inilah konstruktor tipe formulir Anda:

static Form()
{
    Type aspNetPageType = typeof(System.Web.UI.Page);

    onFormRender = aspNetPageType.GetMethod("OnFormRender", BindingFlags.Instance | BindingFlags.NonPublic);
    renderChildrenInternal = typeof(System.Web.UI.Control).GetMethod("RenderChildrenInternal", BindingFlags.Instance | BindingFlags.NonPublic);
    onFormPostRender = aspNetPageType.GetMethod("OnFormPostRender", BindingFlags.Instance | BindingFlags.NonPublic);
}

Jika saya menjawab pertanyaan Anda dengan benar, Anda juga ingin tidak menggunakan POST sebagai tindakan formulir Anda, jadi inilah cara melakukannya:

protected override void RenderAttributes(System.Web.UI.HtmlTextWriter writer)
{
    writer.WriteAttribute("method", "get");
    base.Attributes.Remove("method");

    // the rest of it...
}

Saya kira ini cukup banyak. Beri tahu saya bagaimana kelanjutannya.

EDIT: Saya lupa metode kondisi tampilan Halaman:

Jadi, Formulir kustom Anda: HtmlForm mendapatkan abstrak baru (atau bukan) Halaman: System.Web.UI.Page: P

protected override sealed object SaveViewState()
{
    return null;
}

protected override sealed void SavePageStateToPersistenceMedium(object state)
{
}

protected override sealed void LoadViewState(object savedState)
{
}

protected override sealed object LoadPageStateFromPersistenceMedium()
{
    return null;
}

Dalam hal ini saya menutup metode karena Anda tidak dapat menyegel Halaman (meskipun tidak abstrak Scott Guthrie akan membungkusnya menjadi satu lagi: P) tetapi Anda dapat menyegel Formulir Anda.

pengguna134706
sumber
Terima kasih untuk ini - meskipun kedengarannya agak merepotkan. Solusi Dan bekerja dengan baik untuk saya, tetapi selalu bagus untuk memiliki lebih banyak pilihan.
Jon Skeet
1

Pernahkah Anda berpikir untuk tidak menghapus POST melainkan mengarahkan ke url GET yang sesuai saat formulir di-POST. Artinya, terima GET dan POST, tetapi pada POST buat permintaan GET dan alihkan ke sana. Ini dapat ditangani baik di halaman atau melalui HttpModule jika Anda ingin membuatnya tidak tergantung halaman. Saya pikir ini akan membuat segalanya lebih mudah.

EDIT: Saya berasumsi bahwa Anda telah menetapkan EnableViewState = "false" pada halaman.

tvanfosson.dll
sumber
Ide bagus. Ide yang buruk dalam hal dipaksa untuk melakukannya, tapi bagus dalam hal itu mungkin berhasil :) Akan mencoba ...
Jon Skeet
Dan ya, saya sudah mencoba EnableViewState = false di semua tempat. Itu tidak sepenuhnya menonaktifkannya, cukup potong saja.
Jon Skeet
Jon: Jika Anda tidak menggunakan kontrol server terkutuk (tidak ada runat = "server") dan Anda tidak memiliki <form runat = "server"> sama sekali, Kondisi Tampilan tidak akan menjadi masalah. Itu sebabnya saya mengatakan untuk tidak menggunakan kontrol server. Anda selalu dapat menggunakan koleksi Request.Form.
Mehrdad Afshari
Tetapi tanpa runat = server pada kontrol, sulit untuk menyebarkan nilai ke kontrol lagi saat rendering. Untungnya, kontrol HTML dengan runat = server berfungsi dengan baik.
Jon Skeet
1

Saya akan membuat modul HTTP yang menangani perutean (mirip dengan MVC tetapi tidak canggih, hanya beberapa ifpernyataan) dan menyerahkannya ke halaman aspxatau ashx. aspxlebih disukai karena lebih mudah untuk mengubah template halaman. Saya tidak akan menggunakan WebControlsdalam aspxnamun. Cuma Response.Write.

Untuk menyederhanakan, Anda dapat melakukan validasi parameter dalam modul (karena modul tersebut mungkin berbagi kode dengan perutean) dan menyimpannya ke HttpContext.Itemsdan kemudian merendernya di halaman. Ini akan bekerja hampir seperti MVC tanpa semua bel dan peluit. Inilah yang sering saya lakukan sebelum hari ASP.NET MVC.

Mehrdad Afshari
sumber
1

Saya sangat senang untuk benar-benar meninggalkan kelas halaman sama sekali dan hanya menangani setiap permintaan dengan kasus saklar besar berdasarkan url. Setiap "halaman" menjadi template html dan objek ac #. Kelas template menggunakan regex dengan delegasi pencocokan yang dibandingkan dengan kumpulan kunci.

manfaat:

  1. Sangat cepat, bahkan setelah kompilasi ulang, hampir tidak ada jeda (kelas halaman harus besar)
  2. kontrol benar-benar terperinci (bagus untuk SEO, dan membuat DOM agar berfungsi dengan baik dengan JS)
  3. penyajiannya terpisah dari logika
  4. jQuery memiliki kendali penuh atas html

gelandangan:

  1. hal-hal sederhana memerlukan waktu lebih lama karena satu kotak teks memerlukan kode di beberapa tempat, tetapi skalanya sangat baik
  2. Selalu menggoda untuk melakukannya dengan tampilan halaman sampai saya melihat kondisi tampilan (urgh) lalu saya kembali ke kenyataan.

Jon, apa yang kita lakukan SO pada Sabtu pagi :)?

missaghi
sumber
1
Ini Sabtu malam di sini. Apakah itu membuatnya oke? (Saya ingin melihat grafik sebaran waktu / hari posting saya, btw ...)
Jon Skeet
1

Saya pikir asp: Kontrol repeater sudah usang.

Mesin template ASP.NET bagus tetapi Anda dapat dengan mudah menyelesaikan pengulangan dengan for loop ...

<form action="JonSkeetForm.aspx" method="get">
<div>
    <input type="text" ID="text1" runat="server" />
    <input type="text" ID="text2" runat="server" />
    <button type="submit">Submit</button>
    <% foreach( var item in dataSource ) { %>
        <div>Some text</div>   
    <% } %>
</div>
</form>

Formulir ASP.NET tidak apa-apa, ada dukungan yang layak dari Visual Studio tetapi hal runat = "server" ini, itu salah. ViewState hingga.

Saya sarankan Anda melihat apa yang membuat ASP.NET MVC begitu hebat, siapa yang menjauh dari pendekatan Formulir ASP.NET tanpa membuang semuanya.

Anda bahkan dapat menulis barang penyedia build Anda sendiri untuk menyusun tampilan kustom seperti NHaml. Saya pikir Anda harus melihat di sini untuk kontrol lebih lanjut dan hanya mengandalkan runtime ASP.NET untuk membungkus HTTP dan sebagai lingkungan hosting CLR. Jika Anda menjalankan mode terintegrasi maka Anda juga dapat memanipulasi permintaan / respons HTTP.

John Leidegren
sumber