moleskine di un programmatore

Appunti di viaggio tra bit e byte
FakeCloneTree 0.0.1.0

Just committed the initial release of FakeCloneTree, a simple command line tool for cloning a folder tree in a zero byte length copy.

DISCLAIMER: use at your own risk. this is just an internal util for myself

 

Use

image

 

Result

image image

Sources: http://dotnetmarcheproject.googlecode.com/svn/trunk/src/Projects/FakeCloneTree

Binary: http://dotnetmarche.org/blogs/andreabalducci/Code/FakeCloneTree/FakeCloneTree%200.0.1.0.zip

FakeCloneTree

I’ve to test a massive document import from an external system and I want to test on my dev machine before going to the servers. I’ve to deal with filename analysis (the have the key to the business logic of the external legacy system) so I need to clone the client’s storage for a real massive test.

So I decided to build a simple console app to clone the source tree with zero length file (cannot upload 10+gb over the net) to mimic the production environment (to be sure no one file has naming rules nobody told me).

Here the code snapshot. Planning to build a public os utils with zip compression to create a single compressed fake folder tree.

        static private void DoClone(string baseFolder, string destinationFolder)
        {
            // clone folders tree
            if (!Directory.Exists(destinationFolder))
                Directory.CreateDirectory(destinationFolder);

            string[] folders = Directory.GetDirectories(baseFolder);

            foreach (var folder in folders)
            {
                string dn = folder.Remove(0, baseFolder.Length+1);
                string dest = Path.Combine(destinationFolder, dn);
                if(!Directory.Exists(dest))
                    Directory.CreateDirectory(dest);

                // recursion...
                DoClone(folder, dest);
            }

            // create fake files
            string[] files = Directory.GetFiles(baseFolder);
            foreach (var file in files)
            {
                string dest = Path.Combine(destinationFolder, Path.GetFileName(file));

                if(!File.Exists(dest))
                    File.CreateText(dest).Close();
            }
        }

Questione di sicurezza…

Secondo (e non ultimo) episodio di mala-telesanità: è possibile che un portale per l’accesso alle informazioni sensibili (stato di salute) dei cittadini mostri continuamente errori di certificato scaduto o non valido e che il medico sia “addestrato” a procedere ignorando l’eccezione? ho qualche dubbio in merito…

L’importanza di una corretta messaggistica

Ieri sono stato dal medico per il tagliando dei 35 e ho avuto l’occasione di vedere il fantastico sistema telematico di prenotazione / consultazione esami. A parte la realizzazione veramente di basso livello in termini di usabilità e look & feel (sembra una applicazione vecchia di 20 anni) ha una gestione della messaggistica che la dice lunga: ve lo immaginate un medico che prova a consultare dei dati ed ogni volta vede una bella scritta sul monitor: “errore di connessione socket alla riga 40”. Al quarto tentativo mi guarda e mi chiede:”cosa significa?”.

Posted: May 19 2009, 01:12 PM by andrea.balducci | with no comments
Filed under: , , ,
Introducing ABAnalyzer, an Apache Bench runner and analyzer

In the last few days I've read a lot of blog post about Asp.Net MVC Performance. Following the Simone’s post and Rudi’s post i started to test the Asp.Net MVC engine. First of all i wanted to test using the same Apache Bench Tool used by Rudi and wanted to store and compare different results.

After playing with the command prompt a little I started a quick & dirty test runner with c# (I’m working all day with c++ && MFC so I’m always looking for a way to write some .Net code ; D ) and the result is … ABAnalyzer

image 

I want to test the performances of client-side rendering vs server-side rendering so i want to monitor the overall “Request per second” and “Document length” meters. The source code is published in the public code repository of DotNetMarche hosted by Google Code so you can download and play with it (maybe adding functionalities you need). The solution has 2 projects the frontend gui and the services layer (bench runner, analyzer, simple storage) so you can use ABAnalyzer.Services to write your own test tool.

This is the first release (there are few minor bugs I’ve to fix as I can) with just the minimum functionalities I needed to run my tests.

If you want to play with the tool just download the precompiled release (link at the end of this post).

How it works

The “Loaded tests” combo is the list of the test in the current archive (currently only one archive containing an arbitrary number of test addresses); just enter “google” as the test name (and graph label) and fill the others values following the shapshot.

image

Note: use with care, don’t hammer any website! Stress test should be on local servers.

The “Bootstrap” flag fire a single request before running the test to the test address (to bootstrap the appdomain without affecting the results). The “Add” button just append the test in current list (only one test for single key is allowed), “Start” add the test and run immediately spanning a new process for ab.exe.

The result is shown in plain text (what in the sources I call RawData) (just have to select the test in the combo.. to fix)

image

The two indicators I want to check are displayed using the Microsoft Chart Control

image

Redo the test with ibm.com, microsoft.com; now you have 3 loaded test to analyze just for fun: we’ll use the tool to test the same local page and measure the performances of our Asp.NET MVC after each code optimization.

image

Download: ABRelease-0.1.0.0.zip

Wrong parent > child selector result in jquery-1.3.2-vsdoc2.js

While i was developing a simple demo for a friend of mine i found a bug on the jQuery annotated script for VS intellisense. I found that the parent > child selector give bad results. I issued the bug on jQuery bug tracker and today i want to move first steps with QUnit to create a non regression test for my demo.

First of all i created a simple html page with two span, one inside a div and one outside.

<html>
<head>
<title> test </title>
</head>
<body>
<div id="container">
<span>Inside div</span>
</div>
<span>Outside div</span>
</body>
</html>

Then added all the stuff for testing

<html>
<head>
<title> test </title>
<script type="text/javascript" src="http://jqueryjs.googlecode.com/files/jquery-1.3.2.js"></script>
<link rel="stylesheet" href="http://dev.jquery.com/view/trunk/qunit/testsuite.css" type="text/css" media="screen" />
<script type="text/javascript" src="http://jqueryjs.googlecode.com/svn/trunk/qunit/testrunner.js"></script>
<script type="text/javascript">
$(
function(){

test(
"inside span", function() {
expect(
2);
var list = $('#container > span');
equals( list.length,
1, "span count inside the div = 1" );
equals( list[
0].innerHTML, 'Inside div', "innerHTML of inside span" );
});

test(
"outside span", function() {
expect(
2);
var list = $('span').not('#container > span');
equals( list.length,
1, "span count outside the div = 1" );
equals( list[
0].innerHTML, 'Outside div', "innerHTML of outside span" );
});

test(
"all span", function() {
var list = $('span');
equals( list.length,
2, "all span count = 2" );
});
});
</script>
</head>
<body>
<div id="container">
<span>Inside div</span>
</div>
<span>Outside div</span>
<div id ="test_results">
<h1>QUnit example</h1>
<h2 id="banner"></h2>
<h2 id="userAgent"></h2>

<ol id="tests"></ol>
<div id="main"></div>
</div>
</body>
</html>

Let’s run the test opening the page in the browser

test ok

Now let’s switch to –vsdoc2.js annotated file and re-run the tests…

test fail

The ‘#container > span’ selector returns all the span in the page!

Code

Ticket

Quick & Dirty: Change email addresses in Gravatar icons with jQuery

Warming up this morning with a code snippet: we want to change the mailto: link in a gravatar enabled link using jQuery. First of all surf to http://en.gravatar.com/site/implement/url to understand how the gravatar link is formed. So we need ad md5 of our email addresses, just go to http://www.semnanweb.com/jquery-plugin/md5.html and download the MD5 plugin for jquery.

All we need to do is just find all the mailto link using a simple selector $('a[href^=\"mailto\"]') and then manipulate the content.

<!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>Gravatar demo</title>

    <script type="text/javascript" src="jquery-1.3.2-vsdoc.js"></script>
    <script type="text/javascript" src="jquery.md5.js"></script>

    <script type="text/javascript">
        function unescapeEmailAddress(email) {
            return email
               .replace(new RegExp(" !dot! ", "g"), ".")
               .replace(new RegExp(" !at! ", "g"), "@")
               .replace('mailto:', '');
        }
        
        
        $(function() {
            $('a[href^=\"mailto\"]').each(function() {
                var self = $(this);
                var email = unescapeEmailAddress(self.attr('href'));
                var md5 = $.md5(email);
                var gravatar = 'http://www.gravatar.com/avatar/' + md5 +'?d=monsterid';
                self
                   .html('<img src=\'' + gravatar + '\' alt=\'' + email + '\'/>')
                   .attr('href', 'mailto:'+email)
                ;
            });
        });
    
    </script>
</head>
<body>
    <a href="mailto:mtb !dot! snowboard !at! gmail !dot! com">mtb !dot! snowboard !at! gmail !dot! com</a>
    <a href="mailto:prova !at! prova !dot! com">prova !at! prova !dot! com</a>
</body>
</html>
Blackboard project

Slide

In occasione del 9° Workshop DotNetMarche ho realizzato una mini applicazione demo utilizzando Asp.Net MVC e jQuery per dimostrare alcune modalità di utilizzo di jQuery per la manipolazione JSON e l’utilizzo di client-side templates.

Il codice è disponibile via subversion al repository DotNetMarche http://dotnetmarcheproject.googlecode.com/svn/trunk/src/Projects/Blackboard.

L’applicazione (incompleta) ad ora permette la creazione di presentazioni contenenti testo e grafica, la selezione, lo spostamento e l’allineamento in blocco degli elementi, la definizione del testo, del colore e della dimensione dei blocchi <span> ed un browser di immagini che permette l’inserimento di icone. Nelle prossime settimane cercherò di completare il progetto e di bloggare step by step la realizzazione dell’applicazione sotto forma di tutorial… stay tuned!

proprietà del testo

selezione immagini

nuova slide

Utilizzo di un ajaxloader con Asp.Net MVC e jQuery

In questo breve tutorial utilizzeremo jQuery per caricare in modalità asincrona delle informazioni dal server mostrando all’utente un loader nel caso in cui il server non risponda velocemente.

Prima di tutto creiamo un nuovo progetto utilizzando Asp.Net MVC chiamato MVCAjaxLoader.

Modifichiamo l’homecontroller aggiungendo il metodo Show

   1:  using System.Threading;
   2:  using System.Web.Mvc;
   3:   
   4:  namespace MVCAjaxLoader.Controllers
   5:  {
   6:      [HandleError]
   7:      public class HomeController : Controller
   8:      {
   9:          public ActionResult Index()
  10:          {
  11:              ViewData["Message"] = "Welcome to AjaxLoader demo";
  12:   
  13:              return View();
  14:          }
  15:   
  16:          public ActionResult About()
  17:          {
  18:              return View();
  19:          }
  20:   
  21:          public ActionResult Show(int? delay)
  22:          {
  23:              int d = delay ?? 0;
  24:              if(d > 0)
  25:                  Thread.Sleep(d * 1000);
  26:   
  27:              return View();
  28:          }
  29:      }
  30:  }
 
 

Generiamo la view relativa al metodo Show che contrerrà il nostro frammento di html da inviare al browser (essendo solo un frammento di html che verrà incluso in una pagina non verrà selezionata nessuna masterpage).

   1:  <div>
   2:      <span class='message'>
   3:          <%= DateTime.Now.ToLongTimeString() %>
   4:      </span>
   5:  </div>

Utilizziamo ora ajaxloader.info per generare il nostro loader ed andiamo a modificare il site.css per utilizzare la nuova gif.

   1:  .ajaxloader
   2:  {
   3:      background-image: url('ajax-loader.gif');    
   4:      background-repeat: no-repeat;
   5:      background-position: center center;
   6:      height:100%;
   7:      width:100%;
   8:  }
   9:   
  10:  .message
  11:  {
  12:      font-size:xx-large;
  13:      font-weight:bold;
  14:  }

A questo punto abilitiamo la nostra applicazione all’utilizzo di jQuery modificando la masterpage aggiungendo il riferimento a jQuery ed inserendo un ContentPlaceHolder per i nostri script

   1:  <head runat="server">
   2:      <title><asp:ContentPlaceHolder ID="TitleContent" runat="server" /></title>
   3:      <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
   4:      <script src="../../Scripts/jquery-1.3.1.js" type="text/javascript"></script>
   5:      
   6:      <asp:ContentPlaceHolder ID="scriptPlaceHolder" runat="server" />
   7:  </head>

Ora apriamo la nostra index.aspx per inserire due hyperlink per richiamare l’elaborazione asincrona in modalità veloce ed in modalità delayed.

   1:  <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">
   2:      <h2>
   3:          <%= Html.Encode(ViewData["Message"]) %></h2>
   4:      <a href="#" id="showFast">Fast</a> <a href="#" id="showSlow">Slow</a>
   5:      <div id="result">
   6:      </div>
   7:  </asp:Content>

Inseriamo nel site.css lo stile del nostro box “result”

   1:  #result
   2:  {
   3:      margin-top:4px;
   4:      height: 200px;
   5:      width: 200px;
   6:      border: thin solid #808080;
   7:  }

A questo punto non resta che creare una estensione di jQuery che permetta di mostrare il loader nel nostro box dei risultati ed utilizzare in metodo jQuery.load per eseguire la chiamata asincrona. Prima della chiamata impostiamo un timeout di 1500 ms per visualizzare il loader nel box dei risultati. Quando la chiamata asincrona termina cancelliamo il timeout (in modo tale da bloccare la visualizzazione del loader).

   1:  <asp:Content ID="scriptContent" ContentPlaceHolderID="scriptPlaceHolder" runat="server">
   2:   
   3:      <script type="text/javascript">
   4:   
   5:          $.fn.showLoader = function() {
   6:              return this.each(function() {
   7:                  $(this).html('<div class=\'ajaxloader\'></div>');
   8:              });
   9:          }
  10:   
  11:          function ajaxLoad(delayTimeout) {
  12:              var resultBox = $('#result');
  13:              
  14:              // clear current result...
  15:              resultBox.html('');
  16:              
  17:              // Set loader timeout
  18:              var t = setTimeout(function() {
  19:                  resultBox.showLoader()
  20:              }, 1500);    
  21:   
  22:              // ajax call...
  23:              resultBox.load(
  24:                  '<%= Url.Action("Show") %>',
  25:                  { delay: delayTimeout }, 
  26:                  function(responseText, textStatus, request) {
  27:                      // stop loader timeout
  28:                      clearTimeout(t);
  29:                  }
  30:              );
  31:          }
  32:   
  33:          $(function() {
  34:              $('#showFast').click(function() { ajaxLoad(0); });
  35:              $('#showSlow').click(function() { ajaxLoad(4); });
  36:          });
  37:   
  38:      </script>
  39:   
  40:  </asp:Content>

Qui il codice: MVCAjaxLoader.zip (248kb)

Pimp my code

Sto implementando un sistema di localizzazione da usare su Asp.Net MVC su tutti i layer (db, business logic, presentation). Ho quindi implementato un sistema di dizionari in formato Json (per poterli riutilizzare poi con JQuery lato client all'occorrenza).

Prima di validare il prototipo da includere nel trunk ho creato un test per verificare il corretto funzionamento del mio servizio di localizzazione.

Visto che non sono "troppo" orientato ai test chiedo aiuto per migliorare il codice sottostante...

   1: [Test]
   2: public void CanLocalizeUsingDefaultLocaleFallback()
   3: {
   4:     string jsondicIT = "{\"Core.Info\":\"Informazione\",\"Core.Warning\":\"Attenzione\",\"Core.Error\":\"Errore\"}";
   5:     string jsondicEN = "{\"Core.Info\":\"Info\",\"Core.Warning\":\"Warning\",\"Core.Error\":\"Error\", \"Core.EN\":\"Only en\"}";
   6:     StreamReader readerIT = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(jsondicIT)));
   7:     StreamReader readerEN = new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(jsondicEN)));
   8:     
   9:     MockRepository mocks = new MockRepository();
  10:     IResourceLoader loader = mocks.DynamicMock<IResourceLoader>();
  11:  
  12:     SetupResult.For(loader.EnumLocales()).Return(new[] {"it-IT", "en-US"});
  13:     SetupResult.For(loader.EnumResources("it-IT")).Return(new[] {"it-IT\\Core.dic"});
  14:     SetupResult.For(loader.EnumResources("en-US")).Return(new[] { "en-US\\Core.dic" });
  15:     SetupResult.For(loader.Load("it-IT\\Core.dic")).Return(readerIT);
  16:     SetupResult.For(loader.Load("en-US\\Core.dic")).Return(readerEN);
  17:     
  18:     ILocalizationService localizationService = new LocalizationService("en-US");
  19:  
  20:     mocks.ReplayAll();
  21:     localizationService.AddLocales(loader);
  22:  
  23:     // requested locale dictionary
  24:     Assert.That(localizationService.Localize("it-IT", "Core.Info"), Is.EqualTo("Informazione"));
  25:     // default locale dictionary fallback
  26:     Assert.That(localizationService.Localize("it-IT", "Core.EN"), Is.EqualTo("Only en"));
  27:     
  28:     mocks.VerifyAll();
  29: }
Async data con jQuery, Flexigrid e Asp.Net MVC

Dopo aver introdotto jQuery e Flexigrid nel nostro progetto Asp.Net MVC implementiamo una fonte dati asincrona per la nostra griglia.

Prima di tutto definiamo dei dati di esempio su cui lavorare estendendo la classe SampleData aggiungendo un metodo per ottenere un set di dati su cui lavorare.

Aggiungiamo la proprietà LotOfContacts che genera un set di dati e li memorizza in un datamember statico per mantenere costante il set di dati tra le richieste.

   1: public static class SampleData
   2: {
   3:     static IQueryable<ContactModel> _cache = null;
   4:     
   5:     public static IQueryable<ContactModel> LotOfContacts
   6:     {
   7:         get
   8:         {
   9:             return _cache ?? (_cache = GenerateRandomContacts(100));
  10:         }
  11:     }
  12:     
  13:     private static IQueryable<ContactModel> GenerateRandomContacts(int amount)
  14:     {
  15:         ...
  16:     }
  17:        
  18: }

La generazione pseudo-random dei dati avviene con il seguente algorimo

   1: private static IQueryable<ContactModel> GenerateRandomContacts(int amount)
   2: {
   3:     string[] names = { "Andrea", "Stefano", "Roberto", "Alessandro", "Gian Maria", "Diego" };
   4:     string[] surnames = { "Rossi", "Verdi", "Bianchi", "Conti", "Russo", "Ferrari", "Esposito" };
   5:     string[] addresses = { "Via Garibaldi", "Via Verdi", "Via Bianchi", "Piazza Libertà", "Largo stretto", "Corso Magenta", "Via Litoranea" };
   6:     string[] phones = { "555-1111", "555-2222", "555-3333", "555-4444", "555-5555", "555-6666", "555-7777" };
   7:     Random r = new Random(amount);
   8:  
   9:     IList<ContactModel> list = new List<ContactModel>();
  10:     for (int c = 1; c <= amount; c++)
  11:     {
  12:         list.Add(new ContactModel(c.ToString())
  13:                      {
  14:                          Name =
  15:                              string.Format("{0} {1}", names[r.Next(names.Length)],
  16:                                            surnames[r.Next(surnames.Length)]),
  17:                          Address = string.Format("{0} {1}", addresses[r.Next(addresses.Length)], c),
  18:                          Phone = phones[r.Next(phones.Length)]
  19:                      }
  20:            );
  21:     }
  22:  
  23:     return list.AsQueryable();
  24: }

La scelta di ritornare un IQueryable<T> è dettata dalla necessità di poter interrogare tramite Linq la nostra "base dati". La  classe ContactModel è stata modificata con l'aggiunta di un ID utile per identificare il record nella selezione in griglia.

Modifichiamo ora Tutorial3.aspx per indicare la fonte dati e configurare la griglia

   1: <script type="text/javascript">
   2:     $(function() {
   3:     <%= Html.NewFlexiGrid("Contacts")
   4:         .AddColumn("Name", "Name", 200, true, FlexiGridBuilder.Align.left)
   5:         .AddColumn("Address", "Address", 180, true, FlexiGridBuilder.Align.left)
   6:         .AddColumn("Phone", "Phone", 120, true, FlexiGridBuilder.Align.left)
   7:         .AddSearchItem("Name", "Name", true)
   8:         .AddSearchItem("Address", "Address", false)
   9:         .AddSearchItem("Phone", "Phone", false)
  10:         .SetDataUrl(Url.Action("ListContacts"))
  11:         .SetRecordsPerPage(10)
  12:         .SetHeight(300)
  13:         .SetWidth(600)
  14:         .Create("tablefx")%>
  15:     });
  16: </script>

Flexigrid verrà quindi creata vuota lato client; una chiamata asincrona chiederà al server i dati in formato Json utilizzando l'url passato come fonte dati con
     .SetDataUrl(Url.Action("ListContacts")).

Questo si traduce nella chiamata alla nuova action (creata ad hoc) ListContacts del nostro controller.

   1: public ActionResult ListContacts(int page, int rp, string sortname, string sortorder, string query, string qtype)
   2: {
   3: }

Flexigrid chiamerà la nostra action indicando:

Parametro Descrizione
page Pagina corrente
rp Record per pagina
sortname Colonna usata per l'ordinamento
sortorder Ordinamento ASC o DESC
query Testo da ricercare
qtype Colonna su cui eseguire la ricerca

A questo punto abbiamo tutti i parametri necessari per implementare la nostra logica di interrogazione dati. Per inviare i dati al client creiamo due classi di supporto che useremo come DTO per popolare la nostra Flexigrid.

Definiamo prima di tutto il DTO per le righe

   1: namespace FlexiGridTutorial.Models
   2: {
   3:     public class FlexiGridDataRow
   4:     {
   5:         public string id;
   6:         public string[] cell;
   7:     }
   8: }

ed uno per la visualizzazione corrente

   1: using System.Collections.Generic;
   2:  
   3: namespace FlexiGridTutorial.Models
   4: {
   5:     public class FlexiGridData
   6:     {
   7:         public int page;
   8:         public long total;
   9:         public IList<FlexiGridDataRow> rows;
  10:     }
  11: }

Anche in questo caso la scelta dei nomi dei datamember rispecchia quanto richiesto da Flexigrid in termini di notazione Json.
Il vantaggio di questo approccio è la semplicità del codice necessario a generare la nostra risposta Json:

   1: public ActionResult ListContacts(int page, int rp, string sortname, string sortorder, string query, string qtype)
   2: {
   3:     var data = new FlexiGridData();
   4:     data.rows = new List<FlexiGridDataRow>();
   5:     ...
   6:     return Json(data);
   7: }

Finita l'infrastruttura  implementiamo la logica di interrogazione utilizzando Linq a partire dalla nostra base dati.

   1: // source data
   2: IQueryable<ContactModel> db = SampleData.LotOfContacts;

Per prima cosa eseguiamo l'eventuale ricerca impostata dall'utente

   1: // filter
   2: if (!String.IsNullOrEmpty(query))
   3: {
   4:     switch (qtype)
   5:     {
   6:         case "Name": db = from l in db where l.Name.ToLowerInvariant().Contains(query) select l; break;
   7:         case "Address": db = from l in db where l.Address.ToLowerInvariant().Contains(query) select l; break;
   8:         case "Phone": db = from l in db where l.Phone.ToLowerInvariant().Contains(query) select l; break;
   9:     }
  10: }

memorizziamo quindi le informazioni per la paginazione (pagina corrente e numero totale di record)

   1: // pagination data
   2: data.page = page;
   3: data.total = db.Count();

ed applichiamo il criterio di ordinamento:

   1: // order
   2: bool desc = String.Compare("desc", sortorder, true) == 0;
   3:  
   4: switch (sortname)
   5: {
   6:     case "Name": db = desc ? db.OrderByDescending(x => x.Name) : db.OrderBy(x => x.Name); break;
   7:     case "Address": db = desc ? db.OrderByDescending(x => x.Address) : db.OrderBy(x => x.Address); break;
   8:     case "Phone": db = desc ? db.OrderByDescending(x => x.Phone) : db.OrderBy(x => x.Phone); break;
   9: }

Ora che la nostra query è pronta applichiamo la paginazione per limitare il numero di record da inviare al client:

   1: // pagination
   2: db = db.Skip((page - 1) * rp).Take(rp);

Non resta che creare i DTO da inviare al client

   1: // result...
   2: foreach (var contact in db)
   3: {
   4:     var row = new FlexiGridDataRow
   5:                   {
   6:                       id = contact.ID,
   7:                       cell = new[]
   8:                                  {
   9:                                      contact.Name,
  10:                                      contact.Address,
  11:                                      contact.Phone
  12:                                  }
  13:                   };
  14:  
  15:     data.rows.Add(row);
  16: }

ed inviare la risposta

   1: // response
   2: return Json(data);

Ed ecco la nostra griglia pronta all'uso...

image

Download code: FlexiGridTutorial03.zip (183kb)

jQuery Flexigrid Fluent Interface

Dopo aver introdotto il plugin Flexigrid per jQuery in un progetto Asp.Net MVC per convertire una tabella html statica in una griglia interattiva creiamo una interfaccia fluente in c# per la creazione di una tabella partendo da zero.

Come indicato nella documentazione (esempio 3) è possibile definire nel dettaglio la struttura della griglia e le features abilitate (pulsanti, ordinamento, ricerca, paginazione)

   1: $("#flex1").flexigrid
   2:             (
   3:             {
   4:             url: 'post2.php',
   5:             dataType: 'json',
   6:             colModel : [
   7:                 {display: 'ISO', name : 'iso', width : 40, sortable : true, align: 'center'},
   8:                 {display: 'Name', name : 'name', width : 180, sortable : true, align: 'left'},
   9:                 {display: 'Printable Name', name : 'printable_name', width : 120, sortable : true, align: 'left'},
  10:                 {display: 'ISO3', name : 'iso3', width : 130, sortable : true, align: 'left', hide: true},
  11:                 {display: 'Number Code', name : 'numcode', width : 80, sortable : true, align: 'right'}
  12:                 ],
  13:             buttons : [
  14:                 {name: 'Add', bclass: 'add', onpress : test},
  15:                 {name: 'Delete', bclass: 'delete', onpress : test},
  16:                 {separator: true}
  17:                 ],
  18:             searchitems : [
  19:                 {display: 'ISO', name : 'iso'},
  20:                 {display: 'Name', name : 'name', isdefault: true}
  21:                 ],
  22:             sortname: "iso",
  23:             sortorder: "asc",
  24:             usepager: true,
  25:             title: 'Countries',
  26:             useRp: true,
  27:             rp: 15,
  28:             showTableToggleBtn: true,
  29:             width: 700,
  30:             height: 200
  31:             }
  32:             );   

Sfruttando il serializzatore JSON di .Net è facile creare una classe builder che ci permetta di creare una griglia da zero usando c#, intellisense e tutti i vantaggi che ne derivano.

Inseriamo quindi nella nostra cartella models (per convenienza) una nuova classe che mima la struttura ammessa dal costrutto javascript. Da notare l'esatta corrispondenza dei nomi, necessaria  per avere una rappresentazione fedele in JSON della nostra griglia.

   1: using System.Collections.Generic;
   2: using System.Web.Script.Serialization;
   3:  
   4: namespace FlexiGridTutorial.Models
   5: {
   6:     public class FlexiGridBuilder
   7:     {
   8:         public enum Align
   9:         {
  10:             left,
  11:             center,
  12:             right
  13:         } ;
  14:  
  15:         public class Column
  16:         {
  17:             public readonly string display;
  18:             public readonly string name;
  19:             public readonly string width;
  20:             public readonly bool sortable;
  21:             public readonly Align align;
  22:             public readonly bool hide;
  23:  
  24:             public Column(string display, string name, string width, bool sortable, Align align, bool hide)
  25:             {
  26:                 this.display = display;
  27:                 this.name = name;
  28:                 this.width = width;
  29:                 this.sortable = sortable;
  30:                 this.align = align;
  31:                 this.hide = hide;
  32:             }
  33:         } ;
  34:  
  35:         public class Button
  36:         {
  37:             
  38:         }
  39:  
  40:         public class SearchItem
  41:         {
  42:             public string display;
  43:             public string name;
  44:             public bool isdefault;
  45:  
  46:             public SearchItem(string display, string name, bool isdefault)
  47:             {
  48:                 this.display = display;
  49:                 this.name = name;
  50:                 this.isdefault = isdefault;
  51:             }
  52:         }
  53:         
  54:         public string url;
  55:         public string dataType;
  56:         public IList<Column> colModel;
  57:         public IList<Button> buttons;
  58:         public IList<SearchItem> searchitems;
  59:         public string sortname;
  60:         public string sortorder;
  61:         public bool usepager;
  62:         public string title;
  63:         public bool useRp;
  64:         public short rp;
  65:         public bool showTableToggleBtn;
  66:         public short width;
  67:         public short height;
  68:  
  69:         public FlexiGridBuilder(string param_title)
  70:         {
  71:             this.title = param_title;
  72:             this.url = null;
  73:             this.dataType = "json";
  74:             
  75:             this.usepager = true;
  76:             this.useRp = true;
  77:             this.rp = 15;
  78:             this.showTableToggleBtn = true;
  79:             this.width = 500;
  80:             this.height= 200;
  81:             this.sortorder = "asc";
  82:             
  83:             this.colModel = new List<Column>();
  84:             this.buttons = new List<Button>();
  85:             this.searchitems = new List<SearchItem>();
  86:         }
  87:  
  88:         public FlexiGridBuilder SetDataUrl(string u)
  89:         {
  90:             this.url = u;
  91:             return this;
  92:         }
  93:         
  94:         public FlexiGridBuilder SetWidth(short w)
  95:         {
  96:             this.width = w;
  97:             return this;
  98:         }
  99:  
 100:         public FlexiGridBuilder SetHeight(short h)
 101:         {
 102:             this.height= h;
 103:             return this;
 104:         }
 105:         
 106:         public FlexiGridBuilder ShowToggleButton(bool bSet)
 107:         {
 108:             this.showTableToggleBtn = bSet;
 109:             return this;
 110:         }
 111:  
 112:         public FlexiGridBuilder UsePager(bool bSet)
 113:         {
 114:             this.usepager = bSet;
 115:             return this;
 116:         }
 117:         
 118:         public FlexiGridBuilder SetDefaultSort(string field, string order)
 119:         {
 120:             this.sortname = field;
 121:             this.sortorder = order;
 122:             return this;
 123:         }
 124:  
 125:         public FlexiGridBuilder AddColumn(string col_display, string col_name, short col_width, bool col_sortable, Align col_align, bool col_hide)
 126:         {
 127:             return AddColumn(col_display, col_name, col_width.ToString(), col_sortable, col_align, col_hide);
 128:         }
 129:  
 130:         public FlexiGridBuilder AddColumn(string col_display, string col_name, string col_width, bool col_sortable, Align col_align, bool col_hide)
 131:         {
 132:             colModel.Add(new Column(col_display, col_name, col_width, col_sortable, col_align, col_hide));
 133:             if (this.sortname == null)
 134:                 this.sortname = col_name;
 135:  
 136:             return this;
 137:         }
 138:  
 139:         public FlexiGridBuilder AddColumn(string col_display, string col_name, string col_width, bool col_sortable, Align col_align)
 140:         {
 141:             return AddColumn(col_display, col_name, col_width, col_sortable, col_align, false);
 142:         }
 143:  
 144:         public FlexiGridBuilder AddColumn(string col_display, string col_name, short col_width, bool col_sortable, Align col_align)
 145:         {
 146:             return AddColumn(col_display, col_name, col_width.ToString(), col_sortable, col_align, false);
 147:         }
 148:         
 149:         public FlexiGridBuilder AddSearchItem(string si_display, string si_name, bool si_isdefault)
 150:         {
 151:             searchitems.Add(new SearchItem(si_display, si_name, si_isdefault));
 152:             return this;
 153:         }
 154:  
 155:         public string ToJson()
 156:         {
 157:             var serializer = new JavaScriptSerializer();
 158:             return serializer.Serialize(this);
 159:         }
 160:  
 161:         public string Create(string id)
 162:         {
 163:             return string.Format("$(\"#{0}\").flexigrid({1});", id, ToJson());
 164:         }
 165:     }
 166: }
 167:  

Ora grazie agli extension methods andiamo a creare due helpers per la generazione della griglia estendendo la classe HtmlHelper

   1: using System.Web.Mvc;
   2:  
   3: namespace FlexiGridTutorial.Models
   4: {
   5:     public static class FlexiGridHtmlHelper
   6:     {
   7:         public static string FlexiGridPlaceholder(this HtmlHelper htmlHelper, string id)
   8:         {
   9:             return string.Format("<table id=\"{0}\" style=\"display: none\"></table>", id);
  10:         }
  11:  
  12:         public static FlexiGridBuilder NewFlexiGrid(this HtmlHelper htmlHelper, string title)
  13:         {
  14:             return new FlexiGridBuilder(title);
  15:         }
  16:     }
  17: }

Realizzate le classi di supporto è velocissimo creare una griglia enhanced direttamente nella nostra view. Prima di tutto creiamo la solita Action chiamata Tutorial3 e la relativa view.

Definiamo la nostra tabella nel codice html usando il nuovo helper FlexiGridPlaceholder

   1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
   2:     <%=Html.FlexiGridPlaceholder("tablefx") %>
   3: </asp:Content>

Passiamo poi alla definizione vera e propria della struttura della tabella tramite la nostra interfaccia fluente

   1: Html.NewFlexiGrid("Contacts")
   2:     .AddColumn("Name", "Name", 200, true, FlexiGridBuilder.Align.left)
   3:     .AddColumn("Address", "Address", 180, true, FlexiGridBuilder.Align.left)
   4:     .AddColumn("Phone", "Phone", 120, true, FlexiGridBuilder.Align.left)
   5:     .AddSearchItem("Name", "Name", true)
   6:     .AddSearchItem("Address", "Address", false)
   7:     .AddSearchItem("Phone", "Phone", false)
   8:     .Create("tablefx")

Wrappiamo la chiamata al nostro helper nell'evento "document ready" di jQuery in modo da costruire la griglia al momento opportuno.

Ecco il sorgente completo di Tutorial3.aspx

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true"
   2:     CodeBehind="Tutorial3.aspx.cs" Inherits="FlexiGridTutorial.Views.Home.Tutorial3" %>
   3:  
   4: <%@ Import Namespace="FlexiGridTutorial.Models" %>
   5: <asp:Content ID="Content1" ContentPlaceHolderID="headerPlaceHolder" runat="server">
   6:     <script type="text/javascript">
   7:         $(function() {
   8:         <%= Html.NewFlexiGrid("Contacts")
   9:             .AddColumn("Name", "Name", 200, true, FlexiGridBuilder.Align.left)
  10:             .AddColumn("Address", "Address", 180, true, FlexiGridBuilder.Align.left)
  11:             .AddColumn("Phone", "Phone", 120, true, FlexiGridBuilder.Align.left)
  12:             .AddSearchItem("Name", "Name", true)
  13:             .AddSearchItem("Address", "Address", false)
  14:             .AddSearchItem("Phone", "Phone", false)
  15:             .Create("tablefx")%>
  16:         });
  17:     </script>
  18:  
  19: </asp:Content>
  20: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
  21:     <%=Html.FlexiGridPlaceholder("tablefx") %>
  22: </asp:Content>

Ed ecco il risultato:

image

Per quanto incompleta (non copre tutte opzioni) la nostra interfaccia fluente ci permette agevolmente di creare agevolmente una griglia completa nascondendo completamente l'implementazione JSON e mettendo a disposizione l'intellisense.

Nel prossimo post implementeremo la comunicazione asincrona dei dati con il server.

Buon we.

Download: FlexiGridTutorial02.zip (218KB)

Utilizzo di Flexigrid in ASP.NET MVC

Flexigrid è un plugin di jQuery che permette di aggiungere notevoli funzionalità ed un look & feel avanzato alle nostre tabelle HTML.

Indipendentemente dalle funzionalità lato client la bellezza del plugin sta (secondo me) nella semplicità nel popolare la griglia tramite chiamate JSON asincrone.

Supponiamo di voler creare una semplice rubrica, la prima cosa da fare è definire il nostro modello di dati.

Ecco la definizione della classe ContactModel

   1: namespace FlexiGridTutorial.Models
   2: {
   3:     public class ContactModel
   4:     {
   5:         public string Name { get; set; }
   6:         public string Address { get; set; }
   7:         public string Phone { get; set; }
   8:         
   9:         public ContactModel()
  10:         {
  11:         }
  12:  
  13:         public ContactModel(string name, string address)
  14:         {
  15:             Name = name;
  16:             Address = address;
  17:         }
  18:  
  19:         public ContactModel(string name, string address, string phone)
  20:         {
  21:             Name = name;
  22:             Address = address;
  23:             Phone = phone;
  24:         }
  25:     }
  26: }

 

Aggiungiamo quindi la definizione del modello della nostra rubrica: ContactsListModel

   1: using System.Collections.Generic;
   2:  
   3: namespace FlexiGridTutorial.Models
   4: {
   5:     public class ContactsListModel
   6:     {
   7:         public IEnumerable<ContactModel> Contacts { get; private set; }
   8:         public string ID { get; private set; }
   9:         public ContactsListModel(string listID, IEnumerable<ContactModel> contacts)
  10:         {
  11:             ID = listID;
  12:             Contacts = contacts;
  13:         }
  14:     }
  15: }

Per semplificare il progetto creiamo una classe helper che fornirà i dati necessari al popolamento della rubrica.

   1: using System.Collections.Generic;
   2: using FlexiGridTutorial.Models;
   3:  
   4: namespace FlexiGridTutorial.Helpers
   5: {
   6:     public static class SampleData
   7:     {
   8:         public static IList<ContactModel>    Contacts
   9:         {
  10:             get
  11:             {
  12:                 var result = new List<ContactModel>();
  13:  
  14:                 result.Add(new ContactModel("Mario Rossi", "Via Garibaldi, 9 - Milano", "555-0000"));
  15:                 result.Add(new ContactModel("Giovanni Verdi", "Via Fantasia, 10 - Roma", "555-1111"));
  16:                 result.Add(new ContactModel("Roberto Bianchi", "Piazza dei Partigiani, 20 - Roma", "555-2222"));
  17:                 return result;
  18:             }
  19:         }
  20:     }
  21: }

Aggiungiamo ora il primo step del tutorial modificando la classe HomeController inserendo la action per il primo step

   1: public ActionResult Tutorial1()
   2: {
   3:     return View(SampleData.Contacts);
   4: }

Passiamo alla creazione della relativa view tramite la scorciatoia introdotta in Beta 1, posizionare il caret nello scope di Tutorial1 e premere Ctrl+M, Ctrl+V (Marry View)

image

In questo modo la view creata è tipizzata per gestire l'enumerazione di ContactModel.
Per semplicità creiamo una partial view che renderizza l'elenco dei contatti in una tabella html.

Aggiungiamo un nuovo usercontrol ContactsList.ascx nella cartella Views\Home

Sorgente di ContactsList.ascx

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ContactsList.ascx.cs" Inherits="FlexiGridTutorial.Views.Home.ContactsList" %>
   2:     <table id="<%=ViewData.Model.ID%>">
   3:         <thead>
   4:             <tr>
   5:                 <th class="name">
   6:                     Name
   7:                 </th>
   8:                 <th class="address">
   9:                     Address
  10:                 </th>
  11:                 <th class="phone">
  12:                     Phone
  13:                 </th>
  14:             </tr>
  15:         </thead>
  16:         <tbody>
  17:         
  18:         <%foreach (var contact in ViewData.Model.Contacts)
  19:           {%>
  20:             <tr>
  21:                 <td class="name">
  22:                     <%=contact.Name %>
  23:                 </td>
  24:                 <td class="address">
  25:                     <%=contact.Address%>
  26:                 </td>
  27:                 <td class="phone">
  28:                     <%=contact.Phone%>
  29:                 </td>
  30:             </tr>
  31:             <%} %>
  32:         </tbody>
  33:     </table>

Sorgente di ContactsList.ascx.cs

   1: namespace FlexiGridTutorial.Views.Home
   2: {
   3:     public partial class ContactsList : System.Web.Mvc.ViewUserControl<FlexiGridTutorial.Models.ContactsListModel>
   4:     {
   5:     }
   6: }

In questo caso il nostro controllo è tipizzato per la classe ContactsListModel in modo da poter associare un id alla lista di contatti.

Aggiungiamo gli stili necessari nel site.css per definire la larghezza delle colonne (necessario per una corretta visualizzazione della griglia).

   1: th.name
   2: {
   3:     width:200px;    
   4: }
   5:  
   6: th.address
   7: {
   8:     width:300px;    
   9: }
  10:  
  11: th.phone
  12: {
  13:     width:100px;    
  14: }
  15:  
  16: td.name
  17: {
  18:     width:200px;    
  19: }
  20:  
  21: td.address
  22: {
  23:     width:300px;    
  24: }
  25:  
  26: td.phone
  27: {
  28:     width:100px;    
  29: }

Modifichiamo ora la Tutorial1.aspx per renderizzare la rubrica

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true"
   2:     CodeBehind="Tutorial1.aspx.cs" Inherits="FlexiGridTutorial.Views.Home.Tutorial1" %>
   3: <%@ Import Namespace="FlexiGridTutorial.Models"%>
   4:  
   5: <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
   6:     <%Html.RenderPartial("ContactsList", new ContactsListModel("table1", ViewData.Model));%>
   7: </asp:Content>

Il risultato è il seguente:

image

Modifichiamo la MasterPage per aggiungere il supporto jQuery e Flexigrid. Contestualmente aggiungiamo un nuovo ContentPlaceHolder nel blocco head per poter inserire del javascript dalle pagine.

   1: <head runat="server">
   2:     <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
   3:     <title><%= Html.Encode(ViewData["Title"]) %></title>
   4:     <link href="../../Content/Site.css" rel="stylesheet" type="text/css" />
   5:     <script src="../../Scripts/jquery-1.2.6.js" type="text/javascript"></script>
   6:     <script src="../../Scripts/flexigrid.js" type="text/javascript"></script>
   7:     <link href="../../Content/flexigrid/flexigrid.css" rel="stylesheet" type="text/css" />
   8:     <asp:ContentPlaceHolder ID="headerPlaceHolder" runat="server" />
   9: </head>

Aggiungiamo ora una seconda action a HomeController per generare una pagina con la trasformazione flexigrid

   1: public ActionResult Tutorial2()
   2: {
   3:     return View(SampleData.Contacts);
   4: }

Creiamo la view anche  per questa action e nel codice renderizziamo due volte la rubrica con due id differenti. La seconda griglia verrà trasformata in flexigrid.

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Tutorial2.aspx.cs" Inherits="FlexiGridTutorial.Views.Home.Tutorial2" %>
   2: <%@ Import Namespace="FlexiGridTutorial.Models"%>
   3: <asp:Content ID="Content2" ContentPlaceHolderID="headerPlaceHolder" runat="server">
   4: <script language="javascript">
   5:     $(function() {
   6:         $("#table2").flexigrid();
   7:     });
   8:     
   9: </script>
  10: </asp:Content>
  11:  
  12: <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
  13:     <%Html.RenderPartial("ContactsList", new ContactsListModel("table1", ViewData.Model));%>
  14: <br />
  15:     <%Html.RenderPartial("ContactsList", new ContactsListModel("table2", ViewData.Model));%>
  16: </asp:Content>

Il risultato è il seguente raggiunto praticamente con una sola riga di codice $("#table2").flexigrid();

image

Ora è possibile nascondere, spostare e ridimensionare le colonne interattivamente.

Nel prossimo post vedremo la creazione dinamica della griglia in c# con interfaccia fluente e nell'ultimo il popolamento asincrono dei dati.

Download: FlexiGridTutorial.zip (222kb)

Fun = MvC$

Un personale omaggio all'accoppiata Asp.Net MVC + jQuery.

MvcFun

Estrazione delle embedded resources su file system

Per la realizzazione di un progetto ho incluso come embedded resources diversi file (report, immagini, xml, etc...) in modo da semplificare il deploy. Per lasciare comunque la possibilità di customizzare l'installazione ho creato questa classe helper che permette di fare il dump della risorsa embedded su file system. Il codice che accede alla risorsa chiama sempre la EnsureFileSystemCopy poi accede tramite file system. In questo modo chi installa / assiste ha libertà di azione.

In caso di "disastro" basta svuotare la cartella di dump delle risorse per tornare alla situazione originale.

1. using System;
2. using System.IO;
3. using System.Reflection;
4.
5. namespace Prxm.Pass.Framework.Helpers
6. {
7.     public static class EmbeddedResourcesHelper
8.     {
9.         public static void EnsureFileSystemCopy(Assembly assembly, string resourceName, string filename)
10.         {
11.             if (File.Exists(filename))
12.                 return;
13.
14.             string folder = Path.GetDirectoryName(filename);
15.             if (!Directory.Exists(folder))
16.                 Directory.CreateDirectory(folder);
17.
18.             using (var stream = assembly.GetManifestResourceStream(resourceName))
19.             {
20.                 if (null == stream)
21.                     throw new Exception(string.Format("Cannot find resource {0} in assembly {1}", resourceName,
22.                                                      assembly.FullName));
23.
24.                 using (var reader = new BinaryReader(stream))
25.                 {
26.                     using (var writer = new BinaryWriter(File.Create(filename)))
27.                     {
28.                         byte[] buffer;
29.                         while((buffer = reader.ReadBytes(1024)).Length > 0)
30.                             writer.Write(buffer);
31.
32.                         writer.Flush();
33.                     }
34.                 }
35.             }
36.         }
37.     }
38. }
39.

More Posts Next page »