okinawa

勉強メモ

C# .net Coreの基本

 

参考書籍

この作者さんの本にも大変お世話になっております。

wings.msn.to

 

 コードファーストでDBアクセス

(非スキャフォールディング)モデル作成→DB反映→コントローラからDBアクセスまでの流れ

 

・プロジェクト作成

プロジェクトの作成でASP.NET Core Web アプリ(Model-View-Controller)」を選ぼう!
ASP.NET Core Web アプリ」という似たのがあるので注意!!!
 
次に
EntityFrameworkCore
EntityFrameworkCore.SqlServer
EntityFrameworkCore.SqlServer.Design(DBファーストの時のみ)
EntityFrameworkCore.Tools
Web.CodeGeneration.Design(スキャルフォールディングの時に要求される)
をNuGetする。
 

・モデルクラス作成

モデルクラス作る。
ルールは下記。
・クラス名はテーブル名と同名であること
・プロパティは対応するテーブル列と同盟であること
・主キーはId、もしくはクラス名+Id。(AccountくらすならAccountId)
namespace WebApplication2.Models
{
public class Account
{
public int Id { get; set; }
public int age { get; set; }
public string name { get; set; }
public string email { get; set; }
public string password { get; set; }
 
}
}
 

・コンテキストクラス作成

コンテキストクラス作る。
モデルクラスをDBに橋渡しする役割。
using Microsoft.EntityFrameworkCore;
namespace WebApplication2.Models
{
//DbContextの継承
public class MyContext:DbContext
{
// コンストラク
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
//モデルクラスへのアクセサー
public DbSet<Account> Account { get; set; }
}
}
 
 

・DB接続文字列を定義する

appsettings.jsonにて定義。
"ConnectionStrings": { "接続名": "Server=サーバー名;Database=DB名~"}
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"MyContext": "Server=(localdb)\mssqllocaldb;Database=WebApplication2(←プロジェクト名);Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
 

・アプリにコンテキストクラスを登録する

Startup.cs
接続文字列とコンテキストクラスを紐付けする。
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddControllersWithViews();
//コンテキストクラスを登録
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyContext")));
}
 

マイグレーションを実行する

マイグレーションはモデルクラスを元にDBを作成・変更する仕組み。
ここでは、DBの作成をしている。
ツール→NuGetパッケージマネージャ→パッケージマネージャコンソール
Add-Migration Initial(マイグレーションを追加)
Update-Database (DBに反映)
※注意
表示→SQL Serverオブジェクトエクスプローラーで確認。
なかなか反映されない時はVisualStudioを再起動。
 
rootディレクトリの下にMigrationsディレクトリができる。
これ見ると何やってるかなんとなくわかりそう↓
using Microsoft.EntityFrameworkCore.Migrations;
namespace WebApplication2.Migrations
{
public partial class Initial : Migration
{
//upメソッドでテーブル作成
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Account",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
age = table.Column<int>(type: "int", nullable: false),
name = table.Column<string>(type: "nvarchar(max)", nullable: true),
email = table.Column<string>(type: "nvarchar(max)", nullable: true),
password = table.Column<string>(type: "nvarchar(max)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Account", x => x.Id);
});
}
//Downメソッドでテーブル削除
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Account");
}
}
}
 

・コントローラーにコンテキストを注入

using Microsoft.AspNetCore.Mvc;
using WebApplication5.Models;
namespace WebApplication5.Controllers
{
public class HelloWorldController : Controller
{
 
private readonly MyContext _context;
//コンストラク
//依存性の注入でコンテキストクラスを取得
public HelloWorldController(MyContext context)
{
this._context = context;
}
//アカウント情報をビューに渡す
public IActionResult List()
{
return View(this._context.Account);
}
}
}
 

・画面の作成

上記コードのListにカーソルを合わせて右クリック
→ビューの追加
→Razorビュー
→ビュー名List・テンプレートEmpty・レイアウトページを使用するにチェック
→追加でこんなんができる↓
List.cshtml
@{
ViewData["Title"] = "List";
}
<h1>List</h1>
↓コードを追加
<!--Accountモデルを宣言-->
@model IEnumerable<WebApplication5.Models.Account>
@{
ViewData["Title"] = "List";
}
<h1>List</h1>
<h2>アカウント情報</h2>
@foreach (var item in Model)
{
<p>@item.Id</p>
<p>@item.age</p>
<p>@item.email</p>
<p>@item.password</p>
}
 

(有スキャフォールディング)モデル作成→DB反映→コントローラからDBアクセスまでの流れ

モデルクラス作成(非スキャフォールディングと同じ)
スキャフォールディング
Controllersフォルダを右クリック
→追加→コントローラー
→EntityFrameworkを使用したビューがあるMVCコントローラ
→モデルクラス選択&データコンテキストクラスの+ボタンクリック
→ビューの生成・スクリプトライブラリの参照・レイアウトページを使用するにチェック
→追加
 
・生成ファイル
  • ~Controller.cs:CRUD機能が入ったコントローラ
  • Index.cshtml:一覧画面のテンプレート
  • Details.cshtml:詳細画面テンプレート
  • Create.cshtml
  • Delete.cshtml
  • Edit.cshtml
  • ~Context.cs:DBとの橋渡し役のコンテキストクラス
  • 接続文字列の定義(appsetteings.json)
  • コンテキストの登録(Startup.cs)
 
これでコンテキストクラス作成・DB接続文字列の定義・コンテキストのアプリへの登録も全部やってくれる!
恐ろしい!
最後にマイグレーション。パッケージマネージャコンソールで。
Add-Migration Initial(マイグレーションを追加)
Update-Database (DBに反映)
 

 

リクエスト送信→受け取り

cshtmlからリクエスト送信

・リンク
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
<a asp-action="Delete" asp-route-id="@item.Id">Create</a>
・form
<form asp-action="Create" method="post">
 <input type="text" asp-for="Company">
 asp-for="Company"にするとidとnameがCompanyになる。

コントローラでリクエスト受け取り

アクションメソッド名を同じにするだけ。
public async Task<IActionResult> Edit(int? id){ 処理 }
public async Task<IActionResult> Detail(int? id){  }
public async Task<IActionResult> Delete(int? id){  }
public async Task<IActionResult> Create(int? id){  }
 
formからの受け取り
public async Task<IActionResult> Create(Model company){  }
 引数名をasp-for(name属性)と同じにすると受け取れる。

DBでCRUD操作

検索形

Select
・結果が複数の場合
        // GET: Books/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
 
            var book = await _context.Book.FindAsync(id);
            if (book == null)
            {
                return NotFound();
            }
            return View(book);
        }
 
・結果が1つの場合
        // GET: Books/Details/5
        public async Task<IActionResult> Details(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }
 
            var book = await _context.Book
                .FirstOrDefaultAsync(m => m.Id == id); //複数と1つでここが違う
            if (book == null)
            {
                return NotFound();
            }
 
            return View(book);
        }
 

検索条件を複数にする

・FirstOrDefaultAsync(m=>条件式 && 条件式 || 条件式)
条件に合致するデータを1件だけ返す。(複数ヒットしても1件)
mは検索対象のモデル。
FirstOrDefaultAsync(m.Id=>id == id && m.name == name);
 

更新系

更新系の場合は引数をBind()でモデルバインディングするのがポイント。
オーバーポスティング攻撃対策。
 
※モデルバインドとは
その名の通り、モデルクラスにバインド(紐付け)すること。
多分もっと広い用途があるので要調査。
 
Insert文(新規作成)
public async Task<IActionResult> Create([Bind("Id,Title,Price,Publisher,Sample")] Book book)
{
if (ModelState.IsValid)
{
_context.Add(book); // ここでInsert
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
//return RedirectToAction("Index");
}
return View(book);
}
Update文
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("Id,Title,Price,Publisher,Sample,RowVersion")] Book book)
        {
中略
                try
                {
                    _context.Update(book); //ここでアップデート
                    await _context.SaveChangesAsync();
                }
 
Delete文
        // POST: Books/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(int id)
        {
            var book = await _context.Book.FindAsync(id);
            _context.Book.Remove(book); //ここでDELETE
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }

 

 

コントローラのアレコレ

コントローラーからテンプレートの呼び出す時の挙動

return View();
上記のようにパス指定なしでテンプレートを呼び出すと、以下のようにアクションに対応したテンプレートを呼び出そうとします。
/Views/コントローラー名/アクション名.cshtml
 
こんな感じで引数指定すれば同コントローラ内の別アクションメソッドを指定できる。
return View("Hoge");
/Views/コントローラー名/Hoge.cshtml
 

別のコントローラのアクションメソッドを呼びたい時はRedirect

・Redirect(string url)
url:URLをパス指定する。多分Viewsがルートディレクトリ。
 
・RedirectToPage(string pageName)
pageName:cshtmlのファイル名の拡張子が無い名称。
 
・RedirectToAction(String actionName [,string controllerName] [,object Values])
  • actionName:アクションメソッド名
  • controllerName:コントローラ名
  • Values:ルートパラメータ
 
 

モデルクラスでできること色々

表示名とデータ型と検証属性の指定。データ型は指定する効果がよくわからず。
[DisplayName("値段")] //ビューでの表示名を付与
[DataType(DataType.Currency)] //ビューでのデータ型を指定。
[StringLength(20, ErrorMessage = "{0}は{1}文字以内で入力してください。")] //検証属性
String price {get; set;}
 
参考

セッション

・参考サイト
 
 
 
 
Startup.csでセッションの設定をする
public void ConfigureServices(IServiceCollection services)
{
 
//セッションの設定
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
}
 
public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
//セッションを使う
app.UseSession();
}
 
セッションを使う
// セッションへ値をセット
HttpContext.Session.SetString("key","value");
HttpContext.Session.SetInt32("key2",123);
 
// セッション変数から値を取得
string mystring = HttpContext.Session.GetString("key");
int myint = HttpContext.Session.GetInt32("key2");
 
セッションをビューで表示
@using Microsoft.AspNetCore.Http
 
<div>
<p>@Context.Session.GetString("test")</p>
</div>
 

同時実行制御(トランザクション的なの)

RowVersionというのを使う。
 

コードファーストで同時実行制御

1,モデルクラスにRowVersion列を追加。
[Timestamp]
public byte RowVersion { get; set; }
 
2,Add-Migration→Update-Database
 
3,Edit.cshtmlにRowVersionを追加。
<input type="hidden" asp-for="RowVersion" />
 
4,Controllerで競合検出
if (ModelState.IsValid)
{
try
{
_context.Update(hoge);
await _context.SaveChangesAsync();
 
}
catch (DbUpdateConcurrencyException)
{
 
//ここ↓
ModelState.AddModelError(string.Empty, "競合が検出されました。");
return View(hoge);
}
return ~;
}
 

DBファーストで同時実行制御

SSMSでrowversionを追加。
 
ただし、GUIだとデータ型でrowversion型が存在しない。
 
なのでSQLで打ち込む。
ALTER TABLE tableName ADD
columnName rowversion NULL;
 
 ちなみに上記SQLを打っても、GUI上だとTimestamp型と表示されるが実際にはrowversion型らしい。
 
あとの手順はコードファーストとほぼ一緒。Add-Migration→Update-DatabaseはやらなくてOK。
 
参考サイト

social.msdn.microsoft.com

 

コードファーストで主キーの名前を変える方法

コードファーストだと主キー名が「Id」か「classNameId」しかつけられない。
Companyクラスなら「Id」か「CompanyId」
 
・参考
 
・コンテキストクラスを変更する。
public DbSet<Test> Test { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Test>()
.HasKey(b => b.Test_id) //←こっちがモデルクラスの名前
.HasName("Test_id"); // ←こっちがDBのカラム名
}
 

VisualStudio内のDBで主キー名変更の場合

主キー名を変える場合は一度DBごと消さないとダメ。
手順は
上記の要領でコンテキストクラス変更
→DBを消す
マイグレーションファイルも消す
→Add-Migration
→Update-Database
 

入力チェック(バリデーション)

モデルクラスに検証属性を付与。
[StringLength(20, ErrorMessage = "{0}は{1}文字以内で入力してください。")]
public string TeamName { get; set; }
 
ビューでエラメッセージを表示。
asp-validation-summary="ModelOnly":モデル全体に関わるエラーだけを表示する。
asp-validation-for="CharacterName":CharacterNameに関するエラーを表示。
Hoge.cshtml
<div asp-validation-summary="ModelOnly" class="text-danger"></div> //ModelOnlyはモデル全体に関わるエラーだけを表示する。
<span asp-validation-for="CharacterName" class="text-danger"></span> //CharacterNameに関するエラーを表示。
 
//入力チェックのためのJavaScriptライブラリのインポート。
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
 
・参考
 

ルーティング

pattern:”{controller}/{action}/{id}”
  • {controller}にはコントローラ名
    • {controller=Home}の「=」はこのパラメータの既定値をHomeにする。
  • {action}にはメソッド名(アクションメソッド)
    • {action=Index}の「=」はこのパラメータの既定値をIndexにする。
  • {id}にはメソッドに渡す値
    • /{id?}の「?」はこのパラメータを省略可にする。
 
Startup.cs
```
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
```
 

Razor

Razorテンプレートによる変数出力・処理の実行の基本

・サーバー側
ViewBag.変数名=値
ViewData[“変数名”]=値
 
```
public IActionResult Greet()
{
ViewBag.Message = "こんにちは、世界!";
 
return View();
}
 
```
 
・画面側
@・・・は出力。
@{・・・}はコードブロック。実行。出力するわけではない。
 
@{
ViewData["Title"] = "Greet";
}
 
<h1>Greet</h1>
<p>@ViewBag.Message</p>
 

Razor側でモデルクラスをインポート・取り出し

<!- Bookモデルを参照する></!->
@model IEnumerable<QuickMaster.Models.Book>
 
// 取り出し
@foreach (var item in Model)
{
<tr>
<td>@item.Title</td>
<td>@item.Price</td>
<td>@item.Publisher</td>
<td>@item.Sample</td>
</tr>
}
 

ヘルパーメソッド一覧

  • ViewResult View():テンプレートに基づいてページ出力
  • ContentResutl Content(string content):テキスト出力
  • RedirectResult Redirect(string url):指定されたパスに移動(リダイレクト)
  • RedairectToAction() 指定されたアクションに移動(リダイレクト)
  • File():指定されたバイト配列をファイルとして出力
  • NotFound();404エラーを出力
 

よく使うビューヘルパー/タグヘルパー一覧

ビューヘルパー一覧
  • @foreach
  • @Html.Displayfor データ方に応じて出力を変化させる。
  • @Html.DisplayNamefor プロパティ名↓を表示する。列名のタイトル表示に使う。
 
タグヘルパー一覧
<form asp-controller="">:コントローラ名に関連づいたフォーム
<form asp-action="">:アクション名に関連づいたフォーム
<a asp-controller="">:コントローラ名に関連づいたリンク
<input asp-for="">:入力タグヘルパー 例:asp-for="company"にするとid="company" name="company"になる。
<select asp-for"">:セレクトボックス
<textarea asp-for="">:テキストエリア
<label asp-for="">:ラベル
<span asp-valication-for="">:入力エラー。個別のプロパティ
<div asp-validation-summary="">入威力エラー。モデル全体。
 
公式:組み込みタグヘルパー一覧↓
 
ASP.NET Core MVC の Razor で使える Tag Helpers のメモ書き
 

動的なSelectボックスの使い方

・参考サイト
 
サーバ側でセレクトボックスに渡す値を用意。
SelectList()を使う。
--------------------------
SelectList(IEnumerable items, string dataValueField, string dataTextField, object selectedValue)
items:列挙可能なもの。コレクションとかDBの検索結果をそのままぶち込める。
dataValueField:valueに入る値。
dataTextField:画面に表示される値。
selectedValue;初期値。
-------------------------
// 画面にはTeamNameが表示されるが、valueにはTeamIdが入る。
ViewData["TeamId"] = new SelectList(_context.Teams, "TeamId", "TeamName", character.TeamId);
return View(character);
 
ビューでセレクトボックスを表示。
<select asp-for="TeamId" class="form-control" asp-items="ViewBag.TeamId"></select>
 

部分ビュー

Sharedディレクトリに_bubunView.cshtmlを作成。
@using Microsoft.AspNetCore.Http
<div>
<p>@Context.Session.GetString("test")</p>
</div>
 
cshtmlに表示。
<h1>Create</h1>
<partial name="_bubunView" />
<h4>Team</h4>