Microsoft, ou comment se mettre à dos sa propre communauté en 1 leçon

25. mai 2012 15:00 by Tommy in Actualité, Développement  //  Tags: , , , , , , , ,   //   Commentaires (15)   //  Partager sur Facebook  / Twitter

Nouveau logo Windows 8

 

Microsoft n'est pas connue pour prendre soin de sa communauté de développeurs. Ils ont tendance à retourner régulièrement leur veste (Silverlight c'est le futur - Ah pardon, Silverlight c'est has been, vive HTML5 Cool !), abandonner des technos, des outils, des fonctionnalités, ... au nom du Saint Marketing, notre maître à tous (enfin, surtout le leur...).

Sauf que là, je viens de me prendre une grosse claque, moi qui continuait de soutenir Microsoft malgré leurs choix... Tenez-vous bien :

Visual Studio Express 11, qui sera en fonction sur Windows 8, ne permettra de développer QUE des applications Metro Windows 8.

Et oui, vous avez bien lu... Adieu les sites ASP.Net, les applications console, les web services, les services Windows, les applications Silverlight, ...

Si vous aimez le .Net et que vous voulez continuer à développer gratuitement de petites applications pour vous et vos amis, ben... vous ne pourrez pas. Il va falloir passer à la caisse, et débourser les 500$ que coûte Visual Studio Professional Edition. Et oui, rien que ça.

Qu'est ce que Microsoft espère ? Encourager ses développeurs à passer sur Metro avec Windows 8 ? Il risque juste de les faire fuir. Vous avez les moyens, vous, de dépenser 500€ pour pouvoir développer vos applications personnelles ?

Pour moi, il s'agit clairement d'une trahison envers la communauté de développeurs .Net. On développe des applications pour leur OS phare (= valeur ajoutée), et on nous remercie en nous demandant de l'argent... (Je devrais peut être vous faire payer pour poster des commentaires sur mon blog non ? Undecided)

Je crois qu'il va être temps pour moi de quitter l'univers Microsoft. Et d'arrêter de développer mes applications personnelles en .Net... Cry

EDIT : visiblement il y aura toujours des version de Visual Studio Express pour tout ce qui est web, services, silverlight... Seule la version basique de VS sera limitée aux applications Metro (afin d'obliger les développeurs à passer par le store = argent). C'est un peu moins violent que ce que je croyais, mais c'est quand même pas top... [Merci aux gens qui ont commenté pour clarifier la situation Wink]

source via sebsauvage.net

En vrac, semaine 1

 

Allez, un petit article "en vrac", ça faisait longtemps. Je vais essayer de remplacer ce cher SebSauvage (et je confirme, ça prend un temps fou ! Surprised):

  • Le bilan complet d'Hadopi par nikopik. Je pense que tout est dit.
  • François Hollande réécrit discrètement la partie "numérique" de son programme...
  • J'aime beaucoup utiliser des images ou des parallèles pour expliquer les choses. Et si on essayait avec HADOPI, ACTA, SOPA et PIPA ?
  • La censure 2.0 par SebSauvage. Je pense de plus en plus à supprimer Google Analytics et Google +1 de mon site...
  • L'UFC Que Choisir a décidé de faire la guerre à l'utilisation abusive du terme "illimité". Il était temps !
  • La Chine met la pression sur les blogueurs pour qu'ils s'inscrivent sous leur véritable nom. Pour pouvoir ensuite les poursuivre pour ce qu'ils ont dit ? Hum...
  • Orange se met au DPI pour espionner les faits et gestes de ses clients. Le début de la fin ?
  • 01Net se permet de modifier les installeurs de logiciels (libres ou non) pour y ajouter ses propres toolbars promotionnelles. Site à boycotter d'urgence !
  • L'évolution du "droit d'auteur". On est passé de 20 ans après la création à 70 ans après la mort de l'auteur...
  • Un passionné, qui avait traduit entièrement un livre d'Hemingway car la traduction officielle laissait à désirer, est menacé par Gallimard qui lui demande de supprimer son livre et de payer pour chacun des 22 exemplaires vendus. Pfff...
  • Google se propose de générer et mémoriser vos mots de passe dans le cloud. Très peu pour moi !
  • Un développeur Iranien a été condamné à mort pour usages abusifs d'un logiciel qu'il a créé. Aberrant...
  • Hadopi, en plus de couter une fortune à l'Etat (et donc à nous...), a de grosses dettes envers les FAI. Sympa.
  • Oh, tiens, encore des identifiants bancaires dans la nature ! Vive le cloud !
  • Ouaoutch ! Le meilleur radar de vitesse rapporterait 22 Millions d'euros par an à l'Etat ?! Je comprend pourquoi ils en mettent un peu partout. Euh, oui, la sécurité, c'est cela...
  • A quoi sert vraiment la Téléréalité, par LeHollandaisVolant ?
  • Le quotidien d'un cycliste à Paris, en vidéo. Du temps où je travaillais à Dijon, je faisais tout en vélo. A Paris je n'y songe même pas, c'est trop dangereux. Ce sont vraiment les voitures qui tuent les villes... [via fleid]
  • Ah, les joies d'être honnête : Ubisoft ferme ses serveurs d'authentification pour certains jeux offline. Du coup, ceux qui ont acheté les jeux ne pourront plus jouer, mais ceux qui les ont piraté pourront continuer de jouer. Logique ? Undecided
  • Notre cher président est arrivé sur Twitter (pour se donner une image jeune à deux mois des élections ?), et il semblerait que beaucoup de ses followers ne soient rien de plus que des... bots.
  • Et la guéguerre entre Free Mobile et les autres opérateurs Telecom continue. Quelqu'un peut me passer le popcorn ?
  • L'ordinateur Raspberry Pi à 25$ sort ce mois-ci ! Excellent ! Je pense que je vais m'en prendre un, ne serait-ce que pour voir ce que ça vaut. A ce prix là, on peut se le permettre ! Wink
  • Apple bloque Siri pour les non possesseurs d'iPhone 4S. Ah ben oui, c'est Apple, il faut payer épisétou !
  • Tremble Free Mobile, un nouveau concurrent vient d'apparaître !

Et en plus fun :

  • Le SWAT intervient pour neutraliser un tueur... sur Call of Duty ! Haha !
  • Ah, finalement je ne suis plus sûr d'être un homme...
  • Quelques expérimentations HTML5 / JavaScript. Certaines sont plus qu'impressionnantes ! [via SebSauvage]
  • Une petite explication d'Identitools sur le sommeil polyphasique, ou comment gagner 120 heures par mois. Intéressant, mais je m'interroge quand même un peu sur les conséquences à long terme pour le corps. A creuser.
  • Je veux ça pour ma voiture !
  • Un petit outil pour chiffrer très simplement vos données chez DropBox. A garder sous le coude.
  • Norman nous a fait une petite vidéo géniale sur les Apple addicts. Tellement vrai !
  • Une petite version épique du thème de Metal Gear Solid. A écouter pour tous les fans Cool
  • Jean Dujardin est devenu un mème à son tour. Cool !
  • Le replay de la Techdays TV, pour ceux qui n'ont pas pu y aller et pour ceux qui veulent revoir les meilleurs moments de ces trois jours (Coding 4 fun ? Wink).
  • L'opérateur le plus utile du monde en C# : le ou exclusif ! Bon ok, ou pas.
  • ASP.Net 4.5 gèrera nativement la compression JS et CSS ! Excellent !

image modifiée de edkohler, sous licence CC

Un exemple d'architecture .Net complète

13. décembre 2011 09:30 by Tommy in Développement  //  Tags: , , , , , , , , , , , ,   //   Commentaires (7)   //  Partager sur Facebook  / Twitter

 

Ces derniers temps, j'ai pas mal réfléchi à la meilleure façon de séparer un projet .Net en couches. Classes métier, accès simplifié à la BDD, tests unitaires avec bouchons, injection de dépendances, mapping, ... Tellement de problématiques à gérer lorsqu'on crée une architecture .Net.

Du coup je me suis fixé un objectif simple : créer une architecture de solution .Net "propre, avec

  • des couches bien séparées (classes métier, logique métier, accès aux données, services, tests unitaires, interfaces utilisateur),
  • aucun Framework pour les tests unitaires (histoire que le code reste simple à appréhender et à comprendre pour un débutant),
  • de l'injection de dépendances pour pouvoir brancher facilement les tests unitaires sur des "bouchons",
  • enfin, un accès aux données simplifié, sans SQL ni procédures stockées (donc avec Linq et Entity Framework).

Au final, j'ai créé un simpliste "moteur de blog", capable d'afficher des articles puisés dans une BDD SQL Server 2008 Express. Pour éviter que ce beau projet .Net ne se perde dans les méandres de mon disque dur avant de sombrer dans l'oubli, je me suis dit que le partager avec vous serait le meilleur moyen de lui offrir une seconde vie. Qui sait, peut être sera-t-il utile à de nombreuses personnes ! Smile

Le voici donc : projet HTA.DemoBlog.zip (1,54 mb). Le code source est sous licence LGPL, donc vous pouvez l'utiliser comme bon vous semble ! Wink

Voici, déjà, ce qu'il y a à noter sur cette architecture (pour chaque couche, j'ai créé un solution folder) :

1) Couche Business

  • BusinessEntities : les objets métier, qu'on manipulera dans l'application. L'intérêt est d'avoir des objets complètement détachés de la BDD et qu'on pourra manipuler dans une autre application, dans une bibliothèque de classes, dans un module à part, ... Les objets ne sont vraiment que des conteneurs indépendants de tout le reste.
  • BusinessLogic : Tous les traitements métier. Ici on retrouvera la logique métier, les Try Catch, les calculs compliqués, les logs, ... Ce code sera celui utilisé par les applications (qu'il s'agisse de Winforms, d'ASP.Net, de WPF, ...). Cette couche interagit avec la couche DataAccess et utilise les objets BusinessEntities.

2) Couche Resources

  • DataAccessInterfaces : les interfaces qui définiront les méthodes de la couche DataAccess.
    Intérêt double : pouvoir utiliser des bouchons (mock) qui implémentent cette interface dans les tests unitaires, afin d'avoir des tests unitaires indépendants de la BDD et donc qui peuvent être lancés à chaque build / release.
    L'autre intérêt est de pouvoir changer de source de données ou les méthodes qui interagissent avec la BDD sans avoir à recoder des choses dans d'autres couches (vu qu'elles implémentent ces interfaces et non pas les objets DataAccess directement).
  • DataAccess : la couche qui interroge la BDD. Elle renvoie des objets BusinessEntities et possède des méthodes définies dans DataAccessInterfaces. Vu qu'elle est détachée, on peux utiliser ce qu'on veut pour stocker nos données : Entity Framework, Linq 2 Sql, des procédures stockées, des fichiers XML, ... Si on doit changer de source de données ou de façon d'exploiter notre source de données, on n'aura qu'à recréer une couche qui implémentera les interfaces DataAccessInterfaces, et le tour sera joué.

3) Couche Tests

  • Tests.Mock : des classes bouchon (donc avec des données en dur) qui implémentent les interfaces DataAccessInterfaces.
  • Tests : les tests unitaires. Ces tests testent la couche BusinessLogic via les mocks plutôt que via la couche DataAccess (afin de ne tester que la logique métier, sans risquer de modifier les données en base).

4) Couche Interfaces (UI)

  • La couche qui contiendra les interfaces utilisateur (WebApp, Winforms, WPF, ASP.Net MVC, ...) qui utiliseront uniquement le code métier dans la couche BusinessLogic et qui manipulera des objets BusinessEntities.

5) Couche Core

  • La couche Core contient des méthodes génériques utiles au projet mais qui pourront être réutilisées / utiles pour d'autres projets. (Cryptographie, Logs, ...)

6) Couche Database

  • Le projet Database permet de gérer le contenu de la BDD SQL Server, de mettre à jour les procédures stockées et de modifier les scripts de déploiement, sans avoir à le faire à la main.

7) Couche Service

  • Cette couche permet de créer des services qui utilisent la couche métier et donc de rendre le code accessibles de l'extérieur. (Pour l'utiliser, par exemple, via une application web distante ou via une application Windows Phone 7).

 L'architecture ressemble donc à ceci :

 

Voyons maintenant le code un peu plus en détail.

Dans un premier temps, intéressons-nous à la classe ArticleBE, de la couche BusinessEntities, qui définit la structure d'un article :

    public class ArticleBE : ICloneable
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }
        public string Author { get; set; }
        public string Tags { get; set; }
        public DateTime Date { get; set; }

        public object Clone()
        {
            ArticleBE result = new ArticleBE()
            {
                Id = Id,
                Date = Date,
                Author = Author.Clone() as string,
                Content = Content.Clone() as string,
                Tags = Tags.Clone() as string,
                Title = Title.Clone() as string
            };
            return result;
        }
    }

Comme vous pouvez le voir, le code est vraiment le plus simple possible. Nous avons juste nos propriétés et la méthode Clone (j'expliquerais l'intérêt de la méthode Clone plus tard). Pas de dépendances, pas de contraintes, pas de spécificités, ... ce code peut véritablement être réutilisé n'importe où. Il est possible, dans le cas d'une application demandant une exposition de services, de rendre notre classe Serializable.

Maintenant, nous allons travailler sur la couche DataAccessInterfaces. Dans cette couche, nous allons définir les interfaces et donc les méthodes que notre couche d'accès aux données devra mettre à disposition. Ici, étant donné que nous ne travaillons que sur une table, nous n'allons créer qu'une interface, à savoir IArticleDA :

public interface IArticleDA : IDisposable
    {
        List<ArticleBE> GetArticles();
        ArticleBE GetArticleById(int idArticle);
        void AddArticle(ArticleBE article);
        void EditArticle(ArticleBE article);
        void DeleteArticle(ArticleBE article);
        void Save();
    }

Le code, encore une fois, est assez simple. Nos classes qui implémenteront IArticleDA devront nous fournir toutes ces méthodes + Dispose, puisque nous implémentons IDisposable.

Maintenant que cette interface est définie, nous allons voir la partie accès aux données. Comme vous pouvez le voir, j'ai ajouté au projet DataAccess un ADO.Net Entity Data Model pour pouvoir interroger la base de données très facilement via Linq. Cependant, pour pouvoir transformer nos objets Entity en objets BusinessEntities, j'ai créé une class ArticleMap dans le dossier Mapping :

public static class ArticleMap
    {
        internal static List<ArticleBE> Map(List<Article> articles)
        {
            if (articles == null || articles.Count == 0)
            {
                return new List<ArticleBE>();
            }

            List<ArticleBE> result = new List<ArticleBE>();

            foreach (Article article in articles)
            {
                result.Add(Map(article));
            }

            return result;
        }

        internal static ArticleBE Map(Article article)
        {
            if (article == null)
            {
                return null;
            }

            ArticleBE result = new ArticleBE();

            result.Id = article.IdArticle;
            result.Author = article.Author;
            result.Content = article.FullContent;
            result.Date = article.Date;
            result.Tags = article.Tags;
            result.Title = article.Title;

            return result;
        }

        internal static Article Map(ArticleBE article, Article result = null)
        {
            if (result == null)
            {
                result = new Article();
            }

            result.Author = article.Author;
            result.FullContent = article.Content;
            result.Date = article.Date;
            result.Tags = article.Tags;
            result.Title = article.Title;

            return result;
        }
    } 

Le code est, encore une fois, très simple. Déjà, toutes les méthodes sont en internal car elles ne doivent être accessibles qu'aux classes de notre projet DataAccess. Le mapping Article => ArticleBe est simple, mais le mapping ArticleBE => Article a une petite spécificité. En effet, dans le constructeur, j'ai passé un paramètre facultatif : un objet Article.

Pourquoi ? Lorsqu'on veut mettre à jour un objet Article en base depuis un objet ArticleBE, plutôt que d'avoir à modifier chaque propriété dans notre code d'accès aux données, il suffit d'appeler la méthode Map avec comme second paramètre notre objet Article à mettre à jour. (Il s'agit de la méthode la plus simple que j'ai pu trouver pour mettre à jour un objet Entity à partir d'une classe BusinessEntities.)

Intéressons-nous ensuite au code de notre classe ArticleDA, qui implémente IArticleDA :

public class ArticleDA : IArticleDA
    {
        private HTADemoBlogEntities _entities = null;

        public ArticleDA()
        {
            _entities = new HTADemoBlogEntities();
        }

        public List<ArticleBE> GetArticles()
        {
            return ArticleMap.Map(_entities.Article.OrderByDescending(e => e.IdArticle).ToList());
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            return ArticleMap.Map(_entities.Article.Where(e => e.IdArticle == idArticle).FirstOrDefault());
        }

        public void AddArticle(ArticleBE article)
        {
            _entities.Article.AddObject(ArticleMap.Map(article));
        }

        public void EditArticle(ArticleBE article)
        {
            Article existingArticle = _entities.Article.Where(e => e.IdArticle == article.Id).FirstOrDefault();

            if (existingArticle != null)
            {
                ArticleMap.Map(article, existingArticle);
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            Article existingArticle = _entities.Article.Where(e => e.IdArticle == article.Id).FirstOrDefault();

            if (existingArticle != null)
            {
                _entities.Article.DeleteObject(existingArticle);
            }
        }

        public void Save()
        {
            _entities.SaveChanges();
        }

        public void Dispose()
        {
            if (_entities != null)
            {
                _entities.Dispose();
            }
        }
    }

Rien de très compliqué. J'ai juste une méthode Save (déclarée dans IArticleDA) qui permet de sauver en base les modifications faites à nos objets Article, à l'aide de SaveChanges. Ca nous permet, dans le cas où nous avons de nombreux articles à créer ou à modifier, de ne pas faire de SaveChanges à chaque fois. Nous n'appellerons la méthode qu'à la fin de notre traitement métier.

Voyons maintenant la couche BusinessLogic, avec la classe ArticleBL :

public class ArticleBL : IDisposable
    {
        private IArticleDA _dataFactory = null;

        public ArticleBL()
            : this(new ArticleDA())
        {

        }

        public ArticleBL(IArticleDA dataFactory)
        {
            _dataFactory = dataFactory;
        }

        public List<ArticleBE> GetArticles()
        {
            try
            {
                return _dataFactory.GetArticles();
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
                return new List<ArticleBE>();
            }
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            try
            {
                if (idArticle < 0)
                {
                    throw new ArgumentException();
                }

                return _dataFactory.GetArticleById(idArticle);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
                return null;
            }
        }

        public void AddArticle(ArticleBE article)
        {
            try
            {
                if (string.IsNullOrEmpty(article.Title)
                    || string.IsNullOrEmpty(article.Author)
                    || article.Date == DateTime.MinValue)
                {
                    throw new ArgumentException();
                }

                _dataFactory.AddArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void EditArticle(ArticleBE article)
        {
            try
            {
                if (article.Id < 0
                    || string.IsNullOrEmpty(article.Title)
                    || string.IsNullOrEmpty(article.Author)
                    || article.Date == DateTime.MinValue)
                {
                    throw new ArgumentException();
                }

                _dataFactory.EditArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            try
            {
                if (article.Id < 0)
                {
                    throw new ArgumentException();
                }

                _dataFactory.DeleteArticle(article);
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void Save()
        {
            try
            {
                _dataFactory.Save();
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }

        public void Dispose()
        {
            try
            {
                if (_dataFactory != null)
                {
                    _dataFactory.Dispose();
                }
            }
            catch (Exception ex)
            {
                LogError.Log(ex);
            }
        }
    }

Ici, le code devient un peu plus intéressant. Pour ne pas manipuler directement d'objets ArticleDA (ce qui permet de changer très facilement de source de données), le constructeur de ma classe demande en paramètre un objet IArticleDA. Si aucun objet implémentant IArticleDA n'est passé au constructeur, la classe utilisera le constructeur vide qui créera automatiquement un objet ArticleDA qu'elle passera au constructeur avec paramètre.

Vous pouvez également voir que les tests "métier", à savoir une tentative de lecture d'un article ayant un Id < 0, la tentative d'ajout d'un article sans titre, ... déclencheront la levée d'une ArgumentException. Le code de chaque méthode étant dans un Try Catch, cette exception sera attrapée et logguée (voir classe LogError dans la couche Core).

Enfin, la méthode Dispose appellera la méthode Dispose de notre classe implémentant IArticleDA, ce qui coupera la connexion à la BDD.

Voyons maintenant un exemple d'utilisation de nos méthodes métier. Si nous nous rendons dans le contrôleur ArticleController de l'application MVC, nous pouvons voir ceci :

public class ArticleController : Controller
    {
        public ActionResult Index(string message = "")
        {
            ViewBag.Message = message;

            using (ArticleBL business = new ArticleBL())
            {
                return View(business.GetArticles());
            }
        }

        public ActionResult Details(int id)
        {
            using (ArticleBL business = new ArticleBL())
            {
                return View(business.GetArticleById(id));
            }
        }

        public ActionResult Create()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Create(ArticleBE article)
        {
            using (ArticleBL business = new ArticleBL())
            {
                business.AddArticle(article);
                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been created." });
        }

        public ActionResult Edit(int id)
        {
            ArticleBE article = null;

            using (ArticleBL business = new ArticleBL())
            {
                article = business.GetArticleById(id);
            }

            if (article == null)
            {
                return RedirectToAction("Index", new { message = "This article doesn't exist." });
            }

            return View(article);
        }

        [HttpPost]
        public ActionResult Edit(ArticleBE article)
        {
            using (ArticleBL business = new ArticleBL())
            {
                business.EditArticle(article);
                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been updated." });
        }

        public ActionResult Delete(int id)
        {
            ArticleBE article = null;

            using (ArticleBL business = new ArticleBL())
            {
                article = business.GetArticleById(id);

                if (article == null)
                {
                    return RedirectToAction("Index", new { message = "This article doesn't exist." });
                }

                business.DeleteArticle(article);

                business.Save();
            }

            return RedirectToAction("Index", new { message = "The article has been deleted." });
        }

Et oui, pour utiliser nos données en base, il suffit de faire un using. A la sortie du using, la méthode Dispose de l'objet sera appelée, et donc la connexion sera automatiquement coupée. Cette méthode a ses avantages (sécurité, pas de risques de fuite mémoire) comme ses inconvénients (nombreuses connexions / déconnexions), mais rien ne vous empêche de la modifier.

Regardons enfin, en détail, les tests unitaires. Déjà, dans la couche Tests.Mock, le code est assez parlant :

public class MockArticleDA : IArticleDA
    {
        private List<ArticleBE> articles = new List<ArticleBE>()
        {
            new ArticleBE() {
                Id = 1,
                Title = "Demo 1",
                Tags = "demo,article,.Net",
                Date = DateTime.UtcNow,
                Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            },
            new ArticleBE() {
                Id = 2,
                Title = "Demo 2",
                Tags = "demo,article,PHP",
                Date = DateTime.UtcNow,
                Content = "PHP : Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            },
            new ArticleBE() {
                Id = 3,
                Title = "Demo 3",
                Tags = "demo,article,Java",
                Date = DateTime.UtcNow,
                Content = "JAVA : Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vehicula fringilla quam, nec interdum arcu faucibus id. Sed placerat elementum venenatis. Quisque tempor sem a mi elementum at elementum orci aliquet. Morbi venenatis convallis orci. Sed vitae felis vel sapien cursus viverra. Nulla et elit non ante suscipit dictum. Proin id erat ligula. Vestibulum vehicula augue vel nunc consequat porta. Nam tincidunt lobortis lacus, eu varius velit vestibulum vitae. Sed quis nisi erat, at egestas odio. Cras tempor mauris et dui blandit placerat euismod libero sagittis.",
                Author = "demoUser",
            }
        };

        public List<ArticleBE> GetArticles()
        {
            // we clone the list to avoid to avoid any ref problem
            return articles.Select(e => e.Clone() as ArticleBE).ToList();
        }

        public ArticleBE GetArticleById(int idArticle)
        {
            ArticleBE result = articles.Where(e => e.Id == idArticle).FirstOrDefault();

            if (result != null)
            {
                // we clone the item to avoid to avoid any ref problem
                return result.Clone() as ArticleBE;
            }

            return null;
        }

        public void AddArticle(ArticleBE article)
        {
            article.Id = articles.Max(e => e.Id) + 1;
            articles.Add(article);
        }

        public void EditArticle(ArticleBE article)
        {
            ArticleBE result = articles.Where(e => e.Id == article.Id).FirstOrDefault();
            if (result != null)
            {
                result.Author = article.Author;
                result.Content = article.Content;
                result.Date = article.Date;
                result.Tags = article.Tags;
                result.Title = article.Title;
            }
        }

        public void DeleteArticle(ArticleBE article)
        {
            ArticleBE result = articles.Where(e => e.Id == article.Id).FirstOrDefault();
            if(result != null) 
            {
                articles.Remove(result);
            }
        }

        public void Save()
        {

        }

        public void Dispose()
        {

        }
    }

On voit donc, ici, qu'on implémente bien notre interface d'accès aux données IArticleDA. Ainsi, pour tester notre couche de logique métier sur cette classe, il nous suffira de passer un objet MockArticleDA dans le constructeur de notre classe métier, et le tour est joué !

Notre classe MockArticleDA possède une liste d'articles créée à chaque construction d'un objet. La spécificité, ici, est que j'utilise la méthode Clone de nos objets ArticleBE pour ne renvoyer que des copies de ces objets à notre classe de tests. Cela permet d'éviter de modifier involontairement les données de nos objets par référence (par exemple dans le cas d'un test de modifications des données sans sauvegarde).

Enfin, je vous laisse vous intéresser à la classe ArticleBLTest, dont voici 2/3 méthodes :

[TestInitialize()]
        public void MyTestInitialize()
        {
            IArticleDA mockDataFactory = new MockArticleDA();
            business = new ArticleBL(mockDataFactory);
        }

        [TestMethod()]
        public void DeleteArticle_Generic_Test()
        {
            // params
            int nbArticles = 3;
            // --

            // we check if we have 3 articles
            List<ArticleBE> articles = business.GetArticles();
            Assert.AreEqual(nbArticles, articles.Count);

            // we delete the article
            business.DeleteArticle(articles.First());

            List<ArticleBE> actual = business.GetArticles();
            Assert.AreEqual(nbArticles - 1, actual.Count);
        }

        [TestMethod()]
        public void GetArticleById_With_Wrong_Parameters()
        {
            // params
            int fakeId = -1;
            // --

            ArticleBE article = business.GetArticleById(fakeId);

            Assert.IsNull(article);
        }

        [TestMethod()]
        public void AddArticle_With_Wrong_Parameters()
        {
            // params
            string author = "Me";
            string content = "It's not working !";
            string title = "Test article";
            string tags = "great,article,news";
            DateTime now = DateTime.Now;
            int nbArticles = 3;
            // --

            // we check if we have 3 articles
            List<ArticleBE> articles = business.GetArticles();
            Assert.AreEqual(nbArticles, articles.Count);

            ArticleBE article1 = new ArticleBE()
            {
                Author = author,
                Content = content,
                Tags = tags,
                Title = title
            };

            ArticleBE article2 = new ArticleBE()
            {
                Content = content,
                Date = now,
                Tags = tags,
                Title = title
            };

            ArticleBE article3 = new ArticleBE()
            {
                Author = author,
                Content = content,
                Date = now,
                Tags = tags,
            };

            // we create the articles. it shouldn't work
            business.AddArticle(article1);
            business.AddArticle(article2);
            business.AddArticle(article3);

            articles = business.GetArticles();

            // we check if we still have 3 articles
            Assert.AreEqual(nbArticles, articles.Count);
        }

Il est à noter que la méthode MyTestInitialize est appelée avant chaque test unitaire. Ensuite, pour chaque test, on vérifie les données déjà présentes, les données à la fin du test, et, en cas d'erreur, on verra tout de suite que le test a échoué. Comme vous avez pu le voir, mes méthodes de tests ont des noms très clairs, afin de savoir, au premier coup d'oeil, pourquoi nos tests ont pu échouer.

Je pense avoir fait le tour des spécificités du code de cette minuscule application. Si vous ne comprenez pas quelque chose, ou si vous avez besoin d'une précision, n'hésitez pas à demander dans les commentaires.

Enfin, pour déployer cette application chez vous, il vous suffit de restaurer la BDD dans un SQL Server 2008 (script Backup.bak, dans le zip fourni) et de modifier les chaînes de connexion dans les 3 fichiers de configurations (HTA.DemoBlog.DataAccess, HTA.DemoBlog.WebService et HTA.DemoBlog.Tests).

N'hésitez donc pas à tester cette "application" (HTA.DemoBlog.zip (1,54 mb)) et à me donner votre avis. Wink

Bonne journée à tous !

P.S. Oui je sais, le code n'est pas commenté, mais étant donné la simplicité des classes, je n'ai pas pensé que c'était nécessaire.

P.S.2 Quand j'aurais le temps, je ferais probablement une autre version un peu plus poussée avec Moq + StructureMap.

P.S.3 Je crois que c'est mon article le plus long depuis le début de mon blog. 7869 mots, pour presque 30 000 caractères. L'équivalent d'un document Word de 15 pages ! Wouah !

image modifiée de Bert Kaufmann, sous licence CC

Changement d'adresse et redirection en ASP.Net

26. mai 2011 06:19 by Tommy in Actualité, Développement, HowTo  //  Tags: , , , , , , , ,   //   Commentaires (0)   //  Partager sur Facebook  / Twitter

Hello tout le monde,

Comme vous pouvez le voir, l'adresse de mon blog est désormais blog.howtommy.net. En effet, travaillant depuis quelques temps déjà sur mon site web, je voulais déplacer le blog dès maintenant pour ne pas perdre en référencement (à l'aide de redirections permanentes 301).

Et bien figurez vous que... C'est loin d'être simple !

Après quelques heures de recherche et de bidouille, j'ai réussi à rediriger complètement le trafic de mon site web, ainsi :

La classe hein ?

Là vous vous dites que c'est loin d'être compliqué, mais croyez moi, ça l'est. En effet, j'utilise les noms d'hôte dans mon IIS pour rediriger les utilisateurs vers le site désiré (mon serveur hébergeant 3 sites).

La configuration à travers IIS 7 est assez simple, mais récupérer une erreur 404 levée (par exemple via un ancien lien comme http://howtommy.net/post/2011/05/11/Call-Of-Duty-4-vieux-mais-vraiment-bon-!.aspx) pour rediriger l'utilisateur vers la bonne page est bien plus compliqué !

Je pense poster sous peu un tutoriel expliquant la procédure à utiliser pour parvenir à ce résultat.

Sur ce, bonne journée à tous !

image de swimparallel

Bienvenue

Logo Tommy

Bonjour à tous et bienvenue sur mon blog. Je me présente : Tommy, ingénieur développeur .Net.

Que trouverez-vous ici ? Beaucoup de choses, principalement mon point de vue sur l'actualité numérique, mais également des infos diverses, des idées, des astuces pour mieux développer, des bouts de code, ...

N'hésitez pas m'ajouter dans vos favoris ou à me suivre sur Facebook / Twitter !

Bonne visite !

Liens

Raccourci pour les développeurs : http://dev.howtommy.net

Mes liens dédiés aux développeurs : http://liens.howtommy.net/?searchtags=d%C3%A9veloppement

Commentaires

Comment RSS

Par mois

Dernier posts

Hall of fame

microsoft certified professional