抽象ベースコントローラーでUser(User.Identity.Nameのように)nullになるのはなぜですか?
-
08-07-2019 - |
質問
関連する質問をしていましたが、タイトルを台無しにして、誰もそれを理解できませんでした。質問をより正確に尋ねることができるようになったので、新しい質問に再定式化し、古い質問を閉じることにしました。ごめんなさい。
したがって、私がやりたいことは、データ(dbに保存されているカスタムユーザーのニックネーム)を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を設定していますが、これは問題になり得ないと思います-afaik User.Identity.Nameはnullにできますが、User自体にはできません。
私には、HttpContextが利用できないように見えます(nullもあるため;-)。ここでは、シンプルでありながら重要な点が欠落しています。誰も私にいくつかのヒントを教えてもらえますか?本当にありがたいです。
解決
コントローラーのベースコンストラクターはユーザーに入力していませんが、コントローラーに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であることを証明します。
したがって、コードを別の場所で実行する必要があります。
他のヒント
この問題に対する答えは実際には非常に簡単です。 Raimondが指摘した理由により、コンストラクター内からコードを実行することはできませんが、コンストラクターの外で実行することはできます。
だから、ベースコントローラークラスでonActionExecuting()をオーバーライドし(カスタムAttributeを作成しましたが、メソッドをオーバーライドするだけでも動作するはずです)、そこからユーザールックアップを行いました。
今は期待どおりに動作し、繰り返しコードはありません。
コントローラーがインスタンス化されるまでユーザープロパティは割り当てられませんが、次の方法でコンストラクターから早期アクセスできます:
System.Web.HttpContext.Current.User
次のような方法でこれを取得できますか
HttpContext currentContext = HttpContext.Current;
string userName = currentContext.User.Identity.Name;
またはHttpContextは常に空ですか?
抽象クラスのコンストラクターでhttpContextを設定できますか?このように使用しますか?
Raimondに感謝します。明らかなことを見るには疲れすぎた。 @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の原則に従って実行する方法を検討します。そのためには、属性を使用することは間違いなく自分自身を繰り返すことを意味するからです。たぶんある種のインターセプター(興味深いアイデア)またはフックが役立つ場合があります。
そのために乾杯。
ベースコントローラーの実装でこれを実行していますが、期待どおりに動作します。
public abstract class BaseController : Controller
{
public bool LoggedOn
{
get { return User.Identity.IsAuthenticated; }
}
}
これは常にtrueまたはfalseを返すため、 User!= null
Masterfu: 私はあなたの助けと似たようなことをしました。 私の場合、異なるユーザーのコントローラーのリポジトリを作成する必要がありますが、コントローラーのコンストラクターでは、(プリンシパル)ユーザーの準備ができていません。コントローラーの属性を作成しました:
[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を使用する場合に備えて、リポジトリを作成するコードを別のメソッドに配置します。
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;
}
}
User
が必要な場合は、 IPrincipal
を挿入します。
// startup.cs
// Inject IPrincipal
services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
次に、コンストラクターに IPrincipal
として追加します。 ASPNETでは ClaimsPrincipal
であることが保証されていることに注意してください-これが HttpContext.User
であるためです。