白猫のメモ帳

JavaとかC#とかJavaScriptとかHTMLとか機械学習とか。

省略可能引数のあるメソッドをリフレクションで呼ぶ(C#)

こんばんは。

なんだか妙に暖かい日があったりしましたが、
すっかり冷え込んできて、気づけばもう12月。
今年も残すところあと少しですね。

今回はちょっとリフレクションについて。

そもそもリフレクションでメソッドってどうやって呼ぶ?

軽く確認を。
まずはこんなメソッドを呼んでみましょう。

public class Hoge
{
    public void Fuga(string piyo)
    {
        Debug.WriteLine("Fuga" + piyo);
    }
}

こんな感じ。

// メソッドを実行するインスタンス
var hoge = new Hoge();

// タイプを取得
var type = hoge.GetType();

// メソッドを取得
var method = type.GetMethod("Fuga");

// メソッドを実行
method.Invoke(hoge, new[] { "Piyo" });

タイプからメソッドをとってきて、
第一引数にメソッドを実行するインスタンス、第二引数にメソッドに渡す引数を設定します。
Invokeは戻り値を返しますが、今回はvoidなので取りません。

可変長引数の場合

ちょっと寄り道ですが、可変長引数の場合を軽く確認してみましょう。

public class Hoge
{
    public void Fuga(params string[] piyo)
    {
        Debug.WriteLine("Fuga" + string.Join("", piyo));
    }
}

こんな感じ。

~(略)~

// メソッドを実行
method.Invoke(hoge, new[] { new[] { "Piyo", "Piyo", "Piyo" } });

可変長引数は実体としては配列なので、配列としてパラメタを渡してあげればOKです。

省略可能引数の場合

で、本題。
こうして。

public class Hoge
{
    public void Fuga(string piyo = default(string))
    {
        Debug.WriteLine("Fuga" + piyo);
    }
}

こうすると、

~(略)~

// メソッドを実行
method.Invoke(hoge, new[] { "Piyo" });

普通に呼べますが、

~(略)~

// メソッドを実行
method.Invoke(hoge, null); // エラー
method.Invoke(hoge, new object[0]); // エラー

省略しようと思ってこんなふうにしてもエラーになってしまいます。
じゃあどうするかって言うと、

~(略)~

// メソッドを実行
method.Invoke(hoge, new[] { System.Type.Missing });

こんな感じに「System.Type.Missing」を指定してあげます。
せっかくなので拡張メソッドを作ってみましょう。

public static class Extensions
{
    public static object InvokeDefault(this MethodBase method, object obj, object[] parameters)
    {
        var len = method.GetParameters().Length;
        var param = (parameters ?? new object[0]).Concat(Enumerable.Repeat(System.Type.Missing, len)).ToArray();
        return method.Invoke(obj, param);
    }
}

省略可能パラメタは必須パラメタの後ろに書かなくてはならないので、
後ろに足りない分の「System.Type.Missing」を埋めてからInvokeしてあげる感じです。
パラメタを可変長引数にしてあげても良いかもしれないですね。

どうでもいいけどExtensionってtかsか大体いつもどっちが正解か自信がなくて調べてる気が…。

ふと思いついて

SSL化しました。
ボタン一つでちゃんと変わるのすごいですね。

Chromeで「保護されていない通信」とでたり、
SEO的に不利になったりするのでSSL化は必須の時代かもしれません。
まぁ別にアフィリエイトとかしてるわけじゃないので、検索順位とかあまり気にしてなかったのですが。

ちなみにHTTPには戻せないらしいので、ご利用は計画的に。
全部の記事見たりしていないので、画面崩れてたりMixed-Contentとかあったらこっそり教えていただければ…。

パラメタでルーティングがしたい(ASP.NET)

こんにちは。

11月11日です。
ポッキー食べましたか?
世の中真っ直ぐな食べ物なんてたくさんあるのに、確固たる地位を築いているのはすごいですね。

ちくわ&ちくわぶの日だっていいし、なんならそうめん&スパゲティの日だっていいわけです。
でももうポッキー&プリッツの日です。広まったら勝ちです。強い。

さて、今日はASP.NETのお話。
.NET Coreでもできるかもしれないけど、とりあえず.NET Frameworkということで。

ルーティングを設定しよう

VisualStudioで「ASP.NET Webアプリケーション(.NET Framework)」のプロジェクトを作成すると、
自動的にこんなRouteConfig.csが作成されます。

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

で、Controllerを作ってあげてlocalhostにアクセスすると(書くの面倒なので、ポート番号は脳内で補完していただくということで)、
デフォルトのルーティング設定が読み込まれて無事にHomeControllerのIndexメソッドが呼ばれるわけですね。

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return Content("Index");
    }
}

次にHomeController にメソッドを追加して、localhost/Home/Hoge/にアクセスしてみると、
今度はHomerControllerのHogeメソッドが呼ばれますね。

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return Content("Index");
    }
    
    public ActionResult Hoge()
    {
        return Content("Hoge");
    }
}

パスじゃなくてパラメタでアクションを設定したいのだ

そんな場面あるかい!って言われてしまったら終了なのですが、
URLのルールを変えられないなんてことは稼働中のサービスとかだとあるんですよ。
え、ない?まぁまぁそう言わず…。

とりあえずMapRouteに引数でパラメタって設定できないのかなとか見てみると、
制約をつける「constraints」と名前空間を指定する「namespace」しかありませんでした。そりゃそうか。

とりあえず、他にもパラメタはあるだろってことは完全に無視してこんなルーティングを追加してみます。

routes.MapRoute(
    name: "Hoge",
    url: "?action=Hoge",
    defaults: new { controller = "Home", action = "Hoge" }
);

f:id:Shiro-Neko:20181111123017p:plain

はい、だめでした。

いやそもそも「defaults」って書いてあるくらいなんだから、
ルーティング設定なんて追加しなくてもパラメタに「controller」と「action」を指定して、
localhost?controller=Home&action=Hogeとかにアクセスしてあげれば…。

f:id:Shiro-Neko:20181111123438p:plain

当たり前ですが、パラメタとは別管理です。

しょうがないので自分でルーティングしよう

とりあえずActionが実行される前になにかの値を差し替えてしまえばいけそうなので、
OnActionExecutingをoverrideして、ActionExecutingContextの中身を覗いてみると…

f:id:Shiro-Neko:20181111124351p:plain

「RouteData」というのが多分ルーティングの設定でしょう。
ということは、こんな感じで書けば。

protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var parameter = filterContext.HttpContext.Request.Params;

    if (parameter.AllKeys.Contains("action"))
    {
        filterContext.RouteData.Values["action"] = parameter["action"];
    }

    base.OnActionExecuting(filterContext);
}

f:id:Shiro-Neko:20181111124812p:plain

だめでした。
ExecutingってことはもうActionがExcuteされてるんですかね。
じゃあこれならどうだ。

protected override IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
{
    var parameter = requestContext.HttpContext.Request.Params;

    if (parameter.AllKeys.Contains("action"))
    {
        requestContext.RouteData.Values["action"] = parameter["action"];
    }

    return base.BeginExecute(requestContext, callback, state);
}

f:id:Shiro-Neko:20181111135225p:plain

やったー。

というわけで

BeginExecuteでRouteDataを差し替えてあげれば、Actionは自分で決められるようです。

他にもっと簡単な方法はあるんでしょうか?
誰か教えてくださいまし。