c# テキストエリア上でペーストした画像をSQL Serverに保存する

#概要
HTMLのテキストエリア上でクリップボード上の画像データをペーストすると、jQuery AJAXでPOSTして、ASP.NET MVCからSQL Serverに保存します。その後、SQL Serverの画像データを取り出しHTMLに表示するまでの一連の処理の実装について記載します。

#実現イメージ
テキストエリアに画像をペーストすると、画像が画面に表示されます。Qiitaの編集画面の機能でアイディアを得てASP.NETに実装してみました。
![image](https://qiita-image-store.s3.amazonaws.com/0/78230/bae63889-1ce1-96aa-72d0-d129c2eb28e5.png)

#コード例
##SQL Serverの準備
画像をimage型として保存するテーブルをSQL Serverに作成します。DB名はTestDBとしました。

```SQL:Create.sql
CREATE DATABASE [TestDB]
GO
USE [TestDB];
CREATE TABLE [dbo].[Images](
	[Id] [bigint] IDENTITY(1,1) NOT NULL,
	[Bin] [image] NOT NULL,
CONSTRAINT [PK_Images] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
```

##HTML
jQuery及び後述のtest.jsを読み込んだHTMLです。テキストエリアと格納済のイメージを表示するDIVを配置しました。

```html:sample.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.4.min.js"></script>
    <script src="Scripts/test.js"></script>
    <title></title>
	<meta charset="utf-8" />
</head>
<body>
    <textarea id="Images_Bin" class="upload-image" rows="5" placeholder="ここに画像をペースト"></textarea>
    <div id="results" style="margin-top:10px;"></div>
</body>
</html>
```

##JavaScript(jQuery)
テキストエリア.upload-imageのペーストイベントをフックしてAJAX通信で画像データをペーストします。その後、サーバから返却される画像のIdを表示するimgタグを動的に生成します。

```js:test.js
$(document).on('paste', '.upload-image', function (e) {
    var items = e.originalEvent.clipboardData.items;
    for (var i = 0 ; i < items.length ; i++) {
        var item = items[i];
        if (item.type.indexOf("image") != -1) {
            var data = new FormData();
            data.append('Images_Bin', item.getAsFile());
            requestFile('/images/create', 'post', data);
        }
    }
});

function requestFile(requestUrl, methodType, data) {
    return $.ajax({
        url: requestUrl,
        type: methodType,
        cache: false,
        contentType: false,
        processData: false,
        data: data,
        success: function (o) {
            $('#results').prepend('<img src="/images/show/' + o + '">');
            return true;
        },
        error: function (xhr, status, err) {
            return false;
        }
    });
};
```

##ASP.NET MVC コントローラー
Createメソッドは、クライアントから送信された画像データをSQL Serverに挿入します。ShowメソッドはSQL Serverから取り出した画像データ（byte配列）をFileContentResultに変換してクライアントに送信します。

```csharp:ImagesController.cs
using System.Web.Mvc;
using WebApplication1.Models;
namespace WebApplication1.Controllers
{
    [AllowAnonymous]
    public class ImagesController : Controller
    {
        [HttpGet]
        public ActionResult Show(long id)
        {
            var image = new ImageModel(id).Show();
            return new FileContentResult(image, "image/jpeg");
        }

        [HttpPost]
        public string Create()
        {
            var id = new ImageModel().Create();
            return id.ToString();
        }
    }
}
```

##ASP.NET MVCモデル
CreateメソッドはRequest.Filesに格納されている画像データをbyte配列に変換し、SQL ServerにInsertします。ShowメソッドはSQL Serverの画像データを呼び出し元のコントローラーに返却します。

```csharp:ImageModel.cs
using System.Data;
using System.Data.SqlClient;
using System.Web;
namespace WebApplication1.Models
{
    public class ImageModel
    {
        const string ConnectionString = "Server=tcp:(local),1433;Database=TestDB;Integrated Security=SSPI;";
        public long Id;

        public ImageModel()
        {
        }

        public ImageModel(long id)
        {
            Id = id;
        }

        public byte[] Show()
        {
            var dt = new DataTable();
            var conn = new SqlConnection(ConnectionString);
            var cmd = new SqlCommand("select [Images].[Bin] from [Images] where [Images].[id]=@Id;", conn);
            cmd.Parameters.AddWithValue("@Id", Id);
            var adp = new SqlDataAdapter(cmd);
            adp.Fill(dt);
            return dt.Rows[0][0] as byte[];
        }

        public long Create()
        {
            var conn = new SqlConnection(ConnectionString);
            var cmd = new SqlCommand("insert into [Images]([Images].[Bin]) values(@Bin); select @@identity;", conn);
            cmd.Parameters.AddWithValue("@Bin", File("Images_Bin"));
            conn.Open();
            long.TryParse(cmd.ExecuteScalar().ToString(), out Id);
            conn.Close();
            return Id;
        }

        public static byte[] File(string key)
        {
            var file = HttpContext.Current.Request.Files[key];
            var bin = new byte[file.ContentLength];
            file.InputStream.Read(bin, 0, file.ContentLength);
            return bin;
        }
    }
}
```

#動作確認環境
・Windows 10
・IIS
・.NET Framework4.6
・C# 6.0
・jQuery 2.1.4
・Google Chrome / Microsoft Edge
※ IEの場合、クリップボードに画像が入っているとペースト処理が実行できませんでした。（右クリックで貼り付けもグレーアウト）

#実装のポイント
##ファイルとして送信するためにjQuery Ajaxで指定する
```js:
contentType: false,
processData: false,
```

##ファイルとして送信するためにjQuery Ajaxで指定しない
```js:
// dataType: 'json',
```

##ASP.NET MVCでRequest.Filesから画像を受け取り、バイト配列に変換してDBに格納
```csharp:
var file = HttpContext.Current.Request.Files[key];
var bin = new byte[file.ContentLength];
file.InputStream.Read(bin, 0, file.ContentLength);
return bin;
```

##ASP.NET MVCで画像を出力する際はFileContentResultに変換
```csharp:
return new FileContentResult(image, "image/jpeg");
```

#その他
上記のコードは最小限の記述しかしていないため、エラー処理等は別途考慮が必要です。

#参考にしたサイト
・クリップボードの画像ファイルをアップロードする
http://qiita.com/volpe28v@github/items/dc100c75c2371bb82a3b
・Upload files in ASP.NET MVC with JavaScript and C#.
https://cmatskas.com/upload-files-in-asp-net-mvc-with-javascript-and-c/