Bookmarks dans Visual Studio

icon Tags de l'article : , ,

Aout 17, 2018
Woaw, je viens de découvrir l'existence des bookmarks dans Visual Studio...

Comme vous le savez peut-être, je suis un adepte des raccourcis clavier et de l'efficacité... mais je ne m'étais jamais posé la question de l'existence de bookmarks dans Visual Studio.

Pourtant c'est un besoin fréquent, noter un emplacement pour y revenir... A l'origine je me débrouillais en pinnant un fichier ou avec des breakpoints désactivés... Sauf qu'il y a une feature pensée pour !

Son fonctionnement est méga simple :

CTRL + K + K : Mettre/enlever un bookmark
CTRL + K + N : Aller au bookmark suivant
CTRL + K + N : Aller au bookmark précédent
CTRL + K + W : Afficher la liste des bookmarks

La liste ressemble à ça, et elle permet même de nommer ses bookmarks pour s'y retrouver plus facilement !.



En bref, une feature incontournable !

Architecture d'application web : SPA ou classique (en pages)

icon Tags de l'article : , , ,

Aout 16, 2018
Hello les gens,

Aujourd'hui je vais répondre à une question que beaucoup de développeurs se posent quand ils vont commencer un nouveau projet web : dois-je tenter l'aventure SPA (Single Page Application) ou dois-je rester sur une architecture classique (en pages web).

Pour répondre à ça, j'ai décidé de faire un petit listing des points positifs et négatifs d'une application SPA par rapport à une application classique.
A noter que c'est ma vision perso, après 6 ans à travailler sur des sites en pages web et 4 ans sur une application SPA.

Positif
  • Un code plus propre, mieux organisé, qui force à la rigueur
  • Un code JS beaucoup plus clair et parfaitement intégré aux vues
  • Moins de requêtes (stockage JS, moins de données à envoyer en permanence au serveur)
  • Possibilité d'avoir énormément de choses côté client (gros traitements, calculs, process en X étapes, etc.)
  • Maintenance et évolution plus facile
  • Pour l'utilisateur : une navigation plus agréable dans l'application

Négatif
  • Quel framework choisir ? Angular ? Vue.js ? React ? Un autre ?
  • Demande plus de rigueur en permanence (découpage en fichiers, architecture, dépendances JS, etc.)
  • Un temps de dev plus long (temps d'adaptation au langage, plus de javascript, temps pour trouver quel morceau de code correspond à quoi, plus de classes à écrire, ...)
  • Un debug plus compliqué au quotidien
  • Référencement et Routing
  • Monitoring des performances et tests d'intrusion moins évidents
  • Problématiques de mise à jour (si la personne ne refresh jamais, comment forcer la mise à jour sans lui faire perdre des données en cours de saisie ?)
  • Pour l'utilisateur : un premier chargement plus long

Pour moi, si j'avais à choisir entre une architecture SPA vs design classique, je poserais deux questions :
  • L'utilisateur de l'application fera-t-il de grosses sessions dessus ? (20 minutes ou plus)
  • Ais-je le temps et les moyens d'investir dans une SPA ? (car rien que la structure SPA rajoutera des problématiques à gérer au jour le jour)

Si vous avez un OUI aux deux questions, vous pouvez partir sur une architecture de type SPA sans hésiter. Le confort d'utilisation apporté à l'utilisateur par une SPA vaut vraiment le détour pour une application sur laquelle l'utilisateur passera des heures.

Après, personnellement, j'ai tendance à préférer une approche entre les deux :
  • Un site classique, découpé en pages
  • Chaque page utilisant un modèle de vue JS et un moteur de binding (dans mon cas Knockout)

Pourquoi ?
Après 4 ans passés à travailler sur une SPA, je me suis rendu compte que revenir à un design en pages web ne me posait pas de soucis. Au contraire, le développement devient plus simple.
La vraie problématique que je retrouve... c'est le JavaScript "à l'ancienne". Avoir un peu partout des méthodes qui font des modifications dans l'interface rend le tout extrêmement compliqué à relire et à améliorer.
Garder un design en pages web auquel on ajouterait un moteur de binding JS me parait être le meilleur compromis d'entre les deux mondes.

Après, évidemment... tout dépend de la taille de votre projet, de vos moyens... et de vos goûts ;)

Bonne journée et bon dev à tous/toutes !

Créer un template Razor utilisable en JavaScript

icon Tags de l'article : , , ,

Aout 15, 2018
Hello tout le monde,

Aujourd'hui on va voir comment créer un template Razor utilisable en JavaScript.
C'est une problématique assez courante, et la solution que je propose vient d'une implémentation sur un projet pro... et le résultat est pas si mal.

A noter qu'on est à des années lumières d'un vrai moteur de template, et je ne peux que vous recommander d'en implémenter un si c'est un vrai besoin de votre projet.
Cette solution n'est proposée que pour dépanner, ou à utiliser sur un projet où on ne peut/veut implémenter de moteur de template.


Du coup, allons-y !

Tout d'abord, on va créer une vue partielle pour notre template :

@model Projet.ObjetHtmlTemplate

<div class="objet">
    <h1>@Model.Name</h1>
    <p>@Model.Description</p>
    <span class="price">@Model.Price</span>
</div>

Nous allons maintenant créer notre ViewModel, ici ObjetHtmlTemplate.

Deux choses sont cependant à noter sur ce template :
  • Tous ces champs sont des strings
  • Le constructeur va permettre une initialisation des champs avec une chaine en dur contenant des antiquotes et le nom de la variable.

Voyons la classe avant que j'explique le "pourquoi" :

public class ObjetHtmlTemplate
{
    public string Name { get; set; }
	
	public string Description { get; set; }
	
	public string Price { get; set; }
	
	public ObjetHtmlTemplate(bool initForJs = false)
	{
		if(initForJs) 
		{
			Name = "` + Name + `";
			Description = "` + Description + `";
			Price = "` + Price + `";
		}
	}
}

Voilà. Je pense que vous commencez à voir où je veux en venir : nous allons juste créer une méthode en JS qui prendra en paramètres Name, Description et Price, et cela renverra... le HTML généré préalablement par Razor... mais avec nos valeurs JS !

La prochaine étape est donc ce petit morceau de JavaScript dans notre vue :

<script>
	var getObjetHtml = function(Name, Description, Price) {
		return `@Model.Partial("_ObjetHtmlTemplate", new ObjetHtmlTemplate(true))`;
	};
</script>

Magique non ? Maintenant on a une jolie méthode JavaScript "getObjetHtml" qui prend 3 paramètres, et qui va renvoyer le HTML généré à partir de ces 3 paramètres. <3

Et côté ASP.Net, on peut utiliser notre vue partielle comme à notre habitude :

<ul>
@foreach(var item in Model.Items)
{
	<li>@Html.RenderPartial("_ObjetHtmlTemplate", new ObjetHtmlTemplate() { Name = item.Name, Description = item.Description, Price = Math.Round(item.Price, 2) })<li>
}
</ul>

Et... c'est tout ! Simple et efficace, que demander de plus ?!

Allez, bonne journée et bon dev à tous/toutes !

Manipuler une collection de sous-objets dans une vue ASP.Net MVC

icon Tags de l'article : , , ,

Aout 13, 2018
Hello les devs !

Aujourd'hui on va parler d'une problématique assez commune : envoyer des sous-objets au serveur depuis une vue CSHTML.

En effet, quand on développe un site avec ASP.Net MVC, il peut arriver qu'on ait besoin, dans notre vue, de manipuler des objets qui sont en fait... une collection d'objets liés à notre objet principal.

Exemple : ma classe Guerrier qui a une propriété "Inventaire" contenant une collection d'"Objet".

Dans ma vue, j'aimerais donc pouvoir voir les objets dans l'inventaire, et si besoin... en ajouter/supprimer.

Comment faire ça ?

Et bien en fait c'est ultra simple : le moteur d'ASP.Net MVC sait tout à fait mapper des sous-objets à partir de nommages façon "tableau".

Exemple : <input type="text" name="Guerrier.Inventaire[0].Nom" value="Epée des mille vérités" />

Grâce à la syntaxe "Inventaire[0].Nom", le moteur d'ASP.Net va comprendre de lui-même qu'il s'agit du premier objet dans la liste "Inventaire" et va essayer de le créer et mapper ses propriétés.

Quelques choses à noter tout de même :
  • Attention à la numérotation, si vous avez juste un sous-objet numéroté [1], il ne sera pas mappé et récupéré.
  • Côté serveur vous ne récupérerez que ce qui a été envoyé par le client. S'il s'agit de données à sauvegarder, vous devrez faire une comparaison entre ce que vous avez en base et ce que l'utilisateur a envoyé.
  • Si vous n'avez pas de valeurs, vous n'aurez pas d'objets.
  • Si vous n'avez pas d'objets, vous ne récupérerez pas une collection vide, mais null.
  • Si jamais les champs sont disabled, ils ne seront ni récupérés, ni mappés (pratique pour activer/désactiver un objet en javascript).

Voilà,

Bonne semaine et bon dev à tous/toutes !

Ecrire des tests unitaires pour une méthode de contrôleur d'API qui attend un fichier (ou comment mocker HttpContext)

icon Tags de l'article : , ,

Juillet 02, 2018
Hello tout le monde,

Aujourd'hui un cas de dev très spécifique : comment tester unitairement une méthode de contrôleur MVC / d'API qui attend un fichier ?

Exemple de méthode :

[HttpPost]
[Route("fromDocument")]
public IHttpActionResult CreateFromDocument()
{
	if (!Request.Content.IsMimeMultipartContent())
	{
		return InternalServerError(Resource.Error_RequestNotMimeMultipartContent);
	}

	if (HttpContext.Current.Request.Files.Count != 1)
	{
		return BadRequest(Resource.Error_FileContentIncorrect);
	}

	// We will only consider the first file
	HttpPostedFile uploadedFile = HttpContext.Current.Request.Files[0];

	if (uploadedFile.ContentLength > ConstantApplicationSettings.UploadFileSizeLimitInBytes)
	{
		return BadRequest();
	}

	uploadedFile.InputStream.Position = 0;

	string originFileName = uploadedFile.FileName.Replace("\"", string.Empty);
	_itemBusinessLogic.CreateDocument(item.Id, originFileName, uploadedFile.InputStream, CurrentUserId);

	return Ok();
}

Comment tester cette méthode ? En effet, de l'extérieur le HttpContext n'est pas accessible. Du coup comment faire croire au contrôleur qu'il recoit bien un fichier alors que ce n'est pas le cas ?

Pour ça, il faut passer par plusieurs étapes.

Déjà, la première chose importante à assimiler : il ne faut PAS mocker HttpContext.
La classe est vraiment énorme et embarque énormément de choses. La mocker ou la stubber c'est prendre le risque d'avoir du code qui casse en production sans qu'on s'en rende compte, malgré des tests unitaires parfaits.

Non, la vraie bonne pratique c'est d'interfacer nos besoins d'objets liés à HttpContext. Nous allons donc créer une interface IHttpContextAdapter :

public interface IHttpContextAdapter
{
	HttpFileCollectionBase GetFiles();
}

Comme vous le voyez, nous allons renvoyer un HttpFileCollectionBase. Pas un HttpFileCollection car il s'agit d'une classe sealed, et donc impossible à mock sans passer par de l'interception.

Du coup, implémentons cette interface dans la classe qui nous servira dans nos contrôleurs d'API :

public class HttpContextAdapter : IHttpContextAdapter
{
    public HttpFileCollectionBase GetFiles()
    {
        return new HttpFileCollectionWrapper(HttpContext.Current.Request.Files);
    }
}

Voilà. Comme vous l'avez compris, le HttpFileCollectionWrapper va permettre de transformer le HttpFileCollection en HttpFileCollectionBase, classe qu'on peut mocker / stubber car non sealed.

Du coup, injectons maintenant dans notre constructeur cette interface, et utilisons la :

public class ItemService : ApiControllerBase, IItemService
{
	public readonly IItemBusinessLogic _itemBusinessLogic;
	public readonly IHttpContextAdapter _httpContextAdapter;

    public ItemService(IItemBusinessLogic itemBusinessLogic, IHttpContextAdapter httpContextAdapter)
    {
		_itemBusinessLogic = itemBusinessLogic;
        _httpContextAdapter = httpContextAdapter;
    }
	
	[...]
}

Voilà. Et du coup, au lieu d'appeler HttpContext.Current, nous allons appeler notre _httpContextAdapter :

// avant modification
if (HttpContext.Current.Request.Files.Count != 1)
[...]
HttpPostedFile uploadedFile = HttpContext.Current.Request.Files[0];

// après modification
if (_httpContextAdapter.GetFiles().Count != 1)
[...]
HttpPostedFileBase uploadedFile = _httpContextAdapter.GetFiles().Get(0);

Ca avance, ça avance.

Mais on a toujours un problème : lorsque le contrôleur va vérifier le fichier censé être en entrée... il va faire un Request.Content.IsMimeMultipartContent()...
Or... le mock de notre HttpContext ne suffira pas à faire passer cette méthode.

Heureusement, j'ai trouvé une méthode d'extension qui permet de faire ça !
Elle va tout simplement réinitialiser le ControllerContext de notre ApiController en ajoutant un "faux" fichier de 100 bytes, avec les bons headers :

public static void InitializeMimeMultipartContent(this ApiController controller, string fileName = "filename.txt")
{
	var user = controller.User;
	Uri uri = controller.Request.RequestUri;

	var configuration = new HttpConfiguration();
	var request = new HttpRequestMessage(HttpMethod.Post, string.Empty);
	var content = new MultipartFormDataContent();

	var fileContent = new ByteArrayContent(new byte[100]);
	fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
	{
		FileName = fileName
	};
	content.Add(fileContent);
	request.Content = content;
	request.Properties[HttpPropertyKeys.HttpConfigurationKey] = configuration;

	controller.ControllerContext = new HttpControllerContext(new HttpConfiguration(), new HttpRouteData(new HttpRoute(string.Empty)), request);

	controller.User = user;
	controller.Request.RequestUri = uri;
}

La seule chose à noter est que, comme on va réinitialiser le ControllerContext, on perdra des données qui sont probablement actuellement utilisées (l'utilisateur courant, l'url courante, etc.), d'où le fait qu'on mette de côté l'utilisateur et l'url avant la réinitialisation, pour les remettre dans le contrôleur ensuite.

Et voilà, nous avons tous les outils pour pouvoir tester efficacement notre méthode !

Voici donc un test unitaire commenté utilisant tout ce qu'on vient de coder (avec RhinoMocks, mais vous devriez pouvoir adapter ce code à votre moteur de mocking) :

[Test]
public void ItemController_CreateFromDocument_WithValidData_Ok()
{
	string fileName = "file.png";
	
	// on va d'abord initialiser le contrôleur pour faire croire qu'il s'agit d'un MimeMultipartContent
	_itemController.InitializeMimeMultipartContent(fileName);

	// ensuite on va mocker les appels à GetFile() de IHttpContextAdapter
	HttpFileCollectionBase files = MockRepository.GenerateStub<HttpFileCollectionBase>();
    HttpPostedFileBase file = MockRepository.GenerateStub<HttpPostedFileBase>();
	_httpContextAdapter.Stub(p => p.GetFiles()).Return(files);
	files.Stub(p => p.Get(0)).Return(file);

	// ici on mocke le "nombre" de fichiers envoyés
	files.Stub(p => p.Count).Return(1);
	
	// maintenant, on mocke le fichier "envoyé", avec les propriétés checkées par notre contrôleur
	file.Stub(p => p.FileName).Return(fileName);
	file.Stub(p => p.ContentLength).Return(50000);
	
	// on pense aussi à renvoyer un stub pour le stream (pour éviter que les appels au stream ne plantent)
	file.Stub(p => p.InputStream).Return(MockRepository.GenerateStub<Stream>());

	// Une fois le test prêt, on appelle notre méthode de contrôleur
	HttpResponseMessage httpResponseMessage = _itemController.CreateFromDocument().GetResponse();

	// Et on n'a plus qu'à vérifier que la réponse est bien celle attendue, et que la couche métier a été appelé
	ApiControllerAssert.IsOk(httpResponseMessage);
	_itemBusinessLogic.AssertWasCalled(p => p.CreateDocument(
		Arg<string>.Is.Equal(fileName),
		Arg<Stream>.Is.NotNull,
		Arg<int>.Is.Equal(CurrentUserId)));
}

En espérant que ce soit utile à quelqu'un :)

Bon dev tout le monde !