web-dev-qa-db-ja.com

Dependency Injection循環依存.netコア2.0

ApplicationContextコンストラクターにパラメーターとしてUserManagerを指定したいのですが、依存関係の注入に問題があります。

コード:

public class ApplicationContext : IdentityDbContext<ApplicationUser>
{
    private IHttpContextAccessor _contextAccessor { get; set; }
    public ApplicationUser ApplicationUser { get; set; }
    private UserManager<ApplicationUser> _userManager;

    public ApplicationContext(DbContextOptions<ApplicationContext> options, IHttpContextAccessor contextAccessor, UserManager<ApplicationUser> userManager)
        : base(options)
    {
        _contextAccessor = contextAccessor;
        var user = _contextAccessor.HttpContext.User;
        _userManager = userManager;
        ApplicationUser = _userManager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));
    }
}

そして、startup.cs

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddDbContext<ApplicationContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("RCI.App")));

    services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddEntityFrameworkStores<ApplicationContext>()
        .AddDefaultTokenProviders();

    services.AddAuthentication();

    services.AddMvc();

    // Add application services.
    services.AddTransient<IEmailSender, AuthMessageSender>();
    services.AddTransient<ISmsSender, AuthMessageSender>();
    services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();

    services.AddOptions();

}

エラーコード:

タイプ 'Microsoft.AspNetCore.Identity.UserManager`1 [RCI.App.Models.ApplicationUser]'のサービスの循環依存関係が検出されました。

誰かが私が間違っていることを指摘できますか?

11
rory

循環依存関係は通常、不適切なアプリケーション設計の兆候であり、修正する必要があります。すでにコメントで述べたように、ユーザーマネージャーに依存するdatabaseコンテキストを持つことは良い考えではないようです。これにより、データベースコンテキストが多すぎると想定し、おそらく 単一の責任の原則 に違反します。

データベースコンテキストの依存関係を見るだけで、そこにアプリケーション固有の状態が多すぎます。ユーザーマネージャーだけでなく、HTTPコンテキストアクセサーにも依存しています。また、コンストラクターでもHTTPコンテキストをすぐに解決します(これは通常、最良のアイデアではありません)。

コードの抜粋から、後で使用するために現在のユーザーを取得したいようです。たとえば、これを使用してユーザーのクエリをフィルタリングする場合は、これをデータベースコンテキストインスタンスに静的にベイクすることが本当に良いアイデアかどうかを検討する必要があります。代わりに、メソッド内でApplicationUserを受け入れることを検討してください。このようにして、これらの依存関係をすべて取り除き、データベースコンテキストをよりテストしやすくします(ユーザーがコンテキストのstateになったため)、また、コンテキストの単一の責任を明確にします。

public IList<Thing> GetThings (ApplicationUser user)
{
    // just an example…
    return Things.Where(t => t.UserId == user.Id).ToList();
}

これはalso制御の反転 であることに注意してください。データベースコンテキストactivelyでクエリを実行する必要があるユーザーを取得する代わりに(SRPに違反して別の責任が追加されます)、照会する必要があるユーザー。コントロールを呼び出しコードに移動します。

現在、現在のユーザーのクエリを頻繁に行うと、コントローラーで現在のユーザーを解決してデータベースコンテキストに渡すのが面倒になる場合があります。その場合は、サービスを作成して 自分自身を繰り返す必要はありません にします。そのサービスは、データベースコンテキストなどに依存して、現在のユーザーを把握できます。

しかし、データベースコンテキストをクリアするだけすべきではありませんこの循環依存関係を修正するには十分です。

7
poke

コンストラクタで実際にUserManagerが必要ない場合は、代わりにIServiceProviderへの参照を保存できます。

_private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private IServiceProvider _services;

public ApplicationContext(DbContextOptions<ApplicationContext> options,
    IHttpContextAccessor contextAccessor, IServiceProvider services)
    : base(options)
{
    _contextAccessor = contextAccessor;
    var user = _contextAccessor.HttpContext.User;
    _services = services;
}
_

その後、実際にApplicationUserが必要になったときに、たとえばGetRequiredService<ApplicationUser>()(_Microsoft.Extensions.DependencyInjection_で定義):

_var manager = _services.GetRequiredService<UserManager<ApplicationUser>>();
var user = manager.Users.FirstOrDefault(u => u.Id == _userManager.GetUserId(user));
_

もちろん、_Lazy<T>_を使用して、マネージャーまたはユーザーを初めて遅延ロードし、それへの参照を保存することができます。

一般に、@ pokeは、このような循環依存関係を回避するための再構築については正しいですが、他の誰かが同様の問題を抱えており、リファクタリングを選択できない場合に備えて、この回答はここに残してください。

10
Toby J

解決策について、Tobyに感謝します。 _Lazy<IMyService>_を使用して、使用するたびに_services.GetRequiredService<UserManager<ApplicationUser>>()を呼び出さないようにすることもできます。

_private IHttpContextAccessor _contextAccessor { get; set; }
public ApplicationUser ApplicationUser { get; set; }
private Lazy<UserManager<ApplicationUser>> _userManager;

public ApplicationContext(DbContextOptions<ApplicationContext> options,
    IHttpContextAccessor contextAccessor, IServiceProvider services)
    : base(options)
{
    _contextAccessor = contextAccessor;
    var user = _contextAccessor.HttpContext.User;
    _userManager = new Lazy<UserManager<ApplicationUser>>(() =>
                services.GetRequiredService<UserManager<ApplicationUser>>());
}
_

そしてあなたがそれを使いたいときはただ言う:

__userManager.value.doSomeThing();
_
2
محمد رضا