ASP.NET
[ASP.NET] Sistema realtime di rilevamento degli utenti connessi
Conoscere il numero preciso di utenti connessi in un’applicazione ASP.NET non è possibile, sfruttando ad esempio le Membership API è possibile ottenere il numero di utenti connessi usando come criterio discriminante il timeout della Session (Membership.GetNumberOfUsersOnline()). In questo modo però è possibile monitorare solamente gli utenti loggati e comunque il numero rimane piuttosto approssimato.

Personalmente non ho mai avuto la necessità di tenere una traccia più accurata a scopo statistico, ma vediamo comunque come risolvere il problema :-).

Ah, il famigerato evento Session.End *non* è deterministico, quindi non lo si può sfruttare per questo obiettivo.

La soluzione è piuttosto semplice:
- un WebService che i client chiamano ciclicamente
- un timer Javascript che richiama il WebService 
Web server structure


1) L’attributo ScriptService permette di chiamare il web service tramite AJAX.
[WebService(Namespace = "http://eyeopen.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
[ScriptService]
public class AliveService : WebService
{
    [WebMethod]
    public void SendAlive()
    {
        ////string sessionId = HttpContext.Current.Request.Params["ASP.NET_SessionId"];
        string sessionId = HttpContext.Current.Request.UserHostAddress;
        UsersManager.RegisterUser(sessionId);
   }
}

2) Il file Javascript necessario a chiamare il web service:

function SendAlive() {
    EyeOpen.UsersMonitor.Web.AliveService.SendAlive();
}

function AliveTimerTick() {
    SendAlive();
    setTimeout(AliveTimerTick, 1000);
}

if (typeof (Sys) !== "undefined") {
    Sys.Application.notifyScriptLoaded();
}

setTimeout(AliveTimerTick, 1000);

 


3) Infine la reference al file Javascript tramite lo ScriptManager
e la reference al web service:

<asp:ScriptManager ID="scriptManager" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/Js/Alive.js" />
    </Scripts>
    <Services>
        <asp:ServiceReference Path="~/Core/AliveService.asmx" />
    </Services>
</asp:ScriptManager

 


La rappresentazione della classe di supporto al web service: 
Classe di supporto al web service

In sostanza il web service lato server viene chiamato da un timer javascript ogni secondo, con un basso carico di rete e sul server, segnalando la presenza dell’utente, nel momento in cui vengono chiuse tutte le pagine che richiamano il servizio entro 10 secondi (il tempo di timeout si gestisce tramite web.config) l’utente viene cancellato dalla lista degli utenti connessi.

E’ sufficiente inserire lo ScriptManager in tutte le pagine (ad esempio tramite MasterPage o da codice) e il sistema viene esteso a tutta l’applicazione web.

Qui si trova il progetto di esempio: EyeOpen.UsersMonitor.Web.

Matteo Migliore.
7 Comments Filed Under [ ASP.NET ]
[ASP.NET] Validare i controlli della pagina dato il ValidationGroup

Posto questi due metodi perchè ho trovato divertente riuscire a rendere il codice così striminzito grazie a C# 3.0 e alla ricorsione:

   1:    private void ValidateByGroup(string validationGroup) {
   2:     List<BaseValidator> validators = new List<BaseValidator>(from validator in FindControlsByType<BaseValidator>(this)
   3:                      where validator.ValidationGroup == validationGroup select validator);
   4:     validators.ForEach(validator => validator.Validate());
   5:    }
   6:   
   7:    private static IList<T> FindControlsByType<T>(Control root) where T: Control {
   8:     List<T> controls = new List<T>();   
   9:     foreach (Control child in root.Controls) {
  10:      Type type = child.GetType().BaseType;
  11:      while(type != typeof(object)){
  12:       if (type == typeof(T))
  13:       {
  14:        controls.Add((T)child);
  15:        break;
  16:       }
  17:       type = type.BaseType;
  18:      }
  19:   
  20:      if (child.HasControls())
  21:       controls.AddRange(FindControl<T>(child));
  22:     }
  23:     return controls;
  24:    }


Perchè funzioni è necessario disabilitare il controllo lato client impostando a False la proprietà EnableClientScript dei validator. Per rendere comunque la pagina reattiva sarebbe bene sfruttare AJAX perchè in questo modo il controllo verrà fatto solo lato server.

Matteo Migliore.

Add Comment Filed Under [ ASP.NET Tip ]
[TimeTracker] Dal class-diagram al diagramma ER
Ho iniziato a lavorare ad un progetto di cui parlerò fra qualche settimana. Anticipo che si tratta di un TimeTracker, il cui scopo principale è quello di racchiudere vari principi del design object oriented e di metodologie di sviluppo del software. L'obiettivo sarebbe anche quello di creare un presentation layer che offra degli spunti concreti ed infine avere a disposizione un TimeTracker realmente utilizzabile.

Nel frattempo scriverò qualche post durante "l'avanzamento dei lavori". Ho quasi terminato il disegno del class diagram e la cosa che mi ha dato maggiormente soddisfazione al momento è il fatto di essere in grado di generare il database direttamente dal class diagram attraverso NHibernate e la corretta definizione dei file di mapping.

Per un motivo (database già esistente) o l'altro (mancanza di tempo) non ho mai sfruttato questa feature di NHibernate davvero utile.

I due vantaggi sostanziali sono:
- possibilità di generare qualunque database supportato da NHibernate
- definizione dei vincoli nei file di mapping

Anche se alla fine della fiera si tratta solamente di query T-SQL "create table" o "alter table", è stupefacente avere il database (Sql Server 2005) vuoto un momento e il momento dopo averlo completamente popolato ovviamente ottenendo gratis anche il database diagram.

Una cosa fatta meglio del diagramma ER (Management Studio) rispetto al class diagram (VIsual Studio 2008): il metodo di riorganizzazione del grafico... per il resto, beh è come sparare sulla croce rossa :-D. Il confronto tra i due modelli:
Class diagram
Class diagram TimeTracker
Diagramma ER
Diagramma ER TimeTracker

Spero di avere abbastanza tempo per poter andare avanti speditamente :-). In teoria dovrebbe anche entrarci Silverlight 1.1 in minima parte, ma è da vedere.

A breve seguiranno altre notizie.

Matteo Migliore.
Implementare un custom Membership provider con ASP.NET
Un esempio su come scrivere un custom Membership provider per ASP.NET che consenta di validare correttamente la password nei vari metodi.
In particolare se la password non rispetta i vincoli imposti dal provider, la cosa da fare è lanciare un eccezione, che viene poi intercettata dai Login control e viene comunicato all'utente il problema. Bisogna inoltre invocare il metodo OnValidatingPassword, nel caso in cui la password sia corretta, per scatenare l'evento ValidatingPassword del provider.
Qui il codice:
public override bool ChangePassword(string username, string oldPassword, string newPassword)
{
    PasswordCheck passwordCheck = IsValidPassword(newPassword);
    if (passwordCheck == PasswordCheck.MinRequiredPasswordLength)
        throw new ArgumentException("Password does not respects min required lenght.");

    if (passwordCheck == PasswordCheck.NonAlphanumericCharacters)
        throw new ArgumentException("Password does not respects alphanumeric lenght.");

    ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, newPassword, false);
    OnValidatingPassword(e);
    return true;
}

public override MembershipUser CreateUser
    (string username, string password, 
     string email, string passwordQuestion, 
     string passwordAnswer, bool isApproved, 
     object providerUserKey, out MembershipCreateStatus status)
{
    if (IsValidPassword(password) != PasswordCheck.Valid) {
        status = MembershipCreateStatus.InvalidPassword;
        return null;
    }

    ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true);
    OnValidatingPassword(e);

    DateTime now = DateTime.Now;
    status = MembershipCreateStatus.Success;
    MembershipUser user = new MembershipUser
        (this.GetType().Name, username, Guid.NewGuid(), 
         email, passwordQuestion, string.Empty, true, false, 
         now, now, now, now, now);
    HttpContext.Current.Cache[user.UserName] = user;
    return user;
}

private enum PasswordCheck { 
    Valid = 0,
    NonAlphanumericCharacters = 2,
    MinRequiredPasswordLength = 4
}

private PasswordCheck IsValidPassword(string password) {
    if (password.Length < this.MinRequiredPasswordLength)
        return PasswordCheck.MinRequiredPasswordLength;

    int nonAlphanumericCharacters = 0;
    for (int i = 0; i < password.Length; i++)
    {
        if (!char.IsLetterOrDigit(password, i))
            nonAlphanumericCharacters++;
    }

    if (nonAlphanumericCharacters < this.MinRequiredNonAlphanumericCharacters)
        return PasswordCheck.NonAlphanumericCharacters;

    return PasswordCheck.Valid;
}

public override MembershipUser GetUser(string username, bool userIsOnline)
{
    MembershipUser user = HttpContext.Current.Cache[username] as MembershipUser;
    return user;
}

public override bool ValidateUser(string username, string password)
{
    MembershipUser user = HttpContext.Current.Cache[username] as MembershipUser;
    return ((user != null) && user.GetPassword().Equals(password));
}

Matteo Migliore.
Creare un menu con il controllo Repeater

Da un'altra richiesta su NG :-D, dovrò creare un angolo dedicato, forse in articoli :-), un problema semplice ma non molto per chi è all'inizio: come creare un menu con il controllo Repeater.

Questo esempio parte dal presupposto di avere la struttura delle pagine in uno storage (DB), per questo sarebbe più corretto implementare un SiteMapProvider, come questo.

Per semplificare però eseguo il binding direttamente con un ObjectDataSource.

Il codice html della pagina:

   1:  <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetAll" TypeName="SectionsDataSource" />        
   2:  <asp:Repeater ID="rptMenu" runat="server" OnItemDataBound="rptMenu_ItemDataBound" DataSourceID="ObjectDataSource1">
   3:      <HeaderTemplate><ul></HeaderTemplate>
   4:      <ItemTemplate><li><asp:HyperLink ID="hypSection" runat="server" 
   5:      NavigateUrl='<%# DataBinder.Eval(Container.DataItem, "Url") %>' 
   6:      Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>' /></li>
   7:      </ItemTemplate>
   8:      <FooterTemplate></ul></FooterTemplate>
   9:  </asp:Repeater>

Il code-behind:
   1:  protected void rptMenu_ItemDataBound(object sender, RepeaterItemEventArgs e)
   2:  {
   3:      if ((!e.Item.ItemType.Equals(ListItemType.Item)) 
   4:          && (!e.Item.ItemType.Equals(ListItemType.AlternatingItem)))
   5:          return;
   6:   
   7:      Section section = e.Item.DataItem as Section;
   8:      HyperLink hypSection = e.Item.FindControl("hypSection") as HyperLink;
   9:   
  10:      string virtualPath = VirtualPathUtility.ToAppRelative(Request.Path).ToLower();
  11:      
  12:      //Check if the current item url is the current section
  13:      if (section.Url.Equals(virtualPath, StringComparison.InvariantCultureIgnoreCase)) {
  14:          hypSection.NavigateUrl = string.Empty;
  15:          hypSection.CssClass = "currentSection";
  16:      }
  17:  }

La classe Section:
   1:  public class Section
   2:  {
   3:      public Section(string name, string url) {
   4:          this.name = name;
   5:          this.url = url;
   6:      }
   7:   
   8:      string name;
   9:      public string Name
  10:      {
  11:          get { return name; }
  12:          set { name = value; }
  13:      }
  14:      
  15:      string url;
  16:      public string Url
  17:      {
  18:          get { return url; }
  19:          set { url = value; }
  20:      }
  21:  }

Ed infine il data source:
   1:  public class SectionsDataSource
   2:  {
   3:      public IList<Section> GetAll() {
   4:          List<Section> sections = new List<Section>();
   5:          sections.Add(new Section("Home", "~/default.aspx"));
   6:          sections.Add(new Section("Contacts", "~/contacts.aspx"));
   7:          sections.Add(new Section("Where", "~/where.aspx"));
   8:          return sections;
   9:      }    
  10:  }
Qui si trova il codice dell'esempio. Matteo Migliore.
Add Comment Filed Under [ ASP.NET ]
Cancellazione multipla con Repeater e ObjectDataSource
Sempre a seguito di una domanda su NG ho scritto un breve esempio che mostra come eseguire la cancellazione multipla di "record" usando un Repeaer e un'ObjectDataSource.

L'esempio è composto da un'entity Student e da una classe che si occupa della persistenza StudentDataSource.

Qui è possibile scaricare l'esempio, di seguito il codice.

Classe Student:
   1:  public class Student
   2:  {
   3:      public Student() { }
   4:   
   5:      public Student(Guid id, string name) {
   6:          this.id = id;
   7:          this.name = name;
   8:      }
   9:   
  10:      Guid id;
  11:      public Guid Id
  12:      {
  13:          get { return id; }
  14:          set { id = value; }
  15:      }
  16:      
  17:      string name;
  18:      public string Name
  19:      {
  20:          get { return name; }
  21:          set { name = value; }
  22:      }
  23:   
  24:      public override bool Equals(object obj) {
  25:          if (obj == null || GetType() != obj.GetType())
  26:              return false;
  27:   
  28:          Student other = obj as Student;
  29:          return (this.Id.Equals(other.Id));
  30:      }
  31:   
  32:      public override int GetHashCode() {
  33:          return Id.GetHashCode();
  34:      }
  35:  }

Classe StudentDataSource:
   1:  public class StudentDataSource
   2:  {
   3:      IList<Student> Students {
   4:          get {
   5:              if (HttpContext.Current.Cache["Students"] == null) {
   6:                  HttpContext.Current.Cache["Students"] = new List<Student>();
   7:                  Fill();
   8:              }
   9:              return HttpContext.Current.Cache["Students"] as IList<Student>; 
  10:          }        
  11:      }
  12:   
  13:      void Fill() {
  14:          Students.Add(new Student(Guid.NewGuid(), "Bill"));
  15:          Students.Add(new Student(Guid.NewGuid(), "Steve"));
  16:          Students.Add(new Student(Guid.NewGuid(), "John"));
  17:          Students.Add(new Student(Guid.NewGuid(), "Martin"));
  18:      }
  19:   
  20:      public IList<Student> GetAll() {
  21:          return Students;
  22:      }
  23:   
  24:      public Student Get(Guid id) {
  25:          Student student = new Student();
  26:          student.Id = id;
  27:          int position = Students.IndexOf(student);
  28:          if (position >= 0)
  29:              return Students[position];
  30:          else
  31:              return null;
  32:      }
  33:   
  34:      public void Delete(Student value) {        
  35:          Students.Remove(value);
  36:      }
  37:  }

Il codice HTML:
   1:  <div>
   2:      <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
   3:          DataObjectTypeName="Student" 
   4:          SelectMethod="GetAll" 
   5:          TypeName="StudentDataSource" />
   6:      <asp:Repeater ID="Repeater1" runat="server" DataSourceID="ObjectDataSource1">
   7:          <ItemTemplate>
   8:              <input type="hidden" id="hidKey" runat="server" value='<%# DataBinder.Eval(Container.DataItem, "Id") %>' />
   9:              <asp:CheckBox ID="chkDelete" runat="server" />
  10:              &nbsp;
  11:              <asp:Label ID="lblName" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "Name") %>' />
  12:              <br />
  13:          </ItemTemplate>
  14:      </asp:Repeater>
  15:      <br />
  16:      <br />
  17:      <asp:Button ID="btnDelete" runat="server" Text="Delete" OnClick="btnDelete_Click" />
  18:  </div>

Il codice che esegue la cancellazione:
   1:  protected void btnDelete_Click(object sender, EventArgs e)
   2:  {
   3:      StudentDataSource studentDataSource = new StudentDataSource();
   4:   
   5:      foreach (RepeaterItem item in Repeater1.Items) {
   6:          CheckBox chkDelete = item.FindControl("chkDelete") as CheckBox;
   7:          if (chkDelete.Checked) {
   8:              HtmlInputHidden hidKey = item.FindControl("hidKey") as HtmlInputHidden;
   9:              Guid key = new Guid(hidKey.Value);
  10:              Student student = studentDataSource.Get(key);
  11:              Response.Write(student.Name + " deleted<br/>");
  12:              studentDataSource.Delete(student);                
  13:          }
  14:      }
  15:      Repeater1.DataBind();
  16:  }

Matteo Migliore.
2 Comments Filed Under [ ASP.NET ]
Il .NET Framework 3.5, tutto in poche righe

In genere non mi piace postare contenuti che non siano miei ed originali, ma faccio un'eccezione, vista la chiarezza di quanto riporto più in basso.

Volete conoscere e riassumere tutte le novità del .NET Framework 3.5 rispetto alle versioni precedenti in una pagina?

Qui trovate il decalogo:
http://www.danielmoth.com/Blog/2007/06/net-framework-35.html

Fonte:
http://programmazione.it/index.php?entity=eitem&idItem=36815

Matteo Migliore.

Un nuovo, semplice CMS in ASP.NET 2.0, Open ovviamente

SampleCMS

SampleCMS: un semplice CMS per beginners.

Questa notte, a seguito di varie richieste sul NG di ASP.NET a cui mi stò dedicando parecchio ultimamente, ho sviluppato un piccolo, anzi micro :-), CMS
che dovrebbe permettere ai beginners di capire quali sono le basi, il funzionamento così da poter iniziare a sviluppare una soluzione in, quasi, completa autonomia.

Sarei tentanto di iniziare a farcire il progetto di funzionlità, come un editor per la creazione dinamica di layout, ma questo stravolgerebbe la natura del progetto, che vorrei mantenere semplice, senza installazioni e configurazioni complesse come per i suoi fratelli "molto" maggiori: DotNetNuke, Umbraco.

E' possibile stabilire per ogni sezione se usare il layout di default oppure
una nuova pagina ASPX che utilizza una MasterPage.
Per la gestione dei contenuti ho creato un meccanismo a provider come per le Membership API, attualmente ce ne sono 2: un fake provider che restituisce un contenuto statico e un xml provider che persiste i contenuti in file xml. 

Ogni commento è ben accetto ovviamente.

Lo trovate su CodePlex: http://www.codeplex.com/SampleCMS.


Technorati Tags: ,
2 Comments Filed Under [ ASP.NET ]
Sicurezza delle applicazioni ASP.NET e dimensioni pagine: 1 shot, 2 pigeons

Grazie all'adaptive rendering di ASP.NET, cambiare il modo in cui vengono renderizzati i controlli è molto semplice; è sufficiente creare una classe che erediti da System.Web.UI.Adapters.ControlAdapter e ridefinire l'html che sì vuole emettere.

Qui sì trova un articolo di Cristian Civera http://www.microsoft.com/italy/msdn/risorsemsdn/sviluppoweb/adrendering.mspx

Modificando il modo in cui sì renderizza la pagina però sì possono anche risolvere definitivamente alcuni problemi: è possibile memorizzare il ViewState, che arriva anche a qualche centinaio di kbyte, anzichè nella pagina come accade di default, in Session emettendo verso il client solamente un Id.

In questo modo sì migliora la sicurezza poichè il ViewState non va avanti e indietro [roundtrip] da client a server e sì risparmia molta banda. Davvero notevole!

Ecco un esempio dell'adapter:
http://msdn2.microsoft.com/en-us/library/system.web.ui.sessionpagestatepersister.aspx

E qui un altro sistema per sfruttarlo:
http://msdn2.microsoft.com/en-us/library/aa479403.aspx

Usandolo sì otterrano anche migliori risultati nell'indicizzazione sui motori di ricerca, Google :-), poichè in questo modo aumenta la densità delle parole chiave!

Matteo Migliore.