RSS

月別アーカイブ: 1月 2023

Vue.js 3: Vue.js 単独で使う

Vue.js のサイトの「クイックスタート」では Vite + Vue.js でシングルページアプリ (SPA) を使う例が出ているが、jQuery のように HTML に組み込んで使う従来の方法ももちろんできる。

その場合は、script タグを使って Vue.js を読み込む。これはクイックスタートの「CDN の Vue を使用する」で説明されている。

ここでは、次のようにして Vue.js を HTML ページに読み込む方法が出ている。

https://unpkg.com/vue@3/dist/vue.global.js

この他にも JsDelivr や CloudFlare の CDN も使用できる。

(JsDelivr の例)

https://cdn.jsdelivr.net/npm/vue@3.2.45/dist/vue.global.min.js

(CloudFlare の例)

https://cdnjs.cloudflare.com/ajax/libs/vue/3.2.45/vue.global.min.js

ウェブサーバが自分の PC で稼働している場合、これら CDN のサイトから JS ファイルをダウンロードしてウェブサーバのどこかへ配置して使うこともできる。

(ローカルのウェブサーバで使う例) C:/Apache24/htdocs/js にインストールした。

/js/vue.global.js

次の HTML は ローカルのウェブサーバで Vue.js を使い “Hello World” を表示する例である。(同時に Bootstrap も使用している)

<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="utf-8" />
  <title>Vue.js v3 Hello World</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Bootstrap 5 -->
  <link rel="stylesheet" href="/css/bootstrap.min.css" />
  <style>
    .demo {
      font-family: sans-serif;
      border: 1px solid #eee;
      border-radius: 2px;
      padding: 20px 30px;
      margin-top: 1em;
      margin-bottom: 40px;
      user-select: none;
      overflow-x: auto;
    }
  </style>
 <head>

  <body>
   <article class="container">
    <h1 class="text-center text-primary mt-4 mb-4"><img src="/img/Vuejs.png"> (Vue) Hello World</h1>
    <div id="hello-vue" class="demo">
     {{ message }}
    </div>
    <br />
    <p>&nbsp;</p>
   </article>
  <!-- Bootstrap 5 -->
  <script src="/js/bootstrap.min.js"></script>
  <!-- Vue.js 3 -->
  <script src="/js/vue.global.js"></script>
  <script>
    const HelloVueApp = {
      data() {
        return {
          message: 'Hello Vue!!'
        }
      }
    }

    Vue.createApp(HelloVueApp).mount('#hello-vue')
 </script>
 </body>
</html>
 
コメントする

投稿者: : 2023/01/31 投稿先 JavaScript, Vue.js

 

タグ: ,

Vue.js 3: 最小アプリのスタイル

Vue.js のウェブサイトの「入門 / はじめに」に出ている最小アプリだが、vue-projects/src/assets フォルダ内の main.css と base.css というスタイルが適用されている。

main.css の内容

@import './base.css';

#app {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;

  font-weight: normal;
}

a,
.green {
  text-decoration: none;
  color: hsla(160, 100%, 37%, 1);
  transition: 0.4s;
}

@media (hover: hover) {
  a:hover {
    background-color: hsla(160, 100%, 37%, 0.2);
  }
}

@media (min-width: 1024px) {
  body {
    display: flex;
    place-items: center;
  }

  #app {
    display: grid;
    grid-template-columns: 1fr 1fr;
    padding: 0 2rem;
  }
}

base.css の内容

/* color palette from <https://github.com/vuejs/theme> */
:root {
  --vt-c-white: #ffffff;
  --vt-c-white-soft: #f8f8f8;
  --vt-c-white-mute: #f2f2f2;

  --vt-c-black: #181818;
  --vt-c-black-soft: #222222;
  --vt-c-black-mute: #282828;

  --vt-c-indigo: #2c3e50;

  --vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
  --vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
  --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
  --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);

  --vt-c-text-light-1: var(--vt-c-indigo);
  --vt-c-text-light-2: rgba(60, 60, 60, 0.66);
  --vt-c-text-dark-1: var(--vt-c-white);
  --vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}

/* semantic color variables for this project */
:root {
  --color-background: var(--vt-c-white);
  --color-background-soft: var(--vt-c-white-soft);
  --color-background-mute: var(--vt-c-white-mute);

  --color-border: var(--vt-c-divider-light-2);
  --color-border-hover: var(--vt-c-divider-light-1);

  --color-heading: var(--vt-c-text-light-1);
  --color-text: var(--vt-c-text-light-1);

  --section-gap: 160px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-background: var(--vt-c-black);
    --color-background-soft: var(--vt-c-black-soft);
    --color-background-mute: var(--vt-c-black-mute);

    --color-border: var(--vt-c-divider-dark-2);
    --color-border-hover: var(--vt-c-divider-dark-1);

    --color-heading: var(--vt-c-text-dark-1);
    --color-text: var(--vt-c-text-dark-2);
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  position: relative;
  font-weight: normal;
}

body {
  min-height: 100vh;
  color: var(--color-text);
  background: var(--color-background);
  transition: color 0.5s, background-color 0.5s;
  line-height: 1.6;
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
    Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
  font-size: 15px;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

これらの CSS は最小限のものなので、見栄えのいい画面にはならない。

そこで Bootstrap 5 を使えるようにする。

App,vue は index.html に適用されるので、index.html で Bootstrap を有効にしてやるとよい。次の例は CDN を使って Bootstrap を参照している。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <link rel="icon" href="/favicon.ico">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
  </body>
</html>

次のソースは Bootstrap を使った改良版の App.vue である。

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
</script>

<template>
  <div class="container">
    <div class="row">
      <h3 class="header-1">単一ファイルコンポーネント</h3>
    </div>
    <br>
    <div class="row">
      <button @click="count++" class="btn btn-success">Count is: {{ count }}</button>
    </div>
  </div>
</template>

<style scoped>
button {
  font-weight: bold;
}
</style>

この時のアプリ画面は次のようになる。

 
コメントする

投稿者: : 2023/01/30 投稿先 JavaScript, Vue.js

 

タグ: ,

Vue.js 3: 「はじめに」のサンプルを動かすまで

Vue 3 がリリースされてしばらく経った。

Vue 3 をこれまで試していなかったのだが、Vue 2 からいろいろ変わっているのがわかった。

Vue 3 のウェブサイトで「入門 / はじめに」の(最小限の)サンプルだが、次のようなコードが出ている。

import { createApp } from 'vue'

createApp({
  data() {
    return {
      count: 0
    }
  }
}).mount('#app')

<div id="app">
  <button @click="count++">
    Count is: {{ count }}
  </button>
</div>

しかし、これをどうやって動かすかは、ここでは示されていない。

このサンプルを動かす手順を以下に示す。まず、ベースとなる Vite プロジェクトを作成する。

  • もし、Node.js がインストールされていなければ、v16.x 以上をインストールする。
  • “npm init vue@latest” を実行する。これにより Vite + Vue.js の最新バージョンがインストールされる。
  • vue-project というフォルダが作成されるので、その中へ移動する。
  • “npm install” を実行する。これにより、必要なパッケージがインストールされる。
  • “npm run dev” を実行して、Vite プロジェクトを起動する。
  • 画面に表示された URL (http://localhost/ポート番号) をブラウザで開く。
  • 次のような画面が表示される。

以上の操作をまとめると次のようになる。

PS D:\workspace\Web\Vue3> npm init vue@latest
Need to install the following packages:
  create-vue@3.5.0
Ok to proceed? (y) y

Vue.js - The Progressive JavaScript Framework

√ Project name: ... vue-project
√ Add TypeScript? ... No / Yes
√ Add JSX Support? ... No / Yes
√ Add Vue Router for Single Page Application development? ... No / Yes
√ Add Pinia for state management? ... No / Yes
√ Add Vitest for Unit Testing? ... No / Yes
√ Add an End-to-End Testing Solution? » No
√ Add ESLint for code quality? ... No / Yes

Scaffolding project in D:\workspace\Web\Vue3\vue-project...

Done. Now run:

  cd vue-project
  npm install
  npm run dev

PS D:\workspace\Web\Vue3> cd vue-project
PS D:\workspace\Web\Vue3\vue-project> npm install
npm WARN deprecated sourcemap-codec@1.4.8: Please use @jridgewell/sourcemap-codec instead

added 32 packages, and audited 33 packages in 8s

4 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
PS D:\workspace\Web\Vue3\vue-project> npm run dev

> vue-project@0.0.0 dev
> vite


  VITE v4.0.4  ready in 375 ms

  ➜  Local:   http://127.0.0.1:5173/
  ➜  Network: use --host to expose
  ➜  press h to show help

('h' キーを押す)
  Shortcuts
  press r to restart the server
  press u to show server url
  press o to open in browser
  press q to quit

('q' キーを押す)
PS D:\workspace\Web\Vue3\vue-project> 

「入門 / はじめに」の(最小限の)サンプルのコードで vue-project/src/App.vue の内容を置き換える。

“npm run dev “を実行してブラウザで同じ URL を開くと、次のようなボタンがブラウザ画面に表示される。(そして、ボタンをクリックすると数字がカウントアップする)

プロジェクトの内容のうち、この最小アプリでは使わないファイルがたくさんある。

プロジェクトフォルダの内容は次のようになっている。

src フォルダ

assets サブフォルダ内の .css ファイルは src/main.js で使っているので残しておくか、main.js の import をコメントアウトとする。

components サブフォルダ内のすべてのファイルは使用しない。

public フォルダ

favicon.ico があるが、削除してもアプリの動作に関係ない。サイズが小さいので残しておいてもよい。

 
コメントする

投稿者: : 2023/01/30 投稿先 JavaScript, Vue.js

 

タグ: , ,

Strawberry Perl を使うときの注意

しばらく前に作った Perl プログラムとか Perl モジュールがエラーになって使えなくなった。

Perl のバージョンを表示すると、5.8.8 とかが表示される。

そんな古いのを入れた覚えはないんだがおかしい。

もしかしたら、別のアプリに付属している Perl が実行されているのかもしれない。

そう言えば、Strawberry Perl というをインストールしたというのを思い出して、インストール場所を見つけてフルパスで実行してみたが、やはりバージョンが 5.8.8 と表示される。

そのディレクトリを見ると、perl.exe と perl.5.nn.n.exe (n は数字) というのがある。

perl.5.nn.n.exe を実行してバージョンを表示すると、5.nn.n になった。(最近のバージョン)

perl.exe をリネームし、さらに perl.5.nn.n.exe を perl.exe にリネームしたら、perl のバージョン表示が新しいものになった。

これでエラーが出ていた Perl プログラム・モジュールがちゃんと動くようになった。

 
コメントする

投稿者: : 2023/01/28 投稿先 Perl

 

タグ: ,

.NET MAUI Blazer を試す。

.NET MAUI はマルチプラットフォームのデスクトップアプリを作れるフレームワークである。

MAUI は比較的新しいもの (この記事を書いたのは2023/01) なので、Visual Studio でプロジェクトを作成する場合、「新規作成 / プロジェクト」メニューの中に含まれていないかもしれない。

もし、検索ボックスに MAUI と入力して何も表示されない場合は、「さらにツールと機能をインストールする」リンクをクリックしてインストールする必要がある。

MAUI アプリプロジェクトには、上のダイアログボックスのように

  • .NET MAUI Blazer アプリ
  • .NET MAUI アプリ

がある。

Blazer アプリは HTML ベースの Razor を使って画面をデザインするが、Blazer が付かない MAUI アプリは XML ベースの XAML を使い画面のデザインをする。

WPF に慣れていれば、.NET MAUI アプリのほうがよいが、ASP.NET に慣れているなら .NET MAUI Blazer アプリを使うのがよい。

ここでは、 .NET MAUI Blazer アプリでプロジェクトを作成する。

Visual Studio のソリューションエクスプローラーは次のような感じになる。

ここでは、Pages に Directory.razor を追加した。

このページは、フォームからディレクトリを入力しボタンをクリックすると、そのディレクトリの内容一覧をリスト表示するものである。

この .NET MAUI アプリの起動直後の画面イメージは次のようになる。

メニュー一覧の一番下にある「ディレクトリ内容」メニューをクリックすると、下のような画面が表示される。ここでテキストボックスにディレクトリのパス名を入力し、「一覧取得」ボタンをクリックするとディレクトリの内容一覧が Ready と表示されているところに表示される。

※ ASP.NET Blazer と異なり、ブラウザを使わないデスクトップアプリなので、ローカルコンピュータのリソースにアクセス可能である。

この「ディレクトリ内容」のページ Directory.razor のソースを示す。

ASP.NET の場合と異なり EditForm を使う必要がない (使うとエラーになる)。

あと、このソースで Directory.GetFiles(path) で取得したディレクトリの内容一覧は files という変数に格納されるが、画面はリアルタイムでリフレッシュされているため、static にしておかないと、そのたびごとにリフレッシュされてしまい表示がおかしくなる。

@page "/dirs"
<h3>ディレクトリの内容一覧</h3>

@* ASP.NET Blazer のように EditForm を使う必要はない。*@
<form class="mt-3">
    <div class="mb-2 col-4">
        <label for="folderinput">ディレクトリ</label>
        <input type="text" id="folderinput" @bind-value="folder" class="form-control input-sm" /><br />
    </div>
    <div class="mb-4">
        <button id="button1" @onclick="Button1Click" class="btn btn-primary">一覧取得</button>
    </div>
</form>
<br />

@if (files == null)
{
    <p>Ready..</p>
}
else
{
    <ul>
        @foreach (var item in files)
        {
            <li>@item</li>
        }
    </ul>
}


@code {
    public string folder { get; set; }
    public static string[] files { get; set; }  // static にしておかないと画面がリフレッシュされ、何度も null に初期化される。

    protected override void OnInitialized()
    {
        
    }

    public void Button1Click()
    {
        if (String.IsNullOrEmpty(folder) == false)
            files = System.IO.Directory.GetFiles(folder);
    }
}

メニューは Shared フォルダ内にある NavMenu.razor というファイルに次の項目を追加すると「ディレクトリ内容」メニューが表示されるようになる。href 属性の内容は、Directory.razor の先頭にある @page ディレクティブのものと一致している必要がある。

<div class="nav-item px-3">
       <NavLink class="nav-link" href="dirs">
            <span class="oi oi-list-rich" aria-hidden="true"></span> ディレクトリ内容
        </NavLink>
</div>
 
コメントする

投稿者: : 2023/01/27 投稿先 C#, dotNET

 

タグ: ,

ASP.NET Core Blazer Wasm を試す。

dotnet new コマンドには “blazerserver” と “blazerwasm” というのがある。

Blazer Server については 「ASP.NET Core Blazer Server を試す。」という記事参照。

ここでは、Blazer Wasm を試してみた。

下の画像はマイクロソフトのサイトに出ていたものである。ご覧のように、Blazer Wasm はクライアント側だけのものである。

プロジェクトの作成

次の dotnet new コマンドで Blazer Wasm プロジェクトを作成できる。

$ dotnet new blazerwasm -o BlazerWasm

しかし、デバッガを使うための設定がいろいろ面倒なので、Visual Studio でプロジェクトを作った方がよい。

Visual Studio を使えば、追加のパッケージのインストールや json ファイルの修正をせずにデバッガが使える。

次の画像は Visual Studio のソリューションエクスプローラーの例である。

ここでは、上の画像ように Pages に EchoWebApi.razor という Razor ページを追加する。

このページは文字列を別の ASP.NET Core WebApi プロジェクトで作ったウェブサービス echo に送り、戻ってきた文字列を表示するものである。

この echo というサービスはパラメータで受け取った文字列をそのまま返すだけのものである。

EchoWebApi.razor のソースを示す。

@page "/echowebapi"
@inject HttpClient Http

<PageTitle>Echo from WebApi</PageTitle>
<h1>EchoWebApi</h1>

@if (message == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@message">
        <div class="mb-4">
            <label for="message">
                メッセージ
            </label>
            <InputText id="message" @bind-Value="message" class="form-control col-sm-2" />
        </div>
        <div class="mb-4">
            <button class="btn btn-primary" @onclick="HandleSubmit">送る</button>
        </div>
        <div id="echo" class="mb-4 text-warning display-5">
            @echo
        </div>
    </EditForm>
}

@code {
    public string? message { get; set; }
    public string? echo { get; set; }

    /// <summary>
    /// 初期化されたとき
    /// </summary>
    protected override void OnInitialized()
    {
        message = "Hello World";
        echo = "Waiting echo ..";
    }

    /// <summary>
    /// 送信ボタンがクリックされたとき
    /// </summary>
    async void HandleSubmit()
    {
        string url = "http://localhost:5046/echo/?message=" + message;
        echo = await Http.GetStringAsync(url);
    }
}

このページの表示画面は次のようになる。

メッセージを送るとメッセージの内容がそのまま送り返されてきて、”Waiting echo ..” の部分に表示される。

WebApi 側の echo サービス (EchoController.cs) は次のようになっている。

using Microsoft.AspNetCore.Mvc;

namespace WebApi.Controllers;

[ApiController]
[Route("[controller]")]
public class EchoController : ControllerBase
{
    public EchoController()
    {
    }

    [HttpGet(Name = "GetEcho")]
    public string Get(string message)
    {
        return message;   
    }
}

問題点:このアプリは一応動くのだが、「送る」ボタンのレスポンスがよくなく、1回クリックしただけだとエコーが返ってこない。

 
コメントする

投稿者: : 2023/01/26 投稿先 ASP.NET, C#, dotNET

 

タグ: , ,

ASP.NET Core Blazer Server を試す。

Blazer は WebAssembly や WebSocket を使った新しい方式のウェブアプリを作れる。

従来のウェブアプリだと、C# でクライアント側の機能を作ることはできず、JavaScript を使う必要があったが、Blazer だと C# だけでサーバ側とクライアント側の機能を実現できる。

dotnet new コマンドには “blazerserver” と “blazerwasm” というのがある。

どちらも Blazer アプリを作れるが、blazerserver はサーバとクライアントを含み、サーバ側でページのレンダリングを行う。

blazerwasm はクライアント側でページのレンダリングを行うが、サーバは別に用意する必要があるようだ。

下の図はマイクロソフトの説明ページにある Blazer Server のものである。

つまり、クライアント側は従来の DOM であって、Blazer はサーバ側で動作する。

一方、 Blazer Wasm では次の図のようになっている。こちらは、Blazer はクライアント側で動作しレンダリングを行う。

この図からわかるようにサーバ機能は含まないので、ウェブサービスを行うためのサーバは別に用意する。

プロジェクトの作成

dotnet new コマンドを使い次の例のようにするとプロジェクトフォルダ BlazerServer が作成される。

$ dotnet new blazerserver -o BlazerServer

このプロジェクトフォルダの中身は次のようになっている。

このフォルダでアプリ構築で主に関係するのは次のサブフォルダである。

  • Data: データアクセスのためのモデルクラスの置き場。
  • Pages: Razor ページの置き場。
  • Shared: ページレイアウトファイルなど
  • wwwroot: スタティックなコンテンツの置き場。CSS や JS が含まれる。

サンプルプログラム

ここで作ってみたのは、MySQL テーブル内容一覧表示とテーブルのクエリーを行うためのフォームとクエリー結果の表示である。

なお、エラー処理などは本質的ではないのでほとんど行っていない。

テーブル内容一覧表示

MySQL の user.Medias というテーブルの内容一覧を表示する。

まず Data フォルダの下に MediasModel.cs と MediasModelService.cs を追加する。

前者はテーブルのモデルとテーブルにクエリーを行うためのメソッドを提供する。一方、後者はテーブル内容一覧を取得するためのクエリーを行うメソッドを提供する。

MediasModel.cs

// dotnet add package MySql.EntityFrameworkCore が必要。
using Microsoft.EntityFrameworkCore;

namespace WebBlazerServer.Data;

/// <summary>
/// Medias テーブルのモデル
/// </summary>
public class MediasModel
{
    private MediasContext _mysqldb;

    // コンストラクタ
    public MediasModel()
    {
        _mysqldb = new MediasContext();
    }

    // すべての項目を取得する。
    public List<Medias> QueryAll()
    {
        var result = from item in _mysqldb.Medias select item;
        return result.ToList<Medias>();
    }

    // id で指定した 1 行を取得する。
    public Medias GetRow(int id)
    {
        var result = from item in _mysqldb.Medias where item.id == id select item;
        return result.ToArray<Medias>()[0];

    }

    // id で指定したメディア名を返す。
    public string? GetName(int id)
    {
        var result = from item in _mysqldb.Medias where item.id == id select item.name;
        return result.ToArray<string>()[0];
    }

    // 条件に基づいて項目を取得する。
    public List<Medias> Query(Func<Medias, bool> predicate)
    {
        //var result = from item in _mysqldb.Medias where predicate(item) select item;
        var result = _mysqldb.Medias.Where(predicate).ToList<Medias>();
        return result;
    }

    // 新規追加
    public void Insert(Medias item)
    {
        _mysqldb.Add(item);
        _mysqldb.SaveChanges();
    }

    // 更新
    public void Update(int id, Medias item)
    {
        var data = this.GetRow(id);
        if (item.name != null)
            data.name = item.name;
        if (item.type != null)
            data.type = item.type;
        if (item.format != null)
            data.format = item.format;
        if (item.size != null)
            data.size = item.size;
        if (item.fixed_on != null)
            data.fixed_on = item.fixed_on;
        if (item.info != null)
            data.info = item.info;
        _mysqldb.Update(data);
        _mysqldb.SaveChanges();
    }

    // 削除
    public void Delete(int id)
    {
        var item = this.GetRow(id);
        _mysqldb.Remove(item);
        _mysqldb.SaveChanges();
    }
}

// Medias テーブルの定義
public class Medias
{
    public Medias()
    {
    }

    public int? id { get; set; }
    public string? name { get; set; }
    public string? type { get; set; }
    public string? format { get; set; }
    public string? size { get; set; }
    public string? fixed_on { get; set; }
    public string? info { get; set; }
    public DateTime? date { get; set; }
}

// Medias の DbContext
public class MediasContext : DbContext
{
    public DbSet<Medias>? Medias { get; set; }

    // DB 接続設定
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseMySQL("Server=localhost;Database=user;Uid=user;Pwd=ust62kjy");
    }
}


MediasModelService.cs

namespace WebBlazerServer.Data;

public class MediasModelService
{
    public Task<List<Medias> > QueryMediasAllAsync()
    {
        var medias = new MediasModel();
        return Task.FromResult(medias.QueryAll());
    }
}

次に Pages フォルダの下に MediasList.razor ファイルを追加する。これはクライアント側に表示されるページである。

ページがロードされて初期化が終わったときに MediasModelService オブジェクトの QueryMediasAllAsync() メソッドを呼び出し、結果を受け取りそれを変数 medias に格納する。

medias の中身が null でなくなると、レンダリングが行われてテーブルの内容が HTML の tabale に表示される。

@page "/mediaslist"
@using WebBlazerServer.Data

@inject MediasModelService mediaservice

<PageTitle>Medias List</PageTitle>

<h1>Medias テーブル一覧</h1>

<p>MySQL user.Medias テーブルの内容一覧を表示します。</p>

@if (medias == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>id</th>
                <th>名前</th>
                <th>メディアタイプ</th>
                <th>フォーマット</th>
                <th>サイズ</th>
                <th>固定</th>
                <th>備考</th>
                <th>登録日</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var media in medias)
            {
                <tr>
                    <td>@media.id</td>
                    <td>@media.name</td>
                    <td>@media.type</td>
                    <td>@media.format</td>
                    <td>@media.size</td>
                    <td>@media.fixed_on</td>
                    <td>@media.info</td>
                    <td>@String.Format("{0:d}", media.date)</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    protected List<Medias>? medias;

    protected override async Task OnInitializedAsync()
    {
        var mms = new MediasModelService();
        medias = await mms.QueryMediasAllAsync();
    }
}

このページが表示されると下のような感じになる。

フォームからクエリーを行う

次のようなフォームから Pictures テーブルにクエリーを行い、条件に合う結果を表示する。

まず、Data フォルダに PicturesModel.cs と PicturesModelService.cs を追加する。前者は Pictures テーブルのモデルとテーブルアクセスのためのメソッドを提供する。一方、後者は Razor ページからクエリーを行うとき使うメソッドを提供する。

PicturesModel.cs

// dotnet add package MySql.EntityFrameworkCore が必要。
using Microsoft.EntityFrameworkCore;

namespace WebBlazerServer.Data;

// user.Pictures テーブルのモデル
public class PicturesModel
{
    // DbContext
    private PicturesContext _mysqldb;
    private IQueryable<Pictures>? _pictures;

    // コンストラクタ
    public PicturesModel()
    {
        _mysqldb = new PicturesContext();
        _pictures = _mysqldb.Pictures;
    }

    // 文字列で検索する。
    public List<Pictures> Query(string str)
    {
        IQueryable<Pictures> result = from row in _pictures
            where row.title.Contains(str) || row.creator.Contains(str) || row.creator.Contains(str) || row.path.Contains(str) || row.info.Contains(str)
            select new Pictures {id=row.id, album=row.album, title=row.title, creator=row.creator, path=row.path, media=row.media, mark=row.mark, info=row.info, fav=row.fav, count=row.count, bindata=row.bindata, date=row.date, sn=row.sn};
        return result.ToList<Pictures>();
    }

    // 日付で検索する。
    public List<Pictures> QueryByDate(DateTime fromdate, DateTime todate)
    {
        IQueryable<Pictures> result = from row in _pictures where row.date >= fromdate && row.date <= todate select row;
        return result.ToList<Pictures>();
    }

    // id で検索する。
    public Pictures GetRow(int id)
    {
        List<Pictures> result = _pictures.Where(x => x.id == id).ToList<Pictures>();
        if (result == null)
        {
            return null;
        }
        else
        {
            return result[0];
        }
    }
}

// Pictures テーブルの行の定義
public class Pictures
{
    public Pictures()
    {
        id = 0;
        album = 0;
    }

    public int? id {get; set;}
    public int? album {get; set;}
    public string? title {get;set;}
    public string? creator {get; set;}
    public string? path {get; set;}
    public string? media {get; set;}
    public string? mark {get; set;}
    public string? info {get; set;}
    public int? fav {get; set;}
    public int? count {get; set;}
    public int? bindata {get; set;}
    public DateTime? date {get; set;}
    public int? sn {get; set;}
}

// Medias の DbContext
public class PicturesContext : DbContext
{
    public DbSet<Pictures>? Pictures { get; set; }

    // DB 接続設定
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseMySQL("Server=localhost;Database=user;Uid=user;Pwd=ust62kjy");
    }
}

PicturesModelService.cs

namespace WebBlazerServer.Data;

public class PicturesModelService
{
    private PicturesModel pictures;

    public PicturesModelService()
    {
        pictures = new PicturesModel();
    }

    // 文字列で Pictures テーブルを検索する。
    public Task<List<Pictures>> QueryAsync(string str)
    {
        return Task.FromResult(pictures.Query(str));
    }

    // 日付で Pictures テーブルを検索する。
    public Task<List<Pictures>> QueryByDateAsync(DateTime fromdate, DateTime todate)
    {
        return Task.FromResult(pictures.QueryByDate(fromdate, todate));
    }

    // 指定した id でPictures テーブルの行を取得する。
    public Task<Pictures> GetRowAsync(int id)
    {
        return Task.FromResult(pictures.GetRow(id));
    }

    public Pictures GetRow(int id)
    {
        return pictures.GetRow(id);
    }
}

次に Pages フォルダに QueryPictures.razor を追加する。ソースは次のようになっている。

@page "/querypictures"
@using WebBlazerServer.Data
@using Microsoft.AspNetCore.Components.Forms
@inject PicturesModelService pictureservice

<PageTitle>Query Picture Tables</PageTitle>

<h1>Pictures テーブルの検索</h1>

<p>MySQL user.Pictures テーブルの内容を検索して結果を表示します。</p>

@if (pictureservice == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <EditForm Model="@form" OnSubmit="@HandleSubmit">
        <div class="mb-4">
           <label>検索文字列
            <InputText @bind-Value="form.search" size="80" class="form-control" />
           </label> 
        </div>
        <InputRadioGroup Name="queryitem" @bind-Value="form.item">
        <ul class="mb-4" style="list-style-type:none;">
            <li><InputRadio id="radio1" Name="queryitem" class="form-check-input" Value="@radiovalues[0]" /> 文字列</li>
            <li><InputRadio id="radio2" Name="queryitem" class="form-check-input"  Value="@radiovalues[1]" /> 日付</li>
            <li><InputRadio id="radio3" Name="queryitem" class="form-check-input"  Value="@radiovalues[2]" /> 主キー</li>
        </ul>
        </InputRadioGroup>
        <div class="mb-4">
           <button type="submit" class="btn btn-primary">検索する</button>
        </div>
    </EditForm>
    <p class="text-info mt-4 mb-4">@message</p>
    @if (@piclist != null)
    {
        <table class="table" style="font-size:10pt;">
            <thead class="table-light">
                <tr>
                    <th>id</th>
                    <th>アルバム番号</th>
                    <th>タイトル</th>
                    <th>作者</th>
                    <th>パス</th>
                    <th>メディア</th>
                    <th>マーク</th>
                    <th>備考</th>
                    <th>お気に入り数</th>
                    <th>参照回数</th>
                    <th>サムネール番号</th>
                    <th>登録日</th>
                </tr>
            </thead>
            <tbody>
                @foreach (Pictures p in piclist)
                {
                    <tr>
                        <td>@p.id</td>
                        <td>@p.album</td>
                        <td>@p.title</td>
                        <td>@p.creator</td>
                        <td>@p.path</td>
                        <td>@p.media</td>
                        <td>@p.mark</td>
                        <td>@p.info</td>
                        <td>@p.fav</td>
                        <td>@p.count</td>
                        <td>@String.Format("{0:d}", @p.date)</td>
                    </tr>
                }
            </tbody>
        </table>
    }
    @if (@pic != null)
    {
        <ul>
            <li>@pic.id</li>
            <li>@pic.album</li>
            <li>@pic.title</li>
            <li>@pic.creator</li>
            <li>@pic.path</li>
            <li>@pic.media</li>
            <li>@pic.mark</li>
            <li>@pic.info</li>
            <li>@pic.fav</li>
            <li>@pic.count</li>
            <li>@String.Format("{0:d}", @pic.date)</li>
        </ul>
    }
}

@code
{
    public class FormModel
    {
        public string search {get;set;}
        public string item {get;set;}
    }

    List<Pictures> piclist;
    Pictures pic;
    public FormModel form = new();
    string message;
    string[] radiovalues = {"str", "date", "id"};
    //Dictionary<string, string> attr_checked = new Dictionary<string, string>() {{"checked":"checked"}};

    async void HandleSubmit()
    {
        var pms = new PicturesModelService();
        switch (form.item)
        {
            case "str":
                piclist =  await pms.QueryAsync(form.search);
                message = "Query OK";
                break;
            case "date":
                string[] data = form.search.Split(",");
                DateTime fromdate = DateTime.Parse(data[0]);
                DateTime todate = DateTime.Parse(data[1]);
                piclist =  await pms.QueryByDateAsync(fromdate, todate);
                message = "QueryByDate OK";
                break;
            case "id":
                int? idq = Convert.ToInt32(form.search);
                if (idq is int id)
                {
                    pic =  await pms.GetRowAsync(id);
                    message = $"GetRow {id} OK";
                }
                else
                {
                    message = $"Query {idq} Failed.";
                }
                break;
            default:
                break;
        }
    }
}

次の画像は主キーでクエリーを行った時の例である。

ページ呼び出しのためのリンク

これらのページを呼び出すためのリンクは Shared フォルダの NavMenu.razor ファイルを編集して追加する。

        <div class="nav-item px-3">
            <NavLink class="nav-link" href="mediaslist">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Medias Table List
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="querypictures">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Query Pictures Table
            </NavLink>
        </div>

 
コメントする

投稿者: : 2023/01/26 投稿先 ASP.NET, C#, dotNET

 

タグ: ,

.NET の勉強:HttpClient

これは System.Net.Http に含まれるクラスで ASP.NET WebApi などのテストに使うためいろいろ試してみた。

このクラスに含まれるメソッドの多くが非同期で、HTTP の各メソッド (GET/POST/PUT/DELETE etc) でリクエストを送りレスポンスを得るものである。

GetStringAsync

このメソッドは一番簡単で、GET メソッドでリクエストを送り、レスポンスを文字列として受け取る。

// HttpClient.GetStringAsync メソッドのテスト
using System.Net.Http;

public class Program
{
    static HttpClient client = new();
    static string url = "http://localhost/cgi-bin/API/echo.cgi?message=Hello+HttpClient.GetStringAsync";

    public static async Task Main()
    {
        Console.WriteLine("HttpClient.GetStringAsync メソッドのテスト");
        string response = await client.GetStringAsync(url);
        Console.WriteLine("Response = {0}", response);
    }
}

GetByteArrayAsync

このメソッドは GET メソッドでリクエストを送り、レスポンスをバイト列として受け取る。小さ目の画像ファイルなどを取得するのに使用できる。

// HttpClient.GetByteArrayAsync メソッドのテスト
using System.Net.Http;
using System.IO;

public class Program
{
    static HttpClient client = new();
    static string url = "http://localhost/img/home_green.png";
    static string savePath = @"C:\temp\home_green.png";

    public static async Task Main()
    {
        Console.WriteLine("** HttpClient.GetByteArrayAsync メソッドのテスト **");
        byte[] response = await client.GetByteArrayAsync(url);
        using BinaryWriter writer = new (File.OpenWrite(savePath));
        writer.Write(response);
        Console.WriteLine(@"Saved {0}", savePath);
    }
}

GetStreamAsync

このメソッドは GET メソッドでリクエストを送り、レスポンスをストリームとして受け取る。大きいというか巨大なファイル、例えば高解像度である程度長時間の動画ファイルなどを取得するのに使用できる。

下の例では、小さい画像ファイルを取得するために使っている。大きなファイルならバイトごとでなくブロックごとにストリームを読むべきだが、ここでは簡単のために単純にバイト単位で読み取っている。

// HttpClient.GetStreamAsync メソッドのテスト
using System.Net.Http;
using System.IO;

public class Program
{
    static HttpClient client = new();
    static string url = "http://localhost/img/home_green.png";
    static string savePath = @"C:\temp\home_green.png";

    public static async Task Main()
    {
        Console.WriteLine("** HttpClient.GetStreamAsync メソッドのテスト **");
        Stream response = await client.GetStreamAsync(url);
        var bytes = new List<byte>();
        int b;
        while ((b = response.ReadByte()) != -1)
        {
            bytes.Add((byte)b);
        }
        using BinaryWriter writer = new (File.OpenWrite(savePath));
        writer.Write(bytes.ToArray());
        Console.WriteLine(@"Saved {0}", savePath);
    }
}

GetAsync

このメソッドは GET メソッドでリクエストを送り、生の HTTP レスポンス (HttpResponseMessage) を受け取る。

HttpResponseMessage にはレスポンスの各種情報が含まれており、データ内容は Content プロパティである。

これはデータ内容そのものではなく System.Net.Http.HttpContent クラスのインスタンスなので、そのメソッドを使用してデータを読み取る。(下記の例では ReadAsStringAsync メソッドを使用)

// HttpClient.GetAsync メソッドのテスト
using System.Net.Http;

public class Program
{
    static HttpClient client = new ();
    static string url = "http://localhost/cgi-bin/API/echo.cgi?message=Hello+HttpClient.GetAsync";

    public static async Task Main()
    {
        Console.WriteLine("** HttpClient.GetAsync メソッドのテスト **");
        HttpResponseMessage response = await client.GetAsync(url);
        response.EnsureSuccessStatusCode();
        string responseBody = await response.Content.ReadAsStringAsync();
        Console.WriteLine("Response = {0}", responseBody);
    }
}

SendAsync

このメソッドはリクエストを送るときに HTTP ヘッダをいろいろ設定できる。メソッドはデフォルトでは GET であるが、他のメソッド、例えば DELETE とかにも変更できる。

レスポンスは GetAsync 同様、HttpResponseMessage オブジェクトであるので、GetSync 同様に System.Net.Http.HttpContent クラスのメソッドを使用して内容を取得する必要がある。

// HttpClient.SendAsync のテスト
using System.Net.Http;

public class Program
{
    static HttpClient client = new();
    static HttpRequestMessage request = new();
    static string url = "http://localhost/cgi-bin/API/echo.cgi?message=Hello+HttpClient.SendAsync";

    public static async Task Main()
    {
        Console.WriteLine("** HttpClient.SendAsync のテスト **");
        request.RequestUri = new Uri(url);
        HttpResponseMessage response = await client.SendAsync(request);
        response.EnsureSuccessStatusCode();
        string body = await response.Content.ReadAsStringAsync();
        Console.WriteLine("Response Body = {0}", body);
    }
}

JSON 形式レスポンスの取得

ウェブサービスでよく使われる JSON 形式のデータを受け取るメソッドは HttpClient には含まれていない。

しかし、HttpClient の拡張メソッドとして GetFromJson というメソッドが用意されている。

// HttpClientJsonExtensions.GetFromJsonAsync (拡張メソッド)
using System.Net.Http.Json;

Console.WriteLine("** GetFromJsonAsync **");

string url = "http://localhost/cgi-bin/API/complex.cgi?value=1.0,-1.0";
var client = new HttpClient();
var res = await client.GetFromJsonAsync<ComplexValues>(url);
if (res != null)
{
    Console.WriteLine("conjugate = {0} , {1}", res.conjugate[0], res.conjugate[1]);
    Console.WriteLine("abs = {0:F}", res.abs);
    Console.WriteLine("arg = {0:F}", res.arg);
    Console.WriteLine("poler = {0} , {1}", res.poler[0], res.poler[1]);
    Console.WriteLine("inv = {0} , {1}", res.inv[0], res.inv[1]);
    Console.WriteLine("sqr = {0} , {1}", res.sqr[0], res.sqr[1]);
    Console.WriteLine("sqrt = {0:F} , {1:F}", res.sqrt[0], res.sqrt[1]);
}
else
{
    Console.WriteLine("エラー:結果が null です。");
}

public class ComplexValues
{
    public ComplexValues()
    {
        conjugate = new double[] {0, 0};
        abs = 0.0;
        arg = 0.0;
        poler = conjugate;
        inv = conjugate;
        sqr = conjugate;
        sqrt = conjugate;
    }

    public double[] conjugate { get; set; }
    public double abs { get; set; }
    public double arg { get; set; }
    public double[] poler {get; set;}
    public double[] inv { get; set; }
    public double[] sqr { get; set; }
    public double[] sqrt { get; set; }
}

その他のメソッド

GET メソッド以外の HTTP メソッドに対応した PostAsync, PutAsync, DeleteAsync もある。

 
コメントする

投稿者: : 2023/01/25 投稿先 C#, dotNET

 

タグ: ,

ASP.NET WebApp (MVC) を試す。

MVC とは Model, View, Controller のことで、規模の大きなウェブアプリを作るときに利用されるプロジェクト形式である。

このウェブアプリは次のようにして作成する。

$ dotnet bew mvc -o WebMVC

このコマンドが完了すると ./WebMVC というフォルダが作成され、その内容は次のようになっている。

  • Controller: コントローラを配置する。デフォルトで HomeController.cs が入っている。
  • Models: モデルを配置する。デフォルトでは空である。
  • Views: ビュー (Razor ぺージ) を配置する。次のサブディレクトリを持つ。
    Home: デフォルトでは Index.cshtml と Privacy.cshrml が入っている。
    Shared: レイアウトページなどを含む。
  • wwwroot: 静的なファイルを配置するフォルダ。サブフォルダ css, js, lib を持つ。

サンプル

次のサンプルではホームページで MySQL のテーブル “Medias” の内容一覧を表示する。

また、Medias にデータを新規追加するためのフォームを持つ。

コントローラ Controllers/HomeController.cs

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using MediasTable;

namespace WebAppMVC.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        // Index ページの表示
        public IActionResult Index()
        {
            var medias = new MediasModel();
            ViewData["MediaList"] = medias.QueryAll();
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new WebMvc.Models.ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        // データ挿入フォーム
        public IActionResult FormInsert()
        {
            var medias = new MediasModel();
            int newid = medias.GetNewId();
            ViewData["newid"] = newid;
            ViewData["message"] = "";
            return View();
        }

        [HttpPost]
        public IActionResult FormInsert(int id, string name, string type, string format, string size, string fixed_on, string info)
        {
            string message = "Insert OK";
            try
            {
                var medias = new MediasModel();
                var data = new Medias {id=id, name=name, type=type, format=format, size=size, fixed_on=fixed_on, info = info, date = DateTime.Now};
                medias.Insert(data);

            }
            catch (Exception ex)
            {
                message = ex.Message;
            }
            ViewData["message"] = message;
            return View();
        }
    }
}

ビュー (Views)

Views/Index.cshtml

ホームページのためのビューで、Medias テーブルの内容一覧を表示する。

@{
    ViewData["Title"] = "ASP.NET WebApp MVC Test";
}

<div class="text-center">
    <p class="h1 text-primary">@ViewData["Title"]</p>
    <p>&nbsp;</p>
    <table class="table">
        <tr class="bg-secondary text-white"><th>id</th><th>name</th><th>type</th><th>format</th><th>size</th><th>info</th><th>date</th></tr>
        @foreach (var item in (List<MediasTable.Medias>)ViewData["MediaList"])
        {
            <tr>
                <td>@item.id</td>
                <td>@item.name</td>
                <td>@item.type</td>
                <td>@item.format</td>
                <td>@item.size</td>
                <td>@item.info</td>
                <td>@String.Format("{0:d}", item.date)</td>
            </tr>
        }
    </table>
</div>
<div><a class="nav-link text-danger" asp-area="" asp-controller="Home" asp-action="FormInsert">新規追加</a></div>

Views/FormInsert.cshtml

Medias テーブルにデータを挿入するときに使うフォームである。


@{
    ViewData["Title"] = "FormInsert";
}

<h1 class="h1 text-primary">FormInsert</h1>
<br />
<form method="post">
    <div class="mt-4">
        <label>id
            @{
                int newid = Convert.ToInt32(ViewData["newid"]);
            }
            <input type="number" name="id" id="id" class="form-control" value="@newid" readonly />
        </label>
    </div>
    <div class="mt-4">
        <label>
            name
            <input type="text" name="name" id="name" size="50" class="form-control" />
        </label>
    </div>
    <div class="mt-4">
        <label>
            type
            <input type="text" name="type" id="type" size="30" class="form-control" />
        </label>
    </div>
    <div class="mt-4">
        <label>
            format
            <input type="text" name="format" id="format" size="30" class="form-control" />
        </label>
    </div>
    <div class="mt-4">
        <label>
            fixed_on
            <input type="text" name="fixed_on" id="fixed_on" size="30" class="form-control" />
        </label>
    </div>
    <div class="mt-4">
        <label>
            info
            <input type="text" name="info" id="info" size="100" class="form-control" />
        </label>
    </div>
    <input type="hidden" name="date" id="date" value="javascript:new Date()" />
    <div class="mt-4">
        <button type="submit" id="submitButton" class="btn btn-primary">Submit</button>
    </div>
    <div class="mt-4 text-info">
        @ViewData["message"]
    </div>
</form>

モデル ./Models

ここには MediaModel.cs というファイルを作成した。詳しくは「ASP.NET Web App MVC のモデルの例」を参照。

 
コメントする

投稿者: : 2023/01/22 投稿先 ASP.NET, C#, dotNET

 

タグ: ,

.NETの勉強:拡張された switch 文

C# 7.0 からは switch 文が型判別を行えるようになった。

同時に、型変換と値判別 (when 句) も行える。

次に switch 文の新しい機能の例を示す。

// 拡張された switch 文
Console.WriteLine("** 拡張された switch 文 **");

object x = 1000;

// 型だけを判別
switch (x)
{
    case string:
        Console.WriteLine("x is string");
        break;
    case int:
        Console.WriteLine("x is int");
        break;
    default:
        Console.WriteLine("?");
        break;
}

// 型判別と型変換
switch (x)
{
    case int n:
        Console.WriteLine("x is int {0}", n);
        break;
    default:
        Console.WriteLine("?");
        break;
}

// 型と範囲を判別
switch (x)
{
    case int n when n > 0:
        Console.WriteLine("x is int and plus {0}", n);
        break;
    default:
        Console.WriteLine("?");
        break;
}

// 複数条件で型と範囲を判別 (when)
object y = -1.5;
switch (y)
{
    case int n when n > 0:
        Console.WriteLine("y is int and plus {0}", n);
        break;
    case double a when a > 0.0:
        Console.WriteLine("y is double and plus {0}", a);
        break;
    case double a when a < 0.0:
        Console.WriteLine("y is double and minus {0}", a);
        break;
    default:
        Console.WriteLine("?");
        break;
}

switch 式

C#8 からは switch 文に加え switch 式という機能が追加され、switch が式として書けるようになった。

// switch の拡張 switch 式
var println = (Object o) => Console.WriteLine(o.ToString());

int compare(int? x, int? y) => (x, y) switch
{
 (int i, int j) => i.CompareTo(j),
 ({ }, null) => 1,
 (null, { }) => -1,
 (null, null) => 0
};

println("switch の拡張 switch 式");

println(compare(10, 1));
println(compare(10, null));
println(compare(null, null));

 
コメントする

投稿者: : 2023/01/22 投稿先 C#, dotNET

 

タグ: ,