Software y Aplicaciones Web

Blog de desarrollo de software y aplicaciones web

Comentarios Recientes

Comment RSS

MSDN Home Page (Argentina)


C# Corner


AspAlliance.com

Declaración

Las opiniones en este blog se proporcionan "TAL CUAL", sin garantías,  no confieren derechos y no reflejan, necesariamente, la opinión de quienes me contratan.
Algunas cuestiones que se comentan en el blog no son reales, cualquier similitud con alguna persona viva o muerta no es más que una coincidencia, tampoco significa que necesite terapia, soy asi.

© Copyright 2007-2010

Propaganda

Este sitio implementa publicidad basada en intereses
Jul
21.
2008

  Multiple domains within the same application

This publication shows how we can make multiple domains or subdomains operate within the same application and most importantly, the application take it in mind and manage this situation.

 

Introducction 

An engine blog (the application) is installed in a server, that means there is something like www.pedro.com.ar that allows us to access the blog. However we can make another domain such as www.jose.com.ar point to the same server, therefore you can access the same application with one or another domain, that everything is handled in the tables at the Domain Name System that associated domains at the same IP (basic network architecture).

On the other hand, if hosting allows it we can create subdomains who can take the form of blog.pedro.com.ar or trabajo.pedro.com.ar and have them sign on the same server where the primary domain (pedro.com.ar).

The point is that you need to know wich domain is accessing and then show diferents sides of the application. Case study is Blogger where million of blogs are in nombre_del_blog.blogspot.com; observe that the primary domain is blogspot.com and subdomain is that accesses either blog. 

In the following lines I will explain how this can be done in ASP.NET.

 

Architecture of an ASP application

When someone accesses a website by typing the address in the browser, the following occurs: 

The web server (Internet Information Server or Apache or any other), receives the request and transferred the request to an Active Server Pages. If the application was a Java Server Page (JSP) is the same (of course one is from Microsoft and the other from SUN)

Then, if this is the first time accessing the application creates an instance of the class HttpApplication. If the application have a file Global.asax, ASP.NET creates an instance of the class Global.asax which is derived from the HttpApplication class and used to represent the application. Here is one key to the problem that we are developing.

Can be multiple users accessing the application, ASP.NET provides a way to keep the information or status of each of them.

You can see in the second chart for each client instance other classes that mantinene the information that is requested (HttpRequest), and that is returned (HttpResponse). It is important to clarify that only the first access triggers events that correspond to the initialization of the application in the following accesses are triggered events that correspond to the initialization of a session, this is the other key to this problem

Important

It is important to see that there are several sessions in a single application domain, there are as many instances of HttpApplication class as users connect to the site. 

This feature is what allows us to create objects that maintain static information for the entire application.

It is quite common that one keep the configuration of the application on an object of this kind so that all users can access the same information and also save memory in the server.

 

Usually this is done with a code that allows access to a property (declared static) which gives us access to a field (also static) so that in the first reference to the method we can load the configuration (from the data base or an XML file).


The code can be more or less like this:


 

   1:   
   2:  public class Configuracion
   3:  {
   4:    public Configuracion()
   5:    {
   6:    }
   7:    private static Configuracion _Actual;
   8:    public static Configuracion Actual
   9:    {
  10:      get
  11:      {
  12:        if (_Actual == null)
  13:        {
  14:          _Actual = CargarConfiguracion();
  15:        }
  16:        return _Actual;
  17:      }
  18:    }
  19:    ...
  20:  }

 


With this code we always have access to a single object that "lives" in the domain of the application. There may be millions of sessions accessing the site, all of them access to the same object. 

For those applications as a portal where we need to display the number of people who are connected this comes very nice. 

But for example in Blogger when accessing pedro.blogspot.com we entered to the blog of Peter, an other blog could be jose.blogspot.com and both blogs are (virtually) in the domain blogspot. com (I say virtually because Google uses mechanisms to redireccion their IPs in  server farms, this is pure network architecture that has nothing to do with what I'm developing so do not give importance).



The following graph represents what I think and what we are going to develop: 

The main goal is to maintain in the application domain as many instances of HttpApplication as users connect, but also must maintain instances of Blog that are accessed by different users.

This will incorporate a synchronization problem. We must care for the multiple access (update) information from the same blog in general will have a single manager for blog so that the properties configuration may only be "modified" by a single process, however it may happen that the administrator user accesses through multiple instances of your browser and can cause an inconsistency in the values of the properties. 

Another problem is about the persistence of information. In this regard, if the model is relational (database) simply incorporate an identifier blog in the corresponding tables and filter queries according to that value; if the model is flat (XML documents) simply storing documents in different folders for each blog. 

Finally the most serious problem to resolve is with performance. It would be very easy to make each user instanciate an object that represents the blog that is accessing. Since most users are readers it is not applicable instantiating the same object for all (the only access is reading), so that the alternative is to share an instance of blog according to the domain who ar accessed.

It explained that the objects are static within the domain of application, this means that there is only one object in the entire application. Consequently this approach does not work, because we need an instance for each blog (domain) who is maintained with the application. 

 

Development Exercise

As I said before, in the first instance of the class HttpApplication we can run our code that allows us to begin managing the situation. To study the situation I have developed an exercise that counts how many sessions are opened for each domain associated in one application.

To test this in local mode you need Internet Information Server enabled on the computer and must be a virtual directory pointing to the folder where the application, so that the application can be accessed through a browser like Internet Explorer or Firefox.

Obviously the computer has a name that is known as the host for IIS and that name comes to be the "domain", on the other hand we can always accessible through the reserved name "localhost". 

As you can saw, the machine where I was playing is called "ICARO" but you can also access the "sites" empowered by "localhost". In this exercise the virtual directory (site) is demo.

The excercise has been done with Visual Studio 2008, is a Website for the Framework 2.0 (it does not matter too), but it is important to clarify that in Visual Studio 2005, when writing code in Global.asax file, the 2005 does not generate associate file, all this will have to be write between the marks <script runat="server"> ... </ script>. It's the only difference that I found. 

The following is the code that I put in the file class for the implementation Global.asax.cs. 

I use an instance of dictionary, which is nothing more than a hash table or map that allows us to store and access the objects with a key, see the documentation on MSDN. In this case the dictionary  key is a string who have the domain name and an object. It could define that it is going to be a number but I want to show how you can have either an object and make a cast according to our needs.

The important thing is that the code request of the dictionary is stored in a collection that is accesibe since the entire application. This collection is part of the state of implementation, and is also a dictionary. The property is ApplicationDomains which gives us access to the dictionary with domains assets. It could be accessed directly but the code is safer this way. He published another property for the number of sessions opened while the application is running. 

Then there are the methods that are actually managers of events, which serve to trap situations as the beginning and end of the application, start and end of a session. There are other methods but are beside the point. 

In an attempt to keep track of what is happening, I put a method that recorded in a log App_Data folder (remember to give rights to write for ASPNET user).

Finally there is a method that provides a string with information about domains assets.

File: Global.asax.cs 

   1:  using System;
   2:  using System.Text;
   3:   
   4:  namespace MultiDomainApplicationDemo
   5:  {
   6:    public class Global : System.Web.HttpApplication
   7:    {
   8:   
   9:      #region Keys for Application and Sessions Collection
  10:      /// <summary>
  11:      /// Estas claves son para manipular los campos en esta clase
  12:      /// Todos los campos se guardan en el Objeto Dictionary Application
  13:      /// </summary>
  14:      const string _ApplicationDomains = "_ApplicationDomains";
  15:      const string _ApplicationSessions = "_ApplicationSessions";
  16:   
  17:      #endregion
  18:   
  19:      #region Fields and Properties
  20:   
  21:      /// <summary>
  22:      /// Diccionario que mantiene el número de sesiones por cada host (dominio o subdominio)
  23:      /// </summary>
  24:      public System.Collections.Generic.Dictionary<string, object> ApplicationDomains
  25:      {
  26:        get
  27:        {
  28:          if (Application[_ApplicationDomains] == null)
  29:          {
  30:            Application[_ApplicationDomains] = new System.Collections.Generic.Dictionary<string, object>();
  31:          }
  32:          return (System.Collections.Generic.Dictionary<string, object>) Application[_ApplicationDomains];
  33:        }
  34:      }
  35:   
  36:      /// <summary>
  37:      /// Número de sesiones que se abrieron
  38:      /// </summary>
  39:      public int ApplicationSessions
  40:      {
  41:        get
  42:        {
  43:          if (Application[_ApplicationSessions] == null)
  44:          {
  45:            Application[_ApplicationSessions] = 0;
  46:          }
  47:          return (int) Application[_ApplicationSessions];
  48:        }
  49:        set
  50:        {
  51:          Application[_ApplicationSessions] = value;
  52:        }
  53:      }
  54:   
  55:   
  56:      #endregion
  57:   
  58:   
  59:      #region Application and Events Handlers
  60:      /// <summary>
  61:      /// Handles the Start event of the Application control.
  62:      /// </summary>
  63:      /// <param name="sender">The source of the event.</param>
  64:      /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
  65:      protected void Application_Start(object sender, EventArgs e)
  66:      {
  67:        Application.Lock();
  68:        ApplicationSessions = 0;
  69:        int a = ApplicationDomains.Count;    // esto es para que se cree el diccionario
  70:        Application.UnLock();
  71:   
  72:        WriteLog("Application Start", false);
  73:      }
  74:   
  75:      /// <summary>
  76:      /// Handles the Start event of the Session control.
  77:      /// </summary>
  78:      /// <param name="sender">The source of the event.</param>
  79:      /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
  80:      protected void Session_Start(object sender, EventArgs e)
  81:      {
  82:        Application.Lock();
  83:        ApplicationSessions += 1;
  84:   
  85:        string domain = this.Request.Url.Host;
  86:        if (!ApplicationDomains.ContainsKey(domain))
  87:        {
  88:          ApplicationDomains.Add(domain, 0);
  89:        }
  90:        ApplicationDomains[domain] = (int) ApplicationDomains[domain] + 1;
  91:        Application.UnLock();
  92:   
  93:        WriteLog(string.Format("Session Start in Host: {0}, Number of Sesions: {1} {2}", domain, ApplicationSessions.ToString(), ListDomains()), true);
  94:      }
  95:   
  96:      protected void Application_BeginRequest(object sender, EventArgs e)
  97:      {
  98:      }
  99:   
 100:      protected void Application_AuthenticateRequest(object sender, EventArgs e)
 101:      {
 102:      }
 103:   
 104:      protected void Application_Error(object sender, EventArgs e)
 105:      {
 106:      }
 107:   
 108:      /// <summary>
 109:      /// Handles the End event of the Session control.
 110:      /// </summary>
 111:      /// <param name="sender">The source of the event.</param>
 112:      /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
 113:      protected void Session_End(object sender, EventArgs e)
 114:      {
 115:        Application.Lock();
 116:        ApplicationSessions -= 1;
 117:        string domain = this.Request.Url.Host;
 118:        if (ApplicationDomains.ContainsKey(domain))
 119:        {
 120:          if ((int) (ApplicationDomains[domain] = (int) ApplicationDomains[domain] - 1) < 1)
 121:          {
 122:            ApplicationDomains.Remove(domain);
 123:          }
 124:        }
 125:        Application.UnLock();
 126:   
 127:        WriteLog(string.Format("Session End, Number of Sessions: {0} {1}", ApplicationSessions.ToString(), ListDomains()), true);
 128:      }
 129:   
 130:      /// <summary>
 131:      /// Handles the End event of the Application control.
 132:      /// </summary>
 133:      /// <param name="sender">The source of the event.</param>
 134:      /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
 135:      protected void Application_End(object sender, EventArgs e)
 136:      {
 137:        WriteLog("Application End", true);
 138:      }
 139:   
 140:      #endregion
 141:   
 142:   
 143:      #region Helper Methods
 144:      /// <summary>
 145:      /// Escribre una cadena de texto en el archivo de log
 146:      /// </summary>
 147:      /// <param name="strText">Cadena de texto a escribir en el log</param>
 148:      /// <param name="isAppend">Modo de escritura: true=Agrega, false=Sobreescribe</param>
 149:      void WriteLog(string strText, bool isAppend)
 150:      {
 151:        string fileName = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/log.txt");
 152:        System.IO.StreamWriter writer = new System.IO.StreamWriter(fileName, isAppend);
 153:        writer.WriteLine(DateTime.Now.ToString() + ", " + strText);
 154:        writer.Close();
 155:      }
 156:   
 157:      /// <summary>
 158:      /// Genera una cadena de texto con los dominios activos
 159:      /// </summary>
 160:      /// <returns>Cadena de texto</returns>
 161:      public string ListDomains()
 162:      {
 163:        StringBuilder sb = new StringBuilder();
 164:   
 165:        sb.AppendLine("\nDominios activos: " + ApplicationDomains.Count.ToString());
 166:        foreach (string domain in ApplicationDomains.Keys)
 167:        {
 168:          sb.AppendLine(string.Format("\tDominio: {0} Sessiones: {1}", domain, ((int) ApplicationDomains[domain]).ToString()));
 169:        }
 170:        sb.AppendLine("-------------------------------------------------------");
 171:        return sb.ToString();
 172:      }
 173:   
 174:      #endregion
 175:   
 176:    }
 177:  }

In an attempt to show that static objects do not serve, I put a class (stupid one) which publishes a property that can handle from the entire application. The following code shows this.

File: Estatica.cs 

   1:   
   2:  /// <summary>
   3:  /// Descripción breve de Estatica
   4:  /// </summary>
   5:  public class Estatica
   6:  {
   7:    public Estatica()
   8:    {
   9:    }
  10:    private static string _Dato = string.Empty;
  11:    /// <summary>
  12:    /// Se publica esta propiedad solo para mostrar que 
  13:    /// se comparte en todo el ámbito de la aplicación
  14:    /// </summary>
  15:    public static string Dato
  16:    {
  17:      get
  18:      {
  19:        return _Dato;
  20:      }
  21:      set
  22:      {
  23:        _Dato = value;
  24:      }
  25:    }
  26:  }

Let's see the test page, it has a TextArea to display the log and a mechanism to handle the Dato property described above. 

File: Default.aspx 

   1:  <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="MultiDomainApplicationDemo._Default" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   4:   
   5:  <html xmlns="http://www.w3.org/1999/xhtml" >
   6:  <head runat="server">
   7:    <title>Multi Domain Application Demo</title>
   8:  </head>
   9:  <body>
  10:    <form id="form1" runat="server">
  11:      <div style="text-align: center">
  12:        <h2>
  13:          Estado de la aplicación
  14:        </h2>
  15:        <br />
  16:        <asp:TextBox ID="txtLogFile" runat="server" TextMode="MultiLine" ReadOnly="true" Columns="100" Rows="20" ></asp:TextBox>
  17:        <br />
  18:        <div style="float:right;">
  19:          <asp:Button ID="btnRefresh" runat="server" Text="Actualizar" OnClick="btnRefresh_Click" />
  20:        </div>
  21:        <div style="clear:both;"></div>
  22:        <h2>
  23:          Veamos una propiedad estática
  24:        </h2>
  25:        Valor actual:&nbsp;<asp:Label ID="lblDato" runat="server"></asp:Label><br />
  26:        Nuevo valor:&nbsp;<asp:TextBox ID="txtDato" runat="server"></asp:TextBox>&nbsp;&nbsp;
  27:        <asp:Button ID="btnDato" runat="server" Text="Cambiar" OnClick="btnDato_Click" />
  28:      </div>
  29:    </form>
  30:  </body>
  31:  </html>

Here is the code associated with the page, observed that only loads the log file and the other method is to updated Dato ownership of stupid class. 

File: Default.aspx.cs 

   1:  using System;
   2:   
   3:  namespace MultiDomainApplicationDemo
   4:  {
   5:    public partial class _Default : System.Web.UI.Page
   6:    {
   7:      protected void Page_Load(object sender, EventArgs e)
   8:      {
   9:        if (!IsPostBack)
  10:        {
  11:          LoadLogFile();
  12:        }
  13:        lblDato.Text = Estatica.Dato;
  14:      }
  15:   
  16:      void LoadLogFile()
  17:      {
  18:        string fileName = System.Web.HttpContext.Current.Server.MapPath("~/App_Data/log.txt");
  19:        string fileData = string.Empty;
  20:   
  21:        if (fileName != null && System.IO.File.Exists(fileName))
  22:        {
  23:          fileData = System.IO.File.ReadAllText(fileName, System.Text.Encoding.Default);
  24:        }
  25:        else
  26:        {
  27:          fileData = "No existe el archivo " + fileName;
  28:        }
  29:        txtLogFile.Text = fileData;
  30:      }
  31:   
  32:      protected void btnRefresh_Click(object sender, EventArgs e)
  33:      {
  34:        LoadLogFile();
  35:      }
  36:      protected void btnDato_Click(object sender, EventArgs e)
  37:      {
  38:        Estatica.Dato = lblDato.Text = txtDato.Text + " " + DateTime.Now.ToString();
  39:      }
  40:    }
  41:  }

As ideas are described as "good ideas" only when they are realized, with members of Net Cells Jujuy ASP NET, THE ASP Berries and TeamDJ.Net  we will use these concepts in developing AnOtherBlog as the name says it is another engine blog but it has the characteristic that is to learn and practise. 

This exercise shows only an account of open sessions for each domain associated with the application, but just as we have a number in the dictionary we can maintain an instance of any kind of defined class. We must analyze and define strategies for performance and return of unused memory, remember that the dictionary is in the domain of the implementation and when there are no more sessions linked to a domain we need to free memory allocated. 

Finally here is the entire exercise code: MultiDomainApplicationDemo.zip (11,55 kb)

I hope it serve







Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading