アリスはアノテーションがお嫌い

アリスはリフレクションがお嫌い | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/8092

の続きで、属性/アノテーションの話を追記。

リフレクションはクラスに対して static な情報だということが解ったら、じゃあ「属性」ってのも static な情報というのが分かるので、このあたりは同根なんだなというのが分かる。ケント・ベック氏の「テスト駆動開発入門」に、とあるプログラム言語を学ぼうとしたときに自動テストツール(xUnit)を書いてみると、パターンが網羅されていて学びやすい、ということが書いてある。実際、xUnit のツールにはデザインパターンが多く使われていて、その実現をそれぞれのプログラム言語がどうやって実装しているのかというのを見ることができる。
よくあるテストメソッドとテストクラスの情報収集の方法に

– C++ の場合は、あらかじめ登録が必要
– Java の場合は、先頭にTestがくっついている
– C# の場合は [TestMethod] 属性を付ける

という違いがある。今だともうちょっと違っているけど、初期のころの xUnit のコードを見ると、その当時の文法で駆使されていて、その後にちょっとずつ他の言語の流儀が取り入れられているのが分かります。
C++ の場合は、リフレクションがないので、テストメソッドをいちいち登録するしかない…けど、テンプレートを駆使して明示的には登録しているようにみえないけど、実は登録してるって方法を取ってる。

TEST_CLASS(UnitTest1)
{
public:
	TEST_METHOD(TestMethod1)
	{
		auto m = new Prada();
		m->setStyle(10);
		m->setColor("Pink");
		auto plist = m->properties;
		for each ( auto prop in plist )
		{
			auto n = prop.first;
			auto v = prop.second;
			void*x = v(m);
		}
	}
};

最近の VC++ でテストプロジェクトを作ると、TEST_CLASSとTEST_METHODというマクロが定義されていて、うまく登録できるようになっている。

初期の Java の場合は、属性/アノテーションがなかったので、実行したいテストメソッドと普通のメソッドを区別するために、メソッドの先頭に「Test」とつけておく。でもって、リフレクションを使って先頭が Test になっているメソッドだけを実行するという方式をとる。
これは、結構シンプルでよいのだけど、メソッドの名前自体に意味を持たせてしまうのが、なんだかなーという感じだったので、最近の JUnit では、「@Test」というアノテーションが付けられる。

public class JunitExampleTest {

    @Test
    public void testFoo() {
		JunitExample example = new JunitExample();
        assertEquals(1, example.foo())
    }
}

Java の後発になる C# の場合は、リフレクションだけでは足りないとみて、属性(Attribute)というのものを付けている。実際の使い方はアノテーションと同じだ。

[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestMethod1()
    {
        var m = new Prada { Style = 10, Color = "Pink" };
        var ti = m.GetType().GetTypeInfo();
        var plist = ti.DeclaredProperties;
        foreach (var prop in plist)
        {
            var n = prop.Name;
            var v = prop.GetValue(m);
            Debug.WriteLine($"prop: {n} value: {v}");
        }
    }
}

当初、属性というものが使われていたときに、他の言語にはアノテーション自体の機能がなかったのと、アスペクト指向が流行り始めていて、そのアノテーション(装飾)と区別がつかなかったという経緯もあって、なんかいまいちな言語仕様な感じがしたのだが、MVCパターンのように動的に Action メソッドを呼び出すようなスタイルを考えたとき、ちょっと便利に使える。

[Produces("application/json")]
[Route("api/People")]
public class PeopleApiController : Controller
{
    private readonly ApplicationDbContext _context;

    public PeopleApiController(ApplicationDbContext context)
    {
        _context = context;
    }
    // GET: api/People
    [HttpGet]
    public IEnumerable<Person> GetPerson()
    {
        return _context.Person;
    }
    // GET: api/People
    [HttpGet]
    [Route(&quot;search/name/{value}&quot;)]
    public IEnumerable<Person> GetPersonByName([FromRoute] string value )
    {
        return _context.Person.Where( t => t.Name.Contains(value));
    }
    // GET: api/People
    [HttpGet]
    [Route(&quot;search/age/{value}&quot;)]
    public IEnumerable<Person> GetPersonByAge([FromRoute] int value)
    {
        return _context.Person.Where(t => t.Age >= value);
    }

これは、ASP.NET MVCアプリケーションのルーティングのテスト用に作ったサンプルコードだが、たくさんの属性が使われている。要は、クラスに対して static な情報を属性に詰め込めばよいので、Produces属性でJSON形式を返すとか、Route属性でURL呼び出しの形式を設定するとか、HttpGet属性でGETメソッドだけに呼び出しを限るとかの設定ができる。
これらの情報はDI(インジェクション)という形で、設定ファイルなどに追い出すことも可能なのだが、そもそも設定ファイルとこのControllerクラスが1対1になっているのだったら、その「設定」自体をコードで書こうと外部の設定ファイルに書こうと意味は同じはずだ。ならば、属性で、ということになる。当然、コードに埋め込まれてしまう欠点もあって、ちょっとだけルーティングを変えようと思っても、このコードを変えないといけないので、そういう場合は DI のように設定自体を外付けしてしまったほうがよい。
これは、属性とDIのどちらがいいという話ではなくて、利用するときの状況によりけり、というパターンかなと思っている。一般的な使い分けとしては、コーディング時に決定してしまうもの(あとからの設定では変えられないもの、代替できないもの)は属性に、動作環境によって変わるもの(データベースの接続文字列とか)は設定ファイルに出せばよい。

属性を使わないで実装する

じゃあ、本題として、ルイスはアリスが属性/アノテーションが嫌いだから、属性を使わないで自前で実装するんやという話になっているので、ASP.NET MVC っぽい Action メソッドの呼び出しをどうやって実装するのかを考えてみる。初期の JUnit のように諸々の規約をメソッド名に追加してもよいのだが、あれこれ設定するのは大変だ。

class AliceAttribute : Attribute { }
class DefaultAttribute : Attribute
{
    public string Name { get; set; }
}
[Alice]
class Prada
{
    public int Style { get; set; }
    [Default(Name = "White")]
    public string Color { get; set; }
}

こんな風な Alice 専用の Prada クラスを作ってみる。クラスの属性に「Alice」を付けておくと、Alice 専用のクラスという訳だ。ついていない場合は、普通のクラス。
あと、デフォルトの値を設定できるように、Default属性を作ってみる。

public void TestMethod3()
{
    var m = new Prada { Style = 10, Color = &quot;Pink&quot; };
    var ti = m.GetType().GetTypeInfo();

    var attr1 = ti.GetCustomAttribute<AliceAttribute>();
    var attr2 = ti.GetProperty(&quot;Color&quot;).GetCustomAttribute<DefaultAttribute>();

    Debug.WriteLine($&quot;prop: Color attr: {attr2.Name}&quot;);

}

C# での属性呼び出しは、GetCustomAttributeメソッドを使えばよい。GetCustomAttributeで null が返ってきたら属性が設定されていないという訳で、Alice 属性がついているかどうかが分かる。全ての属性を取りたい場合は、GetCustomAttributes メソッドを使うが、どんな属性にも対応するということはないのであまり使わないだろう。クラス特性を調べるときとかに使うかな。
Default 属性で設定した Name プロパティは、そのまま属性オブジェクトの Name プロパティで見れる。この属性オブジェクトが、クラスやメソッドに設定したときにクラス情報と一緒にくっついてくるというイメージでよい。結局のところ、GetTypeInfoメソッドを呼び出してクラス情報を取るので、リフレクションと同じ方式になる。

これを C++ で書き直してみると、

class AliceAttribute {};
class DefaultAttribute {
public: 
	std::string Name;
	DefaultAttribute(std::string &name) { this->Name = name;  }
};

class Prada2 {
private:
	int _style = 0;
	std::string _color = &quot;&quot;;
public:
	int getStyle() { return _style; }
	void setStyle(int v) { _style = v; }
	std::string getColor() { return _color; }
	void setColor(std::string v) { _color = v; }
public:
	static AliceAttribute *Alice;
	static DefaultAttribute *DefaultColor;
	Prada2() {
		Prada2::Alice = new AliceAttribute();
		Prada2::DefaultColor = new DefaultAttribute(std::string(&quot;White&quot;));
	}
};
TEST_CLASS(UnitTest2)
{
public:
	TEST_METHOD(TestMethod1)
	{
		auto m = new Prada2();
		m->setStyle(10);
		m->setColor(&quot;Pink&quot;);

		auto alice = m->Alice;
		auto color = m->DefaultColor->Name;
	}
};

適当な属性クラス(AliceAttribute、DefaultAttribute)を用意して、Prada2クラスの static メンバとして設定しておく。汎用的に作る場合は、リフレクションのときと同じように map を使うのだが、クラス自身の属性とメンバの属性があるので2つ作らないといけないのがちょっと面倒…だけでも、まあできないことはない程度にはわかるだろう。

逆にいえば、クラスの情報として、static で設定しているメンバ変数は属性に置き換えられる、ということだ。リフレクションを使うので、呼び出し側でひと手間かかるが、この設定の命名規約があれこれと悩むよりも、Attribute クラスを適当に作ってしまって属性に対する設定のほうで、あれこれと変えるということもできるということだ。
そういう意味で、

– クラスが外部公開する const 変数
– 属性
– DI の設定

を使い分けていくことになる。

カテゴリー: 開発 | アリスはアノテーションがお嫌い はコメントを受け付けていません

アリスはリフレクションがお嫌い

長年使っていた Perl+C# の WordPress 投稿ツールを、.net core を機会に F# に直そうとしている途中なのですが、そのなかでふと、リフレクションを使わずに XML-RPC を実装するのはさぞしんどいことではないか?
ということに思い至りました。いや、もともと使っていた XMLRPC.NET はインターフェース形式なのであれこれとややこしくなっているんですよね。そのあたりで使い勝手が悪くなってしまっているのと、ストレス発散ののためにも F# で書き直しをしています。

実は、MVCパターンのControllerの呼び出しにもリフレクションが使われます。ブラウザから呼び出されたURLアドレスからActionメソッドの呼び出しがそれで、Controllerクラスのメソッドをあらかじめ登録しているのではなくて、実行時にメソッドを探します。ASP.NET MVC だといちいちコンパイルをしないと駄目だけど、CakePHPのようなパターンだと動的言語なのでスクリプトを直接修正することができます。

そんなわけで、XML-RPC だと

class Prada
{
    public int Style { get; set; }
    public string Color { get; set; }
}

なクラスを

<struct>
  <member>
    <name>Style</name>
    <value>
      <int>10</int>
    </value>
  </member>
  <member>
    <name>Color</name>
    <value>
      <string>Pink</string>
    </value>
  </member>
</struct>

な風にXML形式に直します。実際はmethodCall要素があったり、今だったらJSON形式のほうが楽だろうってことなんですが、WordpressにXMLRPCで投稿すると楽なので自作ツールを作って使っています。

リフレクションを使う

じゃあ C# でXML形式に直すことを考えると、PradaクラスのStyleプロパティとColorプロパティをどうにかして持ってこないといけないので、プロパティを列挙するリフレクションを使えばいいんだね、ということになります。

using System.Reflection;

public void TestMethod1()
{
    var m = new Prada { Style = 10, Color = "Pink" };
    var ti = m.GetType().GetTypeInfo();
    var plist = ti.DeclaredProperties;
    foreach (var prop in plist)
    {
        var n = prop.Name;
        var v = prop.GetValue(m);
        Debug.WriteLine($"prop: {n} value: {v}");
    }
}

こんな風に、GetTypeInfoメソッドでクラス情報を取ってきて、DeclaredPropertiesコレクションでプロパティの一覧を取得、その後で名前と値を取ってきます。GetValueメソッドで取ってきたものは object 型なので、実際に利用するときは元の型に戻さないといけません。型自体は、PropertyType プロパティで取って来れるので、これをそれぞれの型と is 演算子で比較するか、乱暴な場合は as でキャストしてしまいます。
XML-RPCのときは最終的に文字列に直してしまうので、ToString で代用させています。

ちなみに、F# で書くとこんな感じになります。

type Prada() =
    member val Style = 0 with get, set
    member val Color = &quot;&quot; with get, set

[<TestMethod>]
let TestMethod1() =
    let m = new Prada( Style=10, Color=&quot;Pink&quot; )
    let ti = m.GetType().GetTypeInfo()
    let plist = ti.DeclaredProperties
    for prop in plist do
        let n = prop.Name
        let v = prop.GetValue(m)
        System.Diagnostics.Debug.WriteLine(String.Format(&quot;prop: {0} value: {1}&quot;, n, v ))

この列挙したプロパティを使って XML 形式に直すわけですが、そのあたりは XElement を使うと楽にできるので、ちょっと省略。

で、アリプラの本題にいきます。
これって、リフレクションを使うと「クラス情報」を取れることになるけど、内部実装的にはどうなっているんだろうか。いや、そもそも、C++ のようなクラス情報を持たない言語の場合は、どうやって実装すればいいんだろうか?

という話になります。クラス情報なんだから、クラスに紐づいているわけで、new で作成したオブジェクトとは別ですよね。なので、いくつもオブジェクトを作ったとしても、クラス情報とかプロパティの型とかは一種類しかないわけです。Javascriptのようにプロトタイプで後からどんどん付け加えていくというパターンもあるので、プロパティやメソッドが固定というわけではないけれど(実装的には、後付け用のコレクションを用意するわけだし、dynamic型みたいな感じになる)、もともとの固定のプロパティやメソッドをどうやって扱おうかということになります。

リフレクションもインターフェースも使わないで実装する

なので、アリスは「リフレクション」も「インターフェース」も嫌いなので、ルイスは何か別の方法で実装しなければいけません。本来ならば、インターフェースを作ってという話になるけど、できるだけリフレクションっぽい形にしたい。あと、クラスの属性のように付加情報も取り入れたい。

class PradaNew
{
    public static string ClassName = &quot;PradaNew&quot;;
    public static Dictionary<string, Func<PradaNew, object>> Properties =
        new Dictionary<string, Func<PradaNew, object>>
    {
        {&quot;Style&quot;, m => m.Style  },
        {&quot;Color&quot;, m => m.Color }
    };
    public int Style { get; set; }
    public string Color { get; set; }
}

そんな訳で、ルイスは「PradaNew」クラスを作って、付加情報を独自に追加しました。なんか、それっぽい形で取れるように、クラス名と、プロパティ名を渡すと値が取れるような仕組みを作っておきます。
両方とも static プロパティなので、クラスのみに紐づいた情報になりますね。

でもって、PradaNew を使ったリフレクションっぽい形の例がこっち。

public void TestMethod2()
{
    // リフレクションを使わないパターン
    var m = new PradaNew { Style = 10, Color = "Pink" };
    var ti = m.GetType().GetTypeInfo();
    var plist = PradaNew.Properties;

    foreach (var prop in plist)
    {
        var n = prop.Key;
        var v = prop.Value(m);
        Debug.WriteLine($"prop: {n} value: {v}");
    }
}

プロパティ名を Key で、値のほうを Value で取ってくるのが多少乱暴ですが、先のリフレクションと同じようなスタイルになります。
ここにクラスの属性として「HttpGet」とか加えたいときも、ClassName プロパティのように static なプロパティを追加すれば ok ですね。static なプロパティなので、オブジェクトが大量に増えてもメモリは増えません。また、値を取ってくるところは適当にキャッシュしてやれば、通常のメソッド呼び出しと遜色ないスピードが出せそうだということがわかります。

これを、ちょっと C++ で直してみます。

#include <map>
#include <functional>

class Prada {
private:
	int _style = 0;
	std::string _color = &quot;&quot;;
public:
	int getStyle() { return _style; }
	void setStyle(int v) { _style = v; }
	std::string getColor() { return _color; }
	void setColor(std::string v) { _color = v; }

public:
	const std::string className = &quot;Prada&quot;;
	static std::map <std::string, std::function<void*(Prada*)>> properties;

	Prada() {
		auto func = [](Prada* m) { auto v = m->getStyle(); };
		Prada::properties.insert(std::make_pair(std::string(&quot;Style&quot;), [](Prada* m) { auto v = m->getStyle(); return &v; }));
		Prada::properties.insert(std::make_pair(std::string(&quot;Color&quot;), [](Prada* m) { auto v = m->getColor(); return &v; }));
	}
};
std::map <std::string, std::function<void*(Prada*)>> Prada::properties;;

int main()
{
	auto m = new Prada();
	m->setStyle(10);
	m->setColor(&quot;Pink&quot;);
	auto plist = m->properties;
	for each (auto prop in plist)
	{
		auto n = prop.first;
		auto v = prop.second;
		void* x = v(m);
		// void* から元の型へ戻すには?
	}
	return 0;
}

元の型に戻すのには、プロパティの型を調べる必要があるので、もう少し手を入れないといけないけど、だいたいはこんな感じ。map にしてラムダ式を返すようにしたのでややこしいことになっていますが、構造体を作ってプロパティの型を調べるところと、値を取得するところを作ったほうが楽かも。

まあ、こんな実装は普通はしないわけで、内部的にはこんなことになっているなーと思いつつ、リフレクションを道具として使うとよいのではと思った次第。

カテゴリー: 開発 | アリスはリフレクションがお嫌い はコメントを受け付けていません

ASP.NET Core MVC の .NET Core アセンブリは何処からロードされるのか?

結論から言うと、

  • C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0 配下
  • C:\Users\<ユーザー名>\.nuget\packages

からロードされています。ASP.NET Core MVC アプリを動作させると、以下の分だけロードされている。

File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\Microsoft.CodeAnalysis.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\Microsoft.Win32.Registry.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\mscorlib.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.AppContext.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Buffers.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Collections.Concurrent.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Collections.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.ComponentModel.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Console.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Diagnostics.Debug.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Diagnostics.DiagnosticSource.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Diagnostics.Tracing.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Dynamic.Runtime.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Globalization.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.FileSystem.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.FileSystem.Primitives.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.IO.FileSystem.Watcher.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Linq.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Linq.Expressions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Net.Primitives.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Numerics.Vectors.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.ObjectModel.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Private.CoreLib.ni.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Private.Uri.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Emit.ILGeneration.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Emit.Lightweight.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Extensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.Primitives.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Reflection.TypeExtensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.Extensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.Handles.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.InteropServices.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Runtime.InteropServices.RuntimeInformation.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Text.Encoding.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Text.Encoding.Extensions.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Overlapped.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Tasks.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Thread.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.ThreadPool.dll
File	C:\Program Files\dotnet\shared\Microsoft.NETCore.App\1.0.0\System.Threading.Timer.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Antiforgery\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Antiforgery.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Authorization\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Authorization.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Cors\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Cors.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Cryptography.Internal\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Cryptography.Internal.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.DataProtection.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.DataProtection.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.DataProtection\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.DataProtection.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Diagnostics\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Diagnostics.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Hosting.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Hosting.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Hosting.Server.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Hosting.Server.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Hosting\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Hosting.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Http.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Http.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Http.Features\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Http.Features.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Http\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Http.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Mvc.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.ApiExplorer\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.ApiExplorer.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Core\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Core.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Cors\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Cors.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.DataAnnotations\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.DataAnnotations.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Formatters.Json\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Formatters.Json.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Razor.Host\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Razor.Host.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.Razor\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.Razor.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.TagHelpers\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.TagHelpers.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc.ViewFeatures\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.ViewFeatures.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Mvc\1.0.0\lib\netstandard1.6\Microsoft.AspNetCore.Mvc.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Razor.Runtime\1.0.0\lib\netstandard1.5\Microsoft.AspNetCore.Razor.Runtime.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Razor\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Razor.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Routing.Abstractions\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Routing.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Routing\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Routing.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Server.IISIntegration\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Server.IISIntegration.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.Server.Kestrel\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.Server.Kestrel.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.AspNetCore.StaticFiles\1.0.0\lib\netstandard1.3\Microsoft.AspNetCore.StaticFiles.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.DotNet.InternalAbstractions\1.0.0\lib\netstandard1.3\Microsoft.DotNet.InternalAbstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Caching.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Caching.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Caching.Memory\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Caching.Memory.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Configuration.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.EnvironmentVariables\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Configuration.EnvironmentVariables.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.FileExtensions\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Configuration.FileExtensions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration.Json\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Configuration.Json.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Configuration\1.0.0\lib\netstandard1.1\Microsoft.Extensions.Configuration.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.DependencyInjection.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.DependencyInjection\1.0.0\lib\netstandard1.1\Microsoft.Extensions.DependencyInjection.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.DependencyModel\1.0.0\lib\netstandard1.6\Microsoft.Extensions.DependencyModel.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.FileProviders.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.FileProviders.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.FileProviders.Physical\1.0.0\lib\netstandard1.3\Microsoft.Extensions.FileProviders.Physical.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Localization.Abstractions\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Localization.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging.Abstractions\1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.Abstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging.Console\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Logging.Console.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging.Debug\1.0.0\lib\netstandard1.3\Microsoft.Extensions.Logging.Debug.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Logging\1.0.0\lib\netstandard1.1\Microsoft.Extensions.Logging.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.ObjectPool\1.0.0\lib\netstandard1.3\Microsoft.Extensions.ObjectPool.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Options\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Options.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.PlatformAbstractions\1.0.0\lib\netstandard1.3\Microsoft.Extensions.PlatformAbstractions.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.Primitives\1.0.0\lib\netstandard1.0\Microsoft.Extensions.Primitives.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Extensions.WebEncoders\1.0.0\lib\netstandard1.0\Microsoft.Extensions.WebEncoders.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.Net.Http.Headers\1.0.0\lib\netstandard1.1\Microsoft.Net.Http.Headers.dll
File	C:\Users\masuda\.nuget\packages\Microsoft.VisualStudio.Web.BrowserLink.Loader\14.0.0\lib\netstandard1.5\Microsoft.VisualStudio.Web.BrowserLink.Loader.dll
File	C:\Users\masuda\.nuget\packages\Newtonsoft.Json\9.0.1\lib\netstandard1.0\Newtonsoft.Json.dll
File	C:\Users\masuda\.nuget\packages\System.Collections.NonGeneric\4.0.1\lib\netstandard1.3\System.Collections.NonGeneric.dll
File	C:\Users\masuda\.nuget\packages\System.Collections.Specialized\4.0.1\lib\netstandard1.3\System.Collections.Specialized.dll
File	C:\Users\masuda\.nuget\packages\System.ComponentModel.Primitives\4.1.0\lib\netstandard1.0\System.ComponentModel.Primitives.dll
File	C:\Users\masuda\.nuget\packages\System.ComponentModel.TypeConverter\4.1.0\lib\netstandard1.5\System.ComponentModel.TypeConverter.dll
File	C:\Users\masuda\.nuget\packages\System.Runtime.Serialization.Primitives\4.1.1\lib\netstandard1.3\System.Runtime.Serialization.Primitives.dll
File	C:\Users\masuda\.nuget\packages\System.Text.Encodings.Web\4.0.0\lib\netstandard1.0\System.Text.Encodings.Web.dll

ASP.NET 廻りと MVC 廻りがごっそり NuGet からダウンロードされていることが分かります。たぶん、これプラス、自前の NuGet パッケージが入るのだと思う。

ロードしているアセンブリのチェックは、Process Explorer で。

image

ちなみに、F# 版の ASP.NET MVC アプリを作ると、

File	C:\Users\masuda\.nuget\packages\Microsoft.FSharp.Core.netcore\1.0.0-alpha-160629\lib\netstandard1.6\FSharp.Core.dll

だけが増えます。F# のアセンブリが NuGet で取れるようになっているので、以前のような PCL の混在であれこれと悩むことはなくなりそうな感じです。このあたりは、ぼちぼち調べていきます。

カテゴリー: ASP.NET | ASP.NET Core MVC の .NET Core アセンブリは何処からロードされるのか? はコメントを受け付けていません

HDD性能の再チェック

Hyper-V 上の Visual Studio 2015 の起動が、むちゃくちゃ遅いので、ちょっと SSD/HDD の性能を再チェックしておく。

HDDのアクセススピードは VMWare に影響するのか? | Moonmile Solutions Blog にある3年前の構成をまだ使っている(途中で SSD が逝ったので、交換しているが)ので、HDD が劣化していなか調べるのも兼ねている。SSD のほうは、2年半ぐらいで逝ってしまったので、なんとも言えないのだけど、極端に劣化している感じはしない。途中で、OS も変わっているので、10% 程度は誤差だろう。

image

Cドライブ (TS512)

image

Dドライブ (WDC)

image

Fドライブ (WDC)

image

Hドライブ (PLEXTOR)

image

D:iso (Hitachi)

image

F:dvd (Hitachi)

image

 

Fドライブ内のHyper-Vで

image

SSD(PLEXTOR)のHyper-Vで

– Windows 7

image

– Windows 10

image

Hyper-V 上で Visual Studio を動かす

SSD(PLEXTOR)とHDD(WDC)上に、Hyper-V で Windows 10 + Visual Studio 2015 の環境を作って起動速度を比較してみる。メモリは4GB を割り当てている。

imageimage

左が SSD で、右が HDD。基本性能が違うので、動作にも差がでるであろうというのは予想できるのだが、結論から言えば、

  • Visual Studio の初回起動時は SSD のほうが圧倒的に早い
  • 2回目以降の起動では、4GB のキャッシュに乗っているためか、速度は変わらない

初回起動の比較

2回目の起動比較

どうやら Windows の起動と同じで、初回のみ我慢すれば HDD もそこそこ SSD と変わらない程度で起動してくれるのが分かる。おそらく 4GB のメモリのキャッシュが効いているのだろう。

となると、メモリを 2GB のぎりぎりにした場合、2回目の起動にも差が出るのではないか、という仮定ができる…が、実際やってみると 2GB の場合でも、2回目の起動は対して違いはない。

とはいえ、別件で C++ のコンパイルをしていると、SSD のほうは若干早かったりするので、Hyper-V アクセスであってもストレージへのアクセススピードは早いほうがいい。いくつか ASP.NET MVC 絡みで Hyper-V の仮想環境を複数動かしているので、SSD 480 GB を追加購入。PLEXTOR のほうは、家庭用 PC に移そうと思うので、移行したらベンチマークを取るつもり。家庭用 PC は Core 2 に Windows 10 を入れているというとんでもなく遅い PC なのだが、ブラウザの閲覧ぐらいだったらこれで十分だったりする。それでも、かなりもたつくので、足回りを早くしたときの確認をしてみたい。

カテゴリー: パフォーマンス | HDD性能の再チェック はコメントを受け付けていません

ASP.NET Core MVC のデバッグ実行がとてつもなく遅くなった時

なんだか、分からないが、Visual Studio から IIS Express でデバッグ実行したときに、とてつもなく遅くなって 500 が頻発した。仮想環境のものや、白 vaio の別の環境では大丈夫なので、どうやら業務 PC の Visual Studio 2015 だけ妙な具合になったらしい。

↓なエラーがでる。このエラーが出るまでとてつもなく長い。

image

どうやら、Microsoft シンボルサーバーに問合せをしているところが遅いらしく、このチェックを外すと普通に早くなる。

image

再確認すると、他の PC では入っていなかったので、どっかのタイミングで自動的に入った模様。あるいは、昨日あたりからシンボルサーバーが落ちているとか?

もうひとつ、ASP.NET MVC のプロジェクト作って、別のフォルダーに移すと、IIS Express につながらなくてやっぱり 500 のエラーになる。エラーを見ると、構成ファイルが読み込めないとのことなのだが、プロジェクト内にある web.config は正常のような気がしないでもない。

image

実は、Visual Studio が作る .vs/config/applicationhost.config に IIS Express のための virtualDirectory 書き込まれてあって、フォルダーを移動したときにこの値が以前の場所を指しているので読み込めなくなる。なんだかなー。

というわけで、配布をしたりフォルダーを移動したときは、マメに .vs フォルダーを消す必要がある。このフォルダーは Visual Studio が IIS Express を起動時に再び作るので消し去っても大丈夫。

参考先

asp.net mvc – Visual Studio Debugging/Loading Very Slow – Stack Overflow
http://stackoverflow.com/questions/12567984/visual-studio-debugging-loading-very-slow

c# – ASP.NET 5 MVC: unable to connect to web server ‘IIS Express’ – Stack Overflow
http://stackoverflow.com/questions/35675747/asp-net-5-mvc-unable-to-connect-to-web-server-iis-express

カテゴリー: ASP.NET | 1件のコメント

CheckBox のチェックの□の色を変更する

いわゆる、↓の□の色を変更します。

image

普通にやると、黒で表示されて、どうにも変更できません。が、XAML のスタイルを使って変更します。これ、何をやっても黒にしかならなくて、このような白い四角を表示したいときには、別途自作するとかしていたんですが、原因がわかりました。

XAML ファイルを Blend で開く。

image

「テンプレートの編集」→「コピーして編集」を選択すると、<Page.Resources> にスタイルが追加される。

アニメーションしている storyboard の中で、

<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty=&quot;Stroke&quot; Storyboard.TargetName=&quot;NormalRectangle&quot;>
   <DiscreteObjectKeyFrame KeyTime=&quot;0&quot; Value=&quot;{ThemeResource CheckBoxCheckBackgroundStrokeUncheckedPointerOver}&quot;/>
</ObjectAnimationUsingKeyFrames>

の3行をごっそり消します。
どうやら、ここで設定している CheckBoxCheckBackgroundStrokeUncheckedPointerOver などのリソースが「Black」固定になっているんですよね。テーマ色なので仕方がないんですが、ここの Storoke の色は、Setter Property を使って欲しかったところですね。

<Grid Height=&quot;32&quot; VerticalAlignment=&quot;Top&quot;>
    <Rectangle 
        x:Name=&quot;NormalRectangle&quot; 
        Fill=&quot;{ThemeResource CheckBoxCheckBackgroundFillUnchecked}&quot; 
        Height=&quot;20&quot; 
        Stroke=&quot;White&quot; 
        StrokeThickness=&quot;{ThemeResource CheckBoxBorderThemeThickness}&quot; UseLayoutRounding=&quot;False&quot; Width=&quot;20&quot;/>
    <FontIcon x:Name=&quot;CheckGlyph&quot; Foreground=&quot;{ThemeResource CheckBoxCheckGlyphForegroundUnchecked}&quot; FontSize=&quot;20&quot; FontFamily=&quot;{ThemeResource SymbolThemeFontFamily}&quot; Glyph=&quot;&#xE001;&quot; Opacity=&quot;0&quot;/>
</Grid>

でもって、最後のほうにある、NormalRectangle を見つけて、Stroke=”White” で色を設定しておきます。ここだけ設定しても、先の storyboard がそのままだと色が上書きされているのが、色が変わらない原因です。

要は、こんな感じなのを作りたかっただけです。

image

ちなみ、デザイン時のデータバインドを、

d:DataContext="{d:DesignData Source=/data/sample.json, Type=vm:MyViewModel}"

な感じで設定しているのですが、色のテーマを Dark にしようとして、Page タグに RequestedTheme=”Dark” を設定すると、↓な感じにバインドエラーがでます。

image

RequestedTheme を消すと、↓な感じでデザイン時のバインドが効くので、バグっぽいです。

image

なんだかなー。あとで調査してバグ報告しますか。

カテゴリー: C#, XAML | CheckBox のチェックの□の色を変更する はコメントを受け付けていません

俺のラズパイ3で.NET CoreなF#が動かないわけがない

と、思っていましたが、動きません orz.

dotnet/coreclr:
https://github.com/dotnet/coreclr/
dotnet/cli:
https://github.com/dotnet/cli

.NET Coreをビルドしようと思うと、

な感じで拒否られるし、じゃあ、dotnet コマンドだけでもビルドできないかと思うと、

20160831_06

な感じで、前回ビルド済みの dotnet コマンドを要求されるし、どうにもなりません。

Problem install .Net Core on Raspberry Pi Model B ・ Issue #140 ・ dotnet/core
https://github.com/dotnet/core/issues/140
How to use dotnet cli in debian linux on arm. ・ Issue #3197 ・ dotnet/cli
https://github.com/dotnet/cli/issues/3197

を見る限り、Raspberry Pi で使われてる ARMv7 ではビルドできませんという具合です。.NET CoreのターゲットとしてARMは入っているので、いずれ対応するのかという雰囲気だけはあるんですが、進展はしているのかは不明。

でも、元 dnvm は動いていたよね?

しかし、Raspberry Pi2 に Windows IoT Core がインストールできるわけだし、最初から ARMv7 が開発ターゲットから外されているというのも変名は話なので、いつくか遡ってみると、

Raspberry Pi 2 にインストールした Windows 10 IoT Core 上で DNX と ASP.NET 5 アプリケーションを動かしてみた – しばやん雑記
http://blog.shibayan.jp/entry/20150629/1435546231
How to run .NET Core Application on Pi2 – Win10 IoT Core? – Damir Dobric Posts – developers.de
http://developers.de/blogs/damir_dobric/archive/2015/09/22/how-to-run-net-core-application-on-pi2-win10-iot-core.aspx

を読むと、dotnet コマンドの前身の dnvm コマンドでは Raspberry Pi で(Windows IoT Core限定カモ)動いていた模様です。
「模様」ですというのは、現在、ARMのランタイムを取ろうとすると、エラーが発生します。

dnvm install latest -r coreclr -arch x64 は通るけど、
dnvm install latest -r coreclr -arch ARM は通らなくなっているので、

サーバー自体から消えてしまった感じ。うーん、古いバイナリでよいので残してほしかったのですが、ここが OSS の辛いところ。

ややこしい、3つの Core な関係

でもって、やりたいことは、F# な Web APIアプリをラズパイ上で動かしたいだけなので、別に mono でもいいんだよな、と思い直して .NET Core なところをあれこれと弄っていこうとすると、

– .NET Core なランタイム
– ASP.NET Core
– ASP.NET Core MVC

な、ややこしい関係にぶち当たります。

素直に、.NET Core 環境だけで作るとか、素直に .NET Framework 4.6 な環境で作るとかすればよいのですが、じゃあ、dotnet コマンドは何の環境で動いているのかとか、ASP.NET Core MVC は内部で何がうごいているのかとかとか考えるとややこしいのです。

msbuild に代わるビルド環境としての dotnet コマンドは、dotnet restore してアセンブリを NuGet で取ってきて dotnet run でプロジェクトを実行できます。プロジェクトがある場所に、project.json があって、その中に設定が書いてあるわけですが、この dotnet コマンドが、.NET Core 上なのか .NET Framework 上なのか mono 上なのかが謎です。ただ、dotnet/cli を取って来て Raspi 上でビルドができない(Ubuntu上ではできた)ので、動作はともかくとして、ビルドするのに「動作する dotnet」が必要です。ブートストラップ的になっているの、卵と鶏の関係になっています。

mono 上で動く dotnet コマンドがあればいいのに、と思うんだけど、それがないところみると、たぶん .NET Core 上で動いているんですよね、おそらく。

ASP.NET Core はオープンソースな https://github.com/aspnet/ で開発が進められていますが、内部的に .NET Core 必須なのか?というとそうではなくて、

を見ると、

– ASP.NET Web Application(.NET Frameowork)
– ASP.NET Core Web Application(.NET Core)
– ASP.NET Core Web Application(.NET Framework)

の3種類のASP.NET MVCプロジェクトが作れます。

最初のASP.NETアプリは、従来のASP.NET MVC5とかを作るプロジェクトで .NET Frameworkを使うやつです。
次の、(.NET Core)のほうは、.NET Coreを使っているほうで、dotnet runとかで実行ができるやつです。IISがいらないのでLinuxに移すことができますよね。
じゃあ、最後の「ASP.NET Core Web Application(.NET Framework)」ってのは何でしょう?
.NET Framework上で動く、ASP.NET Core って何?ASP.NET Coreの「Core」って、.NET Coreの「Core」じゃなかったの?と思う訳ですが、どうやら、この2つの Core は違う意味とだということが分かります。どうやら、.NET Framworkを使って、IISのいらない exe ファイルを作って実行することができるですよね。

bin/Dbug/net452/win7-x64 フォルダーにある WebApplication1.exe を実行すると、普通に ASP.NET Core MVC アプリケーションが動きます。

このバイナリをそのまま Raspberry pi に持って行って、mono で動かすと libuv のアセンブリがなくて動きません。Raspberry pi 上に libuv はインストールしたのですが、Linux の動的ライブラリ *.so を探しているかどうかは不明です。

どうやら、ASP.NET MVC のほうは、IIS 上で動いて、
ASP.NET Core MVC のほうは

– Kestrel
– libuv

が肝だということが薄々わかってきました。Kestrel は Webサーバーで、https://github.com/aspnet/KestrelHttpServer からダウンロードが可能です。ただし、これをビルドするときに dotnet コマンドが必要なので、ラズパイ上では頓挫しています。「ASP.NET Core Web Application(.NET Framework)」のプロジェクトが、Kestrel を使っているので、.NET Core 自体は必要ないはずなんですよね(ASP.NET Coreしか必要ない)。
libuv のほうは、Node.js でも使われているクロスプラットフォームな TCP/IP ライブラリです。こっちのほうは、https://github.com/libuv/libuv からダウンロードしてラズパイ上でもビルドができます。

ということは、「dotnet コマンドを使わないでビルドができる Kestrel を .NET Framework 版でビルドすれば、mono 上で動く」ので、ラズパイ上で動くようになるのでは?と思いついたのですがどうでせふ?

オレオレASP.NET Core MVCを作成する

ひとまず、HttpListenerを使ったら mono でも動くので、Raspberry Pi 上に F# な Web APIサーバーが立てられるのではと思って、ちまちまと考えていたり。

type WebHostBuilder() as self =
    let mutable _sv = new System.Net.HttpListener()

    member x.UseStartup() = 
        self
    member x.UseUrls(url) = 
        _sv.Prefixes.Add( url )
        self
    member x.Build() = 
        ()
    member x.Run() =
        if _sv.Prefixes.Count = 0 then
            _sv.Prefixes.Add(&quot;http://localhost:5000/&quot;)
        _sv.Start()
        while true do
            let cont = _sv.GetContext()
            Console.WriteLine(&quot;url: {0}&quot;, cont.Request.Url)
            let sw = new System.IO.StreamWriter(cont.Response.OutputStream)
            sw.WriteLine(&quot;OK&quot;)
            sw.Close()
            cont.Response.Close()
        _sv.Stop()

[<EntryPoint>]
let main argv = 
    printfn &quot;start oreoreHttpServer&quot;

    let host = new WebHostBuilder()
    host.UseStartup()
        .UseUrls(&quot;http://+:5000/&quot;)
        .Build()
    host.Run()
    0

なんとなく、インターフェースだけ Kestrel に似せています。

カテゴリー: ASP.NET, F#, RaspberryPi | 俺のラズパイ3で.NET CoreなF#が動かないわけがない はコメントを受け付けていません

F#でASP.NET CoreのWeb APIを作ろう(SQLite編)

F#でASP.NET CoreのWeb APIを作ろう | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/7996

の続きで、データベースにSQLiteを使ってWeb APIを作成します。
.NET Core用のSQLite は NuGet で「”Microsoft.EntityFrameworkCore.Sqlite”」があるので、これを使えばokです。
C#の.NET CoreのWeb APIプロジェクトからコピーしたあとから、再スタート。

project.jsonの修正

– “includeFiles” にF#のファイルをひとつずつ追加(面倒だけど)

– “dependencies” に4つの参照をを追加
– “Microsoft.EntityFrameworkCore”
– “Microsoft.EntityFrameworkCore.Sqlite”
– “Microsoft.EntityFrameworkCore.Sqlite.Design”
– “Microsoft.EntityFrameworkCore.Tools”

C#のときは “Microsoft.EntityFrameworkCore” が無くても動くのだけど、F#のプロジェクトには入れます。でも、入れるのが普通な気がする。

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true,
    "debugType": "portable",
    "compilerName": "fsc",
    "compile": {
      "includeFiles": [
        "Models/Person.fs",
        "Data/ApplicationDbContext.fs",
        "Controllers/ValuesController.fs",
        "Controllers/PeopleController.fs",
        "Startup.fs",
        "Program.fs"
      ]
    }
  },
  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
    "dotnet-compile-fsc":"1.0.0-preview2-*"
  },

  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ],
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        },
        "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-160629"
      }
    }
  },
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
    "Microsoft.EntityFrameworkCore": "1.0.0",
    "Microsoft.EntityFrameworkCore.Sqlite": "1.0.0",
    "Microsoft.EntityFrameworkCore.Sqlite.Design": "1.0.0",
    "Microsoft.EntityFrameworkCore.Tools":  "1.0.0-preview2-final"
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "Views",
      "Areas/**/Views",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

データベース接続の設定

appsettings.json に “DefaultConnection” を追加。ローカルファイルにSQLiteのデータベースを置きます。

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Filename=./sample.db"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

StartupクラスのConfigureServicesメソッドでSQLiteに接続できるようにする。

type Startup(env:IHostingEnvironment) as this =
    do 
        let builder = new ConfigurationBuilder()
        builder.SetBasePath(env.ContentRootPath)
                .AddJsonFile(&quot;appsettings.json&quot;, true, true)
                .AddJsonFile(&quot;appsettings.&quot; + env.EnvironmentName + &quot;.json&quot;, true)
                .AddEnvironmentVariables() |> ignore
        this.Configuration <- builder.Build()

    [<DefaultValue>]
    val mutable _Configuration:IConfigurationRoot 
    member val Configuration:IConfigurationRoot = null with get, set
    member x.ConfigureServices( services:IServiceCollection ) =
        services.AddDbContext<ApplicationDbContext>( 
            fun options ->
                options.UseSqlite(this._Configuration.GetConnectionString(&quot;DefaultConnection&quot;)) |> ignore
                ) |> ignore
        services.AddMvc() |> ignore
        ()

    member x.Configure( app:IApplicationBuilder, env:IHostingEnvironment, loggerFactory:ILoggerFactory ) =
        loggerFactory.AddConsole(this._Configuration.GetSection(&quot;Logging&quot;)) |> ignore
        loggerFactory.AddDebug() |> ignore
        app.UseMvc() |> ignore
        ()

Modelの作成

シンプルに自動生成のプロパティを使えます。

Person.cs

type Person() =
    member val Id = 0 with get, set
    member val Name = "" with get, set
    member val Age = 0 with get, set

ApplicationDbContextの作成

namespace ApiFSharp.Data
open Microsoft.EntityFrameworkCore
open ApiFSharp.Models

type ApplicationDbContext( options:DbContextOptions<ApplicationDbContext> ) =
    inherit DbContext( options )
    [<DefaultValue>] 
    val mutable _Person:DbSet<Person> 
    member x.Person with get() = x._Person and set value = x._Person <- value
    override x.OnModelCreating( builder:ModelBuilder ) =
        base.OnModelCreating(builder)
        ()

実は、「member x.Person with …」のところで随分悩みました。
自己参照のつもりで、this を追加して this._Person で参照させると実行時に、循環参照のエラーがでます。

type ApplicationDbContext( options:DbContextOptions<ApplicationDbContext> ) as this=
    inherit DbContext( options )
    [<DefaultValue>] 
    val mutable _Person:DbSet<Person> 
    member x.Person with get() = this._Person and set value = this._Person <- value

http://localhost:5000/api/people で、Web APIにアクセスしようとすると、System.InvalidOperationException の例外が発生しています。

Hosting environment: Production
Content root path: D:workblogsrcSQLiteWebApiSQLiteWebApiSQLiteFSharp
Now listening on: http://*:5000
Application started. Press Ctrl+C to shut down.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:5000/api/people
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HKUCHBOFLSDB": An unhandled exception was thrown by the application.
System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed
 recursively before it was fully initialized.
   at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.FailInit()
   at ApiFSharp.Data.ApplicationDbContext.set_Person(DbSet`1 value)
   at Microsoft.EntityFrameworkCore.Internal.DbSetInitializer.InitializeSets(DbContext context)
   at Microsoft.EntityFrameworkCore.DbContext..ctor(DbContextOptions options)
   at ApiFSharp.Data.ApplicationDbContext..ctor(DbContextOptions`1 options)
...

これを this 使わずに

type ApplicationDbContext( options:DbContextOptions<ApplicationDbContext> ) =
    inherit DbContext( options )
    [<DefaultValue>] 
    val mutable _Person:DbSet<Person> 
    member x.Person with get() = x._Person and set value = x._Person <- value

とすると循環しないので、なんか理由があるんですかね?謎です。

Controllerの作成

まめに ignore を追加します。

[<Route(&quot;api/[controller]&quot;)>]
type PeopleController(context) =
    inherit Controller() 
    let _context:ApplicationDbContext = context

    [<HttpGet>]
    member x.Get () = 
        if isNull _context.Person then  
            new List<Person>()
        else
             _context.Person.ToList()
    [<HttpGet(&quot;{id}&quot;)>]
    member x.Get (id:int) = 
        _context.Person.SingleOrDefault( fun m -> m.Id = id )
    [<HttpPost>]
    member x.Post([<FromBody>] person:Person) =
        _context.Add( person ) |> ignore
        _context.SaveChanges() |> ignore
        person.Id
    [<HttpPut(&quot;{id}&quot;)>]
    member x.Put( id:int, [<FromBody>] person:Person) =
        if id <> person.Id then
            -1
        else 
            _context.Update( person ) |> ignore
            _context.SaveChanges() |> ignore
            person.Id
    [<HttpDelete(&quot;{id}&quot;)>]
    member x.Delete( id:int ) =
        let person = _context.Person.SingleOrDefault( fun m -> m.Id = id )
        _context.Person.Remove( person ) |> ignore
        _context.SaveChanges() |> ignore
        person.Id

ビルドして実行

実行するとこんな感じです。データベースのファイル「sample.db」は自動では作ってくれないので、何らかの形であらかじめファイルを作っておきます。

サンプルコード

サンプルコードはこちら。C#版も含めてあります。
https://1drv.ms/u/s!AmXmBbuizQkXgfwM4A7bz3cWyThD_Q

次は…

あれこれ追加が必要ですが、ASP.NET Coreな環境で、F#でWeb APIが作れることがわかりました。本当はASP.NET MVCにしたいけど、ViewページのRazorが対応していないので無理かな。このあたりは後で調べます。
開発環境は Visual Studio Codeを使う(というか、むしろ Visual Studio 2015上では開発できない)ので、LinuxはMac上でも開発&動作ができます。IISとかApacheとかに関係なく、マイクロサーバー的にWeb APIを提供できる環境ができるのはよいかもしれません。Azureに乗せたりすると、自前のなんかのプロキシとか作れるんじゃないですかね。いや、C#で書いたほうが楽かもしれませんが、もうちょっとなんか整理して、Controllerだけさくさくと実験&拡張できるようにすると、手軽なWeb APIが作れるかも。

カテゴリー: ASP.NET, F#, SQLite | F#でASP.NET CoreのWeb APIを作ろう(SQLite編) はコメントを受け付けていません

ASP.NET Core MVC で SQLite を使う

.NET Core上でF#とSQLiteを結び付ける前哨戦として、ASP.NET Core MVC で SQLite を使ってみます。
方法としては、Visual Studio で「ASP.NET Core Web Applicaiton(.NET Core)」は、SQL Serverを使う設定になっているので、これをSQLiteに切り替えるだけです。

NuGetでSQLiteをインストール

– Microsoft.EntityFrameworkCore.SQLite
– Microsoft.EntityFrameworkCore.SQLite.Desgin

の2つをインストールします。Desginのほうは、データベースファーストのほうで使うけど、一応インストール。

SQLiteの設定に切り替え

StartupクラスのConfigureServicesメソッドで、UseSqlServerの箇所をUseSqliteに切り替え。

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
    // Add framework services.
    services.AddMvc();
}

appsettings.jsonを開いて、DefaultConnectionの値をSQLiteが使うファイル名にする。

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Filename=./sample.db"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

Modelクラスを作って、スキャフォールディング

あとは、SQL Serverのときと同じで、ModelクラスになるPersonクラスを使って、スキャフォールディングを実行。

namespace SampleSQLiteMvc.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

マイグレーションとデータベースのアップデートをすれば完了です。

dotnet ef migrations add init
dotnet ef database update

実行

実行するとこんな感じに、SQL Serverのときと同じように動きます。

肝は、NuGet で Microsoft.EntityFrameworkCore.SQLite を入れて、Startupクラスで接続先を変えるだけなので、それほど難しくありません。

サンプルコード

サンプルコードはこちら
https://1drv.ms/u/s!AmXmBbuizQkXgfwL7jS1NrKxv8KbQg

カテゴリー: ASP.NET, SQLite | ASP.NET Core MVC で SQLite を使う はコメントを受け付けていません

F#でASP.NET CoreのWeb APIを作ろう

なぜ、F#で作るのかはさておき、C#のWeb APIアプリをF#にコンバートしてみます。本当のところは、ASP.NET Core MVCのほうをF#に対応させてみたかったのですが、Razor構文がF#に(たぶん)対応していないので無理ってことで、ViewページのないWeb APIで試しています。まあ、F#の関数型なところがステートレスなHTTPプロトコルに合っているんじゃないか、常々思ってはいるのですが。

いわゆる、ASP.NET Core Web Application(.NET Core) をF#に直します。

単純なWebアプリのほうは、いくつかサンプルが見つかるのですが、Web APIのコンバートは見つからなかったので参考になると思います。先行きは、SQLiteを使って、データベースのアクセスまで作りたいところ。

Visual Studio Codeの準備

実は、F#の.NET Coreは、Visual Studio 2015は対応していません。コードのコンバートなのでちまちまとテキストエディタを使ってもよいのですが、さすがにインテリセンスがないとF#のコードは組みづらい。

Visual Studio Code
https://www.visualstudio.com/ja-jp/products/code-vs.aspx

を使うと、.NET CoreのF#でもインテリセンスが使えるようになります。ちなみに、LinuxとMacでもF#の.NET Core環境が使えます。

Visual Studio Codeをダウンロード&インストールして「表示」→「Extentions(拡張)」から、「Ionide-fsharp」をインストールします。
Windows版の場合は、Ionide-fsharpだけでよいのですが、LinuxとMacの場合は別途 mono もインストールします。拡張機能が mono を使っているからだと思うのだけど、そのうち .NET Coreに統一されるかもしれません。

インストール後に「有効」にして、VSCode を再起動すると、F#のコードでインテリセンスが使えるようになります。

C#の.NET CoreのWeb APIプロジェクトからコピー

Visual Studio 2015で.NET CoreのWeb APIプロジェクトを作って、C#のコード(Startup.cs、Program.cs、ValuesController.cs)以外のファイルをごっそりコピーします。

project.jsonの修正

コンソール版のF#コードを「dotnet new –lang F#」で作成して、2つのproject.jsonをうまくマージします。
F#の場合は、ビルドするときに前方参照が必要なので、”buildOptions” – “compile” – “includeFiles” にビルドする順番ファイル名を書いておきます。これが非常に面倒なんですが…先行きはどうなんでしょう?

{
  "version": "1.0.0-*",
  "buildOptions": {
    "emitEntryPoint": true,
    "preserveCompilationContext": true,
    "debugType": "portable",
    "compilerName": "fsc",
    "compile": {
      "includeFiles": [
        "Controllers/ValuesController.fs",
        "Startup.fs",
        "Program.fs"
      ]
    }
  },
  "tools": {
    "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
    "dotnet-compile-fsc":"1.0.0-preview2-*"
  },
  "frameworks": {
    "netcoreapp1.0": {
      "imports": [
        "dotnet5.6",
        "portable-net45+win8"
      ],
      "dependencies": {
        "Microsoft.NETCore.App": {
          "type": "platform",
          "version": "1.0.0"
        },
        "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-160629"
      }
    }
  },
  "dependencies": {
    "Microsoft.NETCore.App": {
      "version": "1.0.0",
      "type": "platform"
    },
    "Microsoft.AspNetCore.Mvc": "1.0.0",
    "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
    "Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
    "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
    "Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
    "Microsoft.Extensions.Configuration.Json": "1.0.0",
    "Microsoft.Extensions.Logging": "1.0.0",
    "Microsoft.Extensions.Logging.Console": "1.0.0",
    "Microsoft.Extensions.Logging.Debug": "1.0.0",
    "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0"
  },

  "runtimeOptions": {
    "configProperties": {
      "System.GC.Server": true
    }
  },

  "publishOptions": {
    "include": [
      "wwwroot",
      "Views",
      "Areas/**/Views",
      "appsettings.json",
      "web.config"
    ]
  },

  "scripts": {
    "postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
  }
}

コードをF#に作り変える

3つのC#のファイルをF#にコンバートします。
大体、1対1でコンバートができるのですが、F#のほうが型チェックが厳しいので、戻り値が void になる関数はまめに ignore していきます。戻り値が推論されて、実行時にメソッドが見つからなくてエラーになるのがハマりどころです。

Startup.cs

namespace ApiFSharp

open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging

type Startup(env:IHostingEnvironment) =
    let mutable _Configuration:IConfigurationRoot = null
    do 
        let builder = new ConfigurationBuilder()
        builder.SetBasePath(env.ContentRootPath)
                .AddJsonFile(&quot;appsettings.json&quot;, true, true)
                .AddJsonFile(&quot;appsettings.&quot; + env.EnvironmentName + &quot;.json&quot;, true)
                .AddEnvironmentVariables() |> ignore
        _Configuration <- builder.Build()
    member x.Cnfiguration 
        with get() =  _Configuration 
        and set( value ) = _Configuration <- value
    member x.ConfigureServices( services:IServiceCollection ) =
        services.AddMvc() |> ignore
        ()
    member x.Configure( app:IApplicationBuilder, env:IHostingEnvironment, loggerFactory:ILoggerFactory ) =
        loggerFactory.AddConsole( _Configuration.GetSection(&quot;Logging&quot;)) |> ignore
        loggerFactory.AddDebug() |> ignore
        app.UseMvc() |> ignore
        ()

Web APIをどのPCからも受け付けられるように UseUrlsメソッドで「”http://*:5000″」のように追加しておきます。こうすると、Linux仮想マシンでWeb APIを動かして、PCのブラウザでチェックする、ということができます。

Program.fs

open System
open System.Collections.Generic
open System.IO
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Builder
open ApiFSharp

[<EntryPoint>]
let Main argv = 
    let host = new WebHostBuilder()
    let ihost = 
        host.UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .UseUrls(&quot;http://*:5000&quot;)
            .Build()
    ihost.Run()
    0

Startup.cs と Program.fs は定番の処理なので、Web APIのプロジェクトテンプレートを作ってしまえばよいと思います。
実際に手を入れていくのは、ValuesControllerクラスです。

ValuesController.cs

namespace ApiFSharp.Controllers 

open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc

[<Route(&quot;api/[controller]&quot;)>]
type ValuesController() =
    inherit Controller() 

    [<HttpGet>]
    member x.Get () = 
        [&quot;value1&quot;; &quot;value2&quot; ]
    [<HttpGet(&quot;{id}&quot;)>]
    member x.Get (id:int) = 
        &quot;value&quot;
    [<HttpPost>]
    member x.Post([<FromBody>] value:string) =
        ()
    [<HttpPut(&quot;{id}&quot;)>]
    member x.Put( id:int, [<FromBody>] value:string) =
        ()
    [<HttpDelete(&quot;{id}&quot;)>]
    member x.Delete( id:int ) =
        ()

データベースにアクセスしていないので、固定文字列しか返していないけど、SQLite を使ってデータアクセスを作れば結構使えるようになるかなと。

ビルドして実行する

リストアして、ビルドして、実行します。

dotnet restore
dotnet build
dotnet run

VSCodeで、Shift+Ctrl+B を押すとビルドができます。

実行する

PowerShell で dotnet run してブラウザで http://localhost:5000/api/values にアクセスします。

ちなみに Ubuntu 上の VSCode を使って F# プログラミングはこんな感じ。

固定の文字列を返すだけとか、計算するだけだとふつうに HttpListener を使って返すだけでも十分だろうという気もしますが、データベースアクセス付け加えたり、認証とかクッキーとかを使ってアクセス制限を考えたり、Controllerクラスのテストなどを考えると、ASP.NET Core を利用した Web API を F# で作るのもアリかな…という無理矢理な発想で :)

サンプルコード

サンプルコードはこちら
https://1drv.ms/u/s!AmXmBbuizQkXgfwK1qbhxJOHVP0bxQ

カテゴリー: ASP.NET, F# | F#でASP.NET CoreのWeb APIを作ろう はコメントを受け付けていません