martedì 26 luglio 2016
Prendendo spunto da ultime attività lavorative, vorrei evidenziare (molti lo sapranno) come il numero di chiamate http parallele eseguite da un browser (browser != istanza di browser) verso lo stesso server (stesso dominio) sia in numero finito e ridotto, e che la cosa possa peggiorare la performance del vostro applicativo web [http://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser].
Partiamo cronologicamente al contrario:
1 - Angular2
Angular2 è la nuova versione del framework di google, scritta in typescript, ed "enormemente" modulare; basti pensare che:
- ogni "view" può essere composta dalla definizione di “component" scritto in typescript (=> js), un css e un html ad esso collegato. Questi possono essere embeddati nel file typescript, ma personalmente preferisco tenerli separati.
- angular promuove la modularizzazione, anche per questioni di performance dovute a shadow dom, change detecion stragegy.. (http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html)
- l’accoppiata di dependency injection di ng2 e l’import di moduli esterni con ES2015 è una __FIGATA PAZZESCA__. Ed ora si scrive molto più C#-like (interfacce, classi, ereditarietà ... il tutto per il single responsibility principle).
Bene. Ottimo. Fantastico:
Il problema è semplice (e di facile risoluzione). Ci sono troppi file da caricare (650+) e… ogni browser può effettuare un massimo di connessioni verso lo stesso dominio (chrome per esempio un massimo di 6).
Tutte le chiamate http vengono dispacciate da questo numero limitato di connessioni, il che rende lungo (9+ secondi) il caricamento dell’applicativo (insieme ovviamente all’elevato numero di handshake).
Soluzione semplice: BUILD (fatelo ‘sempre’ e, soprattutto, pensateci in tempo)
Ad oggi vi sono numerosi strumenti per effettuare la build di un’applicativo web javascript (grunt, gulp, broccoli, webpack…), personalmente mi sto affezionando a webpack, con il seguente risultato:
Perfetto. Numero di richieste ridotte al minimo, quantità di dati ridotta, tempi di caricamento abbassati.
2 – Data recomposition
Un altro applicativo web a cui ho/sto lavorando, molto dinamico, permette ad ogni utente di comporre la sua UI in base alle proprie preferenze.
L’utente può decidere
- quanti grafici aggiungere alla pagina
- per ogni grafico, quante serie mostrare.
A complicare le cose, i grafici vengono aggiornati in realtime.
Per rendere il tutto il più modulare e indipendente possibile, abbiamo architetturato l’applicativo in modo da recuperare ogni serie con una chiamata http separata. Massima flessibilità ma… alcuni dati:
- In media (a spanne) un utente apre 2-4 grafici per pagina
- ogni grafico ha 6 -12 serie scelte dall’utente
- un utente può aprire apre anche più tab con la stessa applicazione (anche 2 o 3 per pc) su diversi monitor (ok, il contesto è un pò particolare)
Risultato:
- una connessione utilizzata da signalR (SSE) per tab aperto
- Le chiamate http, anche se su server è stata implementata una cache abbastanza intelligente, possono richiedere tempo per essere evase. Il tempo di risposta occupa la connessione, non permettendo ad altre serie di essere recuperate velocemente da parte del browser.
- E’ anche capitato (numero eccessivo di tab aperti) che un tab smettesse di recuperare dati da server finché l’utente non ne chiudesse un’altro.
Soluzione: dipende. Come sempre non c’è una verità. Ci stiamo spostando verso la definizione di “view model” che incapsulino più serie, in modo tale da raggruppare e quindi recuperare più serie in un unica chiamata http. Parrebbe inoltre che i websocket non utilizzi connessioni http (https://samsaffron.com/archive/2015/12/29/websockets-caution-required). Altre idee sono ben accette.
I sistemi software sono complicati, e la conoscenza è oro.
mercoledì 24 dicembre 2014
Senza entrare nella discussione di quale lint-tool utilizzare per il verificare la “bontà” del codice js scritto, e poichè JsHint è incluso nel plugin di VS WebEssentials, mi sono ritrovato a faccia a faccia con il seguente messaggio:
Tale messaggio indica chiaramente che la variabile “_” (lodash nel mio caso) è usata senza che essa venga precedentemente dichiarata. Da qui il mio pensiero è stato:
- Vero è che, di buona norma, occorrerebbe evitare di utilizzare variabili globali ed iniettarle tutte tramite injection, però è anche vero che ce ne sono un insieme ($, _, angular, toastr…) che sinceramente si fa “fatica” ogni volta definirle come parametro da iniettare (si, sono molto lazy).
- Perchè non esiste lo stesso problema con angular (anch’essa variabile globale)?
- E’ possibile in qualche modo nascondere questo messaggio di errore selettivamente per altre librerie?
La risposta è ovviamente positiva e può essere risolta nei seguenti modi:
- All’inizio di ogni file .js che utilizza “_”, è possibile aggiungere la direttiva /*global _: false*/, ovvero indica di non considerare “_” in quanto è definito nello scope globale
- Da visual studio, sotto il menù “WEB ESSENTIALS-Edit global JSHint settings” è possibile modificare le impostazioni utilizzate dall’utente corrente:
Il file “.jshintrc” è un file json che, tra le altre cose, definisce quali variabili sono considerate globali:
Ecco quindi il motivo per il quale “angular” non viene mostrato di default nei messaggi di JsHint. La sezione può essere quindi arricchita in modo da aggiungere altre librerie:
- Nel punto precedente tutto funziona perfettamente eccetto che tale setting è a livello di utente e magari “Emix” è una variabile che si vorrebbe considerare globale solo in uno specifico progetto. Per ovviare al problema è possibile creare una copia del file “.jshintrc”, aggiungerla alla solution in modo da utilizzare un set di impostazioni specifiche per il singolo progetto/solution:
In questo modo inoltre, poichè il file è all’interno della solution, le impostazioni possono essere salvate su source control e quindi condivise tra tutti gli sviluppatori del team.
venerdì 28 novembre 2014
Quando iniziai (un po’ di tempo fa a dire il vero) a voler provare/utilizzare AngularJS su alcuni progetti web, mi sono scontrato contro alcuni problemi tra i quali:
- Non voler cambiare “troppo” le tecnologie utilizzate e conosciute dal team (asp.net, webapi, sql server… e quindi niente node, mongo, php, gruntjs…),
- Utilizzare Visual Studio e avere una struttura chiara della solution,
- Creare una SPA, e non solo il motore di binding di ng per le singole view asp.net.
Poichè ai tempi (mesi e mesi fa) non trovai un esempio/template che mi soddisfacesse appieno, in quanto:
- o veniva usato nodejs e niente vs
- o l’organizzazione dell’applicazione ng era a cura del developer
- o gli script ng erano separati dalle view (e per me è una cosa fastidiosa dopo un paio di esperienze tra cui anche la struttura di default di durandaljs)
Ho creato un progetto su github, https://github.com/sierrodc/ASP.NET-MVC-AngularJs-Seed, dove:
- viene utilizzato nuget solo per le libraries .net; ho scelto di non usarlo per i package js perchè altrimenti non avrei potuto organizzarli come segue
ovvero una sorta di struttura a strati con le librerie senza dipendenze in Ring0, e con Ring(N) dipendente da Ring(N-1)
- è configurato angularjs e una semplice SPA con alcune features con la seguente struttura:
dove: - emixApp.js definisce l’applicazione angularjs, impostando tutte le dipendenze e le regole di routing (forse lo estrarrò da qui),
- la cartella directives contiene tutte le directive custom,
- la cartella services contiene i servizi angular, in questo caso solo un proxy alle chiamate verso webapi
- la cartella pages che contiene le pagine che costituiscono la SPA, ogni pagina definita tramite coppia #pagina.html, pagina.js#
- tutte le librerie che ritengo utili (anche per una demo veloce) sono già incluse, configurate e utilizzabili;
Per una lista completa di librerie incluse faccio riferimento al file readme.md presente su repository (cercherò ti mantenerle il più possibile aggiornate).
E dopo questa condivisione, ogni domanda, suggerimento o critica è ben accetta.
giovedì 13 novembre 2014
La giornata di oggi è stata particolarmente ricca di news per noi sviluppatori:
- E’ disponibile l’update 4 di Visual Studio 2013 (non ancora segnalato in VS2013 ma scaricabile qui Download Visual Studio 2013 Update 4 (2013.4) RTM)
La lista delle novità e delle correzioni è disponibile alla seguente pagina: http://www.visualstudio.com/news/vs2013-update4-rtm-vs - E’ stata rilasciata una nuova edizione di VS ovvero la Visual Studio 2013 Community, che include gli ultimi updates, e che supporta sviluppo web, cloud, mobile, Kinect, Unity… permettendo inoltre di installare estensioni. Un cambio di rotta rispetto all’ultima tornata di VS express frammentato in più versioni e con estensioni bloccate.
Interessante notare la seguente frase presente su sito microsoft:
”For all other usage scenarios: In non-enterprise organizations, up to 5 users can use Visual Studio Community. In enterprise organizations (meaning those with >250 PCs or > $1MM in annual revenue), no use is permitted beyond the open source, academic research, and classroom learning environment scenarios described above”
Questo significa che in piccole aziende è possibile utilizzare VS Community senza oneri anche per fini commerciali (correggetemi se sbaglio). - Disponibile Visual Studio 2015 preview (http://www.visualstudio.com/news/vs2015-preview-vs#Net) con EF7, Rosylin, .net4.6, asp.net 5 e chi più ne ha più ne metta
- L’apertura sempre più consistente da parte di Microsoft verso il mondo opensource e sistemi “non-windows”:
- sono stati rilasciati parti importanti di .net framework su github (http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx)
- si è (forse) presa consapevolezza di ciò che molti avrebbero voluto da .NET, ovvero la multipiattaforma (forse dovuto anche al successo di Xamarin [che tra parentesi, tra le altre cose, prevede di aggiungere il supporto a VS anche nella versione free]). ScottGu ha infatti annunciato la volontà di rilasciare una versione ufficiale e supportata di .net core per linux e osx (http://weblogs.asp.net/scottgu/announcing-open-source-of-net-core-framework-net-core-distribution-for-linux-osx-and-free-visual-studio-community-edition)
Che dire… senza parole.
domenica 12 ottobre 2014
Lavorando su un progetto ~MEAN (MongoDB + ExpressJS + AngularJS + nodeJS) mi sono chiesto: “perchè non creare un modulo di registrazione custom dei controller simile a quella delle webapi di asp.net”? da qui è nato il seguente modulo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
var path = require('path');
var fs = require('fs');
var config = require('./config');
var _ = require('lodash');
var getControllersInServerFolder = function (serverFolder) {
if (_.isEmpty(serverFolder)) {
serverFolder = config.settings.controllersFolder;
}
var controllers = [];
var fileSystemObjects = fs.readdirSync(serverFolder); //read folder content
if (_.isArray(fileSystemObjects)) {
_.forEach(fileSystemObjects, function (fso) {
var fsoFullPath = path.join(serverFolder, fso);
var fsoStat = fs.statSync(fsoFullPath);
if (fsoStat.isFile() && fso.endsWith('controller.js')) {
controllers.push(require(path.relative(__dirname, fsoFullPath)));
} else if (fsoStat.isDirectory()) {
_.union(controllers, getControllersInServerFolder(fsoFullPath));
}
});
}
return controllers;
};
exports.getControllers = getControllersInServerFolder;
|
Quindi nello startup del server nodejs:
1
2
3
|
_.forEach(require('controllers-manager')..getControllers(), function(controller) {
server.use(controller.path, controller.router); // register route into expressjs
});
|
Ora i controller sono tutti quei files definiti nella cartella config.settings.controllersFolder (o sottocartelle) che terminano con ‘controller.js’ e hanno tutti la seguente struttura:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | var express = require('express');
var router = express.Router();
//apply before any actions (check authorizations...)
router.use(function(req, res, next) {
next(); //go to next
});
router.get('/message', function(req, res, next) {
res.send({'Hello world'});
});
exports.path = '/api/sample';
exports.router = router;
|
Facile, semplice e veloce.
mercoledì 20 novembre 2013
Se avete un progetto ASP.NET MVC4 in VS2012 e, ingenuamente, credete che basti aggiornare i pacchetti nuget per passare alla nuova versione di MVC5 beh, siete leggermente fuori strada.
In primis occorre modificare opportunamente i web.config e il file csproj come spiegato in questo articolo:
http://www.asp.net/mvc/tutorials/mvc-5/how-to-upgrade-an-aspnet-mvc-4-and-web-api-project-to-aspnet-mvc-5-and-web-api-2
Dopo tali modifiche tuttavia vi renderete conto di non aver più intellisense nelle pagine razor.
Per riaverlo è necessario installare ASP.NET and Web Tools 2013.1 for Visual Studio 2012:
http://blogs.msdn.com/b/webdev/archive/2013/11/18/announcing-release-of-asp-net-and-web-tools-2013-1-for-visual-studio-2012.aspx
Oppure rimane valido il passaggio a VS2013 :)
Buon ASP.NET MVC5 a tutti.
venerdì 17 maggio 2013
EF 5 aggiunge una importante opzione di configurazione riguardante la "traduzione" delle lambda con parametri NULL: UseCSharpNullComparisonBehavior.
Se impostata a true, il comportamento è simile al comportamento che noi Dev siamo abituati con il valore null di C#. Ma andiamo con ordine:
Tabella:
Id Name
1 Uno
2 NULL
3 NULL
4 due
5 tre
Codice:
result = db.NullableTables.Where(e => e.Name == null).Count();
Console.WriteLine("Entries where Name=null: {0}", result);
result = db.NullableTables.Where(e => e.Name != null).Count();
Console.WriteLine("Entries where Name!=null: {0}", result);
string name = null;
result = db.NullableTables.Where(e => e.Name == name).Count();
Console.WriteLine("Entries where Name==name & name=null: {0}", result);
Risultato standard:
"Entries where Name=null: 2" //corretto
"Entries where Name!=null: 3" //corretto
"Entries where Name=name & name=null: 0" //wrong?
Il problema dell'ultimo risultato riguarda il metodo utilizzato da EF per convertire la lamba expression "e=>e.Name=name" in TSQL: "Where Name=@p1, @p1=null". In SQL =NULL è sempre false, al contrario del primo esempio dove EF converte la lambda in "where Name IS NULL".
Come possiamo rendere la conversione più C# friendly? con la seguente istruzione (messa nel costruttore del nostro DbContext):
((IObjectContextAdapter)this).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;
In questo caso, rieseguendo il codice precedente, otterremo il seguente risultato:
"Entries where Name=null: 2" //corretto
"Entries where Name!=null: 3" //corretto
"Entries where Name=name & name=null: 2" //corretto?
Da notare come questa opzione sia disabilitata di default(causa compatibilità).