Share via


Cuestión de contexto

 

Susan Warren
Microsoft Corporation

14 de enero de 2002

Uno de los problemas más comunes con la escritura de aplicaciones web es permitir que el código sepa el contexto en el que se ejecuta. Veamos un ejemplo sencillo( personalización de una página) que ilustra este problema:

     Inicie sesión.

en comparación con

     ¡Bienvenido a Susan!

Parece bastante simple, pero incluso este pequeño bit de interfaz de usuario web requiere un par de bits de información que variará cada vez que se solicite la página. Necesito saber lo siguiente:

  1. ¿El usuario ha iniciado sesión?
  2. ¿Cuál es el nombre para mostrar del usuario?

Por lo general, ¿cuál es el contexto único cadavez que se solicita la página? ¿Y cómo puedo escribir mi código para que tenga en cuenta esta información?

De hecho, debido a la naturaleza sin estado de HTTP, es posible que sea necesario realizar un seguimiento de muchas partes diferentes del contexto de una aplicación web. Cuando un usuario interactúa con una aplicación web, el explorador envía una serie de solicitudes HTTP independientes al servidor web. La propia aplicación tiene que hacer el trabajo de tejer estas solicitudes en una experiencia agradable para el usuario y conocer el contexto de la solicitud es fundamental.

ASP introdujo varios objetos intrínsecos como Request y Application para ayudar a realizar un seguimiento del contexto de una solicitud HTTP. ASP.NET toma el siguiente paso y agrupa estos objetos, además de varios objetos adicionales relacionados con el contexto en un objeto intrínseco extremadamente práctico denominado Context.

Context es un objeto de tipo System.Web.HttpContext. Se expone como una propiedad de la clase ASP.NET Page . También está disponible en los controles de usuario y los objetos empresariales (más adelante). Esta es una lista parcial de los objetos inscritos por HttpContext:

Object Descripción
Aplicación Colección de pares clave-valor de valores a los que puede acceder cada usuario de la aplicación. La aplicación es de tipo System.Web.HttpApplicationState.
ApplicationInstance La aplicación en ejecución real, que expone algunos eventos de procesamiento de solicitudes. Estos eventos se controlan en Global.asax o httpHandler o HttpModule.
Memoria caché Objeto ASP.NET Cache, que proporciona acceso mediante programación a la memoria caché. La columna de ASP.NET Almacenamiento en caché de Rob Howard proporciona una buena introducción al almacenamiento en caché.
Error Primer error (si existe) encontrado al procesar la página. Consulte La excepción de Rob a la regla, parte 1 para obtener más información.
Items Colección de pares clave-valor que puede usar para pasar información entre todos los componentes que participan en el procesamiento de una única solicitud. Los elementos son de tipo System.Collections.IDictionary.
Solicitud Información sobre la solicitud HTTP, incluida la información del explorador, las cookies y los valores pasados en un formulario o en la cadena de consulta. La solicitud es de tipo System.Web.HttpRequest.
Respuesta Configuración y contenido para crear la respuesta HTTP. La solicitud es de tipo System.Web.HttpResponse.
Servidor Server es una clase de utilidad con varios métodos auxiliares útiles, incluidos Server.Execute(),Server.MapPath()y Server.HtmlEncode(). Server es un objeto de tipo System.Web.HttpServerUtility.
Sesión Colección de pares clave-valor de valores a los que puede acceder un solo usuario de la aplicación. La aplicación es de tipo System.Web.HttpSessionState.
Seguimiento El objeto ASP.NET Trace , que proporciona acceso a la funcionalidad de seguimiento. Consulte el artículo Seguimiento de Rob para obtener más información.
User Contexto de seguridad del usuario actual, si se autentica. Context.User.Identity es el nombre del usuario. User es un objeto de tipo System.Security.Principal.IPrincipal.

Si es desarrollador de ASP, algunos de los objetos anteriores tendrán un aspecto bastante familiar. Hay algunas mejoras, pero, por lo general, funcionan exactamente igual en ASP.NET que en ASP.

Conceptos básicos del contexto

Algunos de los objetos de Context también se promueven como objetos de nivel superior en Page. Por ejemplo, Page.Context.Response y Page.Response hacen referencia al mismo objeto, por lo que el código siguiente es equivalente:

[Formulario web de Visual Basic®]

   Response.Write ("Hello ")
   Context.Response.Write ("There")

[Formulario web de C#]

   Response.Write ("Hello ");
   Context.Response.Write ("There");

También puede usar el objeto Context de los objetos de negocio. HttpContext.Current es una propiedad estática que devuelve convenientemente el contexto de la solicitud actual. Esto es útil de todo tipo de maneras, pero este es un ejemplo sencillo de recuperar un elemento de la memoria caché en la clase empresarial:

[Visual Basic]

      ' get the request context
      Dim _context As HttpContext = HttpContext.Current

   ' get dataset from the cache
   Dim _data As DataSet = _context.Cache("MyDataSet")

[C#]

      // get the request context
      HttpContext _context = HttpContext.Current;

   // get dataset from cache
   DataSet _data = _context.Cache("MyDataSet");

Contexto en acción

El objeto Context proporciona la respuesta a varias preguntas comunes ASP.NET "¿Cómo puedo...?". Quizás la mejor manera de comunicar lo valioso que puede ser esta gema es mostrarla en acción. Estos son algunos de los mejores trucos de contexto que sé.

¿Cómo emito una instrucción de seguimiento ASP.NET de mi clase business?

Respuesta: ¡Fácil! Use HttpContext.Current para obtener el objeto Context y, a continuación, llame a Context.Trace.Write().

[Visual Basic]

Imports System
Imports System.Web

Namespace Context

   ' Demonstrates emitting an ASP.NET trace statement from a
   ' business class.

   Public Class TraceEmit
      
      Public Sub SomeMethod()
         
         ' get the request context
         Dim _context As HttpContext = HttpContext.Current
         
         ' use context to write the trace statement
         _context.Trace.Write("in TraceEmit.SomeMethod")

      End Sub

   End Class

End Namespace   

[C#]

using System;
using System.Web;

namespace Context
{
   // Demonstrates emitting an ASP.NET trace statement from a
   // business class.

   public class TraceEmit
   {

        public void SomeMethod() {
        
            // get the request context
            HttpContext _context = HttpContext.Current;

            // use context to write the trace statement
            _context.Trace.Write("in TraceEmit.SomeMethod");
        }
    }
}

¿Cómo puedo acceder a un valor de estado de sesión desde mi clase empresarial?

Respuesta: ¡Fácil! Use HttpContext.Current para obtener el objeto Context y, a continuación, acceda a Context.Session.

[Visual Basic]

Imports System
Imports System.Web

Namespace Context

   ' Demonstrates accessing the ASP.NET Session intrinsic 
   ' from a business class.

   Public Class UseSession
   
      Public Sub SomeMethod()
         
         ' get the request context
         Dim _context As HttpContext = HttpContext.Current
         
         ' access the Session intrinsic
         Dim _value As Object = _context.Session("TheValue")

      End Sub

   End Class

End Namespace

[C#]

using System;
using System.Web;

namespace Context
{
   // Demonstrates accessing the ASP.NET Session intrinsic 
   // from a business class.

   public class UseSession
   {

        public void SomeMethod() {
        
            // get the request context
            HttpContext _context = HttpContext.Current;

            // access the Session intrinsic
            object _value = _context.Session["TheValue"];
        }
    }
}

Respuesta: Controle los eventos BeginRequest y EndRequest de la aplicación y use Context.Response.Write para emitir el código HTML para el encabezado y el pie de página.

Técnicamente, puede controlar los eventos de aplicación como BeginRequest en httpModule o mediante Global.asax. HttpModules son un poco más difíciles de escribir y no se suelen usar para la funcionalidad que usa una sola aplicación, como en este ejemplo. Por lo tanto, usaremos el archivo Global.asax con ámbito de aplicación en su lugar.

Al igual que con una página ASP, se promueven varios de los intrínsecos de contexto de ASP.NET para que sean propiedades de la clase HttpApplication, de la que hereda la clase que representa Global.asax. No es necesario usar HttpContext.Current para obtener una referencia al objeto Context ; ya está disponible en Global.asax.

En este ejemplo, estoy colocando las <html> etiquetas y <body> , además de una regla horizontal en la sección de encabezado, y otra regla horizontal más las etiquetas finales de estas en la sección de pie de página. El pie de página también contiene un mensaje de copyright. El resultado es similar a la ilustración siguiente:

Figura 1. Ejemplo de encabezado y pie de página estándar como se representa en el explorador

Este es un ejemplo trivial, pero puede ampliarlo fácilmente para incluir el encabezado y la navegación estándar, o simplemente generar las <instrucciones -- #include ---> para estos. Una advertencia: si desea que el encabezado o pie de página incluya contenido interactivo, considere la posibilidad de usar ASP.NET controles de usuario en su lugar.

[Origen SomePage.aspx: contenido de ejemplo]

<FONT face="Arial" color="#cc66cc" size="5">
Normal Page Content
</FONT>

[Visual Basic Global.asax]

<%@ Application Language="VB" %>

<script runat="server">

      Sub Application_BeginRequest(sender As Object, e As EventArgs)

         ' emit page header
         Context.Response.Write("<html>" + ControlChars.Lf + _
"<body bgcolor=#efefef>" + ControlChars.Lf + "<hr>" + _ ControlChars.Lf)

      End Sub 
      
      
      Sub Application_EndRequest(sender As Object, e As EventArgs)

         ' emit page footer
         Context.Response.Write("<hr>" + ControlChars.Lf + _
      "Copyright 2002 Microsoft Corporation" + _
      ControlChars.Lf + "</body>" + ControlChars.Lf + "</html>")

      End Sub 

</script>

[C# Global.asax]

<%@ Application Language="C#" %>

<script runat="server">

        void Application_BeginRequest(Object sender, EventArgs e) {

            // emit page header
            Context.Response.Write("<html>\n<body bgcolor=#efefef>\n<hr>\n");
        }

        void Application_EndRequest(Object sender, EventArgs e) {

            // emit page footer
            Context.Response.Write("<hr>\nCopyright 2002 Microsoft Corporation\n");
            Context.Response.Write("</body>\n</html>");
        }

</script>

¿Cómo puedo mostrar un mensaje de bienvenida cuando se autentica el usuario?

La respuesta: Pruebe el objeto de contexto de usuario para ver si el usuario está autenticado. Si es así, obtenga también el nombre del usuario del objeto User . Esto es, por supuesto, el ejemplo desde el principio del artículo.

[Visual Basic]

<script language="VB" runat="server">

    Sub Page_Load(sender As Object, e As EventArgs) {

        If User.Identity.IsAuthenticated Then
            welcome.Text = "Welcome " + User.Identity.Name
        Else
            ' not signed in yet, add a link to signin page
            welcome.Text = "please sign in!"
            welcome.NavigateUrl = "signin.aspx"
        End If

    End Sub

</script>

<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>

[C#]

<script language="C#" runat="server">

    void Page_Load(object sender, EventArgs e) {

        if (User.Identity.IsAuthenticated) {
            welcome.Text = "Welcome " + User.Identity.Name;
        }
        else {
            // not signed in yet, add a link to signin page
            welcome.Text = "please sign in!";
            welcome.NavigateUrl = "signin.aspx";
        }
    }

</script>

<asp:HyperLink id="welcome" runat="server" maintainstate="false">
</asp:HyperLink>

Y ahora para algo realmente maravilloso: Context.Items

Espero que los ejemplos anteriores muestren lo mucho más fácil que es escribir la aplicación web con un poco de información de contexto a mano. ¿No sería genial poder acceder a algún contexto que sea único para la aplicación de la misma manera?

Ese es el propósito de la colección Context.Items . Contiene los valores específicos de la solicitud de la aplicación de una manera que está disponible para cada parte del código que participa en el procesamiento de una solicitud. Por ejemplo, el mismo fragmento de información se puede usar en Global.asax, en la página ASPX, en los controles de usuario de la página y por la lógica de negocios a la que llama la página.

Considere la aplicación de ejemplo IBuySpy Portal . Usa una sola página principal (DesktopDefault.aspx) para mostrar el contenido del portal. El contenido que se muestra depende de la pestaña seleccionada, así como de los roles del usuario, si se autentica.

Ilustración 2. Página principal de IbuySpy

La cadena de consulta incluye los parámetros TabIndex y TabId para la pestaña que se solicita. Esta información se usa durante el procesamiento de la solicitud para filtrar los datos que se muestran al usuario. http://www.ibuyspy.com/portal/DesktopDefault.aspx?tabindex=1&tabid=2

Para usar un valor querystring, primero debe asegurarse de que es un valor válido y, si no es así, realice un pequeño control de errores. No es mucho código, pero ¿realmente quiere duplicarlo en cada página y componente que use el valor? Evidentemente no. En el ejemplo del portal, es aún más implicado, ya que hay otra información que se puede cargar previamente una vez que conocemos el TabId.

El Portal usa los valores querystring como parámetros para construir un nuevo objeto "PortalSettings" y agregarlo a Context.Items en el evento BeginRequest en Global.asax. Puesto que la solicitud begin se ejecuta al principio de cada solicitud, esto hace que los valores relacionados con tabulaciones estén disponibles para todas las páginas y componentes de la aplicación. Una vez completada la solicitud, el objeto se descarta automáticamente, ¡muy ordenado!

[Visual Basic Global.asax]

      Sub Application_BeginRequest(sender As [Object], e As EventArgs)
         
         Dim tabIndex As Integer = 0
         Dim tabId As Integer = 0
         
         ' Get TabIndex from querystring
         If Not (Request.Params("tabindex") Is Nothing) Then
            tabIndex = Int32.Parse(Request.Params("tabindex"))
         End If
         
         ' Get TabID from querystring
         If Not (Request.Params("tabid") Is Nothing) Then
            tabId = Int32.Parse(Request.Params("tabid"))
         End If
         
         Context.Items.Add("PortalSettings", _
New PortalSettings(tabIndex, tabId))

      End Sub

[C# Global.asax]

void Application_BeginRequest(Object sender, EventArgs e) {
        
    int tabIndex = 0;
    int tabId = 0;

    // Get TabIndex from querystring

    if (Request.Params["tabindex"] != null) {               
        tabIndex = Int32.Parse(Request.Params["tabindex"]);
    }
                
    // Get TabID from querystring

    if (Request.Params["tabid"] != null) {              
        tabId = Int32.Parse(Request.Params["tabid"]);
    }

    Context.Items.Add("PortalSettings", 
new PortalSettings(tabIndex, tabId));
}

El control de usuario DesktopPortalBanner.ascx extrae el objeto de PortalSetting de Context para acceder a la configuración de seguridad y el nombre del portal. De hecho, este módulo es un excelente ejemplo de contexto en acción. Para ilustrar el punto, he simplificado un poco el código y marcado todos los lugares en los que se accede a HTTP o contexto específico de la aplicación en negrita.

[C# DesktopPortalBanner.ascx]

<%@ Import Namespace="ASPNetPortal" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<script language="C#" runat="server">

    public int          tabIndex;
    public bool         ShowTabs = true;
    protected String    LogoffLink = "";

    void Page_Load(Object sender, EventArgs e) {

        // Obtain PortalSettings from Current Context
  PortalSettings portalSettings = 
(PortalSettings) Context.Items["PortalSettings"];

        // Dynamically Populate the Portal Site Name
        siteName.Text = portalSettings.PortalName;

        // If user logged in, customize welcome message
        if (Request.IsAuthenticated == true) {
        
            WelcomeMessage.Text = "Welcome " + 
Context.User.Identity.Name + "! <" + 
"span class=Accent" + ">|<" + "/span" + ">";

            // if authentication mode is Cookie, provide a logoff link
            if (Context.User.Identity.AuthenticationType == "Forms") {
                LogoffLink = "<" + "span class=\"Accent\">|</span>\n" + 
"<a href=" + Request.ApplicationPath + 
"/Admin/Logoff.aspx class=SiteLink> Logoff" + 
"</a>";
            }
        }

        // Dynamically render portal tab strip
        if (ShowTabs == true) {

            tabIndex = portalSettings.ActiveTab.TabIndex;

            // Build list of tabs to be shown to user                                   
            ArrayList authorizedTabs = new ArrayList();
            int addedTabs = 0;

            for (int i=0; i < portalSettings.DesktopTabs.Count; i++) {
            
                TabStripDetails tab = 
(TabStripDetails)portalSettings.DesktopTabs[i];

                if (PortalSecurity.IsInRoles(tab.AuthorizedRoles)) { 
                    authorizedTabs.Add(tab);
                }

                if (addedTabs == tabIndex) {
                    tabs.SelectedIndex = addedTabs;
                }

                addedTabs++;
            }          

            // Populate Tab List at Top of the Page with authorized 
// tabs
            tabs.DataSource = authorizedTabs;
            tabs.DataBind();
        }
    }

</script>
<table width="100%" cellspacing="0" class="HeadBg" border="0">
    <tr valign="top">
        <td colspan="3" align="right">
            <asp:label id="WelcomeMessage" runat="server" />
            <a href="<%= Request.ApplicationPath %>">Portal Home</a>
<span class="Accent"> |</span> 
<a href="<%= Request.ApplicationPath %>/Docs/Docs.htm">
                Portal Documentation</a>
            <%= LogoffLink %>
            &nbsp;&nbsp;
        </td>
    </tr>
    <tr>
        <td width="10" rowspan="2">
            &nbsp;
        </td>
        <td height="40">
            <asp:label id="siteName" runat="server" />
        </td>
        <td align="center" rowspan="2">
      &nbsp;
        </td>
    </tr>
    <tr>
        <td>
            <asp:datalist id="tabs" runat="server">
               <ItemTemplate>
                  &nbsp;
<a href='<%= Request.ApplicationPath %>
/DesktopDefault.aspx?tabindex=<%# Container.ItemIndex %>&tabid=
<%# ((TabStripDetails) Container.DataItem).TabId %>'>
<%# ((TabStripDetails) Container.DataItem).TabName %>
</a>&nbsp;
                </ItemTemplate>
                <SelectedItemTemplate>
                  &nbsp;
                  <span class="SelectedTab">
<%# ((TabStripDetails) Container.DataItem).TabName %>
</span>&nbsp;
                </SelectedItemTemplate>
            </asp:datalist>
        </td>
    </tr>
</table>

Puede examinar y ejecutar el origen completo del portal de IBuySpy en línea en Visual Basic y C# en http://www.ibuyspy.com, o descargarlo y ejecutarlo usted mismo.

Resumen

El contexto es otro de esos "cosas buenas se vuelven aún mejores en ASP.NET" características. Amplía la compatibilidad con el contexto ya excelente de ASP para agregar ambos enlaces a las nuevas características en tiempo de ejecución de ASP.NET. Además, agrega Context.Items como un nuevo mecanismo de estado para valores de muy corta duración. Pero la ventaja final para usted como desarrollador es más compacta, más fácil de mantener el código, y ese es un contexto que todos podemos conseguir.