dotnet publish [ProjectDirectory] -o [output] の出力先

project.jsonのものを置き換えていたところで気づいたのでメモ

dotnet publish [ProjectDirectory] -o [outputdirName]

とした場合、感覚としてdotnetコマンドを実行しているシェルのカレントディレクトリだろうと思っていたが、どうやら [ProjectDirectory] がカレントディレクトリになっているようだ。

なのでdotnetコマンド実行しているカレントディレクトリに配置したい場合は

dotnet publish [ProjectDirectory] -o ..\[outputdirName]

としないと予想しないところに出てしまうようだ。

追記 2017-03-11

dotnet pack [ProjectDirectory] -o [outputdirName]

でも同じことが起きた。
このことから、[ProjectDrectory]をカレントにしてしまっているような感じはする

 

xUnit fail of overload Testmethod on Visual Studio 2017 .NET Core

Visual Studio 2017 が出たぜー!というところで、さっそくBlackJumboDogもアップグレードしていったところ。

xUnitで謎のテストエラーが続発した・・・

System.InvalidOperatrionException
普通に考えたところで、メソッドの呼び出し方を間違えたとのことなのだが、型を動的にしているわけでもないので、あれま・・・とか思っていたところ。

よくよく見ていると、そのメソッドはオーバーロードしていた。

引数3つのTestメソッドと、引数2つのTestメソッドがいる状態
エラーメッセージは

メッセージ: System.InvalidOperationException : The test method expected 3 parameter values, but 2 parameter values were provided.

となっていて「引数が3つあるぞ、でも2つしかないぞ。」という困ったぞーという意味の例外。

なので・・・

オーバーロードをやめて名前を付けた。

というわけで、成功!

ひとまず回避策は見えたが、これが仕様なのか、不具合なのかは調べていない。。。

AppVeyorのテストでエラー

前回のログ出力をがっつり作り替えたところのそもそも理由

AppVeyor Bjd.SmtpServer.CoreCLR.Test  1.1.0-51

で、エラーとなっている部分があるんですが、
これはローカルのWin10、VS2015環境では問題なく通るテストなのです。
このテストケース。
実際にSocketサーバーが動いて、それにクライアントが接続にいって
Pop3クライアントとしてメールを受け取るタスクを検証しているのですが、
メインではなく、裏で動いているPop3サーバーにてメールを開いたときのサイズが変わってしまってました。

AppVeyor(抜粋)

[13:38:12.326][1400][151] Mail.Read C:\projects\bjd5-qqab7\Bjd.SmtpServer.CoreCLR.Test\bin\201702191338066076_9_1333341264_23264094\mailbox\user2\MF_00635026511425888292 300 byte.
[13:38:12.326][1400][151] 16189196 SockTcp.SendNoTrace 151
[13:38:12.935][1400][151] 16189196 SockTcp.SendNoTrace 55
[13:38:13.545][1400][151] 16189196 SockTcp.SendNoTrace 12
[13:38:13.951][1400][151] 16189196 SockTcp.SendNoTrace 38
[13:38:14.154][1400][151] 16189196 SockTcp.SendNoTrace 37
[13:38:14.560][1400][151] 16189196 SockTcp.SendNoTrace 1
[13:38:14.967][1400][151] 16189196 SockTcp.SendNoTrace 5
[13:38:15.576][1400][151] 16189196 SockTcp.SendNoTrace 1
[13:38:15.982][1400][151] 16189196 SockTcp.SendNoTrace 2
[13:38:16.388][1400][151] 16189196 SockTcp.Send 3

ローカル

[23:46:07.939][13628][ 26] Mail.Read C:\Users\[username]\Source\Repos\bjd5\Bjd.SmtpServer.CoreCLR.Test\bin\201702212346077840_5_1610115599_50579406\mailbox\user2\MF_00635026511425888292 308 byte.
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 152
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 56
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 13
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 39
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 38
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 2
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 6
[23:46:07.940][13628][ 26] 18701856 SockTcp.SendNoTrace 2
[23:46:07.941][13628][ 26] 18701856 SockTcp.Send 3

トータル 300 byteに308 byte
さらに1行単位の送信では1バイトづつ違うー!
そして、送信してる行数も違う・・・

そもそも、単体テストを実行するような環境でSocketがっつりかましてるのどうなのか!というところもありますが。

おそらくは、改行コードじゃないかなぁ
・・・というところでここまでっ

How to output log for xUnit

ひさしぶりに BlackJumboDogをいじっていて、
ずーっと気になっていたことがあったので、思わずやってしまった・・・。

xUnitでは、並列テストを前提とした場合標準出力に対する出力をキャプチャしない仕様になっている。そのため、出力させるためには、独自のOutputHelperが必要になる。
※ Capturing Output

これやるにあたって、今のSystem.Diagnostics.Traceを全て置き換える必要が
あった。

なぜかというと、System.Diagnostics.Traceは、コンテキストを持たずアプリケーション内で静的に動作してしまうため、並列で動いたときはどのテストの結果であるかを知ることができない。

そのため、ちゃんとやるなら、System.Diagnostics.Traceを全て独自のものに置き換える必要があったが、それにはかなりの修正量になるのがあってためらっていた。

しかしながら、実際のところテストがエラーになったとき、単純なものであればよいが、複雑なものであれば、どこがどうなっているかというのを追いかける必要がある。そのためには、ログも実行された結果がそのまま出てくれているのが良く、関係のないところの情報が出ていることは、逆に紛らわしい。そのため単独で動かしてその結果が出てくれるならいいがそうではなかったりもする。まぁその時点でテストとしての精度が低いという問題もあるのだけれども。

そこで、ログもすべて自分の実装でコンテキスト化してしまおうというのをやってみた。

・ログは出力先が不特定多数(ファイル、コンソール、xUnit、その他)
・ログは気軽に出すことをできるようにしておき、必要に応じて出力できないようにする
・ログはルートのコンテキストに紐づける

といったような考えで挑んでみたところ。

悪くはないが、そのための影響が大きくやはりやりすぎだったか!と思いつつ、前に進んでいることで何かやる気が出てきている今日だった。

change internal architecture for log. 

独自で実装しておくことは、チューニングしやすくなるので、悪くはない。

もし、今後xUnitを用いた開発をするのであれば、ログを出力する機構を
テストごとにコンテキスト化できるような仕組みをお勧めしたいなぁと思った。

 

Azure Managed Disks – stripe 32 disks

びっくりなことに、仮想マシン上で扱えるディスクに
Managed Disksというものが追加された。
それまで、Azureの仮想マシンでは、StorageアカウントにBlobとしてVHDが追加される形で、少しややこしい部分もあり、物理的なVHDファイルを置いておきながら、仮想マシン独自の形で関連付けられていた。
Managed Disksは一つのリソースで一つのディスクと扱えるようになっており、現実世界のディスク1枚または、VHD1つのように考えることができて、サイズの指定もできて料金もわかりやすくなった。

価格

Managed Disks の価格

種類

大きく2種類。

<Premium>

SSDベースな、IOPS、スループット重視な
P10、P20、P30

 

<Standard>

HDDベースな
S4、S6、S10、S20、S30

 

仮想マシン

SSDベースなものは、接続できる仮想マシンのサイズが決まっているようです。DSシリーズ、DS_V2シリーズ、FSシリーズ、GSシリーズ。とSがついているやつ。

AzureのVMには、サイズによって最大IOPS、スループットが制限されるという仕様があるので、サイズの小さいインスタンスでは、思ったほどでないかもしれないですね。最大接続可能なManaged Disksの数も同様にサイズの制限を受けるようです。

Managed Disks 接続

ポータル上からぽちぽちクリック。旧来のようなストレージアカウントがあって、VHDファイルがあるというような実体はない様子。

1.Managed Disksのインスタンスを作成する

2.仮想マシンのディスクに接続する

だけ。

うん。このままだと面白くないので、少し試してみよう!

 

複数束ねてStripe

ディスク-ストライピング

というわけで試してみたこと。
D、DSでデプロイ。
接続するディスクはすべてホストキャッシュの読み取り書き込みを有効にしている。
仮想マシン内で、Stripeしている。

Standard Disk16 Disk32
(per 64GB)

Premium Disk10 Disk20 Disk32
(per 1TB)

おまけ、Temp(D:ドライブ)

わかったこと

Standardを32束ねても、Premiumには追い付かない!
コンシューマのSSDとベンチマークの結果だけで判断してはいけない。
(このディスクが物理ディスク一つではなく、冗長化された存在であることを忘れない)
また、揮発性の用途でよければ、Tempを使う選択肢がある。
大量にPremiumを接続する場合では、最大スループットは、ディスク側ではなく仮想マシン側の制約を受けているためか、20と32に差はほとんどなかった。
なお画像では、32のほうが低いようにみえるが、ホストキャッシュの影響か、非常に安定しない結果となった。ホストキャッシュの無効化によってまた異なる結果が出ると思われる。加えてStripeはファイル・アクセスごとに都合よく負荷分散をするためのものではないことも影響する一つと思われる。

また、この結果は個人的なもので、何かを保証するものではありません。

アプリ

CrystalDiskMark

 

 

 

Microsoft TranslatorのAPIを使うに当たって・・

Azureの認証が必要となるために、単純な呼び出しでは利用できない様子。

https://github.com/MicrosoftTranslator/GetAzureToken

ここのソースが参考になった。

Functionsで利用する場合は

https://github.com/MicrosoftTranslator/GetAzureToken/blob/master/AzureAuthToken.cs

Tokenの取得が必要となるため、上記を参考にするとよさそうだ。
リクエストがTokenの取得と、取得したTokenを渡すAPI呼び出しになっていることに注意が必要。

 

 

Visual Studio Tools for Azure Functions の修正版

Azure Functions 向けの拡張機能を入れたところで、.NET Coreのプロジェクトがだめになるっていうものに対する修正版が出てたようです。

https://blogs.msdn.microsoft.com/webdev/2016/12/01/visual-studio-tools-for-azure-functions/

Update 12-6-16 @5:00 PM: Updated version of the tools are available that fix the ability to open .NET Core projects with Azure Functions tools installed. Install the updated version over your old version to fix the issue, there is no need to uninstall the previous copy.

というわけで、入れてみたところ・・・

2016-12-10

インストール完了。
プロジェクトの新規作成で、ASP.NET Coreのプロジェクトテンプレートから作成したみところ、無事にできました。

2016-12-10-2

良かった良かったー

風邪っぽい・・・?VS2015の拡張機能

Microsoft Azure2 Advent Calendar 2016 の6日目の記事です。
.NET Core Advent Calendarの6日目の記事です。


2016-12-10追記
Visual Studio Tools for Azure Functions の修正版が出たようで、解消しました。

両方空いていたのと、両方を好きな人はこれから私と悲しい気持ちを味わう前に楽しくなってもらえればという願いを込めて書きました。
相変わらず、しょうもないネタなのですが・・・お時間あればお付き合いください。
Visual Studio 2015の話になります。

季節の変わり目で、咳が止まらず目が覚めて何気なく.NET Coreプロジェクトを作ろうとしたら・・・

2016-12-05-1

2016-12-05-2

あ、、、あれれ?コンソールだめなら、Webは・・・?

2016-12-05-3

2016-12-05-4

 

うーむ、VS2017 RCと共存してるからかなぁ・・・とか思いつつ、既存の.NET Coreアプリを開いてみたところ・・・

=====================
2016/12/04 5:47:00
Recoverable
System.Exception: ディスク上のプロジェクトファイルの検出中に次のエラーが発生しました。Could not load file or assembly 'Microsoft.VisualStudio.ProjectSystem.DotNet, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. 指定されたファイルが見つかりません。。
   at Microsoft.VisualStudio.ProjectSystem.DotNet.Common.FileMirroring.SourceItemsInMemoryProject.<InitializeFromDiskAsync>d__54.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.ProjectSystem.DotNet.Common.FileMirroring.SourceItemsInMemoryProject.<Creator>d__52.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.VisualStudio.ProjectSystem.UnconfiguredProjectImpl.AutoLoadMethodStateMachine.<<StartExecution>b__6_0>d.MoveNext()
===================

どうやら風邪がうつったようです

いやいやそんなわけはなく・・・いろいろ調べてみたところ・・・
どうやら、Visual Studio Tools for Azure Funtionsの問題として認識されてるよというところまでは見つけました。

https://blogs.msdn.microsoft.com/webdev/2016/12/01/visual-studio-tools-for-azure-functions/

Warning: We have discovered that installing this Preview breaks the ability to create or open .NET Core projects in Visual Studio 2015.  We are currently investigating, and will make a fix available as soon as it is ready.  In the meantime, do not install this on any machines where you plan to work with .NET Core projects.

.NET Coreプロジェクトを扱うことができなくなってしまうということで、対策をがんばってるそうです。

そう・・・思えばFunctionsへの浮気心が、.NET Coreとの距離を近づけたのかもしれない・・・

そうか、浮気といえば・・・電話番号は二つ・・・つまり、マシンをもう一つAzureで作ればいいじゃない!
この発想には賛否両論あると思いますが…

というわけで、デプロイ!!
で、さりげなく機能紹介。

2016-12-05-5

というわけで、デプロイできた仮想マシンのブレードを開くと、
Automation スクリプトというのがあるのでそこを選択すると出てくるものです。
これは、ご存知の方多いとは思いますが、ARMというテンプレートで、この仮想マシンと同じものを再現するために使える逆起しの設計書みたいな状態です。
ただ、エラーが出てる通り、この仮想マシンに設定した自動シャットダウンと、仮想マシン拡張の診断機能は含まれていません。なので100%ではないですが、このJSONを1から作るよりも、一度作ってみたものをもとにJSON取得して、修正。
ARMとしてテンプレートデプロイをするというシナリオが近道のような気がします。

 

{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "virtualMachines_vm20161206_adminPassword": {
            "defaultValue": null,
            "type": "SecureString"
        },
        "virtualMachines_vm20161206_name": {
            "defaultValue": "vm20161206",
            "type": "String"
        },
        "networkInterfaces_vm20161206128_name": {
            "defaultValue": "vm20161206128",
            "type": "String"
        },
        "networkSecurityGroups_vm20161206_nsg_name": {
            "defaultValue": "vm20161206-nsg",
            "type": "String"
        },
        "publicIPAddresses_vm20161206_ip_name": {
            "defaultValue": "vm20161206-ip",
            "type": "String"
        },
        "virtualNetworks_vm20161206_vnet_name": {
            "defaultValue": "vm20161206-vnet",
            "type": "String"
        },
        "storageAccounts_vm20161206diag122_name": {
            "defaultValue": "vm20161206diag122",
            "type": "String"
        },
        "storageAccounts_vm20161206disks246_name": {
            "defaultValue": "vm20161206disks246",
            "type": "String"
        }
    },
    "variables": {},
    "resources": [
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Compute/virtualMachines/vm20161206' から一般化されました。",
            "type": "Microsoft.Compute/virtualMachines",
            "name": "[parameters('virtualMachines_vm20161206_name')]",
            "apiVersion": "2015-06-15",
            "location": "japaneast",
            "properties": {
                "hardwareProfile": {
                    "vmSize": "Standard_F8s"
                },
                "storageProfile": {
                    "imageReference": {
                        "publisher": "MicrosoftVisualStudio",
                        "offer": "VisualStudio",
                        "sku": "VS-2015-Ent-VSU3-AzureSDK-29-Win10-N",
                        "version": "latest"
                    },
                    "osDisk": {
                        "name": "[parameters('virtualMachines_vm20161206_name')]",
                        "createOption": "FromImage",
                        "vhd": {
                            "uri": "[concat('https', '://', parameters('storageAccounts_vm20161206disks246_name'), '.blob.core.windows.net', concat('/vhds/', parameters('virtualMachines_vm20161206_name'),'20161204060340.vhd'))]"
                        },
                        "caching": "ReadWrite"
                    },
                    "dataDisks": []
                },
                "osProfile": {
                    "computerName": "[parameters('virtualMachines_vm20161206_name')]",
                    "adminUsername": "watashi.kanrisha",
                    "windowsConfiguration": {
                        "provisionVMAgent": true,
                        "enableAutomaticUpdates": true
                    },
                    "secrets": [],
                    "adminPassword": "[parameters('virtualMachines_vm20161206_adminPassword')]"
                },
                "networkProfile": {
                    "networkInterfaces": [
                        {
                            "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm20161206128_name'))]"
                        }
                    ]
                }
            },
            "resources": [],
            "dependsOn": [
                "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_vm20161206disks246_name'))]",
                "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm20161206128_name'))]"
            ]
        },
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Network/networkInterfaces/vm20161206128' から一般化されました。",
            "type": "Microsoft.Network/networkInterfaces",
            "name": "[parameters('networkInterfaces_vm20161206128_name')]",
            "apiVersion": "2016-03-30",
            "location": "japaneast",
            "properties": {
                "ipConfigurations": [
                    {
                        "name": "ipconfig1",
                        "properties": {
                            "privateIPAddress": "10.0.0.4",
                            "privateIPAllocationMethod": "Dynamic",
                            "publicIPAddress": {
                                "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_vm20161206_ip_name'))]"
                            },
                            "subnet": {
                                "id": "[concat(resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vm20161206_vnet_name')), '/subnets/default')]"
                            }
                        }
                    }
                ],
                "dnsSettings": {
                    "dnsServers": []
                },
                "enableIPForwarding": false,
                "networkSecurityGroup": {
                    "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_vm20161206_nsg_name'))]"
                }
            },
            "resources": [],
            "dependsOn": [
                "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddresses_vm20161206_ip_name'))]",
                "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vm20161206_vnet_name'))]",
                "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_vm20161206_nsg_name'))]"
            ]
        },
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Network/networkSecurityGroups/vm20161206-nsg' から一般化されました。",
            "type": "Microsoft.Network/networkSecurityGroups",
            "name": "[parameters('networkSecurityGroups_vm20161206_nsg_name')]",
            "apiVersion": "2016-03-30",
            "location": "japaneast",
            "properties": {
                "securityRules": [
                    {
                        "name": "default-allow-rdp",
                        "properties": {
                            "protocol": "TCP",
                            "sourcePortRange": "*",
                            "destinationPortRange": "3389",
                            "sourceAddressPrefix": "*",
                            "destinationAddressPrefix": "*",
                            "access": "Allow",
                            "priority": 1000,
                            "direction": "Inbound"
                        }
                    }
                ]
            },
            "resources": [],
            "dependsOn": []
        },
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Network/publicIPAddresses/vm20161206-ip' から一般化されました。",
            "type": "Microsoft.Network/publicIPAddresses",
            "name": "[parameters('publicIPAddresses_vm20161206_ip_name')]",
            "apiVersion": "2016-03-30",
            "location": "japaneast",
            "properties": {
                "publicIPAllocationMethod": "Dynamic",
                "idleTimeoutInMinutes": 4
            },
            "resources": [],
            "dependsOn": []
        },
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Network/virtualNetworks/vm20161206-vnet' から一般化されました。",
            "type": "Microsoft.Network/virtualNetworks",
            "name": "[parameters('virtualNetworks_vm20161206_vnet_name')]",
            "apiVersion": "2016-03-30",
            "location": "japaneast",
            "properties": {
                "addressSpace": {
                    "addressPrefixes": [
                        "10.0.0.0/24"
                    ]
                },
                "subnets": [
                    {
                        "name": "default",
                        "properties": {
                            "addressPrefix": "10.0.0.0/24"
                        }
                    }
                ]
            },
            "resources": [],
            "dependsOn": []
        },
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Storage/storageAccounts/vm20161206diag122' から一般化されました。",
            "type": "Microsoft.Storage/storageAccounts",
            "sku": {
                "name": "Standard_LRS",
                "tier": "Standard"
            },
            "kind": "Storage",
            "name": "[parameters('storageAccounts_vm20161206diag122_name')]",
            "apiVersion": "2016-01-01",
            "location": "japaneast",
            "tags": {},
            "properties": {},
            "resources": [],
            "dependsOn": []
        },
        {
            "comments": "リソース '/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/vm20161206/providers/Microsoft.Storage/storageAccounts/vm20161206disks246' から一般化されました。",
            "type": "Microsoft.Storage/storageAccounts",
            "sku": {
                "name": "Premium_LRS",
                "tier": "Premium"
            },
            "kind": "Storage",
            "name": "[parameters('storageAccounts_vm20161206disks246_name')]",
            "apiVersion": "2016-01-01",
            "location": "japaneast",
            "tags": {},
            "properties": {},
            "resources": [],
            "dependsOn": []
        }
    ]
}

このテンプレートを見て、何をデプロイしたか分かった人はすごい!

で、話は戻りまして・・・こうしてどちらかの環境用のマシンを分けておくと、何かと便利なので、あまり使ってないサブスクリプションを持ってる人とかはどうでしょうか?

まとめ

こんな状態でもツールをリリースして、たたかれる覚悟でも一歩でも進もうとしていることに私はすごいことなんじゃないかと思っています。

enjoy azure !
enjoy .NET Core !

Visual Studio 2015 のAzure FunctionsでStartしたときのUAC

Microsoft Azure2 Advent Calendar 2016 の3日目の隙間に向けて・・・隙間なネタを・・・!

開始するたびにダイアログがぽこぽこ上がってくるこれ・・・

UACを切ってしまえば・・・でもいいのだけど。それはそれで不安があるしとか思いつつ、もう一ついけそうだと思ってたのが、Visual Studioを管理者モードで起動する

2016-12-04-1

こうすると、親プロセスであるVisual Studioが管理者状態で動いていているので、そこから開始されるFunctionsのローカル実行のプロセスも自動的に管理者権限で動いてくれるということで、UACのダイアログは出てこなくなる。

ただ、Visual Studioに特権が与えられるので、それはそれで不安になることもあるかもしれない。

クリスマス近いんだねー

(メモ)Visual Studio 2015 の Azure Functions プロジェクトに 参照サービスを追加する

Functions単体では、ただコードを書いて、日付を返すとかはできますが、それだけでは、日付を返すとか文字列を変換するとかのことはできても、それは普通にローカルで動くプログラムでも得られるわけで、あまり意味がありません。
Functionsのプロジェクトを他のサービスに接続してみます。

 

プロジェクトのコンテキストメニュー(失敗)

ソリューションエクスプローラー>Functionsプロジェクト>コンテキストメニュー>追加>Connected Service

2016-12-03-1

追加用の画面が表示される
2016-12-03

Azure Storageを選ぶ
2016-12-03-2

既存を選ぶか、Create a New Storage Accountで新規に追加

2016-12-03-3

  1. Azure サブスクリプション を関連付けられているアカウントを選択
  2. サブスクリプションを選択
  3. Storage Account のURLになるものを入力
  4. お好みで料金体系・サービス体系を選択
  5. リソースグループを選択(ここで新規で作ることもできる)
  6. Locationを選択

Azure Storageの画面でAddとしてみると・・・

2016-12-03-4

NuGetのパッケージを追加できずにエラー。
というわけで、この方法ではだめな様子。
ひとまずフィードバックを送信。

functions の場合、プロジェクトの参照というよりは、bindingの定義があってという感じなのもあるかもしれないのでひとまずこの方法ではなさそう。

Functionsの追加から

試しにFunctionの追加から、
テンプレートは、FaceLocatorという、Blobストレージにある画像から顔がある四角形領域を返してくれるもの。

2016-12-03-5

ストレージアカウントの接続文字列を入力する欄が赤くなってますので、ポータルから引っ張ってくる。

2016-12-03-6

この接続文字列は絶対に公開しないように・・・
このように公開してしまった場合は、アカウントを消すか、接続文字列を再生成する必要が出てくる。その場合既存の接続文字列は使えなくなるので、いろいろ面倒。

追加した「FaceLocatorCSharp」の
function.json

{
  "bindings": [
    {
      "type": "blobTrigger",
      "name": "image",
      "path": "images/{name}.jpg",
      "connection": "DefaultEndpointsProtocol=https;AccountName=functionapp;AccountKey=xxxxxx;",
      "direction": "in"
    },
    {
      "type": "table",
      "name": "outTable",
      "tableName": "faceRectangle",
      "connection": "DefaultEndpointsProtocol=https;AccountName=functionapp;AccountKey=xxxxxx;",
      "direction": "out"
    }
  ],
  "disabled": false
}

このファイルにあるbindingsが、作ったfunctionsの入出力の定義そのものになっている。nameにある文字列がfunctionsのメソッドにある引数として以下のように

public static async Task Run(Stream image, string name, IAsyncCollector<FaceRectangle> outTable, TraceWriter log)
{
    ....
}

しかし・・・

F5デバッグ開始をしてみるもののエラーが発生してしまう。
かなり手詰まりではあるが、実際に動いているものがあるということはそこから得られるヒントがある・・・と信じて

ポータルから・・・

ポータルから生成したもののソースコードを見てみると

  {
  "bindings": [
    {
      "type": "blobTrigger",
      "name": "image",
      "path": "images/{name}.jpg",
      "connection": "functionappXXXX",
      "direction": "in"
    },
    {
      "type": "table",
      "name": "outTable",
      "tableName": "faceRectangle",
      "connection": "functionappXXXX",
      "direction": "out"
    }
  ],
  "disabled": false
}

といった形で、appSettings.jsonに設定されたものを指定しているようだ。
なので置き換えた。

それでもエラーはまだ残るため次に、テンプレート作成時には空となっていた
AzureWebJobsStorageおよびAzureWebJobsDashboardにも接続文字列を追加した

これは、ポータルで生成した場合は自動的に追加されているものだった。

FaceLocatorのキー

さらにわかったことがあって、テンプレート「FaceLocatorCSharp」では、VisionAPIを使うことになっているが、このための接続情報をappSettingsに追加してやる必要があった。

それがわかるコードはここで

        client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", Environment.GetEnvironmentVariable("Vision_API_Subscription_Key"));

「Vision_API_Subscription_Key」という設定値を必要としていたので、appSettings.jsonに追加した。

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=functionapp;AccountKey=xxxx",
    "AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=functionapp;AccountKey=xxxx",
    "Vision_API_Subscription_Key": "xxxxxxxxxxxxxxxxx",
    "functionapp": "DefaultEndpointsProtocol=https;AccountName=functionapp;AccountKey=xxxx"
  }
}

もちろんこれは使えるものではなく、
https://www.microsoft.com/cognitive-services/en-us/subscriptions
より取得する必要がある

 

 

Visual Studio の拡張機能として

まだ、このFunctionsの拡張機能はPreviewであり今後変わる可能性があると思うが、
現状動くポータルを正解としていけば動かす道は掴んでいけそうであることがわかった。