くりーむわーかー

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

MVC

MVCでトランザクションをどこに持たせるか

今更MVCの話。

結局MVCって役割の分離って事じゃないですか。

WebアプリでMVC(厳密に言うと原書のMVCではないけども)っぽく作る場合。

色んなサンプルを見てるとMVCのCにトランザクションのBeginとEndがあったりする。

システムのアーキテクチャ上、コントローラでトランザクション制御するっていうモノももちろんあると思うけど、

それしかないのもどうかなと思うのと、微妙な気がしてるので少し書いておく。

まず、Contorlerとは何をするべきなのかって事。

Webアプリだと、ココはやっぱり何のビジネスロジックを呼び出すかっていう部分と、

認証周りの処理、セッション周りの処理、あとはリクエストの内容を解析して、

ビジネスロジックに渡すイベントオブジェクト的なものを作る処理辺りだと思うのです。

で、良くあるトランザクションの処理で言われてるところが、

AっていうDBを更新する処理があって、その後にBっていう他のデータを更新する処理があって、

その2つの処理を関連付けて一つのトランザクションとして扱いたい

みたいなものがある。

その制御をするためにはモデルを呼ぶコントローラでやらなきゃ!

って感じなのかな~と。

そこに違和感を感じるわけです。

処理を関連付けるとか処理する順番に依存してるとかとか。

それに意味があるなら、それはビジネスロジックだよね?

という事でトランザクションの制御はコントローラではやらない方がいいと思うのです。

Contorlerに入れた時点で、HTTPのリクエスト渡さないと他から使えないじゃない?

モデル側でファサード的なクラスを用意しておけばいいように思うわけです。

MVCってModelViewContorlerに分ける「方向性」で組むと思うんですが、

分割の仕方は3つである必要はないと思うんですよ。

大事なのは役割の分割であって、3つという数に意味はないように思うのです。

なので、Controlerに持たせるのは何か微妙に思う。

あと、結構致命的だと思ってるのは、テスト書き辛くない?

Webアプリのコントローラって認証とセッションにほぼ結合してると思うので、

その辺の調整をテストコード側でやらないと呼ぶの無理じゃん?

テスト書きヅラ!!みたいな。

という事で、MVCを元に構成したWebアプリでトランザクション処理する時は

コントローラでやるべきではないと思うのでした。。。

.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 MVCで環境変えたら403

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

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

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

.NetMVCでWeb.Configの接続文字列をとる

EntityFrameworkを.net MVCで使う場合、Web.Configに↓の感じで接続文字列を設定しておく。

  <connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=***********;Initial Catalog=HOGEDB;Persist Security Info=True; User ID=sa;Password=xxxxxx" providerName="System.Data.SqlClient" />
  </connectionStrings>

で、EFは使い勝手はそこそこいいんだけど、個別にDBにつないで自分でやりたい場合もある。その場合に、この接続文字列を取得したい。やり方は色々あると思うけど、Web。Configの中身を直で持ってくるやり方。

string wkStr = "";
for (int i = 0; i < System.Configuration.ConfigurationManager.ConnectionStrings.Count; i++)
{
    if (System.Configuration.ConfigurationManager.ConnectionStrings[i].Name == "DefaultConnection")
    {
        wkStr = System.Configuration.ConfigurationManager.ConnectionStrings[i].ConnectionString;
        break;
    }
}

設定が複数ある場合もあるので、接続名指定して取得する。色々試してみて、これしかやりようなかった。他に何かいい方法ないかしら。。。

「System.Configuration.ConfigurationManager.ConnectionStringst」ってforeach出来ないのね。Whereとかも使えない。IEnumにどーにかできないものだろうか。

.net MVCでSignalR

.net MVCでSignalRを使う。ちょっとはまったりもしたのでメモメモ。

とりあえず導入は、Nugetで「SignalR」をインストール。そしたらStartupのConfigurationしてる奴に以下を追加。

public void Configuration(IAppBuilder app)
{
    app.MapSignalR();//←これを追加
}

そしたら、Hubを作る。どこでもいいっぽいけど、Hubsってフォルダを作ってやった場合。

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace Hoge.Hubs
{
    [HubName("hogehub")]
    public class HogeHub : Hub
    {
        public void Send(string text)
        {
            Clients.All.HogeShortInfo(text);
        }
    }
}

次はView側。↓の感じ。コメント送信したら皆に飛ぶ感じのよくあるやつ。


<div>
    <input type="text" value="" id="message"/><input type="button" id="sendBtn" value="Send"/>
</div>
<div id="messageDiv">

</div>

<script src="~/Scripts/jquery.signalR-2.2.0.min.js">
<script src="/signalr/hubs">
<script type="text/javascript">
    $(function () {
        var echo = $.connection.hogehub;
        echo.on("HogeShortInfo", function (text) {
            $('#messageDiv').append(text);
        });
        $.connection.hub.start().done(function () {
            $('#sendBtn').click(function () {
                var msg = $('#message').val();
                echo.server.send(msg).done(function () {
                    $('#message').val("");
                });
            });
        });
    });
</script>

一個はまったのが、「signalr/hubs」。この子は動的に作られるらしく探しても出てこない。 ってとこまではいいんだけど、作られる階層がサイトのトップらしく、少し階層いったところのページでやる場合は「src="/signalr/hubs"」って頭に"/"をつけておかないと読み込んでくれない。大部分のサンプルは"signalr/hubs"ってなってるのでちょっとはまる。。。

ついで、小文字と大文字。基本的に小文字しか使えないと思ってたほうがいいのかしら。。。

で、書き方は何種類かあるっぽい。クライアント側のメソッドを定義するのは↓の2つの感じ。

        echo.on("HogeShortInfo", function (text) {
            $('#messageDiv').append(text);
        });
        echo.client.HogeShortInfo= function (text) {
            $('#messageDiv').append(text);
        });

個人的には上の書き方の方が、割り当ててますって感じがして好き。

あと、$.connection.hub.start().done()の中にイベントハンドラ入れとかないと、 接続する前にボタンとか押されてダメになるらしい。試してないけど作った人がそう言ってんだからそうしておきましょう。

って、ここまでは割と見かける。このやり方はチャットみたいな送信って押してみんなにいくみたいな場合。そーじゃなくて、サーバ側から何らかのイベント時に(Hubの外から)全体に送信したい場合はクライアントの呼び出しを下の感じで書く。

var hubContext = GlobalHost.ConnectionManager.GetHubContext<Hubs.HogeHub>();
hubContext.Clients.All.HogeShortInfo("なんか周知");

HogeShortInfoのところは、クライアントで割り当てた関数名を指定する感じ。↑のやつはコントローラーでもモデル側でも入れられるけど、↓のusingが必要。

using Microsoft.AspNet.SignalR;

で、サーバ側から任意のタイミングでクライアントの画面にメッセージ表示するみたいなのが↑ので出来るようになるんだけど、クライアント側のJavaScriptでもちょっとはまった。

    $(function () {
        var echo = $.connection.hogehub;
        echo.on("HogeShortInfo", function (text) {
            $('#messageDiv').append(text);
        });
        $.connection.hub.start();//←これが必須!!!
    });

「$.connection.hub.start();」これを入れないと動かない。

あと、サーバから返すデータは普通にJsonっぽいので↓の感じのオブジェクト作って返してあげてクライアント側で使ったりしやすい。

[System.Runtime.Serialization.DataContract]
public class TestHubJsonData
{
    [System.Runtime.Serialization.DataMember()]
    public int recid = 0;
    [System.Runtime.Serialization.DataMember()]
    public string state = "";
    [System.Runtime.Serialization.DataMember()]
    public string date = "";
}

下の感じで使用。オブジェクトの配列で返してもよきかな。

echo.on("TestDisp", function (data) {
    alert(data.date);
})

.Net MVC4のWebアプリをプロジェクトを分割して構成する

複数のプロジェクトに分けて.Net MVCのアプリを作りたい場合にとりあえずできたこと。

ある程度、大きくなる想定のWebアプリを作る場合に、単一のプロジェクトで構成するとえらいこっちゃになる。 とにかく、プロジェクト分けたいのです。一発プロジェクトは漢らしくていいんだけど、複数チームで作業する場合は機能とかもろもろ考慮で分業しておきたいの。。。

複数のプロジェクトで一個のWebアプリにしたい場合にどうやるかを試したメモ。 わりと当たり前のように必要な事だと思うのですが、調べても調べてもドンピシャの情報に いきあたらないのはなんでなんでしょ。この程度は当たり前の事なのかな・・・。

とりあえず、サンプルとして以下の感じにする。調べてみると、一つのソリューションの中に複数のプロジェクトがある感じのものは見るけど、ソリューションごと別の奴は見たことないのでソリューションも分ける。

MainWeb : 大本のプロジェクト
SubWeb : 分割しておくプロジェクト

まず、MainWebのプロジェクトを作る。とにかく簡素な感じにしたいので、空MVCで作る。↓の感じ

01

そしたら、コントローラーとViewを適当に追加しておく。 出来たら、「Areas」っていうフォルダをプロジェクト直下に追加しておく。とりあえず↓の感じ。

02

SubWebも同じ感じで作る。↓の感じ。

03

SubWebの直下にAreaRegistrationのクラスを作る。↓の感じ。

06

ファイルの名前(クラスの名前)は何でもいいっぽいけど、「SubWebAreaRegistration」にしておいた。 中身は以下にして、SubWebをビルドしておく。

using System.Web.Mvc;

namespace SubWeb
{
    public class SubWebAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get
            {
                return "SubWeb";
            }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "SubWeb_default",
                "SubWeb/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}

次はMainWeb側の参照にSubWeb.dllへの参照を追加して、ビルドしておく。

05

そしたら、IISの設定をする。まず、新しくサイトを作るで、MainWebでサイトを作っておく。

10

出来たら、中のフォルダの「Areas」を右クリして、仮想ディレクトリの追加でSubWebを追加する。

11

12

これで、とりあえず動くようになる。サイトのアクセスは下の感じ。

21

22

これで、プロジェクトを分割したMVCの作りが出来そう。まだいろいろ試せてないけど、 基本はこれでいいのだろうか・・・。いまいち分かってない。

ついでに、今回は仮想ディレクトリで持ってきたけど、MainWebのViewsの中にcshtmlだけコピーしても普通に動く。仮想ディレクトリを追加する前に、SubWebにアクセスしようとすると、cshtmlとかの探してる順番がエラーログに出るので参考に見てみてもいいかも。。。

結局のところは、ルーティングの設定をどうするかっていう事なんだろうか。 単一プロジェクトだと、「Global.asax」の「RouteConfig.RegisterRoutes(RouteTable.Routes);」ってところで、「RegisterRoutes」呼び出してやってるけど、Areaを使う感じにすると、「Global.asax」の「AreaRegistration.RegisterAllAreas();」って呼び出しのところでAreaRegistrationを全部やるみたいな動きになる模様。

で、「RegisterAllAreas()」の中で、参照に登録さているアセンブリの中から「AreaRegistration」を継承して作ってあるクラス(今回の例だと「SubWebAreaRegistration」)を全部探し出して勝手にやってくれるみたい。

次は認証とDB回りをこの構成で試す。あと、ルートの書き方は多分足りてない。要調整。

.net MVCのWebAPIでどうしてもPostのデータが取れない時

APIを作ったはいいがPOSTの各データがなんか取れない場合に、とりあえず受け取る方法。 いろいろ試してこれしか無かった。絶対、他に原因があるはず。。。

System.Web.HttpContext.Current.Request.Form["name"].ToString()

.net MVCのWebAPIでServer.MapPath

.netMVCでApiController継承してるコントローラで Server.MapPathが使いたくなった時。

System.Web.Hosting.HostingEnvironment.MapPath("/hoge/moe");

uploadifyを.Net MVCで使う

ファイルのアップロードを組み込むことはよくある事で。 複数ファイルを一括で上げたいっていう要件もままある話。 uploadifyってゆーライブラリをよく見るので.Net MVCで使ってみた。

uploadify

まず、ライブラリ。Flash版とHTML5版があるらしい。 HTML5版は有料っぽい。Flash版はMITライセンスの様子。 Flash版を使って実装。

Flash版をDLして、テケトーなとこに配置して、使いたいページで読む。CSSも。jqueryも使う。

<script src="/Scripts/jquery-1.11.1.js">
<script src="/Scripts/lib/uploadify/jquery.uploadify.min.js">
<link href="/Scripts/lib/uploadify/uploadify.css" rel="stylesheet" />

HTML側(View)はこんな感じ。

@using (Html.BeginForm("DoUpload", "HogeControl", null, FormMethod.Post, new { @class = "form-horizontal", @enctype = "multipart/form-data", @id = "filesform" }))
{
    <input type="file" name="file_upload" id="file_upload" />
}

そしたらjavascriptはこんな感じ。

$('#file_upload').uploadify({
    'fileSizeLimit': '0',
    'buttonText': 'ファイル選択',
    'swf': "/Scripts/lib/uploadify/uploadify.swf",
    'uploader': "/HogeControl/DoUpload/",
    'onUploadSuccess': function (file, data, response) {
        alert(data + "をアップロードしました。")
    }
});

最後にコントローラ側はこんな感じ。


[HttpPost]
public ActionResult DoUpload(int id, FormCollection collections)
{
    var uploadedFile = Request.Files["Filedata"];

    if (uploadedFile != null && uploadedFile.ContentLength > 0)
    {
        var filePath = Path.Combine(Server.MapPath("~/uploads/uploadyfitmp"), string.Format("{1}_{0}{2}", Path.GetFileNameWithoutExtension(uploadedFile.FileName), DateTime.Now.Ticks, Path.GetExtension(uploadedFile.FileName)));
        uploadedFile.SaveAs(filePath);
        return Content(Path.GetFileNameWithoutExtension(uploadedFile.FileName));
    }

    return Content("Error!!!");
}
問合せ