Как я могу получить больше контроля в ASP.NET? - PullRequest
123 голосов
/ 10 января 2009

Я пытаюсь создать очень, очень простое «микро-веб-приложение», которое, как я подозреваю, будет интересно нескольким переполненным стекам, если я когда-нибудь это сделаю. Я размещаю его на своем C # в глубине сайта, который является vanilla 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, поскольку истина совершенно противоположная ...)

Ответы [ 7 ]

75 голосов
/ 10 января 2009

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

12 голосов
/ 10 января 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 Literal внутри атрибутов value = "" текстовых вводов, поэтому самим текстовым полям не обязательно выполнять runat = "server". Затем результаты помещаются в ASP: Panel, а свойство Visible устанавливается при загрузке страницы в зависимости от того, хотите ли вы показать какие-либо результаты.

2 голосов
/ 09 июля 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 получает свой новый абстрактный (или нет) Page: 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 голос
/ 23 февраля 2009

Мне показалось, что управление asp: Repeater устарело.

Механизм шаблонов 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 to.

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

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

1 голос
/ 10 января 2009

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

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

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

bummers:

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

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

1 голос
/ 10 января 2009

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

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

1 голос
/ 10 января 2009

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

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

...