じいちゃんが亡くなった話

いつもお世話になっております、記事を書いているアキちゃんです。

あまり技術系の備忘録ブログで私生活や個人の思想を書くことはあまり良くないと思いこれまで書いてきませんでしたが、更新頻度が1月に入り激減したことの言い訳も含めて少し書いてみようと思います。

タイトルの通りなのですが、私の祖父が2023年1月18日に他界しました。

86歳(数え年で88歳)の大往生となり、身体が悪く老人ホームに入っている祖母も通夜に来た時に『お父さんは、好きなことして生きてきたから悔いはないでしょう。』と言っていた通り波乱万丈な人生を送ってきた人でした。

祖父は困っている人がいたら助けずにはいられず不公平なことを許せない人で、でもちょっと意地悪なことをする人に愛されるじいさんでした。

入院していた病院の看護師さんからもいつもニコニコしていて可愛らしいおじいちゃんと言ってもらえて、祖母に祖父の他界を伝えるか家族で悩んでいたところ老人ホームの方から『おじいさんとおばあさんは凄い仲のいい夫婦だったので会わせてあげたらどうか』と言ってもらえるくらいばあちゃんと仲良く過ごせていたみたいなので、ここ数年はコロナで会うことはできませんでしたがそれなりに楽しく過ごしてくれていたのかなと思います。

通夜にも70人以上の方々が参列して頂き、親戚では最後に一目会いたいと新潟から会いにきてくれた人もいました。(移動に10万以上かかったそうです)

人の死は避けられないもので、生まれた以上最後は生命を全うすることとなります。

では死んでしまったら生きてきたことに意味がないのかというとそんな訳はなく、その時に自分のことを想ってくれる人と残された人達が故人の想いを繋いで生きていくことがきっとその人の生涯に意味をもたらすのかな、と今回のことで思いました。

今月は寝正月して祖父のお葬式もありバタバタして記事がなかなか書けませんでしたが、来月からはじいちゃんにしゃんとせんかと言われないためにも週1で更新頑張っていこうと思うので引き続き読んでもらえたらと思います。

 

最後にじいちゃんへ

じいちゃんに小6のときに将棋で完膚なきまでにボコボコにされたこととか、その他ありとあらゆる遊びで転がされ続けたことは多分一生忘れないけど、天国からお茶菓子と黒砂糖なめながらこれからも見守っていてね。

 

以上

Blazor Serverで簡易ファイル共有(前編:ファイルアップロード)

初めまして、アキちゃんです。

備忘録をかねてブログを書くことにしました。

今回はBlazor Serverで簡易ファイル共有(前編:ファイルアップロード)について書きます。

それでは早速・・・Blazor Serverで簡易ファイル共有(前編:ファイルアップロード)!

Lesson.1 事の発端

たまにファイルを共有したいことがありますよね、なのでアップロードしたファイルを共有できる簡易ファイル共有アプリを作ろうと思います。

Lesson.2 プロジェクト作成(FileShare)

プロジェクト名はFileShareで、Blazor Server プロジェクト(.Net6)で作成します。

基本デフォルトのまま次へ押しとけば大丈夫ですが一応以下記事にまとめています。

akr9915.hatenablog.com

Lesson.3 事前準備

今回は取り敢えずファイルアップロードを実装しようと思います。

作成したプロジェクトのwwwrootの下にshareフォルダを作成します。

プロジェクト直下にControllersというフォルダを作成します。

ファイルのアップロードとダウンロードはControllerから取得したいのでProgram.csに以下のコードを追加します。

app.UseRouting();
app.UseEndpoints(userEndPpoints =>
{
    userEndPpoints.MapControllers();
});
app.MapBlazorHub();

Controllerを使用するためHttpRequestMessageを使用したいため、Program.csに以下コードを追記します。

builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddHttpClient();

var app = builder.Build();

ここまででプロジェクトファイルは以下のようになっていると思います。

Lesson.4 ファイルアップロード 

ファイルをアップロードするコントローラーを作りたいため、Controllerフォルダに 追加→コントローラー→MVCコントローラー - 空 を選択し、UploadController.csを作成します。

できたUploadController.csをざっくり以下のように修正します。

UploadController.cs(クリックで展開) UploadController.cs(クリックで圧縮)
using Microsoft.AspNetCore.Mvc;
namespace FileShare.Controllers
{
    public class UploadController : Controller
    {
        private readonly IWebHostEnvironment environment;
        public UploadController(IWebHostEnvironment environment)
        {
            this.environment = environment;
        }
        [Route("api/upload/file")]
        public async Task<IActionResult> UploadDocument(IFormFile file)
        {
            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    await file.CopyToAsync(memoryStream);
                    var data2 = memoryStream.ToArray();
                    var filePath = Path.Combine(environment.WebRootPath + @"\share", file.FileName);
                    using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                    {
                        fs.Write(data2, 0, data2.Length);
                        fs.Close();
                    }
                };
                return StatusCode(200);
            }
            catch (Exception ex)
            {
                return StatusCode(500, ex.Message);
            }
        }
    }
}

ファイルをアップロードする画面を作りたいため、Pagesフォルダに追加→Razorコンポーネントを選択し、Upload.razorを作成します。

できたUpload.razorをざっくり以下のように修正します。

Upload.razor(クリックで展開) Upload.razor(クリックで圧縮)
@page "/upload"
@inject IHttpClientFactory ClientFactory
<label style=" height:30px;line-height:30px;color:black;background-color:greenyellow;  border-radius: 5px; ">
    ファイル選択
    <InputFile style="display:none" OnChange="@(args => FileUpload(args))" />
</label>
@code {
    async Task FileUpload(InputFileChangeEventArgs e)
    {
        foreach (var file in e.GetMultipleFiles())
        {
            var ms = new MemoryStream();
            await file.OpenReadStream(file.Size).CopyToAsync(ms);
            ms.Position = 0;
            var fileContent = new StreamContent(ms);
            using var content = new MultipartFormDataContent();
            content.Add(fileContent, "file", file.Name);
            var request = new HttpRequestMessage(HttpMethod.Post, "https://localhost:7281" + "/api/upload/file");
            var client = ClientFactory.CreateClient();
            {
                request.Content = content;
                await client.SendAsync(request);
            }
        }
    }
}

upload.razorにアクセスできるようにするために、Shared/NavMenu.razorを以下のように修正します。

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="upload">
                <span class="oi oi-data-transfer-upload" aria-hidden="true"></span> Upload
            </NavLink>
        </div>
    </nav>
</div>

ここまできたら実行しUploadを選択後、ファイル選択を押下します。

表示された開く画面から任意のファイルを選択し開くorダブルクリックします。

表示された開く画面が閉じた後、プロジェクトのwwwroot/shareフォルダに選択したファイルがアップロードされているはずです。

Lesson.5 あとがき

年明けましたね!

年末年始忙しすぎて全然記事書けなかった!

ソースコードは後編で公開します。

また、記事では使用しないように実装しましたが

OpenReadStream().ReadAsync(buffer)

を使用するとうまく動作しないみたいです。詳しくは以下記事を確認ください。

github.com

github.com

Blazor ServerでExcelに電子印鑑(後編:Excel画像貼り付け)

初めまして、アキちゃんです。

備忘録をかねてブログを書くことにしました。

今回はBlazor ServerでExcelに電子印鑑(後編:Excel画像貼り付け)について書きます。

それでは早速・・・Blazor ServerでExcelに電子印鑑(後編:Excel画像貼り付け)!

Lesson.1 前回まで

前回までで印鑑っぽい画像のBitmapを返すControllerを作成しました。

*2022/12/26に誤りが確認でき、記事のLesson.4の修正を行いました。

akr9915.hatenablog.com

今回はHttpRequestMessageでBitmapを取得しExcelの任意のセルに張り付けるところまでしようと思います。

Lesson.2 NPOI

C#Excelを操作することのできるライブラリは結構たくさんあります。

画像やグラフの操作に関してはNPOIが一番簡潔だと思うので使わせてもらいましょう。

Nugetパッケージの管理からNPOIをインストールします。

Lesson.3 Excel画像貼り付け

前回までに、Stamp画像のStreamを取得できるControllerを作成しました。

なので今回はStreamを渡してExcelに張り付けるControllerを作ろうと思います。

Controllerフォルダに 追加→コントローラー→MVCコントローラー - 空 を選択し、SealStampController.csを作成します。

できたSealStampController.csをざっくり以下のように修正します。

SealController.cs(クリックで展開) SealController.cs(クリックで圧縮)
using Microsoft.AspNetCore.Mvc;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
namespace ExcelStamp.Controllers
{
    public class SealStampController : Controller
    {
        private readonly IWebHostEnvironment environment;
        public SealStampController(IWebHostEnvironment environment)
        {
            this.environment = environment;
        }
        [HttpPost]
        [Route("api/sealstamp")]
        public async Task<IActionResult> SealStamp(IFormFile[] file)
        {
            var guid = Guid.NewGuid();
            try
            {
                var orgFileName = Path.Combine(environment.WebRootPath + @"\excel", "test.xlsx");
                var newFileName = Path.Combine(environment.WebRootPath + @"\delete", $"{guid}.xlsx"); ;
                System.IO.File.Copy(orgFileName, newFileName);
                using (var stampMs = new MemoryStream())
                {
                    await file[0].CopyToAsync(stampMs);
                    var stamp = stampMs.ToArray();
                    var anchor = new XSSFClientAnchor()
                    {
                        AnchorType = AnchorType.DontMoveAndResize,
                        Row1 = 0,
                        Row2 = 4,
                        Col1 = 0,
                        Col2 = 2,
                        Dx1 = XSSFShape.EMU_PER_PIXEL * 5,
                        Dx2 = XSSFShape.EMU_PER_PIXEL * -5,
                        Dy1 = XSSFShape.EMU_PER_PIXEL * 5,
                        Dy2 = XSSFShape.EMU_PER_PIXEL * -5,
                    };
                    SealStamp(newFileName, newFileName, "Sheet1", anchor, stamp);
                }
                return StatusCode(200);
            }
            catch (Exception ex)
            {
                return StatusCode(500, ex.Message);
            }
        }
        protected void SealStamp(string inpath, string outpath, string sheetName, XSSFClientAnchor anchor, byte[] data)
        {
            var book = WorkbookFactory.Create(inpath);
          var sheet = book.GetSheet(sheetName);
            var pictureidx = book.AddPicture(data, (PictureType)XSSFWorkbook.PICTURE_TYPE_PNG);
            var patriarch = sheet.CreateDrawingPatriarch();
            patriarch.CreatePicture(anchor, pictureidx);
         
            using (var fs = new FileStream(outpath, FileMode.OpenOrCreate))
            {
                book.Write(fs, true);
            }
          book.Close();
        }
    }
}

動作としてはwwwroot/excel/text.xlsxを発行したGUID.xlsxでwwwroot/delete/{GUID}.xlsxでコピーを取りそのファイルに画像を貼り付けてStatusCode(200)を返しています。

Excelのシートへの画像貼り付けはRow1,Row2,Col1,Col2で位置調整します。

Lesson.4 HttpRequestMessage

あとは、それぞれ作ったControllerを使用するためHttpRequestMessageを使う準備をします。

Program.csに以下コードを追記します。

builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddHttpClient();

var app = builder.Build();

社名・日付・名前・権限を入力できるようにIndex.razorを以下のように修正します。

Index.razor(クリックで展開) Index.razpr(クリックで圧縮)
@page "/"
@inject IHttpClientFactory ClientFactory
<PageTitle>Index</PageTitle>
<div class="form-group">
    <label>
        会社名:
        <input @bind="compInput" size="50" />
    </label>
</div>
<div class="form-group">
    <label>
        ユーザー名:
        <input @bind="userInput" size="50" />
    </label>
</div>
<div class="form-group">
    <label>
        日付文字列:
        <input @bind="datestrInput" size="50" />
    </label>
</div>
<div class="form-group">
    <label>
        権限文字列:
        <input @bind="powerInput" size="50" />
    </label>
</div>
<button @onclick="Send">作成</button>
@code {
    private string? compInput;
    private string? userInput;
    private string? datestrInput;
    private string? powerInput;
    private async Task Send()
    {
        byte[] stampBinary;
        {
            var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:7009" + "/api/stamp/getstamp/" + compInput + "/" + userInput + "/" + powerInput + "/" + datestrInput);
            var client = ClientFactory.CreateClient();
            var response = await client.SendAsync(request);
            stampBinary = await response.Content.ReadAsByteArrayAsync();
        }
        {
            var request = new HttpRequestMessage(HttpMethod.Post, "https://localhost:7009" + "/api/sealstamp");
            var content = new MultipartFormDataContent();
            content.Add(new ByteArrayContent(stampBinary), "file", "stamp.bin");
            var client = ClientFactory.CreateClient();
            {
                request.Content = content;
                await client.SendAsync(request);
            }
        }
    }
}

赤字のポート番号はお手前のプロジェクトに合わせてください。

ここまで来たら実行してみて以下のように入力して作成を押下します。

するとプロジェクトのwwwroot/deleteにExcelファイルができるのでこれをダブルクリックで開きます。

すると黒印鑑でA1~B4のセルに黒い印鑑画像が貼り付けられていたらここまで完了です。

また権限を1にすることで赤い印鑑画像が貼り付けられます。

Lesson.5 あとがき

前回記事ではソースがないのにミスコードを上げてしまい申し訳なかったです。: (_;´꒳`;) :_

以上・・・

github.com

Blazor ServerでExcelに電子印鑑(前編:印鑑画像)

初めまして、アキちゃんです。

備忘録をかねてブログを書くことにしました。

今回はBlazor ServerでExcelに電子印鑑(前編:印鑑画像)について書きます。

それでは早速・・・Blazor ServerでExcelに電子印鑑(前編:印鑑画像)!

Lesson.1 事の発端

世の中ではDXを推進してるなか、僕の会社では印刷したExcelに印鑑押してスキャンしたpdfでドキュメント管理をしています。

すべてを電子化すればよいというわけではないですが、いくらなんでも社内文書くらいは電子化したいので頑張ろうと思います。

Lesson.2 プロジェクト作成(ExcelStamp)

プロジェクト名はExcelStampで、Blazor Server プロジェクト(.Net6)で作成します。

基本デフォルトのまま次へ押しとけば大丈夫ですが一応以下記事にまとめています。

akr9915.hatenablog.com

Lesson.3 事前準備

今回は取り敢えず印鑑の画像を作成し、取得するところまでしようと思います。

作成したプロジェクトのwwwrootの下にexcelフォルダ、stampフォルダ、deleteフォルダを作成します。

stampフォルダの下に、inkan-brack.png・inkan-red.pngの名前で以下の画像を保存します。

 

excelフォルダの下に、test.xlsxの名前でExcelファイルを作ります。

作成した test.xlsx のシート Sheet1 で A1~B4 セルの背景を白く塗りつぶします。

ここまでしたら、wwwrootフォルダは以下のようになっていると思います。

プロジェクト直下にControllersというフォルダを作成します。

例にもよって印鑑の画像とExcelファイルはControllerから取得したいのでProgram.csに以下のコードを追加します。

app.UseRouting();
app.UseEndpoints(userEndPpoints =>
{
    userEndPpoints.MapControllers();
});
app.MapBlazorHub();

Lesson.4 印鑑画像Stream取得

画像をいじるにあたってNugetパッケージの管理より System.Drawing.Common をインストールします。

Controllerフォルダに 追加→コントローラー→MVCコントローラー - 空 を選択し、StampController.csを作成します。

できたStampController.csをざっくり以下のように修正します。

StampController.cs(クリックで展開) StampController.cs(クリックで圧縮)
using Microsoft.AspNetCore.Mvc;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace ExcelStamp.Controllers
{
    public class StampController : Controller
    {
        string pnginkan_red = string.Empty;
        string pnginkan_brack = string.Empty;
        private readonly IWebHostEnvironment environment;
        public StampController(IWebHostEnvironment environment)
        {
            this.environment = environment;
            pnginkan_red = Path.Combine(environment.WebRootPath, "stamp/inkan-red.png");
            pnginkan_brack = Path.Combine(environment.WebRootPath, "stamp/inkan-black.png");
        }
        [Route("api/stamp/getstamp/{compName}/{userName}/{authority}/{date}")]
        public IActionResult GetStamp(string compName, string userName, int authority, string date)
        {
            try
            {
                byte[] img;
                var userColor = authority > 0 ? pnginkan_red : pnginkan_brack;
                var userFontColor = authority > 0 ? Brushes.Red : Brushes.Black;
                using (var userStamp = CreateStamp(userColor, userFontColor, compName, userName, date))
                using (var ms = new MemoryStream())
                {
                    userStamp.Save(ms, ImageFormat.Png);
                    img = ms.GetBuffer();
                }
               return File(img, "image/png", $"{compName}_{userName}_{authority}_{date}.png");
            }
            catch (Exception ex)
            {
                return StatusCode(500, ex.Message);
            }
        }
        Bitmap CreateStamp(string path, Brush fontColor, string compName, string name, string dateTime)
        {
            var rslt = new Bitmap(path);
            using (var g = Graphics.FromImage(rslt))
            using (var stringFormat = new StringFormat(StringFormat.GenericTypographic))
            using (var font = new Font("Arial", 15, FontStyle.Bold))
            {
                g.SmoothingMode = SmoothingMode.AntiAlias;
                g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                var compSize = g.MeasureString(compName, font, 1000, stringFormat);
                var dateSize = g.MeasureString(dateTime, font, 1000, stringFormat);
                var nameSize = g.MeasureString(name, font, 1000, stringFormat);
              g.DrawString(compName, font, fontColor, rslt.Width / 2 - compSize.Width / 2, rslt.Height / 6 * 1 - compSize.Height / 2 - -5, stringFormat);  
              g.DrawString(dateTime, font, fontColor, rslt.Width / 2 - dateSize.Width / 2, rslt.Height / 6 * 3 - dateSize.Height / 2 - -2, stringFormat); ;
              g.DrawString(name, font, fontColor, rslt.Width / 2 - nameSize.Width / 2, rslt.Height / 6 * 5 - nameSize.Height / 2 - 0, stringFormat);  
                rslt.MakeTransparent(Color.White);
            }
            return rslt;
        }
    }
}

api/stamp/getstamp/{compName}/{userName}/{authority}/{date}のパラメータについてですが

compName:会社名

userName:ユーザーの名前

authority:ユーザーの権限(0なら黒印鑑、1以上なら赤印鑑)

date:印鑑の日付

となっています。

日付はフォーマットが色々想定されるためDatetime型ではなくstring形で実装をしております。

ここまできたら実行して、(port番号:7239は、お手前の環境に合わせてください)

https://localhost:7239/api/stamp/getstamp/Aki/aki/0/20221220

とUrlに入力し実行するとAki_aki_0_20221220.pngという名前で以下画像がダウンロードされ

https://localhost:7239/api/stamp/getstamp/Aki/aki/1/20221220

とUrlに入力し実行するとAki_aki_1_20221220.pngという名前で以下画像がダウンロードされると思います。

Lesson.5 あとがき

後半も見て頂ければと思います。

*2022/12/26追記

紹介したソースコードですがLesson.4のStampController.csに間違いがあり以下に修正しました、

pnginkan_red = Path.Combine(environment.WebRootPath, "stamp/inkan-red.png");
pnginkan_brack = Path.Combine(environment.WebRootPath, "stamp/inkan-black.png");

以後無いように気を付けるので引き続き見て頂けたらと思います。: (_;´꒳`;) :_

以上!

Blazor Serverでチャットアプリ

初めまして、アキちゃんです。

備忘録をかねてブログを書くことにしました。

今回はBlazorでチャットアプリについて書きます。

それでは早速・・・Blazroでチャットアプリ!

Lesson.1 事の発端

C#でチャットアプリ作りたいなあと思っていたところ、以下のような記事を見つけました。

基本なぞるだけですが、せっかくなのでやってみようと思います。

learn.microsoft.com

Lesson.2 プロジェクト作成(SignalRChat)

プロジェクト名は SignalRChatで、Blazor Server プロジェクト(.Net6)で作成します。

基本デフォルトのまま次へ押しとけば大丈夫ですが一応以下記事にまとめています。

akr9915.hatenablog.com

Lesson.3 実装

プロジェクトを右クリック→Nugetパッケージの管理 からMicrosoft.AspNetCore.SignalR.Clientをインストールします。

プロジェクトを右クリック→追加→新しいフォルダーの追加 からHubsフォルダを作成します。

Hubsフォルダを右クリック→追加→クラス からChatHub.csを作成します。

Hubs/ChatHub.csを以下のように修正します。

using Microsoft.AspNetCore.SignalR;
namespace SignalRChat.Hubs // SignalRChatはプロジェクト名に合わせる
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", user, message);
        }
    }
}

Program.csを以下のように修正します。

using SignalRChat.Data;
using Microsoft.AspNetCore.ResponseCompression;
using SignalRChat.Hubs;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddResponseCompression(opts =>
{
    opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/octet-stream" });
});

var app = builder.Build();
app.UseResponseCompression();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.MapBlazorHub();
app.MapHub<ChatHub>("/chathub");
app.MapFallbackToPage("/_Host");
app.Run();

以下リンクの チャット用の Razor コンポーネント コードを追加する の手順通りにPages/Index.razorを修正します。

learn.microsoft.com

ここまできたら実行して出てきたタブを複製して2画面にします。
任意のUserとMessageを送信して画面に表示されたら動作は完了です。

Lesson.4 あとがき

総PV数が100超えました、うれしくて目からソースコードがでてきました。

以上!

Blazor Serverプロジェクトの作り方

初めまして、アキちゃんです。

備忘録をかねてブログを書くことにしました。

今回はBlazor Serverプロジェクトの作り方について書きます。

それでは早速・・・Blazor Serverプロジェクトの作り方!

Lesson.1 事の発端

毎回記事書くたびにおんなじ文章を載せるのは、コードを書く者としてよろしくないと思ったため、記事全般で使用するテンプレートプロジェクトの作り方を以下にまとめます。

環境は Visual Studio 2022 Community を使用します。

Visual Studioに関して、Visual Studio 2019以前のものでは.Net 6に非対応のため恐らくデバックできないとおもいます。

なのでVisual Studio 2022以降のものを使うことを推奨します。

Lesson.2 プロジェクトの作成

Visual Studio 2022 Comunityを開き ファイル→新規作成→プロジェクト を押下し、新しいプロジェクトの作成 を押下します。

Blazor Server アプリを選択し、次へを押下します。

プロジェクト名に任意の名前を入力(今回はサンプルとしてSampleTemplate)し次へを押下します。

僕の記事では特に凝ったことをする予定はないためデフォルトのまま作成を押下します。

以下画面がでたら画面上部の任意のプロジェクト名(今回はSampleTemplate)のボタンをクリックしプログラムを実行します。

以下画面がでたらプロジェクトの作成の完了です。

Lesson.3 あとがき?

毎回このページ開くのも面倒なので覚えておくといいかもしれません。

以上!

【Qiita Advent Calender 2022】Blazor+Radzen & みんなの翻訳でLine風翻訳あぷりつくってみたよ

初めまして、アキちゃんです。

備忘録をかねてブログを書くことにしました。

今回は初の試みで、Qiita Advent Calender 2022に記事を書いてみることにしました。

内容は「Blazor+Radzen & みんなの自動翻訳@TexTraでLine風翻訳あぷりつくってみたよ」です。

それでは早速・・・

Blazor+Radzen & みんなの自動翻訳@TexTraでLine風翻訳あぷり

(ピクチャインピクチャじゃないと潰れてほぼみえませんね・・・)

Lesson.0 事前準備

ある程度の準備はしておきたいと思います。

1.Blazor Serverのプロジェクトの作成し、Radzen導入だけ済ませたものを用意します。本記事では、プロジェクトの名前は"Honya-kun"として作成します。

akr9915.hatenablog.com

2.みんなの自動翻訳@TexTraのAPIを使いたいため、以下サイトでユーザー登録を行います。

mt-auto-minhon-mlt.ucri.jgn-x.jp

3.2で登録したユーザー情報で、みんなの自動翻訳@TexTraにログインしユーザーの設定からWeb APIAPI keyとAPI secretを確認します。(併せてユーザーIDも後で使うので覚えておきます)

Lesson.1 みんなの自動翻訳API

まずみんなの自動翻訳のAPIを使えるようにしたいため、サイトから公開されているソースコードを取得したいとおもいます。

Dataフォルダを右クリックし"追加"→"クラス"Textra.csを追加し、作成されたファイルのソースコードをすべて消します。

みんなの自動翻訳のページに行きログイン→Web APIのページに行きます。

Web API一覧から自動翻訳リクエスト-一覧をクリック

汎用NT 【 英語 - 日本語 】のAPIのインフォメーションアイコンをクリックします。

自動翻訳 WebAPIという画面が表示されるので、下の方にスクロールしていくとアクセス例がでてきます。OAuth→C#を選択し少し下にスクロールしusing System;・・以下をまるまるコピーして先ほど追加したTextra.csにまるまるペーストします。

貼り付けたソースコードの13行目、724行目、764行目を削除します。

13行目 不要な参照のため削除
using System.Windows.Forms;
724行目 設定は明示的にしたいため削除
private static TexTraPlugin.Properties.Settings MySettings = TexTraPlugin.Properties.Settings.Default;
764行目 ASP.Netでは使えない関数のため削除
if (show_message) MessageBox.Show("XMLの読込に失敗しました。\n\n" + e.Message);

724行目を削除した段階でおびただしい数のエラーが発生します、代わりの構造体を用意してあげるために22行目APIAccessor_jaの宣言の直下に以下のコードを追加します。

    public static class APIAccessor_ja
    {
        private static TexTraPlugin MySettings = new TexTraPlugin()
        {
          API_URL = "",
          API_KEY = "",
          API_SECRET = "",
          API_USER = ""
        };
        public class TexTraPlugin
        {
            public string API_自動翻訳 { get; set; }
            public string API_URL { get; set; }
            public string API_KEY { get; set; }
            public string API_SECRET { get; set; }
            public string API_USER { get; set; }
            public string proxy { get; set; }
            public string proxy_id { get; set; }
            public string proxy_password { get; set; }
            public int proxy_port { get; set; }
        }
      #region "API"

371行目辺りにinit_settings()という関数がありますが、この中に定義されているUrlが若干違うのでgeneral→generalNTに修正します。

"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_zh-CN_ja/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_zh-TW_ja/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_zh-CN/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_zh-TW/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_en/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_ko/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_en_ja/"
"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ko_ja/"

実際のところは"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_en/"だけ変えればいいんですが、今後使う機会があるかもしれないので全部変えておきましょう。

24行目に追加したMySettingsに設定を入れようと思います。

API_URLは今回日本語→英語の想定なので"https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_en/"を入力します。それ以外の項目はLesson.0で確認した値を入力します。

private static TexTraPlugin MySettings = new TexTraPlugin()
{
      API_URL = "https://mt-auto-minhon-mlt.ucri.jgn-x.jp/api/mt/generalNT_ja_en/",
      API_KEY = "ここは自分のAPI Keyを入れます",
      API_SECRET = "ここは自分のAPI Secretを入れます",
      API_USER = "ここは自分のUser Nameを入れます"
};

API Keyはユニークであり、第三者に知られると悪用されることもあるため絶対に自分以外の人間にはばれないようにしましょう。(というか知られたら120%悪用されると思ってください)

ここまできたら動くか確認しましょう、Pages/Counter.razorを以下のように修正します。

@page "/counter"
@using static TexTra.APIAccessor_ja
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<RadzenTextBox @bind-Value=@orgValue></RadzenTextBox>
<RadzenTextBox @bind-Value=@newValue></RadzenTextBox>
@code {
    private int currentCount = 0;
    string orgValue = string.Empty;
    string newValue = string.Empty;
    private void IncrementCount()
    {
        currentCount++;
        var get = TexTra.APIAccessor_ja.get_auto_trans(orgValue, Language.ja, Language.en, out APIResponseBean aPIResponseBean);
        if (get.Any())
        {
            newValue = ((TexTra.APIAccessor_ja.AutoTransInfo)get[0].value).text_translated;
        }
    }
}

アプリケーションを起動しCounterに遷移します。

左側のテキストボックスに”野球”と入力し、Click meを押下後右側のテキストボックスにBaseballと表示されたらここまでは完了です。

Lesson.2 Line風導入

あとはLine風の送信画面を追加しようと思います。

RadzenにはHtmlEditorがあるので、これにCssを反映させてそれっぽい画面を作っていこうと思います。

Line風のCssは以下サイトを参考にさせてもらおうと思います。

jisuijisan.com

Pagesフォルダを右クリック後、追加→RadzenコンポーネントからChat.razorを追加し、Chat.razorに作成時書かれているコードを削除します。

取り敢えず上記のサイトように表示できることを確認したいため、HtmlとCssを拝借して以下のコードをChar.razorにコピペします。

Char.razor(クリックで展開) Char.razor(クリックで圧縮)
@page "/chat"
<RadzenHtmlEditor @bind-Value=@value></RadzenHtmlEditor>
@code {
    string value = string.Empty;
    string chatCss
    {
        get => "<style>" +
               "/*==============" +
               "LINE風フキダシ" +
               "===============*/" +
               "/*フレームとフォント*/" +
               ".kaiwa.line {" +
               "    width: 100%;" +
               "    max-width: 500px;" +
               "    margin: 0 auto;" +
               "    padding: 10px 0;" +
               "    background: #769ece;" +
               "    font-family: \"ヒラギノ角ゴ Pro W3\",\"Hiragino Kaku Gothic Pro\",\"Helvetica Neue\", \"Lucida Sans Unicode\", \"Arial\";" +
               "    font-size: 16px;" +
               "    color: #333;" +
               "    line-height: 1.4;" +
               "    overflow: hidden;" +
               "}" +
               "/*フキダシ共通*/" +
               ".kaiwa.line .fukidasi {" +
               "    position: relative;" +
               "    display: inline-block;" +
               "    max-width: 192px;" +
               "    margin: 8px 0 0;" +
               "    padding: 9px 14px;" +
               "    border-radius: 19px;" +
               "    overflow-wrap: break-word;" +
               "    clear: both;" +
               "    box-sizing: content-box;/*はてな用*/" +
               "}" +
               "/*フキダシ左*/" +
               ".kaiwa.line .fukidasi.left {" +
               "    float: left;" +
               "    margin-left: 62px;" +
               "    background: white;" +
               "}" +
               "/*グループのときのフキダシ*/" +
               ".kaiwa.line .name + .fukidasi.left {" +
               "    margin-top: 5px;" +
               "}" +
               "/*フキダシ右*/" +
               ".kaiwa.line .fukidasi.right {" +
               "    float: right;" +
               "    margin-right: 12px;" +
               "    background: #7adc40;" +
               "}" +
               "/*相手の名前*/" +
               ".kaiwa.line .name {" +
               "    clear: right;" +
               "    margin-left: 62px;" +
               "    color: white;" +
               "}" +
               "/*ユーザアイコン*/" +
               ".kaiwa.line .icon {" +
               "    position: absolute;" +
               "    width: 40px;" +
               "    height: 40px;" +
               "    left: -54px;" +
               "    top: -2px;" +
               "    border-radius: 20px;" +
               "}" +
               "/*グループのときのユーザアイコン*/" +
               ".kaiwa.line .name + .left .icon {" +
               "    top: -1.8em;" +
               "}" +
               "/*しっぽ共通*/" +
               ".kaiwa.line .fukidasi::after {" +
               "    position: absolute;" +
               "    content: \"\";" +
               "    width: 24px;" +
               "    height: 36px;" +
               "    top: -21px;" +
               "}" +
               "/*しっぽ左*/" +
               ".kaiwa.line .fukidasi.left:after {" +
               "    left: -10px;" +
               "    border-radius: 18px 0 6px 18px/18px 0 1px 18px;" +
               "    box-shadow: -3px -15px 0 -5px white inset;" +
               "}" +
               "/*しっぽ右*/" +
               ".kaiwa.line .fukidasi.right::after {" +
               "    right: -10px;" +
               "    border-radius: 0 18px 18px 6px/0 18px 18px 1px;" +
               "    box-shadow: inset 3px -15px 0 -5px #7adc40;" +
               "}" +
               "/*フキダシが続いてしっぽがないとき*/" +
               ".kaiwa.line .left + .left::after," +
               ".kaiwa.line .right + .right::after {" +
               "    content: none;" +
               "}"+
               "</style>" ;
    }
    protected override void OnParametersSet()
    {
        value = chatCss;
        value += "<div class=\"kaiwa line\">"+
        "    <div class=\"name\">"+
        "        おなまえ"+
        "    </div>"+
        "    <div class=\"fukidasi left\">"+
        "        <img class=\"icon\" src=\"img/icon.png\" alt=\"\">LINE風です。"+
        "    </div>"+
        "    <div class=\"fukidasi left\">"+
        "        続けて喋るとフキダシのしっぽはつきません。"+
        "    </div>"+
        "    <div class=\"fukidasi right\">"+
        "        HTMLとCSSでできます。"+
        "    </div>"+
        "    <br><!-- 次のフキダシにしっぽをつけたいときはbrを挿入 -->"+
        "    <div class=\"fukidasi right\">"+
        "        続けても、しっぽをつけたいときは間にbrを入れます。"+
        "    </div>"+
        "    <div class=\"fukidasi left\">"+
        "        <img class=\"icon\" src=\"img/icon.png\" alt=\"\">グループじゃないとき。" +
        "    </div>" +
        "</div>";
    }
}

Shared/NavMenu.razorのNavLinkに以下のコードを追加します。

  <div class="nav-item px-3">
          <NavLink class="nav-link" href="chat">
              <span class="oi oi-chat" aria-hidden="true"></span>Chat
            </NavLink>
  </div>

ここまできたら動くか確認しましょう、アプリを実行し左のタブからChatに移動し、以下画面が表示されたらここまでは完了です。

Lesson.3 Line風画面Component化

上記までである程度できてきましたが、もう少しそれっぽい画面にしたいと思います。

プロジェクトにComponentsフォルダを作成し、そのフォルダにLineChat.razorを作成します。

作成したLineChat.razorに以下のコードを貼り付けます。

<RadzenHtmlEditor @bind-Value=@Value class="w-100" style="font-family:Source Han Code JP;height:100%;" UploadUrl="upload/image" Disabled=@false>
    <RadzenHtmlEditorSeparator />
</RadzenHtmlEditor>
@code {
    string _Value = string.Empty;
    [Parameter]
    public string Value
    {
        get => _Value;
        set
        {
            if (_Value == value) return;
            _Value = value;
            ValueChanged.InvokeAsync(value);
        }
    }
    [Parameter]
    public EventCallback<string> ValueChanged { get; set; }
}

Pages/Chat.razorを以下のように修正します。

@page "/chat"
@using Radzen
<RadzenSplitter Orientation="Orientation.Vertical" Style="height:850px;">
    <RadzenSplitterPane Size="90%">
        <div class="h-100" style="overflow-y: scroll;">
            <Honya_kun.Components.LineChat @bind-Value=@value></Honya_kun.Components.LineChat>
        </div>
    </RadzenSplitterPane>
    <RadzenSplitterPane Size="10%">
        <RadzenSplitter Orientation="Orientation.Horizontal">
            <RadzenSplitterPane Size="70%">
                <RadzenTextArea class="w-100 h-100" @bind-Value=@sendStr></RadzenTextArea>
            </RadzenSplitterPane>
            <RadzenSplitterPane Size="30%">
                <RadzenButton class="w-100 h-100" Text="送信" Click=@SendClick></RadzenButton>
            </RadzenSplitterPane>
        </RadzenSplitter>
    </RadzenSplitterPane>
</RadzenSplitter>
@code {
    string value = string.Empty;
    string sendStr = string.Empty;
  string chatCss・・・(省略)
    protected override void OnParametersSet()
    {
        value = chatCss;
        value += "<div class=\"kaiwa line\">" +
        "    <div class=\"name\">" +
        "        ほんやくん" +
        "    </div>" +
        "    <div class=\"fukidasi left\">" +
        "        <img class=\"icon\" src=\"img/icon.png\" alt=\"\">LINE風です。" +
        "    </div>" +
        "    <div class=\"fukidasi left\">" +
        "        続けて喋るとフキダシのしっぽはつきません。" +
        "    </div>" +
        "    <div class=\"fukidasi right\">" +
        "        HTMLとCSSでできます。" +
        "    </div>" +
        "    <br><!-- 次のフキダシにしっぽをつけたいときはbrを挿入 -->" +
        "    <div class=\"fukidasi right\">" +
        "        続けても、しっぽをつけたいときは間にbrを入れます。" +
        "    </div>" +
        "    <div class=\"fukidasi left\">" +
        "        <img class=\"icon\" src=\"img/icon.png\" alt=\"\">グループじゃないとき。" +
        "    </div>" +
        "</div>";
    }
    async Task SendClick()
    {
        await Task.Delay(100);
    }
}

chatCss { get => を以下のように修正します。

 get => "<style>" +
               "/*==============" +
               "LINE風フキダシ" +
               "===============*/" +
               "/*フレームとフォント*/" +
               ".kaiwa.line {" +
               "    width: 100%;" +
               "    height:100%;" +
               //"    max-width: 500px;" +
               "    margin: 0 auto;" +
               "    padding: 10px 0;" +
               "    background: #769ece;" +
Console.WriteLine("Hello, World!");

ここまできたら実行してみて以下画面が表示されたらここまでは完成です。

Lesson.4 送信ボタン実装

任意のテキストを送り変換する送信ボタンを実装しようと思います。

まず、通信している間のBusyフラグを追加します。

@using static TexTra.APIAccessor_ja
<RadzenSplitter Orientation="Orientation.Vertical" Style="height:850px;">
    <RadzenSplitterPane Size="90%">
        <div class="h-100" style="overflow-y: scroll;">
            <Honya_kun.Components.LineChat @bind-Value=@value></Honya_kun.Components.LineChat>
        </div>
    </RadzenSplitterPane>
    <RadzenSplitterPane Size="10%">
        <RadzenSplitter Orientation="Orientation.Horizontal">
            <RadzenSplitterPane Size="70%">
                <RadzenTextArea class="w-100 h-100" @bind-Value=@sendStr></RadzenTextArea>
            </RadzenSplitterPane>
            <RadzenSplitterPane Size="30%">
              <RadzenButton class="w-100 h-100" Text="送信" Click=@SendClick  IsBusy=@IsBusy BusyText="通信中"></RadzenButton>
            </RadzenSplitterPane>
        </RadzenSplitter>
    </RadzenSplitterPane>
</RadzenSplitter>
@code {
    string value = string.Empty;
    string sendStr = string.Empty;
  bool IsBusy = false;

OnParameterSet()の中を以下のように修正します。

    protected override void OnParametersSet()
    {
        value = chatCss;
        value +=
        "<div class=\"kaiwa line\">" +
        "    <div class=\"name\">" +
        "        ほんやくん" +
        "    </div>" +
        "    <div class=\"fukidasi left\">" +
        "        <img class=\"icon\" src=\"img/icon.png\" alt=\"\">こんにちは!" +
        "    </div>" +
        "    <div class=\"fukidasi left\">" +
        "        日本語→英語の翻訳ができるほんや君です!" +
        "    </div>" +
        " </div>";
  }

SendClickの中を以下のように修正します。

    async Task SendClick()
    {
        if (string.IsNullOrEmpty(sendStr))
        {
            // Nothing
        }
        else
      {
            IsBusy = true;
            List<APIResponseBean> get = new List<APIResponseBean>();
            value = value.TrimEnd(new char[6] { '<', '/', 'd', 'i', 'v', '>' });
            value += "<div class=\"fukidasi right\">" + sendStr + "</div>";
            await Task.WhenAll(new Task[] {
                Task.Factory.StartNew(() => { get = TexTra.APIAccessor_ja.get_auto_trans(sendStr, Language.ja, Language.en, out APIResponseBean aPIResponseBean); }) });

            if (get.Any())
            {
                var newValue = ((TexTra.APIAccessor_ja.AutoTransInfo)get[0].value).text_translated;
              value += "<div class=\"fukidasi left\">><img class=\"icon\" src=\"img/icon.png\" alt=\"\">" + newValue + "</div>";
            }
            else
            {
                value += "<div class=\"fukidasi left\">" + "翻訳できませんでした・・・" + "</div>";
            }
            value += " </div>";
           IsBusy = false;
        }
  }

chatCssのフキダシ共通を以下のように修正します

"/*フキダシ共通*/" +
".kaiwa.line .fukidasi {" +
"    position: relative;" +
"    display: inline-block;" +
"    max-width: 500px;" +
"    margin: 8px 0 0;" +
"    padding: 9px 14px;" +
"    border-radius: 19px;" +
"    overflow-wrap: break-word;" +
"    clear: both;" +
"    box-sizing: content-box;/*はてな用*/" +
"}" +

ほんやくんのアイコンを追加したいのでwwwrootの直下にiconフォルダを作り、おなじみのイラストやさんの力をかりicon..pngをiconフォルダに入れます。

www.irasutoya.com

ここまできたら実行して任意の文字を入力し、送信ボタンを押すとほんやくんが返事してくれると思います!

Lesson.5 あとがき

おもったよりも長い記事になってしまいました、何かの役に立てれば幸いです。

以上!

github.com