ASP.NET Core MVCでブログを作ってCI構築してそこからデプロイするようにした

 .NET Core 2.0が出たので、このブログのエンジンを再構築しなおした。ついでにDockerコンテナで運用するようにし、ホスティングもコンテナでやるHyper.shに移る。
デプロイは、
開発→Github→AzureWebApp
だったのが、
開発→Github→TravisCI→Hyper.sh
になり、自動テストを経ることになった。masterへのコミットのみ、Hyper.shへのデプロイが実行されるようにしてある。あとはCIでOAuthつけたらひと段落。
https://github.com/hMatoba/tetsujin


https://blog.hmatoba.net
comment: 0

DockerでのASP.NET Core MVCアプリの作成を始めた

 VisualStudioでのアプリ作成にも、数バージョン前からDockerサポートがオプションで付けられるようになった。もうDockerはただの流行りではないことは明白だ。

 あるところのWebアプリはVPS環境下で運用されており、デプロイはFTP接続で行われている。アプリが動く環境は改修の度に必要になったライブラリや設定の継ぎ足しで、秘伝のタレ化している。
 ある改修で環境を用意しなおしたら、いくらかの機能が動かなくなった。これが秘伝のタレ化の代表的な弊害だろう。この秘伝のタレ化を避ける一つの手段が、Dockerを使って、デプロイと廃棄のサイクルを回していくことだ。Dockerならば、環境構築はDockerfileという設定ファイルに書いて、それでアプリをデプロイしていくのが標準的な運用になる。アプリのバージョン管理さえしていれば、環境構成もバージョン管理されていくことになる。タレの作り方は公知となる。

 アプリをバージョン管理するとなれば、アプリの構成の仕方はちと意識しないといけないことが出てくる。いわゆる「ステートレスにしろ」ということだ。
 ディレクトリ構成をそのままパスにしているようなアプリで、たとえばユーザのアップロードしたファイルがプロジェクトのディレクトリに含まれてしまうとバージョン管理が厄介になってくる。そこはAWSのS3とかAzureのファイルストレージを使うとかでコードと運用で出てくる産物を切り離す。そういうノウハウがまとまっているのが"12 factor app"
The Twelve-Factor App

 そういうわけで、環境構築やデプロイというところでは手作業職人の道ではなく、自動化ができるエンジニアの道をDockerを使ってたどっていくことにする。
comment: 0

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

ASP.NET Core MVC: 認証機能を作る

 前回まででブログを作る前の基本的なものはそろってきた。ブログといえば記事を書いて、それを一般に公開するものだ。ブログを書く管理者がいるので、そいつに権限を与える認証機能を実装しなければならないのでまずそこから。

 まず認証に使うページのコントローラとビューを、認証機能なしの状態で用意する。↓の三つのファイルをプロジェクト内に用意。ちなみに管理者なのでAdminという単語を関係のあるところに使おうとするところだが、Adminをパスにおくとやたら不正なアクセスがある。なのでちょいと意味が違う気がするがMasterという単語を使っている。
/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;

namespace tetsujin.Controllers
{
[Route("Master")]
public class MasterController : Controller
{

[Route("")]
public IActionResult Index()
{
return View();
}

}
}


/Views/Master/Index.cshtml

@{
Layout = "/Views/Shared/_MasterLayout.cshtml";
ViewBag.Title = "Admin Page";
}
管理者用だよ


/Views/Shared/_MasterLayout.cshtml

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - tetsujin</title>
</head>
<body>
@RenderBody()
</body>
</html>


 上記三つのファイルを加えた状態でデバッグを開始する。https://localhost/Masterへアクセスするとコンテンツが表示されてしまうので、これから認証機能を実装する。

 コントローラには属性でフィルタをつけることができる。フィルタには処理を定義できる。これを使ってMasterController内のアクションすべてに、認証がとおっていないユーザにコンテンツを見せないようにする。

 まずフィルタを作成。
/Scripts/AuthorizationFilter.cs

using Microsoft.AspNetCore.Mvc.Filters;
using System;
using tetsujin.Models;

namespace tetsujin.Filters
{

public class AuthorizationFilter : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var token = context.HttpContext.Request.Cookies[Session.SESSION_COOKIE];
if (!Session.isAuthorized(token))
{
context.HttpContext.Response.StatusCode = 403;
throw new ArgumentException("Forbidden access.");
}
}
}
}


 フィルタを定義したのでMasterControllerに付加してする。
/Controllers/MasterController.cs

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

}
}


 管理者ユーザモデルの定義。
/Models/Master.cs

using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;

namespace tetsujin.Models
{
public class Master
{
public const string CollectionName = "Master";

[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }

[BsonElement("pw")]
[BsonRepresentation(BsonType.String)]
[BsonRequired]
public string Password { get; set; }
}
}


 セッションのモデルの定義。
/Models/Session.cs

using MangoFramework;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

namespace tetsujin.Models
{
[MongoDoc]
public class Session
{
public const string SESSION_COOKIE = "markofcain";
private static string hashkey = "";
public static string Hashkey
{
set { Session.hashkey = value; }
get
{
if (Session.hashkey == "" || Session.hashkey == null)
{
throw new ArgumentNullException("Hashkey for session isn't given. Set hashkey in environment value as 'HASHKEY'.");
}
return Session.hashkey;
}
}

public const string CollectionName = "Session";

[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }

[BsonElement("createdAt")]
[BsonRepresentation(BsonType.DateTime)]
[BsonRequired]
public DateTime CreatedAt { get; set; }

public static List<CreateIndexModel<BsonDocument>> indexModels = new List<CreateIndexModel<BsonDocument>>()
{
new CreateIndexModel<BsonDocument>(
new IndexKeysDefinitionBuilder<BsonDocument>().Ascending(new StringFieldDefinition<BsonDocument>("createdAt")),
new CreateIndexOptions(){ ExpireAfter = TimeSpan.FromDays(10) }
)
};

/// <summary>
/// ログインしているかの状態を返す
/// </summary>
/// <param name="token">セッショントークン</param>
/// <returns>bool</returns>
public static bool isAuthorized(string token)
{
if (token == null)
{
return false;
}
var collection = DbConnection.Db.GetCollection<Session>(Session.CollectionName);
var sessionManager = collection.Find<Session>(d => d.Id == token)
.FirstOrDefault<Session>();
var isLogin = (sessionManager == null) ? false : true;
return isLogin;
}


/// <summary>
/// ランダムトークンを取得
/// </summary>
/// <returns>ランダムトークン</returns>
private static string GetToken()
{
var rng = RandomNumberGenerator.Create();
var buff = new byte[25];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}

/// <summary>
/// ユーザIDとパスワードからハッシュを作成する
/// </summary>
/// <param name="userName">ユーザID</param>
/// <param name="pw">パスワード</param>
/// <returns>ハッシュ</returns>
private static string GetSHA256(string userName, string pw)
{
//HMAC-SHA1を計算する文字列
var s = $"{userName}-{pw}";
//キーとする文字列
var key = Session.Hashkey;
if (String.IsNullOrEmpty(key))
{
throw new ArgumentException("Couldn't get Hashkey.");
}

//文字列をバイト型配列に変換する
byte[] data = System.Text.Encoding.UTF8.GetBytes(s);
byte[] keyData = System.Text.Encoding.UTF8.GetBytes(key);


byte[] bs;
//HMACSHA1オブジェクトの作成
using (var hmac = new HMACSHA256(keyData))
{
//ハッシュ値を計算
bs = hmac.ComputeHash(data);
}

//byte型配列を16進数に変換
var result = BitConverter.ToString(bs).ToLower().Replace("-", "");

return result;
}


/// <summary>
/// ログインを実行する
/// </summary>
/// <param name="id">ユーザID</param>
/// <param name="pw">パスワード</param>
/// <param name="cookies">クッキー</param>
/// <returns>ログインの成否</returns>
public static bool Login(string id, string pw, IResponseCookies cookies)
{
Console.WriteLine(id);
var userCollection = DbConnection.Db.GetCollection<Master>(Master.CollectionName);
var filter = Builders<Master>.Filter.Eq("_id", id);
var master = userCollection.Find<Master>(filter).FirstOrDefault<Master>();
if (master == null) // ユーザが登録されていない場合
{
Console.WriteLine("no user");
return false;
}
else // ユーザが登録されていた場合
{
// パスワードをハッシュ化
var sha256 = GetSHA256(master.Id, pw);

// パスワードの一致確認
if (sha256 != master.Password)
{
Console.WriteLine($"not match: ${sha256}");
return false;
}
else
{
// トークンを使ってセッションを開始
var token = GetToken();
var collection = DbConnection.Db.GetCollection<Session>(Session.CollectionName);
collection.InsertOne(new Session
{
Id = token,
CreatedAt = DateTime.Now
});

// cookieにsecure属性を付与
var cookieOption = new CookieOptions()
{
Secure = true
};
cookies.Append(SESSION_COOKIE, token, cookieOption);

return true;
}
}
}


}
}


 あとログイン機能の実装。コントローラから。
/Controllers/AuthorizationController.cs

using Microsoft.AspNetCore.Mvc;
using tetsujin.Models;

namespace tetsujin.Controllers
{
[Route("Auth")]
public class AuthorizationController : Controller
{
[HttpGet]
[Route("Login")]
public IActionResult Login()
{
return View();
}

[HttpPost]
[Route("Login")]
public RedirectResult LoginAuth()
{
var id = Request.Form["_id"];
var password = Request.Form["password"];
var isAuthorized = Session.Login(id, password, Response.Cookies);

if (isAuthorized)
{
return Redirect("/Master");
}
else
{
return Redirect("/Auth/Login");
}
}
}
}


 続いてログインページのビュー(最低限)。
/Views/Authorization/Login.cshtml

@{
Layout = "/Views/Shared/_Layout.cshtml";
ViewData["Title"] = "Login";
}

<form action="/Auth/Login" method="post">
<input type="text" name="_id" /><br />
<input type="password" name="password" /><br />
<input type="submit" name="enter" value="Login" />
</form>


 あとはちょっと設定。
 まずMongoDBのコレクションをよしなにやるライブラリを入れる。パッケージマネージャコンソールでコマンド。
Install-Package MangoFramework



 Startup.csのメソッドStartupに追記がある。
Startup.cs

public Startup(IConfiguration configuration)
{
Configuration = configuration;

// DB接続確立
var dbName = "blog";
DbConnection.Connect(configuration.GetValue<string>("MONGO_CONNECTION"), dbName);

// 宣言されたモデルからDBにコレクションを作る
MongoInitializer.Run(DbConnection.Db, "tetsujin");

// ユーザパスワードのハッシュキー
Session.Hashkey = configuration.GetValue<string>("HASHKEY");
}


 appsettings.json
appsettings.json

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"MONGO_CONNECTION": "mongodb://10.0.75.1",
"HASHKEY": "notmattertome"
}


 VisualStudioでデバッグを開始する。その状態でコンソールなどを開いてMongoDBに接続して、ブログのデータベースのMasterコレクションに、デバッグ環境用の管理者ユーザのドキュメントを一件入れる。ぼくのデバッグ環境では下記。
db.Master.insert({"_id":"testuser", "pw":"0d6a78622e8c92da07a2aa0e34ad7656a644481b849b0b75dc31358a59708251"})


↑のドキュメントは下記情報で、このアプリのハッシュ化方法のもとにパスワードをハッシュ化したもの。
ID: testuser
PW: password
Hashkey: notmattertome

 認証を通していないブラウザでhttps://localhost/Masterへアクセスする。エラーでコンテンツが見られない。



 https://localhost/Auth/Loginにアクセスすればログインフォームが出てくるので、↑の通りにID、パスワードを入れる。成功すれば管理者ページがみられる。


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

comment: 0

ASP.NET Core MVCでブログを作る - データベースを用意

 アプリのデータベースを用意する。今回はMongoDBを使う。

 開発環境のデータベースを用意する。Dockerで一発。
docker run -d --name mymongo -p 27017:27017 mongo

データベースをDockerコンテナで用意した。アプリもDockerコンテナに入っている。アプリコンテナからデータベースコンテナへ接続する手段はいくらかある。ここではDockerで与えられるネットワークのIPでつなぐ。
 ホストマシンでipconfigコマンドを走らせ、DockerNATのネットワークで与えられているIPを調べる。ぼくの環境ではIPが”10.75.0.1”だった。これはデフォルトでDockerに与えられるものから変えていないものなので、たいていはこれと同一だろう。
 というわけでアプリコンテナで使うMongoDBコンテナへの接続文字列は→"mongodb://10.0.75.1"

 アプリにMongoDBと接続を行うためのライブラリを入れる。VisualStudio下部の真ん中にあるパッケージマネージャコンソールで、下記三行を実行。
Install-Package MongoDB.Driver

Install-Package MongoDB.Driver.Core
Install-Package MongoDB.Bson

この実行結果は.csprojに保存されるので、プロジェクトの環境を移動したときの復元はそれほど手間ではない。

 アプリ。まず接続を行うクラスを用意する。Scriptsというディレクトリをアプリのプロジェクト内に用意し、DbConnection.csを入れる。内容は下記。
DbConnection.cs

using MongoDB.Driver;
using System;
using System.Net.Sockets;

public class DbConnection
{
public static IMongoDatabase Db { get; set; }

public static void Connect(string connectionString, string dbName)
{
var url = new MongoUrl(connectionString);
var clientSettings = MongoClientSettings.FromUrl(url);
Action<Socket> socketConfigurator = s => s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
clientSettings.ClusterConfigurator = cb => cb.ConfigureTcp(tcp => tcp.With(socketConfigurator: socketConfigurator));
var client = new MongoClient(clientSettings);
Db = client.GetDatabase(dbName);
}
}


 接続を行う。Startup.csのメソッドStartupがアプリ立ち上がりの処理を行っているので、今回はここにつっこむことにする。
Startup.cs

public Startup(IConfiguration configuration)
{
Configuration = configuration;

var dbName = "blog";
DbConnection.Connect(configuration.GetValue<string>("MONGO_CONNECTION"), dbName);
}


 接続文字列をappsettings.jsonに追記。
{

"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"MONGO_CONNECTION": "mongodb://10.0.75.1"
}


これで、アプリでデータベースが使えるようになった。
今回のコミット
comment: 0