Share via


Uma questão de contexto

 

Susan Warren
Microsoft Corporation

14 de janeiro de 2002

Um dos problemas mais comuns com a escrita de aplicativos Web é informar seu código sobre o contexto no qual ele está sendo executado. Vamos examinar um exemplo simples, personalizando uma página, que ilustra esse problema:

     Entre.

versus

     Bem-vinda Susan!

Parece simples o suficiente, mas mesmo esse pequeno bit da interface do usuário da Web requer alguns bits de informações que variam cada vez que a página é solicitada. Preciso saber:

  1. O usuário está conectado?
  2. Qual é o nome de exibição do usuário?

Em geral, qual é o contexto exclusivo sempreque a página é solicitada? E como posso escrever meu código para que ele leve essas informações em conta?

Na verdade, devido à natureza sem estado de HTTP, há muitas partes diferentes de contexto que um aplicativo Web pode precisar acompanhar. Quando um usuário interage com um aplicativo Web, o navegador envia uma série de solicitações HTTP independentes para o servidor Web. O próprio aplicativo precisa fazer o trabalho de tricô dessas solicitações em uma experiência agradável para o usuário e saber que o contexto da solicitação é fundamental.

O ASP introduziu vários objetos intrínsecos, como Solicitação e Aplicativo , para ajudar a acompanhar o contexto de uma solicitação HTTP. ASP.NET executa a próxima etapa e agrupa esses objetos, além de vários objetos adicionais relacionados ao contexto em um objeto intrínseco extremamente útil chamado Context.

Context é um objeto do tipo System.Web.HttpContext. Ele é exposto como uma propriedade da classe page ASP.NET. Ele também está disponível nos controles de usuário e em seus objetos de negócios (mais sobre isso posteriormente). Aqui está uma lista parcial dos objetos acumulados por HttpContext:

Objeto Descrição
Aplicativo Uma coleção de pares chave/valor de valores acessíveis por cada usuário do aplicativo. O aplicativo é do tipo System.Web.HttpApplicationState.
ApplicationInstance O aplicativo em execução real, que expõe alguns eventos de processamento de solicitação. Esses eventos são tratados em Global.asax ou httpHandler ou HttpModule.
Cache O objeto cache ASP.NET, que fornece acesso programático ao cache. A coluna Caching ASP.NET de Rob Howard fornece uma boa introdução ao cache.
Erro O primeiro erro (se houver) encontrado durante o processamento da página. Confira Exceção de Rob à Regra, Parte 1 para obter mais informações.
Itens Uma coleção de pares chave-valor que você pode usar para passar informações entre todos os componentes que participam do processamento de uma única solicitação. Itens é do tipo System.Collections.IDictionary.
Solicitação Informações sobre a solicitação HTTP, incluindo informações do navegador, cookies e valores passados em um formulário ou na cadeia de caracteres de consulta. A solicitação é do tipo System.Web.HttpRequest.
Resposta Configurações e conteúdo para criar a resposta HTTP. A solicitação é do tipo System.Web.HttpResponse.
Servidor Server é uma classe de utilitário com vários métodos auxiliares úteis, incluindo Server.Execute(), Server.MapPath()e Server.HtmlEncode(). Server é um objeto do tipo System.Web.HttpServerUtility.
Sessão Uma coleção de pares chave/valor de valores acessíveis por um único usuário do aplicativo. O aplicativo é do tipo System.Web.HttpSessionState.
Trace O objeto ASP.NET Trace , que fornece acesso à funcionalidade de rastreamento. Confira o artigo Rastreamento de Rob para obter mais informações.
Usuário O contexto de segurança do usuário atual, se autenticado. Context.User.Identity é o nome do usuário. O usuário é um objeto do tipo System.Security.Principal.IPrincipal.

Se você for um desenvolvedor de ASP, alguns dos objetos acima parecerão bastante familiares. Há alguns aprimoramentos, mas, na maioria das vezes, eles funcionam exatamente da mesma forma em ASP.NET como no ASP.

Noções básicas de contexto

Alguns dos objetos no Context também são promovidos como objetos de nível superior em Page. Por exemplo, Page.Context.Response e Page.Response fazem referência ao mesmo objeto para que o código a seguir seja equivalente:

[Formulário da Web do Visual Basic®]

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

[Formulário da Web em C#]

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

Você também pode usar o objeto Context de seus objetos de negócios. HttpContext.Current é uma propriedade estática que retorna convenientemente o contexto para a solicitação atual. Isso é útil de todos os tipos de maneiras, mas aqui está um exemplo simples de recuperação de um item do cache em sua classe comercial:

[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 em Ação

O objeto Context fornece a resposta a várias perguntas comuns ASP.NET "Como faço...?". Talvez a melhor maneira de comunicar o quão valiosa essa joia pode ser é mostrá-la em ação. Aqui estão alguns dos melhores truques de contexto que conheço.

Como emitir uma declaração de rastreamento ASP.NET da minha classe comercial?

Resposta: Fácil! Use HttpContext.Current para obter o objeto Context e, em seguida, chame 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");
        }
    }
}

Como posso acessar um valor de estado de sessão da minha classe comercial?

Resposta: Fácil! Use HttpContext.Current para obter o objeto Context e, em seguida, acesse 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"];
        }
    }
}

Resposta: Manipule os eventos BeginRequest e EndRequest do aplicativo e use Context.Response.Write para emitir o HTML para o cabeçalho e o rodapé.

Tecnicamente, você pode manipular os eventos do aplicativo como BeginRequest em um HttpModule ou usando Global.asax. HttpModules são um pouco mais difíceis de escrever e normalmente não são usados para funcionalidades usadas por um único aplicativo, como neste exemplo. Portanto, usaremos o arquivo Global.asax no escopo do aplicativo.

Assim como acontece com uma página ASP, vários dos intrínsecos de contexto ASP.NET são promovidos para serem propriedades da classe HttpApplication, da qual a classe que representa Global.asax herda. Não precisaremos usar HttpContext.Current para obter uma referência ao objeto Context ; ele já está disponível em Global.asax.

Neste exemplo, estou colocando as <html> marcas e <body> , além de uma regra horizontal na seção de cabeçalho e outra regra horizontal mais as marcas finais para elas na seção de rodapé. O rodapé também contém uma mensagem de direitos autorais. O resultado se parece com a figura abaixo:

Figura 1. Exemplo de cabeçalho e rodapé padrão renderizados no navegador

Este é um exemplo trivial, mas você pode estender isso facilmente para incluir o cabeçalho e a navegação padrão ou simplesmente gerar as <instruções -- #include ---> para elas. Uma ressalva: se você quiser que o cabeçalho ou rodapé inclua conteúdo interativo, considere usar ASP.NET controles de usuário.

[Fonte SomePage.aspx – conteúdo de exemplo]

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

Como posso mostrar uma mensagem de boas-vindas quando o usuário é autenticado?

A resposta: Teste o objeto de contexto usuário para ver se o usuário está autenticado. Nesse caso, obtenha o nome do usuário do objeto User também. Este é, naturalmente, o exemplo desde o início do artigo.

[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>

E agora para algo realmente maravilhoso: Context.Items

Espero que os exemplos acima mostrem o quanto é mais fácil escrever seu aplicativo Web com um pouco de informações de contexto em mãos. Não seria ótimo poder acessar algum contexto que seja exclusivo para seu aplicativo da mesma maneira?

Essa é a finalidade da coleção Context.Items . Ele mantém os valores específicos da solicitação do aplicativo de uma maneira que está disponível para cada parte do código que participa do processamento de uma solicitação. Por exemplo, a mesma informação pode ser usada em Global.asax, em sua página ASPX, nos controles de usuário dentro da página e pela lógica de negócios que a página chama.

Considere o aplicativo de exemplo portal IBuySpy . Ele usa uma única página de main — DesktopDefault.aspx — para exibir o conteúdo do portal. Qual conteúdo é exibido depende de qual guia é selecionada, bem como das funções do usuário, se autenticada.

Figura 2. Home page do IbuySpy

A querystring inclui os parâmetros TabIndex e TabId para a guia que está sendo solicitada. Essas informações são usadas durante todo o processamento da solicitação para filtrar quais dados são exibidos para o usuário. http://www.ibuyspy.com/portal/DesktopDefault.aspx?tabindex=1&tabid=2

Para usar um valor querystring, primeiro você precisa verificar se ele é um valor válido e, caso contrário, fazer um pequeno tratamento de erro. Não é muito código, mas você realmente deseja duplicá-lo em todas as páginas e componentes que usam o valor? É claro que não! No exemplo do Portal, ele está ainda mais envolvido, pois há outras informações que podem ser pré-carregadas quando conhecemos a TabId.

O Portal usa os valores querystring como parâmetros para construir um novo objeto "PortalSettings" e adicioná-lo a Context.Items no evento BeginRequest em Global.asax. Como a solicitação de início é executada no início de cada solicitação, isso disponibiliza os valores relacionados à guia para todas as páginas e componentes no aplicativo. Quando a solicitação é concluída, o objeto é descartado automaticamente — muito arrumado!

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

O controle de usuário DesktopPortalBanner.ascx efetua pull do objeto portalSetting do Context para acessar o nome e as configurações de segurança do Portal. Na verdade, este módulo é um ótimo exemplo de Contexto em ação. Para ilustrar o ponto, simplifiquei um pouco o código e marquei todos os locais em que o Contexto específico do aplicativo ou HTTP é acessado em negrito.

[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>

Você pode procurar e executar a fonte completa para o portal do IBuySpy online no Visual Basic e no C# em http://www.ibuyspy.comou baixá-lo e executá-lo por conta própria.

Resumo

O contexto é outro daqueles recursos de "coisas boas ficam ainda melhores em ASP.NET". Ele estende o já excelente suporte de contexto do ASP para adicionar ambos os ganchos aos novos recursos de runtime do ASP.NET. Além disso, adiciona Context.Items como um novo mecanismo de estado para valores de curta duração. Mas o benefício final para você como desenvolvedor é mais compacto, mais fácil de manter o código, e esse é um contexto que todos nós podemos ficar para trás.