Как я могу получить больший контроль в ASP.NET?

124

Я пытаюсь создать очень и очень простое «микро-веб-приложение», которое, как я подозреваю, будет интересно некоторым пользователям Stack Overflow, если я когда-нибудь это сделаю. Я размещаю его на своем сайте C # in Depth, который является ванильным ASP.NET 3.5 (то есть не MVC).

Схема очень проста:

  • Если пользователь входит в приложение с URL-адресом, который не определяет все параметры (или если какой-либо из них недействителен), я хочу просто отобразить элементы управления вводом пользователя. (Их всего два.)
  • Если пользователь входит в приложении с URL , который делает имеют все необходимые параметры, я хочу , чтобы отобразить результаты и элементы управления вводом (таким образом они могут изменить параметры)

Вот мои добровольные требования (смесь дизайна и реализации):

  • Я хочу, чтобы при отправке использовался GET, а не POST, в основном, чтобы пользователи могли легко добавлять закладки на страницу.
  • Я не хочу, чтобы URL-адрес после отправки выглядел глупо с посторонними частями и фрагментами. Только основной URL и реальные параметры, пожалуйста.
  • В идеале я бы вообще не хотел использовать JavaScript. В этом приложении нет веских причин для этого.
  • Я хочу иметь возможность получать доступ к элементам управления во время рендеринга и устанавливать значения и т. Д. В частности, я хочу иметь возможность устанавливать значения по умолчанию элементов управления на переданные значения параметров, если ASP.NET не может сделать это автоматически. для меня (с другими ограничениями).
  • Я счастлив сам выполнить всю проверку параметров, и мне не нужно много событий на стороне сервера. Очень просто настроить все при загрузке страницы вместо того, чтобы прикреплять события к кнопкам и т. Д.

По большей части это нормально, но я не нашел способа полностью удалить состояние просмотра и сохранить остальные полезные функции. Используя сообщение из этого сообщения в блоге, мне удалось избежать получения какого-либо фактического значения для состояния просмотра, но оно по-прежнему остается параметром URL-адреса, что выглядит действительно некрасиво.

Если я сделаю это простой HTML-формой вместо формы ASP.NET (то есть вынимаю runat="server"), я не получаю никакого волшебного состояния просмотра, но тогда я не могу получить доступ к элементам управления программно.

Я мог бы сделать все это, игнорируя большую часть ASP.NET, создавая XML-документ с LINQ to XML и реализуя его IHttpHandler. Хотя это кажется немного низким уровнем.

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

Может быть , ASP.NET просто не масштабируется вниз к такому роду приложение? Однако есть очень вероятная альтернатива: я просто дурак, и есть совершенно простой способ сделать это, которого я просто не нашел.

Есть мысли, кто-нибудь? (Подсказка о том, как пали сильные и т. Д. Это нормально - надеюсь, я никогда не утверждал, что был экспертом по ASP.NET, поскольку на самом деле все совсем наоборот ...)

Джон Скит
источник
16
«Реплики о том, как пали сильные» - мы все не ведем, просто о разных вещах. Я только недавно начал здесь участвовать, но вопрос мне нравится больше, чем все пункты. Очевидно, вы все еще думаете и учитесь. Престижность вам.
duffymo
15
Не думаю, что когда-нибудь обратил бы внимание на кого-то, кто бросил учиться :)
Джон Скит,
1
Верно в общем случае. Совершенно верно в информатике.
Mehrdad Afshari
3
И будет ли ваша следующая книга «Подробнее об ASP.NET»? :-P
chakrit
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. Я включил a Repeaterв свой пример, потому что предполагаю, что вы хотите, чтобы поля были на той же странице, что и результаты, и (насколько мне известно) a Repeater- единственный элемент управления DataBound, который будет работать без runat="server"атрибута в теге Form.

Дэн Герберт
источник
1
У меня так мало полей, что сделать это вручную очень просто :) Ключевым моментом было то, что я не знал, что могу использовать runat = server с обычными элементами управления HTML. Я еще не реализовал результаты, но это несложно. Где-то там!
Джон Скит,
Действительно, <form runat = "server"> добавит скрытое поле __VIEWSTATE (и некоторые другие), даже если вы установите EnableViewState = "False" на уровне страницы. Это правильный путь, если вы хотите потерять ViewState на странице. Что касается удобства использования URL-адресов, вариант URL-адреса может быть вариантом.
Серджиу Дамиан,
1
Переписывать не нужно. Этот ответ работает нормально (хотя это означает наличие элемента управления с идентификатором «пользователь» - по какой-то причине я не могу изменить имя элемента управления текстовым полем отдельно от его идентификатора).
Джон Скит,
1
Просто чтобы подтвердить, это действительно сработало очень хорошо. Огромное спасибо!
Джон Скит,
14
Похоже, вы должны были просто написать это классическим asp!
ScottE 09
12

Вы определенно (ИМХО) на правильном пути, не используя 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 внутри атрибутов value = "" текстовых входов, поэтому сами текстовые поля не должны запускатьat = "server". Затем результаты помещаются в ASP: Panel, а свойство Visible устанавливается при загрузке страницы в зависимости от того, хотите ли вы отображать какие-либо результаты или нет.

Дилан Битти
источник
Он работает довольно хорошо, но URL-адреса не будут такими удобными, как, скажем, StackOverflow.
Мехрдад Афшари,
1
Я думаю, URL-адреса будут довольно удобными ... Похоже, это действительно хорошее решение.
Джон Скит,
Ох, я читал ваши твиты раньше, исследовал их, и теперь я пропустил ваш вопрос, готовя моих маленьких детей к ванне ... :-)
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...
}

Думаю, это в значительной степени все. Дай мне знать, как дела.

РЕДАКТИРОВАТЬ: я забыл методы состояния просмотра страницы:

Итак, ваша настраиваемая форма: 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), но вы можете запечатать свою форму.

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

Вы думали не об удалении POST, а о перенаправлении на подходящий URL-адрес GET при отправке формы. То есть принимайте и GET, и POST, но при POST создайте запрос GET и перенаправьте на него. Это можно сделать либо на странице, либо через HttpModule, если вы хотите сделать его независимым от страницы. Думаю, это упростит задачу.

РЕДАКТИРОВАТЬ: Я предполагаю, что у вас установлен EnableViewState = "false" на странице.

tvanfosson
источник
Хорошая идея. Что ж, ужасная идея с точки зрения принуждения к этому, но хорошая с точки зрения того, что она, вероятно, работает :) Постараюсь ...
Джон Скит
И да, я пробовал EnableViewState = false повсюду. Он не отключает его полностью, а просто урезает.
Джон Скит,
Джон: Если вы не используете проклятые серверные элементы управления (no runat = "server") и у вас вообще нет <form runat = "server">, ViewState не будет проблемой. Вот почему я сказал не использовать серверные элементы управления. Вы всегда можете использовать коллекцию Request.Form.
Mehrdad Afshari
Но без runat = server в элементах управления сложно снова передать значение элементам управления при рендеринге. К счастью, элементы управления HTML с runat = server работают хорошо.
Джон Скит,
1

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

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

Мехрдад Афшари
источник
1

Я действительно был счастлив полностью отказаться от класса страницы и просто обрабатывать каждый запрос с большим переключением на основе URL-адреса. «Страница» Evey становится шаблоном html и объектом c #. Класс шаблона использует регулярное выражение с делегатом соответствия, который сравнивает с коллекцией ключей.

преимущества:

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

bummers:

  1. простые вещи занимают немного больше времени, поскольку для одного текстового поля требуется код в нескольких местах, но он действительно хорошо масштабируется
  2. Всегда заманчиво делать это с просмотром страницы, пока я не увижу состояние просмотра (ура), а затем вернусь к реальности.

Джон, что мы делаем на SO в субботу утром :)?

missaghi
источник
1
Здесь субботний вечер. Это нормально? (Я бы хотел увидеть диаграмму разброса времени / дней моих публикаций, кстати ...)
Джон Скит,
1

Я думал, что asp: Repeater control устарело.

Механизм шаблонов 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 поддерживает приличную поддержку, но с runat = "server" это просто неправильно. ViewState в.

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

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

Джон Лейдегрен
источник