Як я можу взяти більше контролю в ASP.NET?


124

Я намагаюся створити дуже-дуже простий "мікро-webapp", який, як я підозрюю, зацікавить декілька Stack Overflow'rs, якщо я коли-небудь це зроблю. Я розміщую його на своєму C # на глибині сайту, який є ванільним ASP.NET 3.5 (тобто не MVC).

Потік дуже простий:

  • Якщо користувач входить у додаток із URL-адресою, в якій не вказані всі параметри (або якщо хтось із них недійсний), я хочу просто відобразити елементи керування введенням користувача. (Є лише два.)
  • Якщо користувач входить в додатку з URL , який робить мають всі необхідні параметри, я хочу , щоб відобразити результати і елементи управління введенням (таким чином вони можуть змінити параметри)

Ось мої власні вимоги (суміш дизайну та реалізації):

  • Я хочу, щоб представлення використовувало GET, а не POST, в основному, щоб користувачі могли легко робити закладки на сторінці.
  • Я не хочу, щоб URL-адреса виглядала нерозумно після надсилання, із сторонніми шматочками та шматочками. Просто основна URL-адреса та реальні параметри, будь ласка.
  • В ідеалі я хотів би взагалі не вимагати JavaScript. У цій програмі немає вагомих причин.
  • Я хочу мати доступ до елементів керування під час часу візуалізації та встановлених значень тощо. Зокрема, я хочу мати змогу встановити значення за замовчуванням елементів керування на значення параметрів, що передаються, якщо ASP.NET не може зробити це автоматично для мене (в межах інших обмежень).
  • Я радий сам зробити всю перевірку параметрів, і мені не потрібно багато на шляху подій на стороні сервера. Налаштувати все на завантаженні сторінки дуже просто замість приєднання подій до кнопок тощо.

Більшість це нормально, але я не знайшов жодного способу повністю видалити зону прогляду і зберегти решту корисної функції. Використовуючи публікацію з цього допису в блозі, мені вдалося уникнути отримання фактичного значення для viewstate - але він все ще закінчується як параметр URL-адреси, який виглядає справді некрасиво.

Якщо я зроблю це звичайною формою HTML замість форми ASP.NET (тобто runat="server"виймаю), я не отримую жодного магічного перегляду, але тоді я не можу отримати доступ до елементів керування програмно.

Я міг би зробити все це, ігноруючи більшість ASP.NET і створюючи XML-документ з LINQ в XML, і впроваджуючи IHttpHandler. Це відчуває трохи низький рівень.

Я усвідомлюю, що мої проблеми можна вирішити, або послабивши свої обмеження (наприклад, використовуючи POST і не піклуючись про параметр надлишку), або використовуючи ASP.NET MVC, але чи мої вимоги справді необгрунтовані?

Можливо, ASP.NET просто не розширюється до такого типу додатків? Хоча є ймовірна альтернатива: я просто дурний, і існує ідеально простий спосіб зробити це, якого я просто не знайшов.

Будь-які думки, хтось? (Cue коментарі про те, як могутні падають і т. Д. Це добре - сподіваюся, я ніколи не претендував на те, щоб бути експертом ASP.NET, оскільки правда зовсім навпаки ...)


16
"Cue коментарі про те, як падіння могутніх" - ми всі невігласи, просто різні речі. Я лише нещодавно почав тут брати участь, але питання захоплююсь більш ніж усіма пунктами. Ти, очевидно, все ще думаєш і вчишся. Кудо вам.
duffymo

15
Я не думаю, що я ніколи не звертав би уваги на когось, хто відмовився від навчання :)
Джон Скіт

1
Правда в загальному випадку. Дуже вірно в інформатиці.
Мехрдад Афшарі

3
А ваша наступна книга буде "ASP.NET в глибині"? :-P
чакрит

20
Так, це має вийти у 2025 році;)
Джон Скіт

Відповіді:


76

Це рішення надасть вам програмний доступ до елементів керування в повному обсязі, включаючи всі атрибути на елементах управління. Крім того, лише значення текстового поля відображатимуться в URL-адресі після подання, щоб ваша URL-адреса GET-запиту була "значущою"

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

Тоді в коді позаду ви можете зробити все необхідне на 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];
    }
}

Якщо ви не хочете форми, яка є runat="server", тоді ви повинні використовувати елементи керування HTML. Простіше працювати з вашими цілями. Просто використовуйте звичайні теги HTML і вставте runat="server"і дайте їм ідентифікатор. Тоді ви можете отримати доступ до них програмно та кодувати без ViewState.

Єдиним недоліком є ​​те, що ви не матимете доступу до багатьох "корисних" елементів керування сервером ASP.NET, таких як GridViews. Я включив Repeaterу свій приклад, тому що я припускаю, що ви хочете мати поля на тій самій сторінці, що й результати, і (наскільки мені відомо) a Repeater- це єдиний елемент управління DataBound, який буде працювати без runat="server"атрибута в тезі Form.


1
У мене так мало полів, що робити це вручну дуже просто :) Ключовим моментом було те, що я не знав, що можу використовувати runat = сервер із звичайними елементами керування HTML. Я ще не реалізував результати, але це простий біт. Близько там!
Джон Скіт

Дійсно, <form runat = "server"> додасть приховане поле __VIEWSTATE (та якесь інше), навіть якщо ви встановите EnableViewState = "False" на рівні сторінки. Це шлях, якщо ви хочете втратити ViewState на сторінці. Що стосується дружелюбності до URL-адреси, то можливий варіант написання URL-адрес.
Сергіу Даміан

1
Не потрібно переписувати. Ця відповідь чудово працює (хоча це означає, що мати елемент управління з ідентифікатором "користувача" - я чомусь не можу змінити ім'я елемента управління текстовим полем окремо від його ідентифікатора).
Джон Скіт

1
Тільки для підтвердження, це справді спрацювало дуже добре. Дуже дякую!
Джон Скіт

14
Схоже, ви повинні були щойно написати це класичним аспіром!
ScottE

12

Ви точно (IMHO) на правильному шляху, не використовуючи runat = "server" у своєму тезі FORM. Це просто означає, що вам потрібно буде безпосередньо витягувати значення з Request.QueryString, як у цьому прикладі:

На самій сторінці .aspx:

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

і в коді:

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

Хитрість тут полягає в тому, що ми використовуємо ASP.NET Literals всередині атрибутів text = "" введення тексту, тому самі текстові поля не повинні runat = "server". Потім результати загортаються всередину панелі ASP: Panel і властивість Visible, встановлена ​​на завантаженні сторінки, залежно від того, хочете ви відображати результати чи ні.


Це працює досить добре, але URL-адреси будуть не такими дружніми, як, скажімо, StackOverflow.
Мехрдад Афшарі

1
URL-адреси будуть досить дружніми, я думаю ... Це виглядає як справді гарне рішення.
Джон Скіт

Argh, я читав твіти раніше, досліджував це, і тепер я пропустив ваше питання, готуючи моїх дітей, що перебувають у ванній ... :-)
splattne

2

Гаразд Джон, спочатку питання про зору:

Я не перевіряв, чи є якась зміна внутрішнього коду з 2.0, але ось, як я впорався з позбавленням від перегляду, кілька років тому. Насправді це приховане поле жорстко кодується всередині HtmlForm, тому вам слід вивести нове і вступити в його надання, здійснюючи дзвінки самостійно. Зауважте, що ви також можете залишити __eventtarget та __eventtarget, якщо ви дотримуєтесь звичайних старих елементів керування введенням (що, мабуть, ви хочете зробити, оскільки це також допомагає не вимагати JS для клієнта):

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

Таким чином, ви отримуєте ці 3 статичні MethodInfo і викликаєте їх, пропускаючи цю частину перегляду;)

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

і ось конструктор типу вашої форми:

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

Якщо я правильно розумію ваше запитання, ви також не хочете використовувати POST як дію ваших форм, ось ось, як це зробити:

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

    // the rest of it...
}

Я думаю, це в значній мірі це. Дайте мені знати, як це йде.

EDIT: Я забув методи перегляду сторінки:

Отже, ваша спеціальна форма: HtmlForm отримує абсолютно нову абстрактну (або ні) сторінку: 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;
}

У цьому випадку я запечатую методи, тому що ви не можете запечатати сторінку (навіть якщо це не абстрактна. Скотт Гетрі загорне її в ще одну: P), але ви можете запечатати форму.


Дякую за це - хоча це звучить як багато роботи. Рішення Дана для мене спрацювало чудово, але завжди добре мати більше варіантів.
Джон Скіт

1

Чи замислювалися ви над тим, щоб не усунути POST, а скоріше перенаправити на відповідний URL-адресу GET, коли форма розміщена. Тобто прийміть і GET, і POST, але на POST побудуйте GET-запит та переспрямуйте на нього. Це може бути вирішено або на сторінці, або через HttpModule, якщо ви хочете зробити його незалежною від сторінки. Я думаю, що це значно полегшило б ситуацію.

EDIT: Я припускаю, що у вас встановлений EnableViewState = "false" на сторінці.


Хороша ідея. Ну, жахлива ідея з точки зору того, що її змушують робити, але приємно з точки зору цього, мабуть, працює :) Спробую ...
Джон Скіт

І так, я спробував EnableViewState = false у всьому місці. Це не повністю відключить його, а просто скоротить.
Джон Скіт

Джон: Якщо ви не використовуєте прокляті серверні елементи управління (немає runat = "сервер") і у вас взагалі немає <form runat = "server">, ViewState не складе проблем. Тому я сказав не використовувати керування сервером. Ви завжди можете використовувати колекцію Request.Form.
Мехрдад Афшарі

Але без runat = сервер на елементах керування, болісно знову поширити значення на елементи керування під час надання. На щастя, керування HTML з runat = сервер працює добре.
Джон Скіт

1

Я створив би HTTP-модуль, який обробляє маршрутизацію (схожу на MVC, але не складну, лише кілька ifзаяв) і передаю їїaspx або на ashxсторінки. aspxкращим, оскільки легше змінювати шаблон сторінки. Я б не використати WebControlsв aspxоднако. Просто Response.Write.

До речі, для спрощення речей ви можете зробити перевірку параметрів в модулі (оскільки він, мабуть, ділиться кодом з маршрутизацією) і зберегти його в HttpContext.Items а потім вивести їх на сторінку. Це буде працювати майже як MVC без усіх дзвіночків. Це я багато робив перед днями ASP.NET MVC.


1

Я дуже радий повністю відмовитися від класу сторінок і просто обробляю кожен запит із великим випадком перемикання на основі URL-адреси. Кожна "сторінка" стає шаблоном html та об'єктом ac #. Клас шаблонів використовує регулярний вираз з делегатом відповідності, який порівнює з колекцією ключів.

переваги:

  1. Це дуже швидко, навіть після перекомпіляції майже немає відставання (клас сторінки повинен бути великим)
  2. контроль дійсно детальний (чудово підходить для SEO та вміння DOM добре грати з JS)
  3. презентація окрема від логіки
  4. jQuery має повний контроль над html

бампери:

  1. Простий матеріал займає трохи більше часу, оскільки для одного текстового поля потрібен код у кількох місцях, але він дійсно добре масштабується
  2. Це завжди спокусливо просто робити це з переглядом сторінки, поки я не побачу viewstate (urgh), а потім я переходжу до реальності.

Джон, що ми робимо в суботу вранці :)?


1
Тут суботній вечір. Це робить це нормально? (Я хотів би побачити графік розкидання моїх разів / днів публікації, btw ...)
Jon Skeet

1

Я подумав, що керування asp: Ретранслятор застаріло.

Двигун шаблонів ASP.NET приємний, але ви можете так само легко виконати повторення за допомогою циклу for ...

<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 Forms - це все нормально, є гідна підтримка Visual Studio, але ця річ = "сервер" - це просто неправильно. ViewState до.

Я пропоную вам поглянути на те, що робить ASP.NET MVC настільки великим, кого він відходить від підходу ASP.NET Forms, не кидаючи його все.

Ви навіть можете написати власні речі постачальника побудови, щоб скласти власні представлення, наприклад, NHaml. Я думаю, ви повинні шукати тут для більшого контролю та просто покладатися на час виконання ASP.NET для завершення HTTP та як середовище хостингу CLR. Якщо ви запускаєте інтегрований режим, ви також зможете маніпулювати запитом / відповіддю HTTP.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.