web-dev-qa-db-ja.com

抽象ベースコントローラーでUser(User.Identity.Nameのように)がnullになるのはなぜですか?

私は関連する質問をしていましたが、タイトルを台無しにして、誰もそれを理解しませんでした。より正確に質問できるようになったので、新しい質問に再定式化し、古い質問を閉じることにしました。そのために残念。

したがって、私がやりたいのは、データ(データベースに格納されているカスタムユーザーのニックネーム)をLoginUserControlに渡すことです。このログインは、Html.RenderPartial()を介してマスターページからレンダリングされるため、実際に行う必要があるのは、すべての呼び出しにViewData ["UserNickname"]が存在することを確認することです。しかし、すべてのコントローラーのすべてのアクションにViewData ["UserNickname"]を入力したくないので、 このアプローチ を使用して、作業を行う抽象ベースコントローラーを作成することにしました。 、 そのようです:

public abstract class ApplicationController : Controller
    {
        private IUserRepository _repUser;

        public ApplicationController()
        {
            _repUser = RepositoryFactory.getUserRepository();
            var loggedInUser = _repUser.FindById(User.Identity.Name); //Problem!
            ViewData["LoggedInUser"] = loggedInUser;
        }
    }

このように、私の派生コントローラーが何をするにしても、ユーザー情報はすでに存在します。

ここまでは順調ですね。今問題のために:

Userがすでにnullであるため、User.Identity.Nameを呼び出すことができません。これは私の派生コントローラーのすべてに当てはまるわけではないので、これは抽象ベースコントローラーに固有です。

コード内の別の場所でFormsAuthenticationを介してUser.Identity.Nameを設定していますが、これは問題ではないと思います。afaikUser.Identity.Nameはnullにすることができますが、User自体はできません。

HttpContextが利用できないように見えます(null;-もあるため)。ここでは、単純でありながら重要なポイントが欠落しています。誰かが私にいくつかのヒントを与えることができますか?とても感謝しております。

24
Masterfu

私の推測では、コントローラーの基本コンストラクターはユーザーを入力していませんが、後でControllerContextがコントローラーに設定されたときにのみ認識されます。これは、MVCアプリケーションのライフサイクルに関するドキュメントで確認する必要があります(プレビューバージョン用であるため、少し古くなっている可能性がありますが、 ここ で十分です)。 MVCのソースコード。

私が持っているMVCのコードから(プレビューバージョンでもありますが、それで問題ありません):(コントローラー内)

 public IPrincipal User {
            get {
                return HttpContext == null ? null : HttpContext.User;
            }
        }

.。

public HttpContextBase HttpContext {
        get {
            return ControllerContext == null ? null : ControllerContext.HttpContext;
        }
    }

コードにデフォルトコンストラクターの実装がありません。これは、構築時にControllerContextがnullであることを証明します。

したがって、コードは別の場所で実行する必要があります。

14

この問題への答えは実際には非常に簡単です。 Raimondが指摘した理由により、コンストラクター内からコードを実行することはできませんが、コンストラクターの外部で実行することはできます。

したがって、私が行ったことは、基本コントローラークラスのonActionExecuting()をオーバーライドし(カスタム属性を作成しましたが、メソッドをオーバーライドするだけでも機能するはずです)、そこからユーザールックアップを実行しました。

これで期待どおりに機能し、コードを繰り返す必要はありません。

23
Masterfu

Userプロパティは、コントローラーがインスタンス化されるまで割り当てられませんが、コンストラクターから次の方法で早期にアクセスできます。

System.Web.HttpContext.Current.User
17
Austin Hummel

レイモンドに感謝します。私は疲れすぎて明らかなものを見ることができませんでした。 @Keeney:はい、コンテキストは常にnullです。レイモンドはその理由を指摘した。とにかくありがとう、私も理由がわかりませんでした:-)

私の現在の実用的なソリューションは(私が望んでいたものではありませんが)、すべてのコントローラーアクションを装飾するために使用する属性です。実装は次のとおりです。

public class MasterPageDataAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            IUserRepository _repUser = RepositoryFactory.getUserRepository();
            IPrincipal siteUser = filterContext.Controller.ControllerContext.HttpContext.User;
            User loggedInUser = null;

            if (siteUser == null || siteUser.Identity.Name == null)
            {
                //do nothing
            }
            else
            {
                loggedInUser = _repUser.findUserById(siteUser.Identity.Name);
            }
            filterContext.Controller.ViewData["LoggedInUser"] = loggedInUser ?? new User { Nickname = "Guest" };
        }
    }

DRYの原則に従う方法でそのコードを実行する方法を検討します。これは、属性を使用することは間違いなく自分自身を繰り返すことを意味するためです。おそらく、ある種のインターセプター( 興味深い)アイデア )またはフックが役立つかもしれません。

乾杯。

4
Masterfu

次のようなものを使用してこれを取得できますか?

HttpContext currentContext = HttpContext.Current;
string userName = currentContext.User.Identity.Name;

または、HttpContextは常に空ですか?

抽象クラスのコンストラクターを介してhttpContextを設定できますか?このように使用しますか?

4
keeney

私はこれをベースコントローラーの実装で行っており、期待どおりに機能します。

public abstract class BaseController : Controller
{
    public bool LoggedOn
    {
        get { return User.Identity.IsAuthenticated; }
    }
}

これは常にtrueまたはfalseを返すので、User != null

1
Schotime

コンストラクターでIPrincipalが必要な場合は、Userを挿入します。

 // startup.cs
 // Inject IPrincipal
 services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);

次に、コンストラクターにIPrincipalとして追加します。 ASPNETではClaimsPrincipalであることが保証されていることに注意してください-それがHttpContext.Userです。

同様の質問

0
Simon_Weaver

コンストラクターからの呼び出しは、MVCパイプラインでは早すぎます。

コードをOnAuthorizationに移動すると、パラメーターで許可されたユーザーを取得します。私のために働いた!

あなたの例から、私はこのようなことをします:

public abstract class ApplicationController : Controller {
    private IUserRepository _repUser;

    protected override void OnAuthorization(AuthorizationContext filterContext)
    {
        _repUser = RepositoryFactory.getUserRepository();
        var loggedInUser = _repUser.FindById(filterContext.HttpContext.User.Identity.Name); //Problem!
        ViewData["LoggedInUser"] = loggedInUser;
    }


}
0
Simon Novak

マスターフへ:私はあなたの助けを借りて同様のことをしました、それが後の訪問者を助けることができることを望みます。私の場合、さまざまなユーザーのコントローラーのリポジトリを作成する必要がありますが、コントローラーのコンストラクターでは、(プリンシパル)ユーザーの準備ができていません。だから私はコントローラーの属性を作成しました:

[CreateRepositoryByUser]
public class MFCController : Controller
{
    protected MFCRepository _repository
    {
        get { return ViewData["repository"] as MFCRepository; }
    }
...

_repositoryは、実際、コントローラーのプライベート変数ではありませんが、属性によって作成されます。

public class CreateRepositoryByUser : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        CreateRepository(filterContext);
    }

    public static void CreateRepository(ActionExecutingContext filterContext)
    {
        if (filterContext.Controller.ViewData["repository"] == null)
        {
            filterContext.Controller.ViewData["repository"] =
                MFCRepository.CreateMFCRepository(filterContext.Controller.ControllerContext.HttpContext.User);
        }
    }
}

この属性がトリガーされる前に他の属性が(principal)Userを使用したい場合に備えて、リポジトリを作成するコードを別のメソッドに配置しました。

0
cheny