WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

Blazor WASM でファイルアップロード

by WebSurfer 28. October 2022 14:12

Blazor Web Assembly (WASM) アプリで、html の form 要素及び input type="file" 要素と jQuery ajax を使ってファイルをアップロードする方法を書きます。

Blazor WASM でファイルアップロード

(.NET 5.0 以降であれば、InputFile クラスを利用できるそうですので、そちらを使う方がいいかもしれません。詳細は Microsoft のドキュメント「ASP.NET Core Blazor ファイルのアップロード」を見てください)

jQuery / JavaScript を使ってファイルのアップロードはできますので、基本ブラウザである Blazor WASM のクライアント側からでも同様にファイルのアップロードはできます。

その jQuery / JavaScript のコードを Blazor WASM のクライアント側に組み込んで呼び出すことで、Blazor WASM アプリとしてファイルのアップロード機能を持たせることが可能です。(以下に概略を書きますが、詳細は Microsoft のドキュメント「ASP.NET Core Blazor で .NET メソッドから JavaScript 関数を呼び出す」を見てください)

なお、input type="submit" ボタンを form 要素内に配置して、それをクリックするというのは NG です。ファイルはアップロードされますが、返ってきた応答でブラウザの画面が書き換えられてしまうので。

jQuery ajax を使ってのファイルのアップロード機能をどのように実装するかの具体例を以下に書いておきます。

ベースとする Blazor WASM ソリューションは、Visual Studio 2022 のテンプレートを使って、フレームワーク .NET 6.0 で、[ASP.NET Core でホストされた(h)]にチェックを入れて作成したものを使います。ソリューション内に Client, Server, Shared という 3 つのプロジェクトができるはずです。

(1) ファイルアップロード用の Razor ページ

Client プロジェクトの Pages フォルダに以下の Razor ページを追加します。

@page "/fileupload"
@inject IJSRuntime JSRuntime;

<h3>File Upload by jQuery Ajax</h3>

<form id="form1" method="post" enctype="multipart/form-data">
    <input type="file" name="postedfile" />
</form>

<button type="button" class="btn btn-primary" @onclick="UploadFile">
    アップロード
</button>

<div id="result"></div>

@code {
    private async Task UploadFile()
    {
        await JSRuntime.InvokeVoidAsync("uploadFile");
    }
}

Shared フォルダの NavMenu.razor に上の fileupload ページへのリンクを追加し、メニューからリンクをクリックしてアクセスできるようにします。

(2) ファイルアップロード用の js ファイル

ファイルをアップロードする jQuery / JavaScript コードのコードを含んだ js ファイルを作成します。コードは以下の通りです。js ファイルの名前は任意ですがここでは exampleJsInterop.js としておきます。

window.uploadFile = () => {
    // FormData オブジェクトの利用
    var fd = new FormData(document.getElementById("form1"));

    $.ajax({
        url: '/Upload',
        method: 'post',
        data: fd,
        processData: false, // jQuery にデータを処理させない
        contentType: false  // contentType を設定させない
    }).done(function (response) {
        $("#result").empty;
        $("#result").text(response);
    }).fail(function (jqXHR, textStatus, errorThrown) {
        $("#result").empty;
        $("#result").text('textStatus: ' + textStatus +
            ', errorThrown: ' + errorThrown);
    });
}

(3) js ファイルの配置

上の exampleJsInterop.js と jQuery.js を、Client プロジェクトの wwwroot フォルダ下に js フォルダを作って、その中に配置します。

js ファイルの配置

(4) index.html に script タグを追加

Client プロジェクトの wwwroot フォルダ下の index.html に、exampleJsInterop.js と jQuery.js がダウンロードされるよう、script タグを追加します。

Index.html に script タグを追加

(5) Server プロジェクトに Controller 追加

Server プロジェクトの Controllers フォルダにアップロードされたファイルを受け取って必要な処置をする Controller を追加します。

具体例は以下のコードを見てください。Web API の Controller としています。名前をステップ (2) の jQuery / JavaScript のコードにある url: '/Upload' と合わせる必要があることに注意してください。

using Microsoft.AspNetCore.Mvc;

namespace BlazorWasmCoreHosted.Server.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class UploadController : ControllerBase
    {
        // .NET 6.0 で作ったプロジェクトは「Null 許容」オプションが「有効化」に
        // 設定してあるので、引数も NULL 許容にしておかないと、クライアントでファ
        // イルを選択しないで送信した場合、HTTP 400 Bad Request エラーになって
        //  "The postedFile field is required."というエラーメッセージが返ってくる
        [HttpPost]
        public string Post([FromForm] IFormFile? postedFile)
        {
            string result = "";
            if (postedFile != null && postedFile.Length > 0)
            {
                // アップロードされたファイル名を取得
                string filename = System.IO.Path.GetFileName(postedFile.FileName);

                // アップロードされたファイルを処理(省略)

                result = $"{filename} ({postedFile.ContentType}) - " +
                    $"{postedFile.Length} bytes アップロード完了";
            }
            else
            {
                result = "ファイルアップロードに失敗しました";
            }

            return result;
        }
    }
}

アプリを実行してファイルアップロードに成功するとこの記事の一番上にある画像のようになります。

Tags: , , ,

Upload Download

jQuery.ajax を使っての事前検証

by WebSurfer 6. February 2022 15:56

ASP.NET Web Forms アプリで jQuery Ajax を使ってポストバックの際にサーバー側で行われる処理が可能かどうかを事前に調べ、可能と分かったら JavaScript で Button クリックしてポストバックする、可能ではないと分かった場合はポストバックせずメッセージを表示してユーザーに知らせるという話です。

ポストバックして処理完了

元の話は Teratail のスレッド「asp:button 押下前にサーバサイドが応答しているか確認したいためPingのようなことを行いたい」のものです。実際のそのような検証が必要なケースはあまりなさそうですが、せっかく考えた話ですし jQuery Ajax の timeout の使い方が今後の参考になるかもしれないと思ったので備忘録として書いておくことにしました。

Button クリックでポストバックしてサーバー側でユーザーが送信したデータを受けて、データベースサーバーにクエリを投げる等の処理をすることはよくあると思います。

その際、Button クリックでいきなりポストバックするのではなく、その前に jQuery Ajax を使って事前にサーバー側での処理が可能かどうかを調べるというストーリーです。

(いきなりポストバックしても結果をきちんとユーザーに知らせることはできるし、やりすぎるとサーバーに無駄な負荷を増やすし軽くすると検証の意味が薄れるということで、そのような検証の必要性は低いとは思いますがそこは置いときます)

jQuery Ajax で呼び出してサーバー側で事前検証を行う HTTP ジェネリックハンドラを作ります。以下のコードがそのサンプルです。

HTTP ジェネリックハンドラ Handler1.ashx

using System.Web;

namespace WebForms1
{
    public class Handler1 : IHttpHandler
    {

        public void ProcessRequest(HttpContext context)
        {
            // Button クリックでポストバックされた時にサーバー
            // で行われる処理が可能か事前に検証する。処理に最
            // 長 3 秒かかるとして、ここでは単純に 3 待つ
            System.Threading.Thread.Sleep(3000);
            
            // 検証に成功したら "検証成功" という文字列を返す
            var validationResult = "検証成功";

            context.Response.ContentType = "text/plain";
            context.Response.Write(validationResult);
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}

上の Handler1.ashx を jQuery Ajax を使って呼び出します。Handler1.ashx は検証に成功すると "検証成功" という文字列を返すので、それを受けたら JavaScript で Button をクリックしてポストバックします。その結果がこの記事の一番上にある画像です。

Handler1.ashx を呼び出す JavaScript を含む Web Form ページのサンプルコードは以下の通りです。

WebForm1.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs"
    Inherits="WebForms1.WebForm1" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title></title>
    <script src="Scripts/jquery-3.4.1.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#btnSec").on("click", function () {
                $.ajax({
                    type: "get",
                    url: "Handler1.ashx",
                    timeout: 5000
                }).done(function (data) {
                    // 呼び出し先 Handler1.ashx で検証が成功すると "検証成功" 
                    // という文字列が返ってきて引数 data に代入される
                    if (data == "検証成功") {
                        var hiddenbutton = document.getElementById("Button1");
                        // Button をクリック
                        hiddenbutton.click();
                    } else {
                        $("#Label1").text("処理できません - 検証結果 NG");
                    }
                }).fail(function (jqXHR, textStatus, errorThrown) {
                    $("#Label1").text("処理できません - " + textStatus);
                });
            });
        });
    </script>
    <style type="text/css">
        .style1
        {
            display: none;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <h2>WebForm1 - jQuery.Ajax を使っての事前検証</h2>
            
            <input id="btnSec" type="button" value="反映" />

            <%--ポストバックを行う Button コントロール。上の style1 で
                隠しボタンに設定。クリックは JavaScript で行う。クリ
                ックされるとポストバックが発生し、サーバー側のイベント
                ハンドラ ClickBtnSection で必要な処理が行われる--%>
            <asp:Button ID="Button1" runat="server" 
                OnClick="ClickBtnSection" CssClass="style1" />

            <asp:Label ID="Label1" runat="server">
                Label1 初期値
            </asp:Label>
        </div>
    </form>
</body>
</html>

WebForm1.aspx.cs

using System;

namespace WebForms1
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void ClickBtnSection(object sender, EventArgs e)
        {
            // サーバー側での必要な処理

            Label1.Text = "Button1 がクリックされました";
        }
    }
}

Handler1.ashx を呼び出して、そこでの処理中にサーバーエラーが発生した場合は以下のようになります。

サーバーエラー

Handler1.ashx を呼び出した後、jQuery Ajax の timeout オプションに設定した時間(上のコード例では 5 秒)待っても応答が返ってこない場合は以下のようになります。

timeout

Handler1.ashx から帰ってきた文字列が "検証成功" 以外の場合は(実際にそういうケースはないかもしれませんが)以下のようになります。

検証結果 NG

Tags: , , ,

Validation

jQuery ajax で部分ビューの呼出・表示 (CORE)

by WebSurfer 7. March 2020 12:25

jQuery ajax を使って、部分ビューを応答として返すアクションメソッドにフォームデータを POST 送信し、返ってきた部分ビューの html をページ内の指定の場所にレンダリングする方法を書きます。

jQuery ajax で部分ビューの呼出・表示

先の記事「ASP.NET Core MVC と Ajax ライブラリ」では、Microsoft の Ajax ライブラリ jquery.unobtrusive-ajax.js を利用して、Ajax で部分ビューを呼び出してページ内の指定の場所に表示する方法を書きました。

それと機能的に同じことを、Microsoft の Ajax ライブラリの助けを借りないで、jQuery ajax を利用して実装する方法です。

CSRF 用の隠しフィールドのデータを含めてフォームデータを全て jQuery ajax を使って POST 送信し、部分ビュー用のアクションメソッドに付与した [ValidateAntiForgeryToken] 属性での検証ができるようにしていることに注目してください。

問題はフォームデータをクライアント側でどのように取得するかですが、jQuery の .serialize() メソッドを使うと form データを application/x-www-form-urlencoded 形式の文字列として取得できます。それを jQuery ajax の data パラメータに設定して POST 送信してやればアクションメソッドで受けることができます。

Controller と View のサンプルコードを以下に書いておきます。コードは ASP.NET Core 3.1 MVC のものです。.NET Framework MVC5 の場合はライブラリ jquery.unobtrusive-ajax.js と AjaxHelper を使う方が簡単でよさそうです。

使っているのは Microsoft が提供する Northwind サンプルデータベースの Customers テーブルです。その CompanyName をドロップダウンリストに表示し、ユーザーが選択してボタンをクリックすると部分ビューを呼び出して、選択した Customers のレコードの詳細を指定した場所(下の View のコードで言うと <div id="result"></div> の中)に書き出すものです。

Controller / Action Method

先の記事「ASP.NET Core MVC と Ajax ライブラリ」のものと同じです。

using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Controllers
{
  public class AjaxController : Controller
  {
    private readonly NorthwindContext _context;

    public AjaxController(NorthwindContext context)
    {
      _context = context;
    }

    // jQuery ajax で下のアクションメソッド Details
    // を呼んで詳細 Customer データの部分ビューを表示。
    // CSRF 用の隠しフィールドのデータも送信して検証で
    // きるようにする。
    public IActionResult Index2()
    {
      // 全部取得すると長すぎるので Take(10) した
      var list = _context.Customers.Take(10);
      ViewData["customers"] = 
        new SelectList(list, "CustomerId", "CompanyName");
      return View();
    }

    // 部分ビュー用のアクションメソッド
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Details(string customerid)
    {
      if (string.IsNullOrEmpty(customerid))
      {
        return NotFound();
      }

      var customer = _context.Customers.Find(customerid);

      if (customer == null)
      {
        return NotFound();
      }

      return PartialView(customer);
    }
  }
}

View (Index2.cshtml)

ボタンには input type="submit" タイプ(クリックすると form を submit する)と input type="button" タイプ(クリックしても何も起こらない)の 2 つを配置して、両方期待通り動くことを確認してみました。

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

<h1>Index2</h1>

<form id="form1" asp-action="Details" 
      asp-controller="Ajax" method="post">

    <select id="customerid" name="customerid" 
            asp-items="@ViewBag.customers">
    </select>

    <input type="submit" value='詳細表示 (type="submit")' />
    <input id="button1" type="button"
                         value='詳細表示 (type="button")' />

</form>

<div id="result"></div>

@section Scripts {
    <script type="text/javascript">
        //<![CDATA[

        // [詳細表示 (type="submit")] ボタンをクリック
        // したときの処理

        // ボタンクリックで form が submit されるという
        // 動きになるので、form 要素の submit イベントに
        // リスナをアタッチして jQuert ajax で処理する
        $("#form1").on("submit", function (event) {
            // submit されては困るのでキャンセル
            event.preventDefault();

            // jQuery ajax を使って、部分ビューを応答と
            // して返すアクションメソッド Ajax/Deteils
            // にフォームデータを POST 送信する
            $.ajax({
                type: "POST",
                url: "/Ajax/Details",

                // フォームデータを取得
                data: $(this).serialize(),

                // コールバック function の引数 data に
                // 部分ビューの html ソースが渡される 
                success: function (data) {                    
                    $("#result").empty();
                    $("#result").append(data);
                },

                error: function (jqXHR, status, error) {
                    $('#result').text('Status: ' + status +
                        ', Error: ' + error);
                }
            });
        })

        //]]>
    </script>

    <script type="text/javascript">
        //<![CDATA[

        // [詳細表示 (type="button")] ボタンをクリック
        // したときの処理

        // ボタン要素の click イベントにリスナをアタッチ
        // して jQuert ajax で処理する
        $("#button1").on("click", function () {

            // input type="button" タイプのボタンはクリ
            // ックしても from が submit されることは
            // ないので event.preventDefault(); は不要

            // jQuery ajax を使って、部分ビューを応答と
            // して返すアクションメソッド Ajax/Deteils
            // にフォームデータを POST 送信する
            $.ajax({
                type: "POST",
                url: "/Ajax/Details",

                // フォームデータを取得
                data: $("#form1").serialize(),

                // コールバック function の引数 data に
                // 部分ビューの html ソースが渡される 
                success: function (data) {
                    $("#result").empty();
                    $("#result").append(data);
                },

                error: function (jqXHR, status, error) {
                    $('#result').text('Status: ' + status +
                        ', Error: ' + error);
                }
            });
        })

        //]]>
    </script>
}

部分ビューのコードは、先の記事「ASP.NET Core MVC と Ajax ライブラリ」のものとほとんど同じなので省略します。

Microsoft の Ajax ライブラリ jquery.unobtrusive-ajax.js は ASP.NET Core MVC ではサポートされてないのかもしれません。なので、上記のように jQuery ajax を使うのが正解のような気がします。

Tags: , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  June 2023  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

View posts in large calendar