ダウンロードする時にファイル名の空白が+に置き換わる

ブラウザからファイルをダウンロードさせるために次のようなコードで処理すると、ファイル名に半角空白が含まれている場合に半角空白が+に置き換わってしまいます。

string sourceFile = @"D:\sample.dat";
string fileTitle = HttpUtility.UrlEncode("ダウンロード ファイル名.dat");
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=" + fileTitle);
Response.Flush();
Response.WriteFile(sourceFile);
Response.End();

原因はASP.NETのUrlEncodeの処理と、ブラウザ側のURLエンコードの処理が異なる事にあります。.NETのUrlEncodeは半角空白を+にエンコードしますが、逆にInternet Explroerは+を半角空白にデコードしません。

次のような行を追加して、UrlEncodeした文字列に含まれている+を%20に置き換えてしまえば解決します。

fileTitle = fileTitle.Replace("+", "%20");

混乱の原因はRFC1738→RFC1808→RFC2396→RFC3986と度々変更されたURLの仕様と、World Wide Web Consortium(W3C)のHTML4移行の仕様にあるFORMで入力したデータをエンコーディングに関する仕様の不整合にあるようです。

HTML4移行の仕様ではFORMに入力した空白を+に置き換える事が定義されてます。ASP.NETのUrlEncodeはFORMで使用するデータのエンコードを前提として実装しているため空白を+に置き換えているのに対して、Internet ExplorerはRFC2396まではURLで使用を認められてきた+をそのまま通す仕様になっている・・・と言う事のようです。UrlEncodeの仕様はどのレベルRFCに準拠するのか、FORMでの変換ルールに従うか否かといった、開発者の考え方によって様々な実装が生まれているのが現状です。

よってUrlEncodeを使う場合には、相手側の実装がどうなっているかを都度テストして合わせていく必要があります。なんとも混沌としていて残念な限りです。

参考URL:https://www.glamenv-septzen.net/view/1170#idb783f7

Visual Studio 2015での.NET Core開発

.NET CoreはLinux環境で開発を・・・と思ったのだけど、結局使い慣れたWindowsに戻ってきました。.NET Core(Windows)の開発環境構築手順などを残しておきます。

1. .NET Core SDKのインストール
.NET DownloadsからWindows版の.NET Core 1.1 SDK – Installerをダウンロードしてインストールする。

2..NET Core tools preview for Visual Studioのインストール
.NET Core installation guideに従って.NET Core tools preview for Visual Studioをインストールします。これで新規プロジェクト作成時に.NET Coreプラットフォームを選択する事ができるようになります。

3. 新規にコンソールアプリケーションを作成して実行します。

といった感じで開発環境を作るだけならとっても簡単。ただし困りものなのが.NET Coreで使用できるライブラリの取捨選択・・・。
例えばHTTPクライアントを実装したい場合System.Net.WebClientは.NET Coreに対応しない。System.Net.Http.HttpClientは.NET Coreに対応するが個別にNuGetからダウンロードが必要。System.Data.SqlClientはNuGetからダウンロードすれば使えるけど、SqlDataAdapter クラスは存在しないのでDataSetを使うことは出来ない。AzureStorageの依存情報を見ると.NET Coreの記述が無いので使えないのかと思いきや、NuGetでダウンロードする前におまじないを書けば使えたりします。万事この調子なので慣れるまでは大分苦労しそうです。

.NET Coreを使ってみる(Ubuntu)

.NET CoreをUbuntuに導入して、現在Windows上で動作させているバッチ処理をLinuxに移すことを考えています。

.NET Coreのインストール方法はMicrosoftの.NET Core(https://www.microsoft.com/net/core#ubuntu)のページがわかりやすいです。

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
sudo apt-get update
sudo apt-get install dotnet-dev-1.0.0-preview2-003131

以上でインストールは完了。
Ubuntu 14.xの場合には設定するリポジトリが次のように変わるので注意して下さい。

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet-release/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'

Hellowordプロジェクトを作成して動作を確認します。
以下のコマンドで適当にディレクトリを作成して、新規プロジェクトを作成します。新規プロジェクトにはHellowordと表示する処理が既に記述されています。

mkdir hwapp
cd hwapp
dotnet new

後は以下のコマンドを実行すると、コンソールに”HelloWorld”と表示されます。

dotnet restore
dotnet run

続いて開発環境を用意します。Windows上で開発するか、Linux上で開発するか迷うところですが、デプロイ時のトラブルを避けるためにもターゲット環境上で開発することにします。
まずはVisual Studio Codeをインストールしていきます。Download Visual Studio Code(https://code.visualstudio.com/Download)からUbuntu用にdebファイルをダウンロードして以下のコマンドを実行します。

sudo dpkg -I code_1.5.3-1474533365_amd64.deb

Visual Studio Codeをインストールしたら、Visual Studio Codeを起動してメニューの「表示→拡張機能」から「C# for Visual Studio Code」をインストールしておきます。最後に「有効」ボタンを押すまで機能しないので、押し忘れないように。

.NET Coreのプロジェクトをビルドしたときに「1. The project has not been restored or restore failed – run `dotnet restore`~」等のエラーが表示される

.NET Coreのプロジェクトをビルドした時に下記のエラーが表示される場合があります。

error : Can not find runtime target for framework '.NETCoreApp,Version=v1.0' compatible with one of the target runtimes: 'win10-x64, win81-x64, win8-x64, win7-x64'. Possible causes:
error : 1. The project has not been restored or restore failed - run `dotnet restore`
error : 2. The project does not list one of 'win10-x64, win81-x64, win8-x64, win7-x64' in the 'runtimes' section.
error : 3. You may be trying to publish a library, which is not supported. Use `dotnet pack` to distribute libraries.

OS等の実行環境に依存するパッケージを追加している場合に表示されます。この場合にはproject.jsonに下記の様に「runtimes~」を追記し実行環境を明記します。

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true
  },

  "dependencies": {
    "Microsoft.NETCore.App": {
      "type": "platform",
      "version": "1.0.1"
    }
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": "dnxcore50"
    }
  },

  "runtimes": {
    "win10-x64": {}
  }
}

参考:Project.json Reference

.Net CoreでWindowsAzure.Storageを使用する

WindowsAzure.Storageが参照しているODataLibが.NET Coreに対応していないため、そのままNuGetパッケージをインストールするとエラーになります。WindowsAzure.Storageを.NET Coreで使用するには、project.jsonの下記の部分を変更し「portable-net451+win8」を追加します。

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dnxcore50",
        "portable-net451+win8"
      ]
    }
  }

その後、あらためてWindowsAzure.StorageをNuGetからインストールすると正常に導入できます。

参考:Microsoft Azure Storage SDK for .NET (8.0.1)

ASP.NETでPage.Titleを設定しても反映されない

ASP.NETでPage.Titleを設定しても反映されないので暫く悩んでいた。原因は、自動生成されるASPXファイルの先頭に記述されているページディレクティブで空白が指定されているためだった。

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="CitySearchDetail.aspx.cs" Inherits="WeatherArchiveWeb.CitySearchDetail" %>

ASPXファイルの先頭に<%@で始まる一行があるが、これがページディレクティブによるパラメータの設定。ここでTitle=""と指定されているので、コード中でPage.Titleを変更しても常に上書きされてしまう。

Google Blogger API v3.0を使ってエントリを書き込む

先日、C#からGoogle Blogger API v3.0を使用した。
検索してもサンプルが見当たらなかったので、ブログを書き込むサンプルをメモとして公開しておく。

準備

Google Blogger API v3.0はNuGetからインストールします。

コード

using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Google.Apis.Blogger;
using Google.Apis.Blogger.v3;
using Google.Apis.Blogger.v3.Data;
//...
    static void Main(string[] args)
    {
       // OAuth 認証を行う
        UserCredential credential;
        using (var stream = new FileStream("client_id.json", FileMode.Open, FileAccess.Read))
        {
            credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    new[] { BloggerService.Scope.Blogger },
                    "user", CancellationToken.None);
        }

       // Bloggerのインスタンスを取得
        BloggerService service = new BloggerService(new BaseClientService.Initializer()
        { HttpClientInitializer = credential, ApplicationName = "Blogger Convert" });

       // Blogの一覧を取得
       var blogList = service.Blogs.ListByUser("self").Execute();

      // Blogに新しいエントリを作成する
       var newPost = new Post();
       newPost.Title = "blog title";
       newPost.Content = "blog body text
"; newPost.Published = DateTime.Prase("2016-01-01 12:00"); var updPost = service.Posts.Insert(newPost, blogList.Items[0].Id).Execute(); //...

ちなみにバグがあってブログのラベルを設定することは出来ない。もう随分長いこと放置されているので、修正の見込はないと思っておこう。
画像データをアップロードする機能は見当たらない。おそらくはPicasaと連携しろって事なのだとも思うけど・・・Picasaって2016年5月にサービス終了するんだよな。

C#でSIMDを使って高速化

そういえばVisual Studio 2015からC#でもSIMDが使えるようになったんだっけ・・・と言うことで使ってみた。
SIMDを有効にするには3つの条件がある。

  • .NET Framework 4.6を使用すること。
  • 64bitコードを選択すること。
  • NuGetからNumerics.Vectors(v4.1)をインストールすること。

プロジェクトのプロパティを開いて次の二ヶ所のオプションを変更しておく。
option#2
option#1
次にNuGetパッケージの管理を開いて、System.Numerics.Vectorsを追加する。
nuget

これで準備は出来たのでコードを書いてみる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;

namespace SimdTest
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;
            double answer = 0.0;
            for (int i = 0;i < 1000000;i++)
            {
                answer = 1.0;
                for (int j = 1;j <= 100;j++)
                {
                    answer = answer * (double )j;
                }
            }
            DateTime lastTime = DateTime.Now;
            Console.WriteLine("{0}ms, answer = {1}", (lastTime - startTime).TotalMilliseconds, answer);

            Console.WriteLine("Count = {0}, Hardware = {1}", Vector<double>.Count, Vector.IsHardwareAccelerated);
            startTime = DateTime.Now;
            for (int i = 0; i < 1000000; i++)
            {
                Vector<double> answerTemp = new Vector<double>(new double[] {1, 1, 1, 1});
                Vector<double> multiTemp = new Vector<double>(new double[] {0, 25, 50, 75});
                for (int j = 1; j <= 25; j++)
                {
                    multiTemp += Vector<double>.One;
                    answerTemp *= multiTemp;
                }
                answer = answerTemp[0] * answerTemp[1] * answerTemp[2] * answerTemp[3];
            }
            lastTime = DateTime.Now;
            Console.WriteLine("{0}ms, answer = {1}", (lastTime - startTime).TotalMilliseconds, answer);
        }
    }
}

と言うわけで、100の階乗を求める処理を書いてみた。
非常にシンプルな例なのだが、35%程度の高速化にしかならない。
Vector.IsHardwareAcceleratedを参照すると確かにSIMDが使われている事が分かる。
Vector<double>.Countが4を返すのでSSEではなくIntel AVXにも対応している様子。AVX非対応(SSE2対応)PCだとVector<double>.Countが2を返してきます。
本来的にはVector<double>.Countの値を見て並列数を判断するべきなのかもしれないが、4で決め打ちしています。

#OpenCL使えば手っ取り早いダロという話もあるけど、自宅や会社で使ってるPCが対応していないんだよね。

C#でExcelに画像データを埋め込む

※追記…ClosedXML v0.88移行は標準で画像の埋め込みに対応しており、ClosedXMLImageSupportを使う必要がなくなりました。

OpenXML SDKで画像を埋め込んだExcelファイルを作成しようとすると、ステップ数が一気に増えてしまって現実的ではありません。ClosedXMLはExcelファイルを作るのにとっても便利なのですが、画像データを扱う機能がありません・・・・と思ったら、ClosedXMLをForkして画像データを扱う機能を追加している方が、ここは感謝して使わせていただきましょう。

ClosedXML.DLLをビルドする

NuGetでダウンロードできるCloseXMLは画像データを扱えませんから、自分でソースコードからビルドします。Fork: ajwhiteway/ClosedXMLImageSupportからソースコードをダウンロードします。
ClosedXML_for_image_download
ダウンロードしたzipファイルを解凍するとソースコードが得られるので、ClosedXML.slnファイルを開いてビルドします。ClosedXML\bin\Releaseに生成されるClosedXML.dllを参照設定します。

これで準備は出来ました。

using BarcodeLib;
using ClosedXML.Excel;
using ClosedXML.Excel.Drawings;
<<中略>>
            var book = new XLWorkbook();
            var sheet = book.AddWorksheet("sheet1");

            // 画像データを指定
            XLPicture pic = new XLPicture
            {
                NoChangeAspect = true,
                NoMove = true,
                NoResize = true,
                ImageStream = File.OpenRead("sample.png")
            };

            // 表示位置を指定
            XLMarker fMark = new XLMarker
            {
                ColumnId = 1,
                RowId = 1
            };
            pic.AddMarker(fMark);

            // 画像データを追加
            sheet.AddPicture(pic);
            book.SaveAs("sample.xlsx");
<<中略>>

と、かなりシンプルに記述できます。
こんなに便利なのだから是非mainに取り込んで欲しいよね。

VBAを今後どのように扱ったら良いか?

VBAはエンドユーザーコンピューティングとして優れたプロダクトだと思っている。ほとんどのユーザーが購入して持っているであろうOfficeアプリケーションさえインストールされていれば開発できる。高価なミドルウェアを購入したり、面倒なコーディングをしなくても標準でレポーティングツールや、データベースだって利用できる。このような言語は中々代替えがないのだが、VBAと離別すべき時も刻一刻と近づいている。

今VBAが置かれている現状を整理したい。

ActiveX Data Object(ADO)

VBAから直接データベースに接続するにはADOぐらいしか選択肢がありません。にもかかわらずADOのサポートは停滞したままで、ADO2.8がリリースされて以降は新たなリリースがありません。機能的にはSQLServer2005で止まっていて、それ以降に追加された型には対応していません。たとえばXML型とかGEOMETRY型を扱うことができません。特にSQL Azureの動作対象リストからはADOやDAOが外されており、別の方法で接続する事を求められます。
唯一幸いなことは、ADOのコンポーネントはOSとともに配布されているので、OSのサポートが終了するまではセキュリティアップデートが提供され続けることです。

ADO以外の方法でデータベースに接続しようとすると、VBA以外の言語でコードを書く必要が出てきます。どんな手段が考えられるでしょうか?

XML WEBサービス

Visual Studio .NETでは容易にXML WEBサービスを作成する事ができます。これをVBAから呼び出す方法が考えられます。
しかし残念なことに無償で使用できるExpress版ではWEBサービスを開発できません。最も安価なVisual Studio 2015 Professionalでも6万円台になります。エンドユーザーコンピューティングを考えたときに6万円超の負担は避けたいところです。Community版があるじゃないかと考えるかもしれませんが、ライセンス的に業務利用は難しいので対象外とします。
もうひとつ問題となるのがXMLパーサー部分です。XML WEBサービスを使用するクライアント用にMicrosoft SOAP Toolkitが配布されています。しかしながら、このコンポーネントは標準ではインストールされていないため「Officeさえインストールされていれば動作する」と言う利点を失います。
Windowsに標準でインストールされているMSXMLをパーサーとして使用する方法もありますが、MSXMLはInternet Explorerのモジュールです。そしてMicrosoft EDGEはMSXMLに対応しないことがアナウンスされています。将来性を考えるとこれも避けたいところです。

根本的な問題はVBAがCOMを基幹技術としているところです。Microsoftの基幹技術は既にとっくに.NETに移行しており、旧技術であるCOMは完全においていかれています。もういっそ、VBAは完全にあきらめて、VB.NET等の他言語で作成するというのはどうでしょうか?

Microsoft Office Interop

Visual Studioで作成したアプリケーションからMicrosoft Office Interopを使用してOfficeアプリケーションを操作する方法があります。幸いなことにOffice InteropはExpressエディションからも使用できるので追加投資も不要です。.Net Frameworkのバージョンを適切に選択すれば、実行ファイルをコピーするだけでも動作することが多く、展開も手軽です。
しかしながらOffice Interopはお世辞にも使いやすいlibraryではありません。単純なCOMのラッパーなのでインスタントの解放はプログラマーの責任で記述する必要があります。実際にコードを書くとインスタンスの生成と解放ばかりになって効率が悪いです。ライブラリが不出来なのが要因でコーディングの難易度が上がるのは、エンドユーザーコンピューティングでは避けたいところです。

Visual Studio Tools for Office(VSTO)

.NETでOfficeのプラグインを作成するために提供されたミドルウェアです。作成したプラグインを動作させるにはインストール作業が必要になるため展開はやや面倒になります。残念なことにVSTOもExpress版では使用できません。

Apps for Office

.NETでOfficeのプラグインを作成するために提供されたミドルウェアです。作成したプラグインを動作させるにはインストール作業が必要になるため展開はやや面倒になります。残念なことにApps for Office もExpress版では使用できません。

ClosedXML

ClosedXMLは Microsoft Open XML Formatのファイルを読み書きできるオープンソースの.NET用のミドルウェアです。Office2007以降ファイル拡張子がXMLXやDOCXに変更され、ファイル仕様が完全に公開されるようになりました。もちろんExpress版からも使えます。
この辺りが現実解では無いでしょうか。VBAを扱っていた経験があるなら、VB.NETに慣れるのもそれ程時間はかからないでしょう。Powersellから呼び出すのも面白いかもしれません。PowersellはWindows7以降に追加された、OSの操作に使用する言語です。Powersellからしか設定できない項目などもあり、Windowsを使う上で覚えておくととても便利です。

Excel REST API

https://channel9.msdn.com/Events/Visual-Studio/Connect-event-2015/315
まだPublic preview段階ですが、OneDriveやOffice365のクラウドストレージに保存したExcelファイルを編集するためのWebAPIです。現在、新しいOffice製品はクラウドストレージとの連携を前提として開発されています。WebAPI経由なら言語は選びませんし、各種のオープンデータとの連携も取りやすくなります。もしかしたらエンドユーザスクリプトの主流になるかもしれまけん。

VSTOやApps for Officeには無償でProfessional相当の機能を使えるCommunity版という選択もあるのですが、商用利用は著しく制限されているため、業務でのエンドユーザーコンピューティングには使いにくいです。私の場合には、ぐるっと回って「C#デスクトップアプリケーション + LINQ for MSSQL + ClosedXML」で組んでいくことにしてみました。

参考:
Roadmap for Apps for Office, VSTO, and VBA(http://blogs.msdn.com/b/vsto/archive/2013/06/18/roadmap-for-apps-for-office-vsto-and-vba.aspx)