ASP.NET'te nasıl daha fazla denetim alabilirim?


124

Çok, çok basit bir "mikro-webapp" oluşturmaya çalışıyorum ki bunu başarırsam birkaç Stack Overflow'unun ilgisini çekeceğinden şüpheleniyorum. Bunu, Vanilla ASP.NET 3.5 olan (yani MVC olmayan) Depth sitemde barındırıyorum.

Akış çok basit:

  • Bir kullanıcı uygulamaya tüm parametreleri belirtmeyen bir URL ile girerse (veya bunlardan herhangi biri geçersizse) yalnızca kullanıcı giriş kontrollerini görüntülemek istiyorum. (Sadece iki tane var.)
  • Bir kullanıcı bir URL ile uygulamayı girerse yaptığı tüm gerekli parametreleri var, sonuçlarını görüntülemek istediğiniz ve giriş kontrolleri (bunlar parametrelerini değiştirebilir böylece)

İşte kendi kendime empoze edilen gereksinimlerim (tasarım ve uygulamanın karışımı):

  • Gönderimin POST yerine GET kullanmasını istiyorum, çoğunlukla kullanıcılar sayfaya kolayca yer işareti koyabilir.
  • Ben yok URL üzerinde gereksiz bit ve parçaları ile gönderildikten sonra saçma görsterme istiyorum. Lütfen sadece ana URL ve gerçek parametreler.
  • İdeal olarak JavaScript gerektirmekten kaçınmak isterim. Bu uygulamada bunun için iyi bir neden yok.
  • Oluşturma zamanı sırasında denetimlere erişebilmek ve değerleri vb. Ayarlamak istiyorum. Özellikle, ASP.NET bunu otomatik olarak yapamazsa, denetimlerin varsayılan değerlerini, iletilen parametre değerlerine ayarlayabilmek istiyorum. benim için (diğer kısıtlamalar dahilinde).
  • Tüm parametre doğrulamasını kendim yapmaktan mutluyum ve sunucu tarafı olayları açısından çok ihtiyacım yok. Düğmelere etkinlik eklemek yerine her şeyi sayfa yüklemesinde ayarlamak gerçekten çok basit.

Bunların çoğu sorun değil, ancak görünüm durumunu tamamen kaldırmanın ve yararlı işlevselliğin geri kalanını korumanın bir yolunu bulamadım . Yayının kullanarak bu blog yayınında herhangi fiili almamak başardınız değeri Görünüm durumu için - ama yine de gerçekten çirkin görünüyor URL üzerinde bir parametre olarak sona erer.

Eğer onu bir ASP.NET formu yerine düz bir HTML formu yaparsam (yani çıkarırsam runat="server") o zaman sihirli bir görünüm durumu elde edemem - ama o zaman kontrollere programla erişemiyorum.

Ben olabilir ASP.NET çoğunu görmezden ve XML LINQ ile bir XML belgesi oluşturarak ve uygulayarak tüm bu yapmak IHttpHandler. Bu biraz düşük seviyede olsa da.

Sorunlarımın ya kısıtlamalarımı gevşeterek (örneğin, POST kullanarak ve artı parametresini önemsemeyerek) ya da ASP.NET MVC kullanarak çözülebileceğinin farkındayım, ancak gereksinimlerim gerçekten mantıksız mı?

Belki ASP.NET sadece ölçek değildir aşağı uygulamanın bu tür? Yine de çok muhtemel bir alternatif var: Sadece aptallaşıyorum ve bunu yapmanın henüz bulamadığım mükemmel basit bir yolu var.

Herhangi bir fikrin var mı? (Kudretli olanların nasıl düştüğüne dair ipucu yorumlar, vb. Sorun değil - Umarım hiçbir zaman bir ASP.NET uzmanı olduğumu iddia etmemiştim, çünkü gerçek tam tersi ...)


16
"Kudretli olanın nasıl düştüğüne dair ipuçları" - hepimiz cahiliz, sadece farklı şeyler. Buraya daha yeni katılmaya başladım, ancak soruyu her noktadan daha çok takdir ediyorum. Belli ki hala düşünüyor ve öğreniyorsun. Tebrikler.
duffymo

15
Öğrenmeyi
bırakmış

1
Genel durumda doğrudur. Bilgisayar biliminde çok doğru.
Mehrdad Afshari

3
Bir sonraki kitabınız "Derinlemesine ASP.NET" mi olacak? :-P
chakrit

20
Evet, 2025'te çıkması bekleniyor;)
Jon Skeet

Yanıtlar:


76

Bu çözüm, kontrollerdeki tüm öznitelikler dahil olmak üzere denetimlere bir bütün olarak programlı erişim sağlayacaktır. Ayrıca, gönderildikten sonra URL'de yalnızca metin kutusu değerleri görünecektir, böylece GET isteği URL'niz daha "anlamlı" olacaktır.

<%@ 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>

Ardından arka plan kodunuzda ihtiyacınız olan her şeyi PageLoad'da yapabilirsiniz

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];
    }
}

Sahip bir form istemiyorsanız runat="server", HTML kontrollerini kullanmalısınız. Amaçlarınız için birlikte çalışmak daha kolay. Sadece normal HTML etiketlerini kullanın runat="server"ve onlara bir kimlik koyun ve verin. Sonra programlı erişebilirsiniz ve bir olmadan kod ViewState.

Tek dezavantajı, "yararlı" ASP.NET sunucu denetimlerinin çoğuna erişemeyeceğinizdir GridView. Ben dahil Repeaterben Sonuçlardan aynı sayfada alanlara sahip istiyorum ve (bildiğim kadarıyla) bir varsayarak olduğum için benim örnekte Repeaterbir olmadan çalışacaktır sadece veri sınırlama kontrolüdür runat="server"Formu etiketinde öznitelik.


1
Bunu elle yapmanın gerçekten kolay olduğu o kadar az alanım var :) Anahtar nokta, normal HTML kontrolleri ile runat = server kullanabileceğimi bilmiyordum. Sonuçları henüz uygulamadım, ama bu işin kolay kısmı. Neredeyse orada!
Jon Skeet

Aslında, bir <form runat = "server">, sayfa düzeyinde EnableViewState = "False" ayarını yapsanız bile __VIEWSTATE (ve başka bazı) gizli alanı ekler. Sayfadaki ViewState'i kaybetmek istiyorsanız gitmenin yolu budur. Url dostluğuna gelince, urlrewriting bir seçenek olabilir.
Sergiu Damian

1
Yeniden yazmaya gerek yok. Bu yanıt gayet iyi çalışıyor (ancak "kullanıcı" kimliğine sahip bir kontrole sahip olmak anlamına gelse de - bazı nedenlerden dolayı bir metin kutusu kontrolünün adını kimliğinden ayrı olarak değiştiremiyorum).
Jon Skeet

1
Sadece teyit etmek için, bu gerçekten çok iyi çalıştı. Çok teşekkürler!
Jon Skeet

14
Görünüşe göre bunu klasik asp ile yazmalıydın!
ScottE

12

FORM etiketinizde runat = "server" kullanmayarak kesinlikle (IMHO) doğru yoldasınız. Bu sadece, aşağıdaki örnekte olduğu gibi, doğrudan Request.QueryString'den değerleri çıkarmanız gerektiği anlamına gelir:

.Aspx sayfasının kendisinde:

<%@ 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>

ve arkasındaki kodda:

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));
        }
    }

Buradaki hile, metin girişlerinin value = "" özniteliklerinin içinde ASP.NET Literals kullanmamızdır, bu nedenle metin kutularının kendilerinin runat = "server" olması gerekmez. Sonuçlar daha sonra bir ASP: Panel içine sarılır ve herhangi bir sonucu görüntülemek isteyip istemediğinize bağlı olarak Sayfa yüklemesinde Görünür özellik ayarı yapılır.


Oldukça iyi çalışıyor, ancak URL'ler, örneğin StackOverflow kadar kolay olmayacak.
Mehrdad Afshari

1
URL'ler oldukça kolay olacak, bence ... Bu gerçekten iyi bir çözüm gibi görünüyor.
Jon Skeet

Argh, daha önce tweetlerini okudum, araştırdım ve şimdi ittle çocuklarımı küvete hazırlarken sorunuzu kaçırdım ... :-)
splattne

2

Tamam Jon, önce görüntü durumu sorunu:

2.0'dan bu yana herhangi bir dahili kod değişikliği olup olmadığını kontrol etmedim, ancak birkaç yıl önce görünüm durumundan kurtulmayı şu şekilde ele aldım. Aslında bu gizli alan HtmlForm içinde kodlanmıştır, bu nedenle yenisini türetmeli ve aramaları kendiniz yaparak oluşturmaya başlamalısınız. Ayrıca, düz eski giriş kontrollerine bağlı kalırsanız __eventtarget ve __eventtarget'i dışarıda bırakabileceğinizi unutmayın (sanırım istemcide JS gerektirmemesine yardımcı olduğu için bunu yapmak isteyeceksiniz):

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);
}

Böylece bu 3 statik MethodInfo'yu elde edersiniz ve onları bu görünüm durumu bölümünü atlayarak çağırırsınız;)

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

ve işte formunuzun tür oluşturucusu:

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);
}

Sorunuzu doğru anlıyorsam, formlarınızın eylemi olarak POST'u da kullanmak istemezsiniz, bu nedenle bunu şu şekilde yaparsınız:

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

    // the rest of it...
}

Sanırım bu hemen hemen o. Nasıl gittiğini bana bildirin.

DÜZENLEME: Sayfa görüntüleme durumu yöntemlerini unuttum:

Böylece özel Formunuz: HtmlForm yepyeni özetini alır (veya almaz) Sayfa: 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;
}

Bu durumda yöntemleri mühürleyeceğim çünkü Sayfayı mühürleyemezsiniz (bu soyut olmasa bile Scott Guthrie onu bir başkasına saracaktır: P) ancak Formunuzu mühürleyebilirsiniz.


Bunun için teşekkürler - kulağa oldukça fazla iş gibi gelse de. Dan'in çözümü benim için iyi çalıştı, ancak daha fazla seçeneğe sahip olmak her zaman iyidir.
Jon Skeet

1

POST'u ortadan kaldırmayı değil, form POST yapıldığında uygun bir GET url'sine yeniden yönlendirmeyi düşündünüz mü? Yani, hem GET hem de POST'u kabul edin, ancak POST'ta bir GET isteği oluşturun ve ona yeniden yönlendirin. Bu, sayfadan bağımsız yapmak istiyorsanız, sayfada veya bir HttpModule aracılığıyla ele alınabilir. Bunun işleri çok daha kolaylaştıracağını düşünüyorum.

DÜZENLEME: Sayfada EnableViewState = "false" ayarınız olduğunu varsayıyorum.


İyi fikir. Yapmak zorunda kalma açısından korkunç bir fikir, ama muhtemelen işe yaraması açısından güzel :) Deneyecekler ...
Jon Skeet

Ve evet, her yerde EnableViewState = false denedim. Tamamen devre dışı bırakmaz, sadece keser.
Jon Skeet

Jon: Lanet olası sunucu kontrollerini kullanmazsanız (runat = "server" yok) ve bir <form runat = "server"> yoksa, ViewState sorun olmayacaktır. Bu yüzden sunucu kontrollerini kullanmamamı söyledim. Her zaman Request.Form koleksiyonunu kullanabilirsiniz.
Mehrdad Afshari

Ancak kontrollerde runat = server olmadan, işleme sırasında değeri tekrar kontrollere yaymak acı verir. Neyse ki runat = server ile HTML kontrolleri iyi çalışıyor.
Jon Skeet

1

Yönlendirmeyi işleyen (MVC'ye benzer, ancak karmaşık değil, sadece birkaç ififade) ve onu aspxveya ashxsayfalara aktaran bir HTTP modülü oluşturardım . aspxsayfa şablonunu değiştirmek daha kolay olduğu için tercih edilir. Ben kullanmak ister WebControlsde aspxancak. Sadece Response.Write.

Bu arada, işleri basitleştirmek için, modülde parametre doğrulaması yapabilir (muhtemelen yönlendirmeyle kodu paylaştığı için) ve bunu kaydettikten HttpContext.Itemssonra sayfada görüntüleyebilirsiniz. Bu, neredeyse tüm zil ve ıslık olmadan MVC gibi çalışacak. Bu, ASP.NET MVC günlerinden önce çok yaptığım şeydi.


1

Sayfa sınıfını tamamen terk ettiğim için gerçekten mutlu oldum ve her isteği url'ye dayalı büyük bir anahtar durumuyla ele alıyorum. Evey "sayfası" bir html şablonu ve ac # nesnesi olur. Şablon sınıfı, bir anahtar koleksiyonuyla karşılaştıran bir eşleşme temsilcisine sahip bir normal ifade kullanır.

faydaları:

  1. Gerçekten hızlıdır, yeniden derlemeden sonra bile neredeyse hiç gecikme olmaz (sayfa sınıfı büyük olmalıdır)
  2. denetim gerçekten ayrıntılıdır (SEO için harikadır ve DOM'u JS ile iyi oynamak için hazırlamak)
  3. sunum mantıktan ayrıdır
  4. jQuery html üzerinde tam kontrole sahiptir

bummers:

  1. basit şeyler biraz daha uzun sürer, çünkü tek bir metin kutusu birkaç yerde kod gerektirir, ancak ölçeği gerçekten iyi
  2. Bir görünüm durumu (urgh) görene kadar bunu sadece sayfa görünümüyle yapmak her zaman cazip geliyor ve sonra gerçeğe geri dönüyorum.

Jon, Cumartesi sabahı SO'da ne yapıyoruz :)?


1
Cumartesi akşamı burada. Bu onu düzeltir mi? (Gönderme sürelerimin / günlerimin dağılım grafiğini görmek isterim, btw ...)
Jon Skeet

1

Asp: Tekrarlayıcı kontrolünün eski olduğunu düşündüm.

ASP.NET şablon motoru güzel ancak bir for döngüsü ile yinelemeyi kolayca başarabilirsiniz ...

<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>

ASP.NET Formları bir nevi tamam, Visual Studio'dan yeterli destek var ama bu runat = "sunucu" olayı, bu çok yanlış. ViewState to.

ASP.NET MVC'yi neyin bu kadar harika kıldığına, ASP.NET Forms yaklaşımından kimin uzaklaştığına bir göz atmanızı öneririm.

NHaml gibi özel görünümleri derlemek için kendi derleme sağlayıcınızı bile yazabilirsiniz. Bence daha fazla kontrol için buraya bakmanız ve HTTP'yi sarmak ve bir CLR barındırma ortamı olarak ASP.NET çalışma zamanına güvenmeniz gerektiğini düşünüyorum. Entegre modu çalıştırırsanız, HTTP isteğini / yanıtını da işleyebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.