ASP.NET Core MVCでブログ: 記事作成機能を作る

 前回は認証機能を実装した。今回は記事の作成を実装する。

 まずは管理者ページのコントローラに、編集ページとそこからのPOSTを受け取るアクションを加える。

/Controllers/MasterController.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using tetsujin.Models;
using tetsujin.Filters;

namespace tetsujin.Controllers
{
[AuthorizationFilter]
[Route("Master")]
public class MasterController : Controller
{
[Route("")]
public IActionResult Index()
{
return View();
}

[Route("Edit/{id:int?}")]
public IActionResult Edit(int? id)
{
Entry entry;
if (id != null)
{
int entryId = id ?? 0;
entry = Entry.GetEntry(entryId, true);
}
else
{
var timedelta = 9;
entry = new Entry
{
Tag = new List<string> { },
PublishDate = DateTime.Now.AddHours(timedelta),
IsShown = true
};
}
ViewBag.entry = entry;
return View(entry);
}

[Route("Edit")]
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditPost(Entry entry)
{
entry.InsertOrUpdate();
return View();
}
}
}

POSTを受け取るアクションにはValidateAntiForgeryTokenという属性をつける。これで、CSRF対策のトークンチェックが行われるようになる。あとはビューで、トークンのセットを行う。

/Views/Master/Edit.cshtml

@model tetsujin.Models.Entry
@{
Layout = "/Views/Shared/_MasterLayout.cshtml";
ViewBag.Title = "Edit | Master Page";
}

<form action="/Master/Edit" method="post">

@Html.AntiForgeryToken()

<input type="hidden" name="EntryID" value="@ViewBag.entry.EntryID" />



<div>
@Html.LabelFor(model => model.Title)
<div>
@Html.EditorFor(model => model.Title,
new { htmlAttributes = new { @class = "edit-input" } })
</div>
</div>

<div>
@Html.LabelFor(model => model.PublishDate)
<div>
@Html.EditorFor(model => model.PublishDate)
</div>
</div>

<div>
@Html.LabelFor(model => model._Tag)
<div>
@Html.EditorFor(model => model._Tag,
new { htmlAttributes = new { @class = "edit-input" } })
</div>
</div>

<div>
@Html.LabelFor(model => model.Body)
<div>
@Html.EditorFor(model => model.Body,
new
{
htmlAttributes = new
{
@cols = "60",
@rows = "20",
@ondrop = "drop(event, this)",
@ondragover = "allowDrop(event)"
}
})
<div id="images"></div>
</div>
</div>
<div>
@Html.LabelFor(model => model.IsShown)
@Html.EditorFor(model => model.IsShown)
</div>

<input type="submit" value="post">

</form>


/Views/Shared/_MasterLayout.cshtml

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - Hello Absurd World!</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" />
<style>
html, body {
background-color: #000;
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}

header {
min-height: 40px;
padding-left: 15px;
padding-top: 11px;
}

#title {
color: #eee;
margin-bottom: 0px;
}

#footerbar {
background-color: #000;
width: 100%;
height: auto;
color: #ccc;
}


.right {
text-align: right;
padding-right: 10px;
}

.body-content {
width: 100%;
min-height: 600px;
background-color: #216B72;
padding-top: 80px;
padding-bottom: 80px;
padding-left: 40px;
padding-right: 20px;
}

.border {
background-image: linear-gradient(to right, #333 0%, #fff 15%, #555 30%, #333 100%);
width: 100%;
height: 2px;
}

#entry {
background: #fff;
box-shadow: 0px 0px 2px 2px #ccc inset;
}

#pageLink {
text-align: left;
}

#sidebar {
background: #fff;
box-shadow: 0px 0px 2px 2px #ccc inset;
padding: 8px;
}

.sidebar-title {
background: #000;
color: white;
padding: 3px;
}

.sidebar-body {
color: black;
padding: 3px;
}

.entry-title {
position: relative;
left: 30px;
}

.entry-date {
background-color: blueviolet;
color: white;
text-align: center;
width: 40px;
height: 75px;
padding-top: 5px;
position: relative;
top: -50px;
right: 30px;
font: "Georgia", "Times New Roman", "Times", serif;
box-shadow: 3px 1px 3px #aaa;
}

.entry-body {
padding: 25px;
}

.entry-commentcount {
text-align: right;
}

.edit-input {
width: 400px;
}

.page {
text-align: center;
width: 25px;
height: 25px;
margin-left: 25px;
margin-top: 8px;
background-color: black;
}

.img {
max-width: 100%;
max-height: 100%;
box-shadow: 0px 0px 6px 3px #e3e3e3;
margin-top: 10px;
}

#Body {
display: inline-block;
vertical-align: top;
}

#images {
display: inline-block;
vertical-align: top;
width: 270px;
height: 400px;
overflow-y: scroll;
}
</style>
</head>
<body>

<header class="">
<a id="title" class="h4" href="/Master">Master Page</a>
</header>
<div class="border"></div>

<div class="container body-content">

<div id="entry" class="col-xs-12 col-md-8">
@RenderBody()
</div>

<div id="sidebar" class="col-xs-12 col-md-offset-1 col-md-3">
<div>
<a href="/Master/Edit">記事追加</a>
</div>
</div>
</div>

<div class="border"></div>
<footer id="footerbar"></footer>
</body>
</html>


 記事をPOSTしたときに返されるビューを追加。
/Views/Master/EditPost.cshtml

@{
Layout = "/Views/Shared/_MasterLayout.cshtml";
}

記事が投稿されました。


 記事モデルを用意。
/Models/Entry.cs

using MangoFramework;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace tetsujin.Models
{
[MongoDoc]
public class Entry
{
public static string CollectionName = "Entry";

[BsonId]
[BsonElement("_id")]
[BsonRepresentation(BsonType.Int32)]
[HiddenInput]
public int? EntryID { get; set; } = null;

[DisplayName("タイトル")]
[BsonRepresentation(BsonType.String)]
[BsonElement("title")]
public string Title { get; set; }

[DisplayName("発行日時")]
[BsonRepresentation(BsonType.DateTime)]
[BsonElement("publishDate")]
[DataType(DataType.DateTime)]
public DateTime PublishDate { get; set; }

[BsonRepresentation(BsonType.String)]
[BsonElement("tag")]
public List<string> Tag { get; set; }

[DisplayName("タグ")]
[BsonIgnore]
public string _Tag { get; set; }

[DisplayName("本文")]
[BsonRepresentation(BsonType.String)]
[BsonElement("body")]
[DataType(DataType.MultilineText)]
public string Body { get; set; }

[DisplayName("掲載フラグ")]
[BsonRepresentation(BsonType.Boolean)]
[BsonElement("isShown")]
public bool IsShown { get; set; }

public static Entry GetEntry(int id, bool admin = false)
{
var collection = DbConnection.Db.GetCollection<Entry>(Entry.CollectionName);

FilterDefinition<Entry> filter;
var f = Builders<Entry>.Filter;
if (admin)
{
filter = f.Eq(e => e.EntryID, id);
}
else
{

filter = f.Eq(e => e.EntryID, id) &
f.Eq(e => e.IsShown, true);
}

var entry = collection.Find<Entry>(filter).FirstOrDefault();
if (entry != null && entry.Tag.Count() > 0)
{
entry._Tag = String.Join(",", entry.Tag);
}

return entry;
}

public void InsertOrUpdate()
{
this.Tag = !String.IsNullOrEmpty(this._Tag) ? this._Tag.Split(',').ToList<string>() : new List<string> { };

if (EntryID == null)
{
Insert();
}
else
{
Update();
}
}

public void Insert()
{
var collection = DbConnection.Db.GetCollection<Entry>(Entry.CollectionName);

var sortDoc = Builders<Entry>.Sort.Descending(e => e.EntryID);
var d = collection.Find<Entry>(new BsonDocument { })
.Sort(sortDoc)
.FirstOrDefault<Entry>();

var id = (d == null) ? 0 : d.EntryID + 1;

this.EntryID = (int)id;
collection.InsertOne(this);
}

public void Update()
{
Tag.RemoveAll(item => item == null);

var collection = DbConnection.Db.GetCollection<Entry>(Entry.CollectionName);

var filter = Builders<Entry>.Filter.Eq("_id", this.EntryID);
collection.ReplaceOne(filter, this);
}

}

}


 デバッグを開始する。適当に入力して投稿してみる。


 投稿できたらデータベースを確認してみる。入っている。



 /Master/Edit/[Entry ID]にアクセスしてみる。投稿した記事が編集できる。


今回のコミット
※追記
/Controllers/MasterController.csにおいてAuthorizationFilterをつける場所を間違えていた。AuthorizationFilterはアクションではなくコントローラクラスにつける。
 
comment: 0