¿Cómo puedo tomar más control en ASP.NET?


Estoy tratando de construir una muy, muy simple "micro-webapp" que sospecho que será de interés para algunos Desbordamientos de Pila si alguna vez lo hago. Lo estoy alojando en mi sitio de C # in Depth, que es vanilla ASP.NET 3.5 (es decir, no MVC).

El flujo es muy simple:

  • Si un usuario ingresa a la aplicación con una URL que no especifica todos los parámetros (o si alguno de ellos no es válido), solo quiero mostrar los controles de entrada del usuario. (Solo hay dos.)
  • Si un usuario ingresa aplicación con una URL que tiene todos los parámetros requeridos, quiero mostrar los resultados y los controles de entrada (para que puedan cambiar los parámetros)

Aquí están mis requisitos autoimpuestos (mezcla de diseño e implementación):

  • Quiero que el envío use GET en lugar de POST, principalmente para que los usuarios puedan marcar la página fácilmente.
  • I don't want the URL to end up looking silly after submission, with extraneous bits and pieces en ello. Solo la URL principal y los parámetros reales por favor.
  • Idealmente me gustaría evitar requerir JavaScript en absoluto. No hay una buena razón para ello en esta aplicación.
  • Quiero poder acceder a los controles durante el tiempo de renderizado y establecer valores, etc. En particular, quiero poder establecer los valores predeterminados de los controles a los valores de parámetro pasados, si ASP.NET no puede hacer esto automáticamente por mí (dentro de las otras restricciones).
  • Estoy feliz de hacer toda la validación de parámetros yo mismo, y no necesito mucho en el camino de los eventos secundarios del servidor. Es muy simple configurar todo en la carga de la página en lugar de adjuntar eventos a botones, etc.

La mayor parte de esto está bien, pero no he encontrado ninguna manera de completamente eliminar el viewstate y mantener el resto de la funcionalidad útil. Usando la publicación de esta publicación de blog He logrado evitar obtener cualquier valor real para el estado de vista, pero aún así termina como un parámetro en la URL, lo que se ve muy feo.

Si lo hago un formulario HTML simple en lugar de un ASP.NET form (es decir, take out runat="server") entonces no obtengo ningún estado de vista mágico - pero entonces no puedo acceder a los controles mediante programación.

Yo podría hacer todo esto ignorando la mayor parte de ASP.NET y la creación de un documento XML con LINQ a XML, y la implementación de IHttpHandler. Eso se siente un poco bajo nivel sin embargo.

Me doy cuenta de que mis problemas podrían resolverse relajando mis restricciones (por ejemplo, usando POST y no preocuparse por el parámetro excedente) o mediante el uso de ASP.NET MVC, ¿pero mis requisitos son realmente irrazonables?

Tal vez ASP.NET simplemente no escala hacia abajo a este tipo de aplicación? Sin embargo, hay una alternativa muy probable: estoy siendo estúpido, y hay una forma perfectamente simple de hacerlo que simplemente no he encontrado.

¿Algún pensamiento, alguien? (Comentarios de Cue de cómo los poderosos han caído, etc. Eso está bien - espero que nunca he afirmado ser un ASP.NET experto, como la verdad es todo lo contrario...)

Author: Pale Blue Dot, 2009-01-10

7 answers

Esta solución le dará acceso programático a los controles en su totalidad, incluidos todos los atributos de los controles. Además, solo los valores del cuadro de texto aparecerán en la URL al momento del envío, por lo que la URL de la solicitud GET será más "significativa"

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

Entonces en su código-detrás usted puede hacer todo lo que necesita en 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];
    }
}

Si no quieres un formulario que tenga runat="server", entonces deberías usar controles HTML. Es más fácil trabajar con él para tus propósitos. Solo usa HTML normal etiquetas y poner runat="server" y darles una identificación. Luego puede acceder a ellos programáticamente y código sin un ViewState.

El único inconveniente es que no tendrá acceso a muchos de los "útiles" ASP.NET controles de servidor como GridView s. Incluí un Repeater en mi ejemplo porque asumo que desea tener los campos en la misma página que los resultados y (que yo sepa) un Repeater es el único control de DataBound que se ejecutará sin un atributo runat="server" en la etiqueta del Formulario.

 75
Author: Dan Herbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-23 21:41:31

Definitivamente estás (en mi humilde opinión) en el camino correcto al no usar runat="server" en tu etiqueta de FORMULARIO. Esto solo significa que necesitará extraer valores de la solicitud.QueryString directamente, sin embargo, como en este ejemplo:

En el .aspx página en sí:

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

Y en el código detrás:

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

El truco aquí es que estamos usando ASP.NET Literales dentro de los atributos value="" de las entradas de texto, por lo que los cuadros de texto en sí no tienen que runat="server". Los resultados se envuelven entonces dentro de un panel ASP:, y la propiedad Visible establecida en la carga de la página dependiendo de si desea mostrar resultados o no.

 12
Author: Dylan Beattie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-10 17:02:23

Bien Jon, el problema de viewstate primero:

No he comprobado si hay algún tipo de cambio de código interno desde 2.0, pero así es como manejé deshacerse de viewstate hace unos años. En realidad, ese campo oculto está codificado dentro de HtmlForm, por lo que debe derivar su nuevo campo y entrar en su renderizado haciendo las llamadas por sí mismo. Tenga en cuenta que también puede dejar _ _ eventtarget y _ _ eventtarget si se adhiere a los controles de entrada antiguos (que supongo que querría ya que también ayuda a no requerir JS en el cliente):

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

Así que obtienes esos 3 MethodInfo estáticos y los llamas saltando esa parte viewstate;)

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

Y aquí está el constructor de tipo de formulario:

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

Si estoy entendiendo bien tu pregunta, también quieres no usar POST como la acción de tus formularios, así que así es como lo harías:

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

    // the rest of it...
}

Supongo que esto es más o menos todo. Hazme saber cómo va.

EDITAR: Olvidé los métodos Page viewstate:

Así que su formulario personalizado: HtmlForm obtiene su nueva página abstracta (o no): Sistema.Web.UI.Página: 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;
}

En este caso sello los métodos porque no puedes sellar la Página (incluso si no es abstracta Scott Guthrie la envolverá en otra más :P) pero puedes sellar tu Formulario.

 2
Author: user134706,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-09 19:16:45

¿Has pensado en no eliminar la PUBLICACIÓN, sino redirigir a una url GET adecuada cuando se publique el formulario? Es decir, acepte tanto GET como POST, pero en POST construya una solicitud GET y redirija a ella. Esto podría ser manejado ya sea en la página o a través de un HttpModule si desea hacerlo independiente de la página. Creo que esto haría las cosas mucho más fáciles.

EDIT: Asumo que tienes EnableViewState="false" establecido en la página.

 1
Author: tvanfosson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-10 17:01:25

Crearía un módulo HTTP que maneje el enrutamiento (similar a MVC pero no sofisticado, solo un par de sentencias if) y lo entregaría a las páginas aspx o ashx. Se prefiere aspx ya que es más fácil modificar la plantilla de página. No usaría WebControls en el aspx sin embargo. Solo Response.Write.

Por cierto, para simplificar las cosas, puede hacer la validación de parámetros en el módulo (ya que comparte código con el enrutamiento probablemente) y guardarlo en HttpContext.Items y luego renderizarlos en la página. Esto va a funcionar bastante al igual que el MVC sin todas las campanas y silbatos. Esto es lo que hice mucho antes ASP.NET Días de MVC.

 1
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-10 17:02:33

Realmente he estado feliz de abandonar totalmente la clase de página por completo y solo manejar cada solicitud con un gran caso de cambio basado en la url. Cada "página" se convierte en una plantilla html y un objeto c#. La clase de plantilla utiliza una expresión regular con un delegado de coincidencia que se compara con una colección de claves.

Beneficios:

  1. Es muy rápido, incluso después de una recompilación, casi no hay retraso (la clase page debe ser grande)
  2. el control es realmente granular (ideal para el SEO y la elaboración del DOM para jugar bien con JS)
  3. la presentación es independiente de la lógica
  4. jQuery tiene el control total del html

Bummers:

  1. las cosas simples tardan un poco más en que un solo cuadro de texto requiere código en varios lugares, pero lo hace escala muy bien
  2. siempre es tentador hacerlo con la vista de página hasta que vea un viewstate (urgh) luego vuelvo a realidad.

Jon, ¿qué estamos haciendo en SO un sábado por la mañana:)?

 1
Author: rizzle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-10 17:18:32

Pensé que el control asp:Repetidor era obsoleto.

El ASP.NET el motor de plantillas es agradable,pero también puedes repetir fácilmente con un bucle 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 está bien, hay un soporte decente de Visual Studio, pero esto de runat = "server" está mal. ViewState.

Le sugiero que eche un vistazo a lo que hace ASP.NET MVC tan grande, que se aleja de la ASP.NET Las formas se acercan sin tirarlo todo por la borda.

Puedes incluso escriba su propio material de proveedor de compilación para compilar vistas personalizadas como NHaml. Creo que usted debe buscar aquí para más control y simplemente confiar en la ASP.NET tiempo de ejecución para empaquetar HTTP y como un entorno de alojamiento CLR. Si ejecuta el modo integrado, también podrá manipular la solicitud/respuesta HTTP.

 1
Author: John Leidegren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-25 10:05:28