Как я могу взять больше контроля в ASP.NET?

Я пытаюсь создать очень, очень простой "micro-webapp", который, как я подозреваю, будет интересен нескольким Stack Overflow'rs, если я когда-нибудь это сделаю. Я размещаю его на моем сайте С# на глубине, который является ванильным 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 to XML и внедряя IHttpHandler. Тем не менее, это немного низко.

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

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

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

Ответ 1

Это решение даст вам программный доступ к элементам управления в целом, включая все атрибуты элементов управления. Кроме того, в 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 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>

Затем в вашем коде вы можете делать все, что нужно, на странице Load

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.

Ответ 2

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

Ответ 3

Хорошо, первый вопрос в представлении:

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

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

Ответ 4

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

EDIT: Я предполагаю, что на странице установлен EnableViewState = "false".

Ответ 5

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

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

Ответ 6

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

Преимущества

:

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

bummers:

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

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

Ответ 7

Я думал, что управление 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 = "сервер", это просто неправильно. ViewState to.

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

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