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

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

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

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

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

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

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

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

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

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

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

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


person Jon Skeet    schedule 10.01.2009    source источник
comment
Реплики о том, как пали сильные, - мы все невежественны, только о разных вещах. Я только недавно начал здесь участвовать, но восхищаюсь вопросом больше, чем всеми пунктами. Очевидно, вы все еще думаете и учитесь. Престижность вам.   -  person duffymo    schedule 10.01.2009
comment
Не думаю, что когда-нибудь обратил бы внимание на того, кто бросил учиться :)   -  person Jon Skeet    schedule 10.01.2009
comment
Верно в общем случае. Совершенно верно в информатике.   -  person mmx    schedule 10.01.2009
comment
И будет ли ваша следующая книга «Подробно об ASP.NET»? :-П   -  person chakrit    schedule 10.01.2009
comment
Да, он выйдет в 2025 году;)   -  person Jon Skeet    schedule 10.01.2009
comment
Между прочим, это круто. feeds.feedburner. com / ~ r / JonSkeetCodingBlog / ~ 3/513263291 /   -  person David Basarab    schedule 15.01.2009
comment
@GK: Не используйте здесь мое имя, вы удаляете важную ссылку как контекст. Я никогда этого не делал.   -  person GEOCHET    schedule 04.03.2009


Ответы (7)


Это решение предоставит вам программный доступ ко всем элементам управления, включая все атрибуты элементов управления. Кроме того, при отправке в 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 в свой пример, потому что предполагаю, что вы хотите, чтобы поля были на той же странице, что и результаты, и (насколько мне известно) Repeater - единственный элемент управления DataBound, который будет работать без атрибута runat="server" в теге формы. .

person Dan Herbert    schedule 10.01.2009
comment
У меня так мало полей, что сделать это вручную очень просто :) Ключевым моментом было то, что я не знал, что могу использовать runat = server с обычными элементами управления HTML. Я еще не реализовал результаты, но это несложно. Почти там! - person Jon Skeet; 10.01.2009
comment
Действительно, форма runat = server ›добавит скрытое поле __VIEWSTATE (и некоторые другие), даже если вы установите EnableViewState = False на уровне страницы. Это правильный путь, если вы хотите потерять ViewState на странице. Что касается удобства использования URL-адресов, возможно использование URL-адресов. - person Sergiu Damian; 10.01.2009
comment
Нет необходимости переписывать. Этот ответ работает нормально (хотя это означает наличие элемента управления с идентификатором пользователя - по какой-то причине я не могу изменить имя элемента управления текстовым полем отдельно от его идентификатора). - person Jon Skeet; 10.01.2009
comment
Просто чтобы подтвердить, это действительно сработало очень хорошо. Большое спасибо! - person Jon Skeet; 11.01.2009
comment
Будет ли это работать, если вы используете мастер-страницы? ClientId элементов управления обычно изменяется при использовании главных страниц. - person Binoj Antony; 21.02.2009
comment
Это будет работать на главных страницах, вам просто нужно использовать свойство ClientID ваших текстовых полей вместо жесткого кодирования идентификаторов в поиске строки запроса. - person Dan Herbert; 21.02.2009
comment
Похоже, вы должны были просто написать это классическим asp! - person ScottE; 09.07.2009

Вы определенно (ИМХО) на правильном пути, не используя 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 устанавливается при загрузке страницы в зависимости от того, хотите ли вы отображать какие-либо результаты или нет.

person Dylan Beattie    schedule 10.01.2009
comment
Он работает довольно хорошо, но URL-адреса не будут такими удобными, как, скажем, StackOverflow. - person mmx; 10.01.2009
comment
Я думаю, URL-адреса будут довольно удобными ... Похоже, это действительно хорошее решение. - person Jon Skeet; 10.01.2009
comment
Ох, я читал ваши твиты раньше, исследовал их, и теперь я пропустил ваш вопрос, готовя моих маленьких детей к ванне ... :-) - person splattne; 10.01.2009

Хорошо, Джон, сначала вопрос о состоянии просмотра:

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

person user134706    schedule 09.07.2009
comment
Спасибо за это - хотя, похоже, это довольно большая работа. Решение Дэна сработало для меня, но всегда хорошо иметь больше вариантов. - person Jon Skeet; 09.07.2009

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

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

person tvanfosson    schedule 10.01.2009
comment
Хорошая идея. Что ж, ужасная идея с точки зрения принуждения, но хорошая с точки зрения, наверное, работает :) Постараюсь ... - person Jon Skeet; 10.01.2009
comment
И да, я пробовал EnableViewState = false повсюду. Он не отключает полностью, а просто урезает. - person Jon Skeet; 10.01.2009
comment
Джон: Если вы не используете проклятые серверные элементы управления (без runat = server) и у вас вообще нет ‹формы runat = server›, ViewState не будет проблемой. Вот почему я сказал не использовать серверные элементы управления. Вы всегда можете использовать коллекцию Request.Form. - person mmx; 10.01.2009
comment
Но без runat = server в элементах управления сложно снова передать значение элементам управления при рендеринге. К счастью, элементы управления HTML с runat = server работают хорошо. - person Jon Skeet; 10.01.2009

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

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

person mmx    schedule 10.01.2009

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

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

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

обломки:

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

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

person missaghi    schedule 10.01.2009
comment
Здесь субботний вечер. Это нормально? (Я бы хотел увидеть точечный график моего времени / дней публикации, кстати ...) - person Jon Skeet; 10.01.2009

Я думал, что 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-запросом / ответом.

person John Leidegren    schedule 23.02.2009