KnockoutJS, JavaScript et JQuery : notes et exemples perso

icon Tags de l'article : , ,

Juin 24, 2016
Hello,

Une petite liste de notes perso, de morceaux de code et de comment faire 2/3 choses, pour moi, mais qui peut vous servir.

Enjoy !

<!-- quelques databind par défaut -->

<input type="text" data-bind="value: searchText" />
<td data-bind="text: 1 === Role() ? 'User' : 'Admin'"></td>
<p data-bind="visible: currentlyWorking">

<!-- A noter : si on est dans un cas de visible "si pas ce booléen", il faut mettre les parenthèses : -->
<div data-bind="visible: !currentlyWorking()">
<p data-bind="visible: !showSearchResults() && firstSearchDone()">

<!-- Plusieurs paramètres -->
<input class="btn" type="button" value="Add" data-bind="attr: { 'data-value': Id }, visible: !model.IsMethod(Id)" /> 

<!-- Appeler le modèle parent de l'objet KO courant : $parent -->
<span data-bind="visible: null !== ItemId && $parent.apiConsumerId() !== ApiConsumerId">

  • Dans l'idéal il vaut mieux éviter d'avoir de la logique côté vue, il vaut mieux faire des computed :
self.showSearchResults = ko.computed(function () {
    return self.firstSearchDone() && self.searchedUsers().length > 0;
});

// Attraper la touche entrée sur un champ texte
$('#txtSearch').on('keyup', function (e) {
    if (e.which !== 13) {
        return;
    }

    $('#btSearchUser').click();
});

// Passer des informations depuis la vue ASP.Net MVC vers le fichier JavaScript 
<script type="text/javascript">
    var urls = {
        'promoteUser': '@Url.Action("Promote", "User")',
    };
    var initialData = @MvcHtmlString.Create(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Users));
    var currentItemId = @Model.Item.Id;
</script>

// Demander confirmation pour une action dans certaines conditions ?
// Créer une méthode JS globale "mustConfirm()" qui sera appelée avant chaque action critique comme ça :
if (confirm('Are you sure that you want to DELETE this user account? This action CANNOT BE UNDONE.') && mustConfirm()) {

}

// Un appel en direct dans le HTML :
<input type="submit" value="Purge" class="btn btn-default" onclick="return mustConfirm(); " />

// Et cette méthode dans la Layout CSHTML :
var mustConfirm = function () {
    @{
        if (UserHelper.GetCurrentEnvironment().AskForConfirmation)
        {
            <text>return confirm('Attention: This is a production environment with sensitive personal and customer data. Are you sure?');</text>
        }
        else
        {
            <text>return true;</text>
        }
    }
};

// Recharger la page courante
location.reload();

// Récupérer une collection d'observables à partir d'un JSON
// Pour ça on utilise la lib JS ko.mapping :
self.Users = ko.mapping.fromJS(initialData);

// Envoyer un fichier en POST à un contrôleur ASP.Net MVC
var data = new FormData();
var files = $('#fileUpload').get(0).files;
if (files.length === 1) {
    data.append("UploadedFile", files[0]);
    data.append("CreatorId", $('#creatorId').val());
    $.ajax({
        type: "POST",
        url: urls.import,
        contentType: false,
        processData: false,
        data: data,
        success: function (result) {
            if (0 === result.ErrorMessages.length) {
                setTimeout(function() { document.location = urls.importsList; }, 1000);
            } else {
                model.currentlyWorking(false);
                model.LoadResult(result, false);
            }
        }
    });
} else {
    alert('Error: you must choose a file');
}

// et côté ASP.Net MVC
public ActionResult Import(int? creatorId, int? userId)
{
    var context = this.CreateContext();

    var model = new ImportVM();

    if (Request.Files.Count != 1)
    {
        model.ErrorMessages.Add("You must upload a file to import profiles");
        return Json(model);
    }

    var uploadedFile = Request.Files[0];

    if (!uploadedFile.FileName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase))
    {
        model.ErrorMessages.Add("Invalid extension: only CSV file can be used for import");
        return Json(model);
    }

    _importBusiness.Import(uploadedFile.InputStream, context);

    return Json(model);
}

// Exemple complet côté JS 
var model;

(function () {
    var Users = function () {
        var self = this;

        self.firstSearchDone = ko.observable(false);
        self.searchText = ko.observable("");
        self.searchedUsers = ko.observableArray();

        self.currentlyWorking = ko.observable(false);

        self.showSearchResults = ko.computed(function () {
            return self.firstSearchDone() && self.searchedUsers().length > 0;
        });

        self.cleanSearch = function () {
            self.searchedUsers.removeAll();
            self.searchText('');
            self.firstSearchDone(false);
        }
    }

    $('#txtSearch').on('keyup', function (e) {
        if (e.which !== 13) {
            return;
        }

        $('#btSearchUser').click();
    });

    $('#btSearchUser').on('click', function () {
        $('#txtSearch').val('');
        model.currentlyWorking(true);

        $.post(urls.searchUserByName, { text: model.searchText() }, function (response) {
            if (!response.ok) {
                alert(response.errorMessage);
                model.cleanSearch();
            } else {
                model.firstSearchDone(true);
                model.searchedUsers(response.searchedUsers);
            }
            model.currentlyWorking(false);
        });
    });

    // we initialize the model
    model = new Users();

    ko.applyBindings(model);
})();

Modifier ses AppSettings et ConnectionStrings via les fichiers de config en .Net

icon Tags de l'article : , , ,

Juin 24, 2016
Hello,

Petit article, ça faisait longtemps :)

Aujourd'hui on va parler .Net, AppSettings et ConnectionStrings.

Lorsqu'on développe, la bonne pratique est d'utiliser les fichiers de configuration pour stocker nos paramètres d'application et nos chaines de connexion. Ce fichier de configuration s'appelle, pour ceux qui ne le savent pas, Web.Config ou App.Config. Exemple :

<appSettings>
     <add key="MyValue" value="42"/>
     <add key="MyIPAddress" value="::1"/>
</appSettings>
<connectionStrings>
     <add name="MyConnectionString" connectionString="Data Source=.\;Initial Catalog=MyDB;Persist Security Info=True;User ID=MyLogin;Password=MyPassword;MultipleActiveResultSets=True" />
</connectionStrings>

Classique vous me direz. Maintenant, savez vous comment faire pour que les valeurs de vos AppSettings et ConnectionStrings changent lorsque vous publiez en mode Release ?

Et oui, il faut modifier le fichier Web.Release.Config (ou App.Release.Config).

Dans ce fichier, vous pouvez ajouter de nouveaux AppSettings ou ConnectionStrings, mais vous pouvez aussi modifier les valeurs par défaut (du Web.config ou App.config)

Voici un exemple qui va :
  • Supprimer l'AppSettings MyValue
  • Modifier la valeur de l'AppSettings MyIPAddress
  • Modifier la valeur de la ConnectionString MyConnectionString

<appSettings>
     <add key="MyValue" xdt:Transform="Remove" xdt:Locator="Match(key)"/>
     <add key="MyIPAddress" value="1.2.3.4" xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
</appSettings>
<connectionStrings>
     <add name="MyConnectionString" connectionString="Server=tcp:myserver.database.windows.net,1433;Database=mydb;User ID=mylogin@mydb;Password=mypassword;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;" xdt:Transform="SetAttributes" xdt:Locator="Match(name)"/>
</connectionStrings>

Simple non ?

En résumé, lors de la publication dans une config sélectionnée (debug, release ou autre) :
  • Supprimer une ligne: xdt:Transform="Remove" xdt:Locator="Match(key)"
  • Modifier la valeur d'une ligne : xdt:Transform="SetAttributes" xdt:Locator="Match(key)"
  • Ajouter une ligne : il suffit de l'ajouter dans le fichier concerné

Une dernière chose, non négligeable : ces modifications / suppressions n'impacteront pas votre application lorsque vous la lancez en local, même dans la configuration sélectionnée ! Pour ça, il faut faire les modifications à la main dans le web.config ou app.config.

Bon dev à tous !

Le célèbre bug du cookie ASP.Net qui disparait

icon Tags de l'article : , ,

Septembre 18, 2015
Tout le monde se fait avoir, et ça vient de m'arriver.

Alors que je développais tranquillement ma solution, je me suis rendu compte que ma connexion à l'application cassait juste après que je me sois connecté.

Après vérification, il s'avérait que mon cookie disparaissait sans raison dès que je faisais la moindre action après ma connexion.

J'ai tout vérifié, mon code de création de cookie était bon :

Response.Cookies["UserSettings"]["IdUser"] = user.Id.ToString();
Response.Cookies["UserSettings"]["Token"] = user.Token;
Response.Cookies["UserSettings"]["Name"] = user.FirstName + " " + user.LastName;

Response.Cookies["UserSettings"].Expires = DateTime.UtcNow.AddDays(WebAppConstants.COOKIE_LIFETIME);

D'où ça vient ? C'est très simple : je devais probablement faire un Response.Cookies à un endroit au lieu d'un Request.Cookies pour lire le contenu de mon cookie.

Et effectivement c'était le cas, à un endroit où j'essayais de lire mon cookie :
if(Response.Cookies["UserSettings"] != null && Response.Cookies["UserSettings"]["Name"] != null) 
{
     //...
}

En effet, en ASP.Net, dès qu'on essaie de lire un cookie avec Response.Cookies, un cookie qui sera automatiquement renvoyé au navigateur est créé.

Or comme on ne spécifie pas la date d'expiration du cookie... il s'agit du 01/01/0001. Et du coup, quand le navigateur recevra ce cookie, il se dira que son cookie existant est obsolète... et le supprimera.

Donc rappelez-vous en : dès qu'un cookie disparait sans raison après avoir été créé, il y a de fortes chances que vous ayez essayé d'y accéder en passant par Response.Cookies au lieu de Request.Cookies !

Un petit outil pour compter le nombre de lignes de code de son projet .Net

icon Tags de l'article : ,

Septembre 17, 2015
Parce que ça fait longtemps, et que c'est toujours marrant ce genre de chiffres :)

Voici un petit morceau de code à mettre dans une application Console et qui va aller compter le nombre de lignes de code C# et JavaScript d'un de vos projets.

Son fonctionnement est simple, il va parcourir récursivement tous les dossiers de la solution, récupérer les fichiers .cs et .js, filtrer les lignes inutiles (vides, commentaires, accolades, ...) et vous renvoyer le total de lignes pour chaque langage.

Sur mon projet, on est à 52000 lignes de C# pour 53000 lignes de JavaScript. Le JS l'emporte de peu !

Voici le code :

// on récupère récursivement tous les fichiers du dossier de la solution
var allfiles = System.IO.Directory.GetFiles(
    @"/path/to/solution",
    "*.*",
    System.IO.SearchOption.AllDirectories);

// on récupère uniquement les fichiers CS
var csFiles = allfiles.Where(e => e.EndsWith(".cs"));

int cptCs = 0;
foreach (var csFile in csFiles)
{
    // pour chaque fichier on vire les tabulations et \r, et on split sur les \n
    var parts = File.ReadAllText(csFile).Replace("\r", "").Replace("\t", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    // on ne garde que les morceaux non vides et qui ne sont pas une simple accolade ou un commentaire
    parts =
        parts.Where(e => e.Trim() != "" && e.Trim() != "{" && e.Trim() != "}" && !e.Contains("//"))
            .ToArray();
    // on additionne le nombre de lignes récupérées au compteur
    cptCs += parts.Count();
}

// on récupère uniquement les fichiers JS
var jsFiles = allfiles.Where(e => e.EndsWith(".js"));
// on exclut le dossier de libraries
jsFiles = jsFiles.Where(e => !e.Contains(@"/fichier/de/librairies/js/à/exclure"));

int cptJs = 0;
foreach (var jsFile in jsFiles)
{
    // même traitement que pour le C#
    var parts = File.ReadAllText(jsFile).Replace("\r", "").Replace("\t", "").Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
    parts =
        parts.Where(e => e.Trim() != "" && e.Trim() != "{" && e.Trim() != "}" && !e.Contains("//"))
            .ToArray();
    cptJs += parts.Count();
}

Console.WriteLine(cptCs + " lines of code in C#");
Console.WriteLine(cptJs + " lines of code in JS");
Console.Read();

Arrêtez avec vos dates "user-friendly"

icon Tags de l'article :

Juin 01, 2015
Un petit article coup de gueule, une fois n'est pas coutume, rapport à une habitude de beaucoup de développeurs que je DETESTE : afficher des dates "user friendly".
Par exemple : "Il y a trois jours".

Franchement, vous considérez que vos utilisateurs sont des chèvres et ne savent pas faire la différence entre deux dates ?

Non, perso je préfère cent fois lire "Le 29/05/2015" plutôt que "il y a deux jours".

Je veux une date, une heure, je veux savoir à quel moment l'évènement s'est passé. Je ne veux pas avoir à aller, en permanence, mettre ma souris sur ta PUTAIN de date "simplifiée" pour savoir quelle est la véritable date.

Sans compter les ratés. Là j'ai un beau logiciel qui m'indique "il y a deux jours" pour un truc qui date du 29/05.

Si vous considérez que les gens sont débiles à ce point là, peut-être devriez-vous arrêter de développer pour eux.

A bon entendeur...

Relax your eyes

icon Tags de l'article : , ,

Avril 16, 2015
Hello les gens,

Comme beaucoup de développeurs, j'ai la mauvaise habitude de passer plusieurs heures sur le PC sans faire de pauses.

Le problème, c'est que mes yeux n'aiment vraiment pas, et qu'en plus les années passent, en plus ma myopie s'amplifie (et je suis sûr et certain que c'est à cause de mes trop longues sessions devant le PC).

Du coup, afin de me forcer à faire des pauses, j'ai décidé de développer un tout petit logiciel qui verrouille ma session 3 minutes toutes les heures. Ca peut sembler radical, mais c'est ce que j'ai trouvé de mieux. J'avais testé EyeDefender, plus complet, mais trop gentil (on appuie sur Echap et hop, la pause est terminée).

Le code source est disponible sur GitHub.

Et le zip est disponible ici.

L'application, ultra simpliste (pour le moment), ressemble à ça :



3 minutes avant la pause, elle affichera une infobulle d'alerte :



Et si vous êtes vraiment sur une tâche critique, vous pouvez décaler la pause de 3 minutes (mais une seule fois !) :



Enfin, vous pouvez la lancer avec l'argument -autostart pour qu'elle se lance automatiquement, ce qui peut être pratique si, comme moi, vous voulez la mettre au démarrage.

Bon dev et bonne journée à tous !

Afficher des bulles de notification dans la barre des tâches en C#

icon Tags de l'article : , ,

Avril 16, 2015
Hello,

Un besoin qui revient souvent en C#, pour des appli Winforms simples : pouvoir afficher des notifications dans la barre des tâches en C#.

Pour cela, j'utilise toujours la même méthode : un notifyIcon et l'appel à la méthode ShowBalloonTip :

this.notifyIconFormMain.Visible = true;
this.notifyIconFormMain.BalloonTipIcon = ToolTipIcon.Info;
this.notifyIconFormMain.BalloonTipTitle = "Relax your eyes";
this.notifyIconFormMain.BalloonTipText = string.Format("You will have to relax your eyes in {0} minutes.", minutes);
this.notifyIconFormMain.ShowBalloonTip(4); // 4 = le nombre de secondes d'affichage du message

Et voilà, une bulle apparaitra dans le coin de votre écran pour afficher votre message pendant 4 secondes !



Bon dev à tous !

Verrouiller une session en C#

icon Tags de l'article : , , ,

Avril 15, 2015
Hello,

Juste un mini article : comment verrouiller une session en C#.

La réponse est simple, à l'aide de ce petit morceau de code qui utilise user32.dll :

private const int WmSyscommand = 0x0112;
private const int ScMonitorpower = 0xF170;
private const int HwndBroadcast = 0xFFFF;
private const int ShutOffDisplay = 2;
[DllImport("user32.dll")]
private static extern void LockWorkStation();
[DllImport("user32.dll", SetLastError = true)]
private static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

Voilà, vous n'avez plus qu'à appeler la jolie méthode LockWorkStation qui s'occupera de verrouiller la session utilisateur ! Pensez juste à faire attention quand vous testez, car si vous mettez ça dans une boucle vous risquez d'être bloqué... [oui, ça sent le vécu]

Bon dev!

source

Comment stocker ses mots de passe en .Net

icon Tags de l'article : , ,

Novembre 24, 2014
Il existe de nombreuses façons de stocker les mots de passe pour une application ou un site web. La triste nouvelle, c'est que la majorité des sites et des applications n'utilisent pas les bonnes.

Aujourd'hui nous allons voir comment bien faire les choses.

Déjà, voici les différentes façons de faire, de la plus ridicule à la plus paranoïaque :

  • Niveau 0 : tout en clair. Rien de bien compliqué, aucune sécurité, la moindre fuite de données et on doit demander à nos utilisateurs de changer de mots de passe... Vraiment pas top pour l'image. Sécurité : 0%

  • Niveau 1 : mots de passe hashés avec une clef en dur. Pourquoi pas, sauf que... ça ne sert à rien. Le hacker n'aura qu'à générer des rainbow tables à partir de votre clef, et il aura les correspondances en clair pour tous les mots de passe hashés en base. Sécurité : 1%

  • Niveau 2 : on hashe les mots de passe avec un sel, généré aléatoirement pour chaque utilisateur. Un petit peu mieux. Seul bémol : il suffit de régénérer des Rainbow Tables, une par utilisateur, et ... on a tous les mots de passe. Sécurité : 5%

  • Niveau 3 : on hashe les mots de passe avec un sel, généré aléatoirement pour chaque utilisateur, à l'aide d'un algorithme lent : BlowFish. Ici on corrige le principal problème du niveau 2 : générer des Rainbow Tables pour chaque utilisateur peut prendre des jours, voire des mois... Sécurité : 80%

  • Niveau 4 : on utilise un module physique pour hasher les mots de passe. Si quelqu'un essaye de le déplacer ou d'y accéder, il effacera automatiquement toutes les données. Vous l'avez compris, il s'agit du mode paranoïaque ! Sécurité : 99%

Vous l'aurez compris, on va surtout s'intéresser au niveau 3, facilement réalisable et ne demandant pas, lui, d'investissements financiers. La clef repose sur le fait d'avoir un sel généré unique par utilisateur, et d'utiliser l'algorithme lent BlowFish. A l'échelle d'un site web, le temps de hash de l'algorithme est court. Mais lorsqu'on fait du bruteforce, le temps pour générer un dictionnaire entier de mots de passe est gigantesque !

La mise en place est plutôt simple :

Il faut d'abord créer deux entrées dans notre base, une pour le mot de passe hashé, et une pour le sel. Il ne faut jamais stocker ni déplacer le mot de passe non hashé. Si vous voulez bien faire les choses, utilisez HTTPS et stockez le sel ailleurs.



Ensuite, il faut ajouter BCrypt.Net à notre projet (via NuGet) :



Cette implémentation de l'algorithme BlowFish offre 2 méthodes : GenerateSalt et HashPassword, à utiliser comme suit :



Maintenant vous n'avez plus qu'à implémenter la bonne logique dans votre code :
  • Lorsque l'utilisateur s'inscrit, on génère et stocke un salt aléatoire pour lui, puis on se sert de ce salt pour hasher son mot de passe.
  • Lorsqu'il se connecte, on récupère le salt, et on hashe le mot de passe saisi à l'aide du salt pour ensuite comparer les deux hashs générés.
  • Lorsqu'il a perdu son mot de passe, on génère un mot de passe et un salt aléatoires, pour ensuite lui envoyer un mail avec son nouveau mot de passe.

Voilà, j'espère que ce petit article vous a plu,

Bonne soirée à tous !

Un petit outil pour gérer ses fichiers de traduction en PHP

icon Tags de l'article : , ,

Septembre 17, 2014
Hello,

Juste pour ceux que ça intéresse, voici un petit outil très simple que j'ai développé pour moi afin de gérer les traductions dans mes applications en PHP.

Resmanager (sur Github)

Le fonctionnement est très simple :
  • on remplit le fichier resources.lng avec nos traductions
  • on lance le petit exécutable ResourcesSplitter.exe
  • ce dernier va générer automatiquement des fichiers PHP avec nos traductions, un fichier par langue
  • on n'a plus qu'à inclure/utiliser ces fichiers dans nos applications

Le petit exe est fait en .Net, il est très simple et son code source est également présent sur le repository (fichier ResourcesSplitter.cs).

Voilà, on ne sait jamais, ça vous servira peut-être ! Si vous avez des questions n'hésitez pas !

Bon développement à tous !

(Et oui, pour ceux qui se demandent, je m'en sert pour la nouvelle version à venir de MyCryptoChat :)