WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

VB.NET の ByVal / ByRef と値型 / 参照型

by WebSurfer 20. January 2019 12:37

Visual Basic .NET (VB.NET) のメソッドの引数の値渡し (ByVal) / 参照渡し (ByRef) と、変数の値型 / 参照型をそれらの引数に渡す場合の違いについて、このように考えると理解しやすいと思っていることを書きます。

ちなみに、C# では ByVal に相当するキーワードがなくデフォルトで値渡しになるだけで、考え方は同じです。(ByRef に相当する ref というキーワードはあります)

まず、メソッドの引数に付与する ByVal, ByRef の違いですが、メソッドの呼び出し元から引数に値を渡す時、以下のようしていると考えれば良いと思います。

  • ByVal: 別の入れ物に中身をコピーして渡す
  • ByRef: 入れ物を中身ごと渡す

上記で「入れ物」というのは、例えば、Dim X As Integer で宣言された変数 X で、「中身」というのは X = 10 のようにして変数 X に代入された値 10 のことです。

次に、変数の値型・参照型ですが、クラスや配列は参照型、Integer のような構造体は値型になります。詳しくは、3-2 値型と参照型を見てください。

メソッドに ByVal, ByRef どちらで渡そうと、変数の参照型が値型に変わったり、値型が参照型に変わることはありません。

という訳で、以下の 4 つのケースに分けて考えると良いと思います。

(1) 値型を ByVal で渡す

呼び出し元の入れ物の中身を、メソッド側で用意した別の入れ物に、コピーして渡す。

呼び出し元とメソッドで入れ物が異なるので、メソッド側で入れ物の中身をどのように操作しようと呼び出し元の変数には何の影響もない。

(2) 値型を ByRef で渡す

呼び出し元の入れ物とメソッドに渡された入れ物は同じなので、メソッド側で中身を変更すれば呼び出し側でも中身が変更される。

(3) 参照型を ByVal で渡す

参照型なので渡すのはオブジェクト(インスタンス)を指すアドレス情報。ByVal なのでメソッド側で用意した別の入れ物にアドレス情報をコピーして渡している。

つまり、呼び出し元の変数とメソッド側の変数の中身は同じアドレス情報即ち単一のオブジェクトを指している。

なので、メソッド側で変数が指すオブジェクトを変更すると、呼び出し元の変数が指すオブジェクトでも同じ結果となる。

(4) 参照型を ByRef で渡す

基本は上記 (3) と同じ。

ただし、呼び出し側の入れ物とメソッドに渡された入れ物は同じなので、メソッド側で新しくオブジェクト(インスタンス)を生成してそれのアドレス情報を入れ物に代入すると、呼び出し元の入れ物の中身が指すオブジェクトも、メソッド側で生成した新しいオブジェクトになる。

Tags: , , ,

.NET Framework

ASP.NET MVC 用の CompareValidator

by WebSurfer 31. December 2018 12:06

ASP.NET Web Forms には CompareValidator という検証コントロールがあって、2 つの TextBox に入力された値の大小比較をして検証できる機能があります。

それと同様な検証機能を ASP.NET MVC で実装する方法を書きます。(元の話は Teratail のスレッド ASP.NET MVC4 TimeSpan型入力値の検証です)

MVC の CompareValidator

ASP.NET MVC には CompareAttribute という検証用のデータアノテーション属性がありますが、それには大小を比較する機能はなく、同じか否かを検証するのみです。

なので、例えば、ユーザーが開始時間と終了時間をテキストボックスに入力した際に、終了時間が開始時間より後になっていることを検証するような場合は、カスタム検証機能を実装することになります。

複数のプロパティにまたがった検証をする場合、Teratail のスレッド「MVC モデルのバリデーションについて」の回答に書いたように、CustomValidationAttribute を使うか、モデルに IValidatableObject インターフェイスを実装するという手段を使うのが普通のようです。

しかし、2 つのテキストボックス入力の大小比較程度なら、CustomValidationAttribute を使わなくても、リフレクションを使って比較対象のプロパティの値を取得し、それと自身のプロパティの値を比較して検証結果を返すということができます。

参考にしたのは Compare Validator in MVC という記事の Limitations of the Compare DataAnnotation 以下のセクションです。

上の画像を表示するのに使ったサンプルコードは以下の通りです。TimeSpan 型のプロパティを検証の対象としていますが、DataTime 型その他でもキャストを変更して同様に検証可能です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

namespace Mvc5App.Models
{
  public class KintaiModel
  {
    public IList<Record> Records { get; set; }
  }

  public class Record
  {
    public int Id { set; get; }
    public string Week { set; get; }
    public TimeSpan Open { set; get; }

    [TimeGreaterThan("Open", 
                   "{0} は Open より後に設定してください")]
    public TimeSpan Close { set; get; }

    // ・・・中略・・・
  }

  [AttributeUsage(AttributeTargets.Property)]
  public class TimeGreaterThanAttribute : ValidationAttribute
  {
    private string _startTimePropertyName;

    // コンストラクタ
    public TimeGreaterThanAttribute(
            string startTimePropertyName, string errorMsg)
    {
      this._startTimePropertyName = startTimePropertyName;
      this.ErrorMessage = errorMsg;
    }

    public override string FormatErrorMessage(string name)
    {
      return string.Format(CultureInfo.CurrentCulture,
                           ErrorMessageString, name);
    }

    protected override ValidationResult IsValid (
        object value, ValidationContext validationContent)
    {
      // ObjectType はこの属性を付与したプロパティが属する
      // クラス、即ち上のコードの Record クラスとなる
      System.Reflection.PropertyInfo propertyInfo = 
          validationContent.ObjectType.
          GetProperty(this._startTimePropertyName);
      object propertyValue = 
          propertyInfo.
          GetValue(validationContent.ObjectInstance, null);

      if ((TimeSpan)value > (TimeSpan)propertyValue)
      {
        return ValidationResult.Success;
      }
      else
      {
        // ValidationResult の引数に null または "" を設定する
        // と FormatErrorMessage メソッドの戻り値が設定される
        return new ValidationResult(null);
      }
    }
  }
}

上の画像・コードではクラスのコレクションのモデルバインディングを行っていますが、そのような場合でもリフレクションを使って比較対象のクラスのプロパティを特定して検証可能です。ただ、リフレクションを使うということで、性能的にどうかという懸念は消せてませんが。

また、上のサンプルコードはサーバー側だけの検証で、クライアント側での JavaScript / jQuery による検証はかかりません。

単一のプロパティのカスタム検証であれば、@IT の記事の「第4回 検証属性の自作とクラス・レベルのモデル検証」のセクションに書かれているようにして、サーバー側とクライアント側両方の検証機能を実装できます。

しかし、今回のケースのように 2 つのプロパティにまたがった検証をする場合、スクリプトを ASP.NET MVC の検証システムの中に無理なく不整合なく取り込む具体例は少なくとも自分は見たことがないです。(自分が知らないだけだという可能性は否定しきれませんが)

Tags: ,

Validation

ASP.NET MVC に Web API 追加

by WebSurfer 22. December 2018 18:04

既存の ASP.MET MVC5 アプリに Web API 2.2 のコントローラを追加してみました。その方法を備忘録として書いておきます。需要はないかもしれませんが。(笑)

MVC + Web API プロジェクト

既存の MVC5 アプリは VS2015 のテンプレートで生成した .NET Framework v4.6.1 ベースの単独 MVC プロジェクトで、ASP.NET Identity を利用してクッキーベースの認証を行っています。

その既存の MVC5 アプリに Web API の機能を追加するのですが、Web API の認証は既存のクッキーベースとするのではなく、Web API で推奨されているトークンベースとします。その際、既存の MVC5 アプリが持つ ASP.NET Identity からユーザー情報を得てトークン認証を行うようにします。

基本的には、下の画像のように VS2015 の Web API テンプレートを使ってプロジェクトを新規に作り、それから必要な部分を既存の MVC プロジェクトに追加していくという感じです。

Web API プロジェクトの作成

Web API プロジェクトを作った後、以下の手順で、既存の MVC プロジェクトに必要なパッケージ、コードを追加します。

(1) SSL 有効化

開発環境でも SSL 通信下で検証ができるように、先の記事「IIS Express で SSL 通信」に従って IIS Express で SSL 通信を利用できるように設定します。

(2) NuGet パッケージのインストール

既存の MVC プロジェクトに Web API 関係の NuGet パッケージをインストールします。必要なパッケージは、Web API テンプレートで自動生成されたプロジェクトの NuGet パッケージの管理画面で「インストール済み」の WebApi 関係のパッケージを表示すると分かります。自分の環境では以下の 6 つでした。

  • Microsoft.AspNet.WebApi
  • Microsoft.AspNet.WebApi.Client
  • Microsoft.AspNet.WebApi.Core
  • Microsoft.AspNet.WebApi.WebHost
  • Microsoft.AspNet.WebApi.Owin
  • Microsoft.AspNet.WebApi.HelpPage

Microsoft.AspNet.WebApi を選んでインストールすると自動的に Client, Core, WebHost もインストールされます。Owin, HelpPage はその後で追加インストールしました。

(3) RequireHttpsAttribute の追加

SSL 通信を強制するためのフィルター RequireHttpsAttribute を追加します。(基本的な動作には影響ないのですが、実装しておいた方がよさそうですので)

まず、MVC 側ですが、MVC 5.2 以降であれば System.Web.Mvc 名前空間に RequireHttpsAttribute Class が用意されていますので、それを FilterConfig.cs で以下のように追加します。

public static void RegisterGlobalFilters(
                GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());

    // これを追加
    filters.Add(new RequireHttpsAttribute());
}

Web API 側では上記のフィルターは使えないので、カスタムフィルターを作ってそれを使うことになります。(MVC 用と Web API 用とではフィルターは違うので注意)

カスタムフィルターは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 からリンクが張ってある GitHub のページ からダウンロードできるプロジェクトの Filters フォルダにサンプルがありましたので、それを借用しました。

そのカスタムフィルター RequireHttpsAttribute.cs のコードをそのまま載せておきます。(名前空間は自分のプロジェクトに合わせて変更しました)

using System;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Mvc5.Filters
{
  public class RequireHttpsAttribute : 
                            AuthorizationFilterAttribute
  {
    public int Port { get; set; }

    public RequireHttpsAttribute()
    {
      Port = 443;
    }

    public override void OnAuthorization(
                           HttpActionContext actionContext)
    {
      var request = actionContext.Request;

      if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
      {
        var response = new HttpResponseMessage();

        if (request.Method == HttpMethod.Get || 
            request.Method == HttpMethod.Head)
        {
          var uri = new UriBuilder(request.RequestUri);
          uri.Scheme = Uri.UriSchemeHttps;
          uri.Port = this.Port;

          response.StatusCode = HttpStatusCode.Found;
          response.Headers.Location = uri.Uri;
        }
        else
        {
          response.StatusCode = HttpStatusCode.Forbidden;
        }

        actionContext.Response = response;
      }
      else
      {
        base.OnAuthorization(actionContext);
      }
    }
  }
}

このカスタムフィルターを有効にする方法は下の「(4) WebConfig.cs の追加」のセクションを見てください。

(4) WebConfig.cs の追加

Web API プロジェクトから WebConfig.cs をコピーして MVC プロジェクトの App_Start フォルダにコピーします。名前空間は自分のプロジェクトに合わせて変更してください。

Register メソッドに、上の「(3) RequireHttpsAttribute の追加」のセクションで用意した Web API 用のカスタムフィルターを有効化するため、以下のコードを追加します。

public static void Register(HttpConfiguration config)
{
  // ・・・中略・・・

  // カスタム RequireHttpsAttribute フィルターを追加
  config.Filters.Add(new Mvc5.Filters.RequireHttpsAttribute());
}

その後、WebConfig を Global.asax の Application_Start メソッドに登録します。

protected void Application_Start()
{
    // ・・・中略・・・

    // これを追加
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

(5) UseOAuthBearerTokens 追加

トークン認証を有効にするため、Startup.Auth.cs の ConfigureAuth メソッドに以下のコードを追加します。コードは Web API テンプレートで作成したプロジェクトにありますので、それをコピーして修正すればいいです。

public partial class Startup
{
  // 追加
  public static OAuthAuthorizationServerOptions 
                          OAuthOptions { get; private set; }

  // 追加
  public static string PublicClientId { get; private set; }

  public void ConfigureAuth(IAppBuilder app)
  {

    // ・・・中略・・・

    // 追加
    PublicClientId = "self";
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
      TokenEndpointPath = new PathString("/Token"),
      Provider = new ApplicationOAuthProvider(PublicClientId),

      // 不要と思われるのでコメントアウト(説明下記)
      //AuthorizeEndpointPath = 
      //        new PathString("/api/Account/ExternalLogin"),

      // デフォルトで 20 分。MVC 側に合わせて 14 日に設定
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),

      // SSL 強制のため false に設定
      AllowInsecureHttp = false
    };
    app.UseOAuthBearerTokens(OAuthOptions);

    // ・・・後略・・・

AllowInsecureHttp プロパティは SSL 通信強制のため false に設定してください。

ApplicationOAuthProvider は Web API テンプレートで作成したプロジェクトの Providers フォルダにあるものをコピーして使います。詳しくは下の「(6) ApplicationOAuthProvider 追加」セクションを見てください。

AuthorizeEndpointPath プロパティについては、stackoverflow の記事 What is AuthorizeEndpointPath? に説明があります。

ユーザーがクレデンシャルを入力してトークンを得るという条件に限定すれば、AuthorizeEndpointPath プロパティの設定はコメントアウトしても問題なさそうです。(それで 100% 問題ないと言い切れる自信はないですが)

(6) ApplicationOAuthProvider 追加

Web API テンプレートで作成したプロジェクトの Providers フォルダにある ApplicationOAuthProvider.cs をフォルダごと MVC プロジェクトにコピーします。名前空間は自分のプロジェクトに合わせて変更してください。

GrantResourceOwnerCredentials メソッドの中で使用されている第 2 引数に string を持つ GenerateUserIdentityAsync メソッドは、Web API プロジェクトの IdentityModel.cs に定義されているものをコピーして、MVC プロジェクトの IdentityConfig.cs にペーストしてください。

このプロバイダは、OWIN ミドルウェアのプラグインとして、OWIN ミドルウェアで発生するイベントを処理するためのものだそうです。詳しくは Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring the Authorization Server のセクションを見てください。

(7) トークン取得・削除のスクリプト

トークン取得・削除のスクリプトは以下のコードのようにします。

var tokenKey = 'accessToken';

function getToken() {
    var email = document.getElementById("email").value;
    var password = document.getElementById("password").value;

    var loginData = {
        grant_type: 'password',
        username: email,
        password: password
    };

    $.ajax({
        type: "POST",
        url: "/Token",
        data: loginData,
        success: function (data) {
            sessionStorage.setItem(tokenKey, data.access_token);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            //・・・中略・・・
        }
    });
}

function removeToken() {
    sessionStorage.removeItem(tokenKey);
}

以上で MVC 側はクッキーベースで、Web API 側はトークンベースで独立して認証が働きます。それを可能にしているのは WebConfig.cs に含まれている以下の 2 行です。

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(
                    OAuthDefaults.AuthenticationType));

この 2 行をコメントアウトすると Web API 側もクッキーベースの認証となります。なお、その際 Web API に匿名アクセスすると 401 応答ではなく、以下のように 200 応答となりますので注意してください。

クッキー認証の場合の 200 応答

Tags:

Web API

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  September 2019  >>
MoTuWeThFrSaSu
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

View posts in large calendar