くりーむわーかー

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

.net

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に来たリクエストが見れる。

.netMVCのGDI+エラーをどうにか回避したい

この前、.NetMVCのWebアプリでSystem.Drawing使って画像作るとGDI+エラーが出るっていうの書いた。

System.Drawing使えないとなると結構しんどいので、他のライブラリを使ってみる。画像っていったらやっぱりImageMagickかしらね。ちょっと調べてみると結構色々あるのね。↓のやつら。

ImageSharpなんか良さげなので使ってみよーかと思ったら、.net4.5は対応外だそうです。残念。対応したら使ってみよ。

で、結局ImageMagick(Magick.NET)に落ちついたわけですが。Nugetでインストールして↓の感じで作ってみた。

//全部をImageMagickでやるのはしんどかったので、
//エラーが出るところだけ書き換え
Color c = Color.FromArgb(0xFF, Color.FromArgb(Convert.ToInt32("0xD9333F", 16)));
Font f = new Font("MS 明朝", 12);
using (Bitmap img = new Bitmap(100, 64))
{
    using (Graphics g = Graphics.FromImage(img))
    using (Pen pen = new Pen(c))
    {
        g.DrawLine(pen, 0, 0, 100, 0);
        g.DrawString("Something Text...", f, new SolidBrush(c), 5, 5);
    }
    string folder = "適当なフォルダ";
    string filename = "適当なファイル名";
    fullFileName = System.IO.Path.Combine(folder, filename);
    
    //GDI+エラーが出るのは保存のところ ↓の感じ
    //img.Save(fullFileName, ImageFormat.Png);
    
    //↑の保存をImageMagickを使って保存に変える
    using (ImageMagick.MagickImage image = new ImageMagick.MagickImage(img))
    {
        image.Format = ImageMagick.MagickFormat.Png;
        image.Write(fullFileName);
    }
}

今のところエラーは出てないけど、様子見なり。

で、保存の部分だけ使ったのですが、ホントはImageMagickで日本語のテキスト出せなかったので保存だけに逃げました。

コマンドラインから使う場合は出す方法が色々見つかるんだけど、.netから呼ぶ場合のがどうしてもわからなかった。何となく、フォントの指定がうまくできない。気が向いたら調べてみよう。

端末変えたら500.21のエラー

最近、PC変えたんですけど、ローカルのIISで.netのWebアプリ動かしたときに500.21エラーが出た。

sample

ManagedPipelineHandlerってなんぞ?

調べてみると、.netが完全にはインストールされてないらしい。SDK入れたような気がするんですが・・・。

↓のコマンドでインスコ。管理者で実行したコマンドプロンプトじゃないときっとダメ。

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis -i

↑で治った。

C#でLDAP認証

タイトル通り。MSDNにサンプル載ってるから、まーそのままなんですが。。。

//参照にも追加しないとダメ
using System.DirectoryServices;

public bool authLdap(string domain, string username, string pwd)
{
	string lserver = @"LDAP://server.domain.hoge.co.jp";//port:389
	string domainAndUsername = domain + @"\" + username;
	try
	{
	    DirectoryEntry entry = new DirectoryEntry(lserver, domainAndUsername, pwd);
	    //認証しくじるとここで例外が出る。
	    object obj = entry.NativeObject;
	    
	    DirectorySearcher search = new DirectorySearcher(entry);
	    search.Filter = "(SAMAccountName=" + username + ")";
	    SearchResult result = search.FindOne();
	    if (null == result)
	    {
	        return false;
	    }
	}
	catch (Exception ex)
	{
	    Console.WriteLine(ex.Message);
	    return false;
	}
	return true;
}

属性取りたいときはSearchResultのとこで↓の感じ。

DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("sAMAccountName");//ID
search.PropertiesToLoad.Add("sn");//氏名
search.PropertiesToLoad.Add("displayName");//表示名
search.PropertiesToLoad.Add("givenName");//名前
search.PropertiesToLoad.Add("initials");//イニシャル
search.PropertiesToLoad.Add("description");//説明
search.PropertiesToLoad.Add("mail");//メール
search.PropertiesToLoad.Add("company");//会社名
search.PropertiesToLoad.Add("department");//所属
search.PropertiesToLoad.Add("memberOf");//所属の色々

SearchResult result = search.FindOne();
if (null == result)
{
    return false;
}
//例えばメールアドレスが欲しいとき
if(prm.Properties["mail"].Count > 0)
{
    result = prm.Properties["mail"][0].ToString();
}

で、なんか自分、すごい勘違いしてると思うんだけど、他の言語だと接続用(読み取り用)のアカウントと認証したいユーザって別で書いてると思うのです。なぜだか.netだと、認証したいアカウントだけでやっちゃう。多分他の言語でも接続用と認証したいアカウント同じにすれば同じことなんだろうけど、なんか釈然としない。

.netはこういうもんなのかしら。。。出来れば接続用は分けたいのですが。。。

.net mvc レスポンスヘッダつける

別ドメインのURLにAjaxでリクエスト送りたかったんだけど、↓のアクセスエラーが返ってくる。

XMLHttpRequest cannot load http://***. Origin http://*** is not allowed by Access-Control-Allow-Origin.

やる前からだいたい予想してたけど、やっぱ別ドメインにAjaxで飛ばすのは大変だ。

Jsonpにすればいけるかと思ったけどなんかダメ。なので結果を返す側でレスポンスヘッダつけた。返す側は.netMVCで作ってるサイト。コントローラーで以下をつけてあげる。

Response.Headers.Add("Access-Control-Allow-Origin", "*");//呼び出せるようにクロスドメインをOKにしておく。

まーだめな対応の仕方だとは思う。自分が作ってるサイト間でのやり取りだからこその話。それでもだめだと思うけど。。。

.net MVC プロジェクト分割

以前、複数のプロジェクトで一つのサイトを構成するエントリを書いた。それの続き。

色々試してみてるけど、とりあえず今のとこ理解した事のメモ。

  • 単純にプロジェクト(ソリューション)分割しても、dllは分かれるけど、アプリケーションプールが分けられない。
  • 上記の場合はSessionの共有ができる。(以前書いたエントリのやり方だと)

個人的にアプリケーションプールも分けたい。ちょろちょろ調べると、アプリケーションプール分けると普通のやり方ではSessionの共有が出来ないっぽい。

基盤的なものを作らないとだめそーですね。ということで、とりあえず、アプリケーションプールも分けられる構成方法は以下。

とりあえず、ベースとなるMVCアプリを作る。(認証系は全部ここに入れるか、別アプリで認証サーバ的な何かを作っておかないとダメ)

で、サブ的なMVCアプリを作る。サブのほうの参照にベースのDLLを入れてコンパイル。

そしたら、Global.asax.csを削除する。して、Global.asaxの編集。以下の感じ。

<%@ Application Codebehind="Global.asax.cs" Inherits="**ここをベースと同じにする**" Language="C#" %>

ここまででベース側の起動処理とかが組み込まれる。逆にサブ側の起動処理的なものはガン無視されるので、不要なファイルは全部消す。

これで全てのサブアプリで共通の基盤的な動きになる。

で、大きな問題が一つ。ルートの定義が出来ない。出来ないというか、Area的なやり方ではないので、.netに組み込まれてる「Html.ActionLink」でサブアプリ側へ行く記述が出来ない。ここはヘルパー自作するしかないかしらね。。。

あと、bundle系。こーゆーのやるなら、もちろんCSSとかスクリプトとかも一か所で管理したいじゃない。。。

困ったことにbundleって”~/hoge/fuga”的な書き方じゃないと許してくれなかった。レイアウトに直書きしかないのかしら。。。ヘルパー作るか?

あと、この構成だと、セッションの共有が出来ないからもちろん認証の共有もデフォじゃ無理。なので、何か仕組みを作っておかないと無理かな。セッションの共有はSQLServerで行けそうだけど微妙かな。。。

さらにためし中。

C# .net MVCでCSSのキャッシュを変えさせるクエリ文字列を自動でつける

タイトル通り。CSSとか更新した際に、普通はクライアントでF5しないと読み込み直さない。それを無理矢理やらせるのに、「site.css」みたいなのに「site.css?20160508113250」みたいなクエリ文字列をつけると別ファイルと認識して読み込み直させるというやつがある。↓の感じ。

<link href="/Content/site.css" rel="stylesheet"/>
↑を↓にする
<link href="/Content/site.css?20160508113250" rel="stylesheet"/>

.netMVCだと、CSSとかJavaScriptとかはcshtml側で、「@Styles.Render("~/Content/css")」みたいに書くけど、これの結果を↑の日付のクエリ文字列をつけた形に自動で変えたい。日付はファイルの更新日で。

で、これをやるのに、何がいいかなーで探したら一番よさそうなのは下の感じ。

修正するのは「BundleConfig」。こいつを下の感じに変える。

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        //とりあえず追加したいBundle作る
        var addBundle = new StyleBundle("~/Content/css").Include("~/Content/bootstrap.css", "~/Content/site.css");
        //ここで↓の変換クラスをセットする
        addBundle.Transforms.Add(new FileHashVersionBundleTransform());
        //Bundleの追加
        bundles.Add(addBundle);
    }
}

//IBundleTransformでクエリ文字列を付加するように定義
//キャッシュされてるCSSをクエリストリングでファイルの更新日を付けて認識を変える
public class FileHashVersionBundleTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        foreach (var file in response.Files)
        {
            string version = System.IO.File.GetLastWriteTime(System.Web.Hosting.HostingEnvironment.MapPath(file.IncludedVirtualPath)).ToString("yyyyMMddHHmmssfff");
            file.IncludedVirtualPath = string.Concat(file.IncludedVirtualPath, "?v=", version);
        }
    }
}

結果は↓の感じ。

<link href="/Content/site.css?v=20160508113250" rel="stylesheet"/>

参考にしたのはココ。っていうかまんまですが。

上のサイト見てて思ったけど、このクエリ文字列って「キャッシュバスティング(cache-busting)」って言うのかしら。

2017/8/6追記:Ver2

.Net COMエラー:80040154

久しぶりにCOMエラーに遭遇した。.Net MVCのアプリで。↓の感じのエラー。

含むコンポーネントの COM クラス ファクトリを取得中に、
次のエラーが発生しました: 80040154

基本的にコレが出るときは使おうとしてるdllとかocxがregsvr32で登録されてない場合。なので下の感じでdllを登録する。

regsvr32 C:\Windows\System32\hoge.dll

64bitの場合は下にあるかもね。

regsvr32 C:\Windows\SysWOW64\hoge.dll

いつもはこれで上手くいくんだけど、今回はダメだった。調べてみると、AnyCPUになってるとダメになることがある模様。使う先のDLLに合わせないとダメらしい。

で、やってみたけどまだダメだった。

最終的にIISのアプリケーションプールの「32bitのDLLを有効にする」をTrueにしないとダメみたい。

iissample

.net MVCで環境変えたら403

.net MVCのサーバをちょっと変えたら403で動かなくなった。調べてみると、web.configに以下を入れるといいらしい。

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true">
  <handlers>
    <remove name="UrlRoutingHandler"/>
  </handlers>
</system.webServer>

これでとりあえず動いた。参考にしたのはココ

問合せ