From 9c7575892104d6418e10347cd87d25ab25e31325 Mon Sep 17 00:00:00 2001 From: Julien Leicher Date: Fri, 11 Dec 2020 12:02:45 +0100 Subject: [PATCH] add vote for comments and listing (#25) --- Application/GetLink/GetLinkQueryHandler.cs | 4 +- Application/GetLinkComments/CommentDto.cs | 18 +++ .../GetLinkComments/GetLinkCommentsQuery.cs | 17 +++ .../GetLinkCommentsQueryHandler.cs | 34 +++++ Application/{IDbContext.cs => IHNContext.cs} | 3 +- .../ListLinks/ListLinksQueryHandler.cs | 6 +- .../VoteForComment/VoteForCommentCommand.cs | 22 ++++ .../VoteForCommentCommandHandler.cs | 36 ++++++ .../Website/Controllers/CommentsController.cs | 17 ++- Apps/Website/Controllers/LinksController.cs | 4 +- Apps/Website/Models/ShowLinkViewModel.cs | 4 +- Apps/Website/Startup.cs | 4 +- Apps/Website/Views/Links/Show.cshtml | 12 ++ Apps/Website/Views/Shared/_CommentItem.cshtml | 11 ++ Apps/Website/Views/Shared/_LinkItem.cshtml | 4 +- Domain/Comment.cs | 14 ++ Domain/ICommentRepository.cs | 3 + Domain/Vote.cs | 2 +- Infrastructure/CommentRepository.cs | 12 ++ .../EntityTypes/CommentEntityType.cs | 9 ++ Infrastructure/HNDbContext.cs | 3 +- ...201211102901_CreateCommentVote.Designer.cs | 122 ++++++++++++++++++ .../20201211102901_CreateCommentVote.cs | 36 ++++++ .../Migrations/HNDbContextModelSnapshot.cs | 63 +++++---- README.md | 33 +++++ 25 files changed, 454 insertions(+), 39 deletions(-) create mode 100644 Application/GetLinkComments/CommentDto.cs create mode 100644 Application/GetLinkComments/GetLinkCommentsQuery.cs create mode 100644 Application/GetLinkComments/GetLinkCommentsQueryHandler.cs rename Application/{IDbContext.cs => IHNContext.cs} (77%) create mode 100644 Application/VoteForComment/VoteForCommentCommand.cs create mode 100644 Application/VoteForComment/VoteForCommentCommandHandler.cs create mode 100644 Apps/Website/Views/Shared/_CommentItem.cshtml create mode 100644 Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs create mode 100644 Infrastructure/Migrations/20201211102901_CreateCommentVote.cs diff --git a/Application/GetLink/GetLinkQueryHandler.cs b/Application/GetLink/GetLinkQueryHandler.cs index fa6ab91..65dc04e 100644 --- a/Application/GetLink/GetLinkQueryHandler.cs +++ b/Application/GetLink/GetLinkQueryHandler.cs @@ -8,9 +8,9 @@ namespace HN.Application { public sealed class GetLinkQueryHandler : IRequestHandler { - private readonly IDbContext _context; + private readonly IHNContext _context; - public GetLinkQueryHandler(IDbContext context) + public GetLinkQueryHandler(IHNContext context) { _context = context; } diff --git a/Application/GetLinkComments/CommentDto.cs b/Application/GetLinkComments/CommentDto.cs new file mode 100644 index 0000000..0ff565d --- /dev/null +++ b/Application/GetLinkComments/CommentDto.cs @@ -0,0 +1,18 @@ +using System; + +namespace HN.Application +{ + public sealed class CommentDto + { + public Guid Id { get; set; } + public string Content { get; set; } + public DateTime CreatedAt { get; set; } + public int UpVotes { get; set; } + public int DownVotes { get; set; } + + public CommentDto() + { + + } + } +} \ No newline at end of file diff --git a/Application/GetLinkComments/GetLinkCommentsQuery.cs b/Application/GetLinkComments/GetLinkCommentsQuery.cs new file mode 100644 index 0000000..278ca4f --- /dev/null +++ b/Application/GetLinkComments/GetLinkCommentsQuery.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; +using MediatR; + +namespace HN.Application +{ + public sealed class GetLinkCommentsQuery : IRequest + { + [Required] + public Guid LinkId { get; set; } + + public GetLinkCommentsQuery(Guid linkId) + { + LinkId = linkId; + } + } +} \ No newline at end of file diff --git a/Application/GetLinkComments/GetLinkCommentsQueryHandler.cs b/Application/GetLinkComments/GetLinkCommentsQueryHandler.cs new file mode 100644 index 0000000..08e3a0d --- /dev/null +++ b/Application/GetLinkComments/GetLinkCommentsQueryHandler.cs @@ -0,0 +1,34 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using HN.Domain; +using MediatR; + +namespace HN.Application +{ + public sealed class GetLinkCommentsQueryHandler : IRequestHandler + { + private readonly IHNContext _context; + + public GetLinkCommentsQueryHandler(IHNContext context) + { + _context = context; + } + + public Task Handle(GetLinkCommentsQuery request, CancellationToken cancellationToken) + { + var comments = from comment in _context.Comments + where comment.LinkId == request.LinkId + select new CommentDto + { + Id = comment.Id, + CreatedAt = comment.CreatedAt, + Content = comment.Content, + UpVotes = comment.Votes.Count(c => c.Type == VoteType.Up), + DownVotes = comment.Votes.Count(c => c.Type == VoteType.Down) + }; + + return Task.FromResult(comments.OrderBy(c => c.CreatedAt).ToArray()); + } + } +} \ No newline at end of file diff --git a/Application/IDbContext.cs b/Application/IHNContext.cs similarity index 77% rename from Application/IDbContext.cs rename to Application/IHNContext.cs index f67beff..1facc5e 100644 --- a/Application/IDbContext.cs +++ b/Application/IHNContext.cs @@ -6,8 +6,9 @@ namespace HN.Application /// /// Interface permettant l'accès aux DbSet pour toute la partie Query. /// - public interface IDbContext + public interface IHNContext { DbSet Links { get; } + DbSet Comments { get; } } } diff --git a/Application/ListLinks/ListLinksQueryHandler.cs b/Application/ListLinks/ListLinksQueryHandler.cs index 5360ef2..8851c34 100644 --- a/Application/ListLinks/ListLinksQueryHandler.cs +++ b/Application/ListLinks/ListLinksQueryHandler.cs @@ -36,9 +36,9 @@ namespace HN.Application public sealed class ListLinksQueryHandler : IRequestHandler { - private readonly IDbContext _context; + private readonly IHNContext _context; - public ListLinksQueryHandler(IDbContext context) + public ListLinksQueryHandler(IHNContext context) { _context = context; } @@ -55,7 +55,7 @@ namespace HN.Application DownVotes = link.Votes.Count(v => v.Type == VoteType.Down) }; - return Task.FromResult(links.ToArray()); + return Task.FromResult(links.OrderByDescending(l => l.CreatedAt).ToArray()); } } } \ No newline at end of file diff --git a/Application/VoteForComment/VoteForCommentCommand.cs b/Application/VoteForComment/VoteForCommentCommand.cs new file mode 100644 index 0000000..3e2b416 --- /dev/null +++ b/Application/VoteForComment/VoteForCommentCommand.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations; +using HN.Domain; +using MediatR; + +namespace HN.Application +{ + public sealed class VoteForCommentCommand : IRequest + { + [Required] + public Guid CommentId { get; set; } + + [Required] + public VoteType Type { get; set; } + + public VoteForCommentCommand(Guid commentId, VoteType type) + { + CommentId = commentId; + Type = type; + } + } +} \ No newline at end of file diff --git a/Application/VoteForComment/VoteForCommentCommandHandler.cs b/Application/VoteForComment/VoteForCommentCommandHandler.cs new file mode 100644 index 0000000..153ce76 --- /dev/null +++ b/Application/VoteForComment/VoteForCommentCommandHandler.cs @@ -0,0 +1,36 @@ +using System.Threading; +using System.Threading.Tasks; +using HN.Domain; +using MediatR; + +namespace HN.Application +{ + public sealed class VoteForCommentCommandHandler : IRequestHandler + { + private readonly ICommentRepository _commentRepository; + + public VoteForCommentCommandHandler(ICommentRepository commentRepository) + { + _commentRepository = commentRepository; + } + + public async Task Handle(VoteForCommentCommand request, CancellationToken cancellationToken) + { + var comment = await _commentRepository.GetByIdAsync(request.CommentId); + + switch (request.Type) + { + case VoteType.Up: + comment.Upvote(); + break; + case VoteType.Down: + comment.Downvote(); + break; + } + + await _commentRepository.UpdateAsync(comment); + + return Unit.Value; + } + } +} \ No newline at end of file diff --git a/Apps/Website/Controllers/CommentsController.cs b/Apps/Website/Controllers/CommentsController.cs index 1f73833..927e23f 100644 --- a/Apps/Website/Controllers/CommentsController.cs +++ b/Apps/Website/Controllers/CommentsController.cs @@ -3,6 +3,8 @@ using Website.Models; using Microsoft.AspNetCore.Mvc; using MediatR; using System.Threading.Tasks; +using HN.Domain; +using System; namespace Website.Controllers { @@ -21,7 +23,9 @@ namespace Website.Controllers { if (!ModelState.IsValid) { - return View("../Links/Show", new ShowLinkViewModel(await _bus.Send(new GetLinkQuery(command.LinkId)), command)); + var link = await _bus.Send(new GetLinkQuery(command.LinkId)); + var comments = await _bus.Send(new GetLinkCommentsQuery(command.LinkId)); + return View("../Links/Show", new ShowLinkViewModel(link, command, comments)); } await _bus.Send(command); @@ -30,5 +34,16 @@ namespace Website.Controllers return RedirectToAction(nameof(LinksController.Show), "Links", new { id = command.LinkId }); } + + [HttpPost("{controller}/{id:guid}/vote")] + [ValidateAntiForgeryToken] + public async Task Vote(Guid id, VoteType type, string redirectTo) + { + await _bus.Send(new VoteForCommentCommand(id, type)); + + SetFlash($"Comment {type} added!"); + + return Redirect(redirectTo); + } } } \ No newline at end of file diff --git a/Apps/Website/Controllers/LinksController.cs b/Apps/Website/Controllers/LinksController.cs index 47b7d49..09e2bb7 100644 --- a/Apps/Website/Controllers/LinksController.cs +++ b/Apps/Website/Controllers/LinksController.cs @@ -26,7 +26,9 @@ namespace Website.Controllers [HttpGet("{controller}/{id:guid}")] public async Task Show(Guid id) { - return View(new ShowLinkViewModel(await _bus.Send(new GetLinkQuery(id)), new CommentLinkCommand(id))); + var link = await _bus.Send(new GetLinkQuery(id)); + var comments = await _bus.Send(new GetLinkCommentsQuery(id)); + return View(new ShowLinkViewModel(link, new CommentLinkCommand(id), comments)); } public IActionResult Create() diff --git a/Apps/Website/Models/ShowLinkViewModel.cs b/Apps/Website/Models/ShowLinkViewModel.cs index 739fc09..b8fbda3 100644 --- a/Apps/Website/Models/ShowLinkViewModel.cs +++ b/Apps/Website/Models/ShowLinkViewModel.cs @@ -7,11 +7,13 @@ namespace Website.Models public LinkDto Link { get; set; } public CommentLinkCommand CommentForm { get; set; } + public CommentDto[] Comments { get; set; } - public ShowLinkViewModel(LinkDto link, CommentLinkCommand commentForm) + public ShowLinkViewModel(LinkDto link, CommentLinkCommand commentForm, CommentDto[] comments) { Link = link; CommentForm = commentForm; + Comments = comments; } } diff --git a/Apps/Website/Startup.cs b/Apps/Website/Startup.cs index ac9ec92..d02c726 100644 --- a/Apps/Website/Startup.cs +++ b/Apps/Website/Startup.cs @@ -25,10 +25,10 @@ namespace Website public void ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseSqlite(Configuration.GetConnectionString("Default"))); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddMediatR(typeof(HN.Application.IDbContext)); + services.AddMediatR(typeof(HN.Application.IHNContext)); services.Configure(options => { diff --git a/Apps/Website/Views/Links/Show.cshtml b/Apps/Website/Views/Links/Show.cshtml index 656418b..8e1bfd6 100644 --- a/Apps/Website/Views/Links/Show.cshtml +++ b/Apps/Website/Views/Links/Show.cshtml @@ -1,4 +1,16 @@ @model ShowLinkViewModel + +@if(Model.Comments.Length == 0) { +

No comments yet

+} else { +
    + @foreach (var comment in Model.Comments) + { +
  • + } +
+} + \ No newline at end of file diff --git a/Apps/Website/Views/Shared/_CommentItem.cshtml b/Apps/Website/Views/Shared/_CommentItem.cshtml new file mode 100644 index 0000000..18aedb4 --- /dev/null +++ b/Apps/Website/Views/Shared/_CommentItem.cshtml @@ -0,0 +1,11 @@ +@model HN.Application.CommentDto + +@Model.Content +
+ 👍: @Model.UpVotes / 👎: @Model.DownVotes +
+
+ + + +
\ No newline at end of file diff --git a/Apps/Website/Views/Shared/_LinkItem.cshtml b/Apps/Website/Views/Shared/_LinkItem.cshtml index ecc19fc..cf16e40 100644 --- a/Apps/Website/Views/Shared/_LinkItem.cshtml +++ b/Apps/Website/Views/Shared/_LinkItem.cshtml @@ -4,6 +4,6 @@
- 👍 - 👎 + +
\ No newline at end of file diff --git a/Domain/Comment.cs b/Domain/Comment.cs index c1e1ba7..91c688e 100644 --- a/Domain/Comment.cs +++ b/Domain/Comment.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace HN.Domain { @@ -8,6 +9,8 @@ namespace HN.Domain public Guid LinkId { get; private set; } public string Content { get; private set; } public DateTime CreatedAt { get; private set; } + private List _votes; + public IReadOnlyList Votes => _votes; internal Comment(Guid linkId, string content) { @@ -15,6 +18,17 @@ namespace HN.Domain LinkId = linkId; Content = content; CreatedAt = DateTime.UtcNow; + _votes = new List(); + } + + public void Upvote() + { + _votes.Add(new Vote(VoteType.Up)); + } + + public void Downvote() + { + _votes.Add(new Vote(VoteType.Down)); } } } \ No newline at end of file diff --git a/Domain/ICommentRepository.cs b/Domain/ICommentRepository.cs index d5d48fb..77706bc 100644 --- a/Domain/ICommentRepository.cs +++ b/Domain/ICommentRepository.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; namespace HN.Domain @@ -5,5 +6,7 @@ namespace HN.Domain public interface ICommentRepository { Task AddAsync(Comment comment); + Task UpdateAsync(Comment comment); + Task GetByIdAsync(Guid id); } } \ No newline at end of file diff --git a/Domain/Vote.cs b/Domain/Vote.cs index e48b4ff..ea28898 100644 --- a/Domain/Vote.cs +++ b/Domain/Vote.cs @@ -7,7 +7,7 @@ namespace HN.Domain public VoteType Type { get; private set; } public DateTime CreatedAt { get; private set; } - public Vote(VoteType type) + internal Vote(VoteType type) { Type = type; CreatedAt = DateTime.UtcNow; diff --git a/Infrastructure/CommentRepository.cs b/Infrastructure/CommentRepository.cs index 7652178..3fbe0f7 100644 --- a/Infrastructure/CommentRepository.cs +++ b/Infrastructure/CommentRepository.cs @@ -1,5 +1,7 @@ +using System; using System.Threading.Tasks; using HN.Domain; +using Microsoft.EntityFrameworkCore; namespace HN.Infrastructure { @@ -13,5 +15,15 @@ namespace HN.Infrastructure { return base.AddAsync(comment); } + + public Task GetByIdAsync(Guid id) + { + return Entries.SingleOrDefaultAsync(o => o.Id == id); + } + + public Task UpdateAsync(Comment comment) + { + return base.UpdateAsync(comment); + } } } \ No newline at end of file diff --git a/Infrastructure/EntityTypes/CommentEntityType.cs b/Infrastructure/EntityTypes/CommentEntityType.cs index 610b27c..b6b4a66 100644 --- a/Infrastructure/EntityTypes/CommentEntityType.cs +++ b/Infrastructure/EntityTypes/CommentEntityType.cs @@ -16,6 +16,15 @@ namespace HN.Infrastructure .IsRequired(); builder.Property(o => o.Content).IsRequired(); builder.Property(o => o.CreatedAt).IsRequired(); + + builder.OwnsMany(o => o.Votes, vote => + { + vote.ToTable("comment_votes"); + vote.WithOwner().HasForeignKey("CommentId"); + vote.HasKey("CommentId"); + vote.Property(o => o.Type).IsRequired(); + vote.Property(o => o.CreatedAt).IsRequired(); + }); } } diff --git a/Infrastructure/HNDbContext.cs b/Infrastructure/HNDbContext.cs index d51e40a..6236ca5 100644 --- a/Infrastructure/HNDbContext.cs +++ b/Infrastructure/HNDbContext.cs @@ -5,10 +5,11 @@ using Microsoft.Extensions.Logging; namespace HN.Infrastructure { - public sealed class HNDbContext : DbContext, IDbContext + public sealed class HNDbContext : DbContext, IHNContext { private readonly ILoggerFactory _loggerFactory; public DbSet Links { get; set; } + public DbSet Comments { get; set; } public HNDbContext() { diff --git a/Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs b/Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs new file mode 100644 index 0000000..8259df3 --- /dev/null +++ b/Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs @@ -0,0 +1,122 @@ +// +using System; +using HN.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(HNDbContext))] + [Migration("20201211102901_CreateCommentVote")] + partial class CreateCommentVote + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.0"); + + modelBuilder.Entity("HN.Domain.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LinkId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LinkId"); + + b.ToTable("comments"); + }); + + modelBuilder.Entity("HN.Domain.Link", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Url") + .IsUnique(); + + b.ToTable("links"); + }); + + modelBuilder.Entity("HN.Domain.Comment", b => + { + b.HasOne("HN.Domain.Link", null) + .WithMany() + .HasForeignKey("LinkId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("HN.Domain.Vote", "Votes", b1 => + { + b1.Property("CommentId") + .HasColumnType("TEXT"); + + b1.Property("CreatedAt") + .HasColumnType("TEXT"); + + b1.Property("Type") + .HasColumnType("INTEGER"); + + b1.HasKey("CommentId"); + + b1.ToTable("comment_votes"); + + b1.WithOwner() + .HasForeignKey("CommentId"); + }); + + b.Navigation("Votes"); + }); + + modelBuilder.Entity("HN.Domain.Link", b => + { + b.OwnsMany("HN.Domain.Vote", "Votes", b1 => + { + b1.Property("LinkId") + .HasColumnType("TEXT"); + + b1.Property("CreatedAt") + .HasColumnType("TEXT"); + + b1.Property("Type") + .HasColumnType("INTEGER"); + + b1.HasKey("LinkId"); + + b1.ToTable("link_votes"); + + b1.WithOwner() + .HasForeignKey("LinkId"); + }); + + b.Navigation("Votes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Infrastructure/Migrations/20201211102901_CreateCommentVote.cs b/Infrastructure/Migrations/20201211102901_CreateCommentVote.cs new file mode 100644 index 0000000..c30a381 --- /dev/null +++ b/Infrastructure/Migrations/20201211102901_CreateCommentVote.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Infrastructure.Migrations +{ + public partial class CreateCommentVote : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "comment_votes", + columns: table => new + { + CommentId = table.Column(type: "TEXT", nullable: false), + Type = table.Column(type: "INTEGER", nullable: false), + CreatedAt = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_comment_votes", x => x.CommentId); + table.ForeignKey( + name: "FK_comment_votes_comments_CommentId", + column: x => x.CommentId, + principalTable: "comments", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "comment_votes"); + } + } +} diff --git a/Infrastructure/Migrations/HNDbContextModelSnapshot.cs b/Infrastructure/Migrations/HNDbContextModelSnapshot.cs index 16756d5..200017a 100644 --- a/Infrastructure/Migrations/HNDbContextModelSnapshot.cs +++ b/Infrastructure/Migrations/HNDbContextModelSnapshot.cs @@ -61,22 +61,6 @@ namespace Infrastructure.Migrations b.ToTable("links"); }); - modelBuilder.Entity("HN.Domain.Vote", b => - { - b.Property("LinkId") - .HasColumnType("TEXT"); - - b.Property("CreatedAt") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("INTEGER"); - - b.HasKey("LinkId"); - - b.ToTable("link_votes"); - }); - modelBuilder.Entity("HN.Domain.Comment", b => { b.HasOne("HN.Domain.Link", null) @@ -84,19 +68,50 @@ namespace Infrastructure.Migrations .HasForeignKey("LinkId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - }); - modelBuilder.Entity("HN.Domain.Vote", b => - { - b.HasOne("HN.Domain.Link", null) - .WithMany("Votes") - .HasForeignKey("LinkId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + b.OwnsMany("HN.Domain.Vote", "Votes", b1 => + { + b1.Property("CommentId") + .HasColumnType("TEXT"); + + b1.Property("CreatedAt") + .HasColumnType("TEXT"); + + b1.Property("Type") + .HasColumnType("INTEGER"); + + b1.HasKey("CommentId"); + + b1.ToTable("comment_votes"); + + b1.WithOwner() + .HasForeignKey("CommentId"); + }); + + b.Navigation("Votes"); }); modelBuilder.Entity("HN.Domain.Link", b => { + b.OwnsMany("HN.Domain.Vote", "Votes", b1 => + { + b1.Property("LinkId") + .HasColumnType("TEXT"); + + b1.Property("CreatedAt") + .HasColumnType("TEXT"); + + b1.Property("Type") + .HasColumnType("INTEGER"); + + b1.HasKey("LinkId"); + + b1.ToTable("link_votes"); + + b1.WithOwner() + .HasForeignKey("LinkId"); + }); + b.Navigation("Votes"); }); #pragma warning restore 612, 618 diff --git a/README.md b/README.md index 3cbcf30..d2bdf16 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,41 @@ Le but est de réaliser de A à Z une application Web imitant le fonctionnement de Hacker News en abordant des bonnes pratiques de développement en .Net. +## Plan de la formation + +- Présentation d'ASP.Net Core +- Mise en place du domaine (Création de librairies de classes) +- Mise en place d'une couche applicative découplée avec MediatR +- Persistence des données avec EF Core + - Aperçu de l'outil + - Définir le modèle grâce à l'API Fluent + - Gérer les migrations +- Réalisation d'un projet MVC + - Rappels sur l'architecture MVC + - Injection de dépendances + - Afficher une page et naviguer entre les pages + - Gestion des formulaires + - Routage conventionnel / par attributs + - Découpage des vues grâce aux vues partielles et composants + - Utilisation de TempData pour les messages de statut + - Authentification et autorisation + - Déploiement +- Réalisation d'une API Rest + - Rappels sur l'architecture REST + - Exposer nos cas d'utilisations au travers de ressources + - Documenter l'API +- Réalisation d'un client en Blazor + - Présentation + - Découpage en composants + - Gestion des formulaires + ## Ressources utiles - https://aspnetcore.readthedocs.io/en/stable/mvc/index.html - https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/ - https://stackoverflow.com/a/47011478 - https://stackoverflow.com/a/34291650 +- https://chrissainty.com/securing-your-blazor-apps-authentication-with-clientside-blazor-using-webapi-aspnet-core-identity/ ## Commençer par le domaine ! @@ -19,6 +48,10 @@ Mise en place de la couche applicative avec `MediatR` et implémentation du prem ## Le site internet en MVC +### Présentation de l'architecture du projet de base + +Aborder l'injection de dépendances et le fichier `Startup.cs` en général. + ### Options pattern Permet d'avoir des objets de configuration facilement sortable depuis l'appsettings. Utilisation de https://docs.microsoft.com/fr-fr/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0.