add vote for comments and listing (#25)
This commit is contained in:
parent
66cc78d30e
commit
9c75758921
@ -8,9 +8,9 @@ namespace HN.Application
|
||||
{
|
||||
public sealed class GetLinkQueryHandler : IRequestHandler<GetLinkQuery, LinkDto>
|
||||
{
|
||||
private readonly IDbContext _context;
|
||||
private readonly IHNContext _context;
|
||||
|
||||
public GetLinkQueryHandler(IDbContext context)
|
||||
public GetLinkQueryHandler(IHNContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
18
Application/GetLinkComments/CommentDto.cs
Normal file
18
Application/GetLinkComments/CommentDto.cs
Normal file
@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Application/GetLinkComments/GetLinkCommentsQuery.cs
Normal file
17
Application/GetLinkComments/GetLinkCommentsQuery.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MediatR;
|
||||
|
||||
namespace HN.Application
|
||||
{
|
||||
public sealed class GetLinkCommentsQuery : IRequest<CommentDto[]>
|
||||
{
|
||||
[Required]
|
||||
public Guid LinkId { get; set; }
|
||||
|
||||
public GetLinkCommentsQuery(Guid linkId)
|
||||
{
|
||||
LinkId = linkId;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Application/GetLinkComments/GetLinkCommentsQueryHandler.cs
Normal file
34
Application/GetLinkComments/GetLinkCommentsQueryHandler.cs
Normal file
@ -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<GetLinkCommentsQuery, CommentDto[]>
|
||||
{
|
||||
private readonly IHNContext _context;
|
||||
|
||||
public GetLinkCommentsQueryHandler(IHNContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public Task<CommentDto[]> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,8 +6,9 @@ namespace HN.Application
|
||||
/// <summary>
|
||||
/// Interface permettant l'accès aux DbSet pour toute la partie Query.
|
||||
/// </summary>
|
||||
public interface IDbContext
|
||||
public interface IHNContext
|
||||
{
|
||||
DbSet<Link> Links { get; }
|
||||
DbSet<Comment> Comments { get; }
|
||||
}
|
||||
}
|
||||
@ -36,9 +36,9 @@ namespace HN.Application
|
||||
|
||||
public sealed class ListLinksQueryHandler : IRequestHandler<ListLinksQuery, LinkDto[]>
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Application/VoteForComment/VoteForCommentCommand.cs
Normal file
22
Application/VoteForComment/VoteForCommentCommand.cs
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Application/VoteForComment/VoteForCommentCommandHandler.cs
Normal file
36
Application/VoteForComment/VoteForCommentCommandHandler.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using HN.Domain;
|
||||
using MediatR;
|
||||
|
||||
namespace HN.Application
|
||||
{
|
||||
public sealed class VoteForCommentCommandHandler : IRequestHandler<VoteForCommentCommand>
|
||||
{
|
||||
private readonly ICommentRepository _commentRepository;
|
||||
|
||||
public VoteForCommentCommandHandler(ICommentRepository commentRepository)
|
||||
{
|
||||
_commentRepository = commentRepository;
|
||||
}
|
||||
|
||||
public async Task<Unit> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<IActionResult> Vote(Guid id, VoteType type, string redirectTo)
|
||||
{
|
||||
await _bus.Send(new VoteForCommentCommand(id, type));
|
||||
|
||||
SetFlash($"Comment {type} added!");
|
||||
|
||||
return Redirect(redirectTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,9 @@ namespace Website.Controllers
|
||||
[HttpGet("{controller}/{id:guid}")]
|
||||
public async Task<IActionResult> 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()
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,10 +25,10 @@ namespace Website
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<HNDbContext>(options => options.UseSqlite(Configuration.GetConnectionString("Default")));
|
||||
services.AddScoped<IDbContext, HNDbContext>();
|
||||
services.AddScoped<IHNContext, HNDbContext>();
|
||||
services.AddScoped<ILinkRepository, LinkRepository>();
|
||||
services.AddScoped<ICommentRepository, CommentRepository>();
|
||||
services.AddMediatR(typeof(HN.Application.IDbContext));
|
||||
services.AddMediatR(typeof(HN.Application.IHNContext));
|
||||
|
||||
services.Configure<RouteOptions>(options =>
|
||||
{
|
||||
|
||||
@ -1,4 +1,16 @@
|
||||
@model ShowLinkViewModel
|
||||
|
||||
<partial name="_LinkItem" model="@Model.Link" />
|
||||
|
||||
@if(Model.Comments.Length == 0) {
|
||||
<p>No comments yet</p>
|
||||
} else {
|
||||
<ul>
|
||||
@foreach (var comment in Model.Comments)
|
||||
{
|
||||
<li><partial name="_CommentItem" model="@comment" /></li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
<partial name="_CommentForm" model="@Model.CommentForm" />
|
||||
11
Apps/Website/Views/Shared/_CommentItem.cshtml
Normal file
11
Apps/Website/Views/Shared/_CommentItem.cshtml
Normal file
@ -0,0 +1,11 @@
|
||||
@model HN.Application.CommentDto
|
||||
|
||||
<span>@Model.Content</span>
|
||||
<div>
|
||||
👍: @Model.UpVotes / 👎: @Model.DownVotes
|
||||
</div>
|
||||
<form asp-action="Vote" asp-controller="Comments" asp-route-id="@Model.Id" method="post">
|
||||
<input type="hidden" name="redirectTo" value="@Context.Request.Path" />
|
||||
<input type="submit" name="type" value="up" />
|
||||
<input type="submit" name="type" value="down" />
|
||||
</form>
|
||||
@ -4,6 +4,6 @@
|
||||
|
||||
<form asp-controller="Links" asp-action="Vote" asp-route-id="@Model.Id" asp-route-url="@Model.Url" method="post">
|
||||
<input type="hidden" name="redirectTo" value="@Context.Request.Path" />
|
||||
<input type="submit" name="type" value="up">👍</button>
|
||||
<input type="submit" name="type" value="down">👎</button>
|
||||
<input type="submit" name="type" value="up" />
|
||||
<input type="submit" name="type" value="down" />
|
||||
</form>
|
||||
@ -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<Vote> _votes;
|
||||
public IReadOnlyList<Vote> 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<Vote>();
|
||||
}
|
||||
|
||||
public void Upvote()
|
||||
{
|
||||
_votes.Add(new Vote(VoteType.Up));
|
||||
}
|
||||
|
||||
public void Downvote()
|
||||
{
|
||||
_votes.Add(new Vote(VoteType.Down));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Comment> GetByIdAsync(Guid id);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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<Comment> GetByIdAsync(Guid id)
|
||||
{
|
||||
return Entries.SingleOrDefaultAsync(o => o.Id == id);
|
||||
}
|
||||
|
||||
public Task UpdateAsync(Comment comment)
|
||||
{
|
||||
return base.UpdateAsync(comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<Link> Links { get; set; }
|
||||
public DbSet<Comment> Comments { get; set; }
|
||||
|
||||
public HNDbContext()
|
||||
{
|
||||
|
||||
122
Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs
generated
Normal file
122
Infrastructure/Migrations/20201211102901_CreateCommentVote.Designer.cs
generated
Normal file
@ -0,0 +1,122 @@
|
||||
// <auto-generated />
|
||||
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<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("LinkId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("LinkId");
|
||||
|
||||
b.ToTable("comments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HN.Domain.Link", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("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<Guid>("CommentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<int>("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<Guid>("LinkId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
|
||||
b1.ToTable("link_votes");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("LinkId");
|
||||
});
|
||||
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Guid>(type: "TEXT", nullable: false),
|
||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,22 +61,6 @@ namespace Infrastructure.Migrations
|
||||
b.ToTable("links");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HN.Domain.Vote", b =>
|
||||
{
|
||||
b.Property<Guid>("LinkId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("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();
|
||||
|
||||
b.OwnsMany("HN.Domain.Vote", "Votes", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CommentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.HasKey("CommentId");
|
||||
|
||||
b1.ToTable("comment_votes");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CommentId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HN.Domain.Vote", b =>
|
||||
{
|
||||
b.HasOne("HN.Domain.Link", null)
|
||||
.WithMany("Votes")
|
||||
.HasForeignKey("LinkId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("HN.Domain.Link", b =>
|
||||
{
|
||||
b.OwnsMany("HN.Domain.Vote", "Votes", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("LinkId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.HasKey("LinkId");
|
||||
|
||||
b1.ToTable("link_votes");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("LinkId");
|
||||
});
|
||||
|
||||
b.Navigation("Votes");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
|
||||
33
README.md
33
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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user