くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

2018年01月

ASP.Net Core Publish メモ

.net core のMVCなアプリを発行する時のメモ。ちゃんと書き残す前にコレすぐ忘れる故。

普通に発行すると、.net core のランタイム?というかSDK?とかのDLLまわりのDLLは資源として含まれないけど、含みたい場合の設定。

.csprojに↓を追記する。

  <PropertyGroup>
    <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
  </PropertyGroup>

これで発行先のフォルダに必要なDLLが本当に全部含まれるようになる。このフォルダ内のみで完結できるのかしら。。。

.net core mvc で Redis使用でSession共有

比較的大きめ(10万行くらい?)のWebアプリを作るのに、アプリケーションを分けて構成したくなったりする。そういう時に問題になるのが、アプリ間でのセッション共有。いちをSQLServerで出来るんだけど、やっぱDBをストアにすると多少遅くなる。

.net core でのセッションの実装はなんだかんだでココが一番わかりやすい。

.net core では 昔の「Session」みたいなのはいきなりは使えないのね。キャッシュを使う模様。

で、.net core でもサイト間でセッション共有するやり方は提供されてて、SQLServerとRedisが使えるみたい。今回はRedisを使ったんだけど、すごくはまった。

Redisを使ったセッションの共有はここに書いてある。

まず、Redisのインストール。上記に書いてある通りでいけるんだけど、プロキシ使ってる環境だと上手くいかない。その場合はココが一番参考になる。

Redisがインストール出来たら、コマンドプロンプトで「redis-server」って打って実行しておく。

そしたらNugetで「Microsoft.AspNetCore.Session」と「Microsoft.AspNetCore.DataProtection.Redis」をプロジェクトにインストールする。

そしたら.net core mvc の StartUpのコンフィグを下の感じにする。

using Microsoft.AspNetCore.DataProtection;
using StackExchange.Redis;

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    //キャッシュにRedisを使う場合
    services.AddDistributedRedisCache(options =>
    {
        options.Configuration = "localhost";
        options.InstanceName = "SampleInstance";
    });
    
    //プロテクションの設定をする(セッション共有する場合はここが必須。
    var redis = ConnectionMultiplexer.Connect("localhost:6379");
    services.AddDataProtection()
        .SetApplicationName("HogeApp")//これが必要!!!!!
        .PersistKeysToRedis(redis, "DataProtection-Keys");
    
    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromSeconds(600);
        options.Cookie.HttpOnly = true;
        options.Cookie.Name = "NetCoreWebAppSessionKey";//これは適当に(なくてもいい)
    });
}

SetApplicationNameでそれぞれのサイトで名称一緒にしないと共有できなかった。

あー、これは同一ホスト上で複数のWebアプリを動かして、アプリ間でセッション共有したい場合の話。ホストが別の場合はまた色々考えないといけない模様。つかホスト跨いでセッション共有とかすることあるのかな?

ロードバランサで分けると思うんだとも、その場合ってIPかセッションクッキーでのバランシングするよね?そうするとユーザは1サーバにしか基本いかないからホスト跨ぐことないような気がするけど、どうなんでしょね。リクエスト毎にサーバを回されることってあるんかな?まぁアプリの作り次第か。。。

あと、Redisにどんなクエリが来てるのか見たい場合は、コマンドプロンプトで「redis-cli」を実行して、「moniter」って入れるとredisに来たリクエストが見れる。

ASP.NET Core で作ったMVCをIISで動かす

これからは「ASP.NET Core」だ!!みたいな話を見た。そういうノリは冷ややかに見る人間なのですが、いずれにしろ、そろそろ触ってみようかと思いまして、とりあえずIISで動かす。調べてみると色々黎明期っぽく、ちょっと混乱気味?ひとまず動かした流れ。

Visual Studioで作った空のテンプレアプリをIISで動かすまで。

まず、必要なもののインストール。

IISとかの最初のセットアップはこの辺。ちなみに、Visual Studioで.net core Web アプリケーションを新規作成すると、ほんとにしょっぱなは.net core sdkが勝手にインストールされるっぽい気がする。

そしたら環境の確認。まず「sdk」。コマンドで「dotnet --version」とかやって返事があればOK。 sample02

次は「ASP.NET Core モジュール」。IIS開いて↓を確認。 sample03
sample04

そしたら、Visual Studioでアプリを作って、テンプレのまま何もせずビルド。

sample01

コマンドプロンプトで「.csproj」があるフォルダに移って、「dotnet run」を実行。実行するとlocalhostのURLが出るので、ブラウザでアクセスすると↓の感じのサイトがでる。

sample05
sample06

んで、これは.net coreに標準でついてる「Kestrel」っていうWebサーバで動いてるらし。

IISで動かすためには、「publish」ってコマンドでそれようのフォルダ構成やらDLLやらをまとめる必要がある様子。日本語で「発行」ってことらしい。コマンドでやるのは結構見るのでVisual Studioでやる場合は↓。発行先は色々あるっぽいけど、フォルダでやる。

sample07
sample08

そしたらIISでサイト作る。サイトの物理パスは上記の発行で指定したフォルダ。

sample09

ポートとかはテキトーに。で、アプリケーションプールを下の「マネージコードなし」する。

sample10

ここまでで、作ったサイトにアクセスすると「Kestrel」で動かしたときのページと同じものがでる。

おしまい。あと、認証とか色々ためしてみよ。

RでLDA

タイトルまま。形態素解析した結果のCSV読み込んでやる。色々あると思いますがとりあえず↓の感じ。

#ライブラリ
library(text2vec)
library(LDAvis)

#ワーキングディレクトリ
setwd("C:\\tmp\\hoge")
wdata <- read.csv("data1.csv",header=F,stringsAsFactors=F)
wdata.ids <- as.list(wdata[,1])
wdata.word <- read.csv("data2.csv",header=F,stringsAsFactors=F)

it <- itoken(wdata.word$V1, ids = as.character(wdata.ids))
v = create_vocabulary(it)

#これ!!!
v$term <- iconv(v$term,from="cp932",to="UTF-8")
dtm = create_dtm(it, vocab_vectorizer(v))
lda_model = LDA$new(n_topics = 9)
ldaRes = lda_model$fit_transform(dtm, n_iter = 500)
lda_model$plot()

text2vecを使ったんですが、日本語でやる場合、create_vocabularyのところで、中のtermがunicodeのエスケープ文字に変換されてしまい、そのあとの関数が軒並みダメになる。なので、「iconv(v$term,from="cp932",to="UTF-8")」で文字を戻す必要あり。

最初、utf-8に変換されてるのかと思い、「iconv(v$term,from="UTF-8",to="cp932")」←こう書いてたんだけど上手くいかず。読み込みデータの文字コード色々変えたり試行錯誤した結果、逆だったという事です。はまった。

あと、CSV読み込むときは、「stringsAsFactors=F」←これ付けないと、factor扱いになって、後続の関数が動かない。要留意。

C#からRを使う

この前、Rを外部から実行するっていうの書いたけど、C#から呼ぶためのライブラリが普通にあったので、書き直し。探し方が悪かったかしらね。。。

R.Netっていうのを使う。NuGetからインスコ。「R.NET.Community」っていうのもあるんだけど、何が違うんだろうか?リンク先とかも一緒なんだけど。。。あー、もちろん使う場合は端末にR本体がインストールされてる必要あり。

そしたらとりあえず、呼び出しサンプル。

using RDotNet;
static void Main(string[] args)
{
    REngine.SetEnvironmentVariables();//これは省略可。
    using (REngine engine = REngine.GetInstance())
    {
        engine.Evaluate("sample.data <- iris");
        engine.Evaluate("str(sample.data)");
    }
    Console.WriteLine("おしまい");
    Console.ReadLine();
}

↑は呼び方的には公式のチュートリアルのまま。

まず、32bitか64bitでビルド変えないとだめかも。64bitでやる場合はプロジェクトのプロパティ⇒ビルドで「32ビットを優先」のチェックを外さないとだめそうですね。あと、使うR.dllのフォルダも設定が必要。さらに、Rのパスを通してない場合は、実行時に環境変数のPATHにRのパスを追加しておいた方が良さげ。で、色々したのが↓。パスを編集するメソッドはさすがのすたっくおーばーふろーがサンプル。

static void Main(string[] args)
{
    string rpath = SetupPath("R-3.4.2");//インストールしたバージョンを指定
    REngine.SetEnvironmentVariables(rpath);
    using (REngine engine = REngine.GetInstance())
    {
        engine.Initialize();//最近はこれがいるらしい
        engine.AutoPrint = false;//これ付けないと代入してる式で中身が丸ごと出てきてうざい
        engine.Evaluate("sample.data <- iris");
        engine.Evaluate("str(sample.data)");
    }
    Console.WriteLine("おしまい");
    Console.ReadLine();
}
public static string SetupPath(string Rversion)
{
    string rDllPath = Environment.Is64BitProcess ?
                    string.Format(@"C:\Program Files\R\{0}\bin\x64", Rversion) :
                    string.Format(@"C:\Program Files\R\{0}\bin\i386", Rversion);
    //DLLのフォルダが無かったら例外
    if (!Directory.Exists(rDllPath))
    {
        throw new DirectoryNotFoundException(string.Format("Not found : {0}", rDllPath));
    }   
    var newPath = string.Format("{0}{1}{2}", rDllPath, System.IO.Path.PathSeparator, Environment.GetEnvironmentVariable("PATH"));
    Environment.SetEnvironmentVariable("PATH", newPath);
    return rDllPath;
}

でだ、ここまでやっても何故だか実行時に「Rlapack.dllがありません」みたいなエラーが出る端末が稀にいる。意味が分からん。どう見てもあるんだけどね。。。プローブ?がおかしいのだろうか。そういう時はとりあえず、実行ファイルと同じフォルダに「Rlapack.dll」をRのインストール先からコピーしてあげると動くようになる。なんだろね。

ほいで、あとは色々な呼び出し。

static void Main(string[] args)
{
    string rpath = SetupPath("R-3.4.2");
    REngine.SetEnvironmentVariables(rpath);

    using (REngine engine = REngine.GetInstance())
    {
        engine.Initialize();
        engine.AutoPrint = false;

        ////////////////////////////////////////
        //arulesでアソシエーション分析
        ////////////////////////////////////////

        //ライブラリの読み込み
        engine.Evaluate("library(arules)");
        //アソシエーション分析
        engine.Evaluate("data(\"Adult\")");
        engine.Evaluate("rules <- apriori(Adult,parameter=list(minlen=2, maxlen=4, support=0.5, confidence=0.9))");
        //結果のサマリー
        engine.Evaluate("summary(rules)");
        //結果の表示
        engine.Evaluate("inspect(rules)");
        //RのデータフレームをC#で扱えるデータにする
        var dd = engine.Evaluate("Adult@itemInfo").AsDataFrame();
        //つまみ食い
        Console.WriteLine(dd[1, 1]);


        ////////////////////////////////////////
        //kmeansの結果を取得
        ////////////////////////////////////////
        //irisデータ使う(いつものやつ)
        engine.Evaluate("iriswk <- iris[1:150, 1:4]");
        engine.Evaluate("iriswk.km <- kmeans(iriswk, 2)");
        engine.Evaluate("iriswk.km");
        engine.Evaluate("iriswk.km$cluster");
        var kmlist = engine.Evaluate("iriswk.km$cluster").AsNumeric();

        ////////////////////////////////////////
        //Plotの画像をファイルにはく
        ////////////////////////////////////////
        var path = Path.Combine(Path.GetTempPath(), Path.GetTempFileName() + ".png");
        string Code = @"plot(1:10, pch=1:10, col=1:10, cex=seq(1, 2, length=10))";
        engine.Evaluate(string.Format("png('{0}', {1}, {2})", path.Replace('\\', '/'), 420, 420));
        engine.Evaluate(Code);
        engine.Evaluate("dev.off()");
        
    }
    Console.WriteLine("おしまい");
    Console.ReadLine();

}
問合せ