微软在 OWIN 实现中扩展方法 CreatePerOwinContext 的目的是啥

Posted

技术标签:

【中文标题】微软在 OWIN 实现中扩展方法 CreatePerOwinContext 的目的是啥【英文标题】:What is the purpose of the extension method CreatePerOwinContext in OWIN implementation by Microsoft微软在 OWIN 实现中扩展方法 CreatePerOwinContext 的目的是什么 【发布时间】:2014-10-29 08:56:22 【问题描述】:

我是 ASP.NET 的新手,目前正在学习 ASP.NET Identity。我知道它是建立在 Microsoft 的 OWIN 实现之上的,我也在学习这一点。所以,我在 Owin 启动代码中遇到了扩展方法 CreatePerOwinContext,我没有看到使用它的明确目的。它是某种依赖注入容器吗?该方法的真正目的是什么?应该在什么情况下应用?

【问题讨论】:

【参考方案1】:

CreatePerOwinContext 注册一个静态回调,您的应用程序将使用该回调来取回指定类型的新实例。 此回调将在每个请求中调用一次,并将对象/对象存储在 OwinContext 中,以便您可以在整个应用程序中使用它们。

假设您已经定义了自己的 IdentityDbContext 实现:

public class ApplicationDatabaseContext : IdentityDbContext<MyApplicationUser, MyRole, Guid, MyUserLogin, MyUserRole, MyUserClaim>

    public ApplicationDatabaseContext() : base("<connection string>")
    
    

    public static ApplicationDatabaseContext Create()
    
        return new ApplicationDatabaseContext();
    

        protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
        
        base.OnModelCreating(modelBuilder);

        // Customize your table creation here.

            #region USERS - INFOS

        modelBuilder.Entity<UserInfo>()
            .Property(p => p.FirstName)
            .HasColumnType("varchar")
            .HasMaxLength(70);

        modelBuilder.Entity<UserInfo>()
            .Property(p => p.LastName)
            .HasColumnType("varchar")
            .HasMaxLength(70);

        modelBuilder.Entity<UserInfo>()
            .Property(p => p.Address)
            .HasColumnType("varchar")
            .HasMaxLength(100);

        modelBuilder.Entity<UserInfo>()
            .Property(p => p.City)
            .HasColumnType("varchar")
            .HasMaxLength(100);

        modelBuilder.Entity<UserInfo>()
            .ToTable("UsersInfo");

        #endregion  
        

        public DbSet<UserInfo> UsersInfo  get; set; 

以及你对UserManager的实现:

public class ApplicationUserManager : UserManager<MyApplicationUser, Guid>

    public ApplicationUserManager(IUserStore<MyApplicationUser, Guid> store) : base(store)
        
        

        public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
        
            var manager = new ApplicationUserManager(new MyUserStore(context.Get<ApplicationDatabaseContext>()));

            manager.UserValidator = new UserValidator<MyApplicationUser, Guid>(manager)
            
                AllowOnlyAlphanumericUserNames = false,
                RequireUniqueEmail = true
            ;

            manager.PasswordValidator = new PasswordValidator()
            
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,    
                // RequireDigit = true,
                RequireLowercase = false,
                RequireUppercase = false,
            ;

            var dataProtectionProvider = options.DataProtectionProvider;

            if (dataProtectionProvider != null)
            
                manager.UserTokenProvider = new DataProtectorTokenProvider<MyApplicationUser, Guid>(dataProtectionProvider.Create("PasswordReset"));
            

            return (manager);
        

在您的 Owin Startup 中,您将注册回调:

// IAppBuilder app

app.CreatePerOwinContext<ApplicationDatabaseContext>(ApplicationDatabaseContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

将调用静态方法:

public static ApplicationDatabaseContext Create()

    return new ApplicationDatabaseContext();

public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)

    ...

现在您将能够以简单直接的方式访问您的数据库上下文和用户管理器:

ApplicationDatabaseContext dbContext = context.OwinContext.Get<ApplicationDatabaseContext>();
ApplicationUserManager userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

在您的 ApiController 中(如果您使用 WebApi):

IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
ApplicationUserManager applicationUserManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();

【讨论】:

感谢您的解释。所以它看起来像是某种系统服务定位器。在启动代码中,通过提供一个静态方法来注册服务工厂,该方法在第一次被请求时创建服务,并且可以在应用程序中的某个位置从 OwinContext 检索服务 @NextDeveloper:没错。当然,如果您使用自己的 IoC,则可以跳过该注册并使用自己的 implementation 有没有办法检索 OWIN 字典中使用的密钥?如果我使用 Nancy,我只有 Context.GetOwinEnvironment 来获取字典。那里的对象是这样的“”AspNet.Identity.Owin:WebApplication2.ApplicationDbContext,WebApplication2,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null“。最初的调用是app.CreatePerOwinContext(ApplicationDbContext .create); 谁知道这个名字? @LeftyX ,请注意澄清。您和 MSDN 声明此回调将在每个请求中调用一次,但是我只看到在第一个请求后服务器初始化时调用它。 @ajhuddy:我可以确认每个 http 请求都会调用 ApplicationDatabaseContext.Create【参考方案2】:

该方法的真正目的是什么?在什么情况下应该 申请了吗?

要更直接地回答您的问题,这是没有用的。

    这是某种 IoC 工厂,有些人喜欢使用它。 这个让你使用他们的(IoC)而不是你的选择。 (我不喜欢 IoC,对于那些想要感到温暖和模糊并使用“架构”一词的人来说,这感觉像是一种反模式。) 但是说真的,这种模式不是 IoC 接口,而是 IoC 静态工厂函数!那是谁的主意?为什么不自己使用工厂功能呢?现在您必须记住 (Google) 一个额外的 API 调用,当您在 Get 上按 F12 时,它对您毫无帮助。

那你应该怎么做呢?

就我个人而言,我非常喜欢为此使用 OO,还记得 OO 吗?佩珀里奇农场记得。使用 OO,您可以保持控制,可以调试、记录和扩展。

public class BaseApiController : ApiController

    private AppDbContext _db = null;

    protected AppDbContext db
    
        get
        
            if (_db == null)
            
                _db = AppDbContext.Create(); //Hey look a proper factory that you can extend with other overloads! And I can debug this line - neat!
            
            return _db;
        

    

    protected override void Dispose(bool disposing)
    
        if (disposing)
        
            if (_db != null)
                _db.Dispose();
        
    


所有这一切都可能是浪费时间,如果有人找到一些文档为什么 Microsoft 工程师将其放入,他们可能有充分的理由,但我对此表示怀疑,所以同时让我们支持这个答案。

更新 1

这就是为什么它存在于 Microsoft 的原因:https://blogs.msdn.microsoft.com/webdev/2014/02/12/per-request-lifetime-management-for-usermanager-class-in-asp-net-identity/

基本上,UserManager 和所有这些都是为这种结构而构建的。安全检查发生在管道中,那么为什么不将单例链接到请求以减少浪费呢?因为它被隐藏了。

我仍然建议在基类上创建自己的 db 上下文实例,这样使用起来更加简洁。如果你真的想要,你可以在你的基类中有一个属性,它从 OwinContext 中检索单例。

当我们想做的只是:

public void DoSomething()

   DemandAuthenticated();
   DemandAuthorised(typeof(somethingClass), "DoSomething");

显然,我更喜欢您可以看到的详细代码。

更新 2

EF 上下文不应作为单例保存,当然也不应通过任何 IoC 或存储库模式。

一般来说,是的,IoC 在某些情况下可能会很好。但专门针对 dbContext?没有。

1) EF DB 上下文是一个工作单元,它们应该是短暂的。如果让它们长时间运行,对象缓存会减慢查询速度,并且对底层数据库的更新/插入会变慢。它的设计寿命很短。 2) 此外,EF 上下文已经松散耦合。您可以更改连接字符串中上下文后面的 RDBMS,甚至可以仅使用内存。 3) EF 具有非常灵活、富有表现力和类型安全的 LINQ。 4) 数据库不是 IoC 的业务级服务,它是服务用来与数据库通信的工具。也许,您可能有某种通过 IoC 访问的服务 IEmail。但它应该使用一个新的 EF 上下文访问内部数据库,该上下文在完成查询后会立即处理。 5) 鉴于上述 1-4,我们当然不希望任何中间接口层(服务或存储库)首先破坏使用 EF 的所有好处。

【讨论】:

IoC 以各种方式为您提供了更多的控制权,而不是直接创建可能需要抽象出来的对象,它仍然是 OO。就在这里使用而言,它对我来说很难看,感觉就像“穷人的 IoC”。如果实施得当,IoC 会为您提供特定类型的延展性和实用性(例如单元测试),您无法通过“穷人”或直接实例化“轻松”获得。 @Hardrada 请参阅我的回答中的更新 2 - 人们理解这一点很重要。 1.是的,2。是的,3。当然,4。很公平,但是您在对象上创建的工厂仍然阻碍抽象,并且仍然需要您有一些可用和耦合的非接口类。截至 2019 年,ASP.NET EF Core,您可以将 dbcontext 作为服务添加到控制器中,并且它们的生命周期很短(按请求生命周期限定)。更加灵活和解耦。【参考方案3】:

您可以使用typeof 来获取这样的名称:

HttpContext.GetOwinContext().Get<ApplicationDbContext>(typeof(ApplicationDbContext).ToString());

【讨论】:

以上是关于微软在 OWIN 实现中扩展方法 CreatePerOwinContext 的目的是啥的主要内容,如果未能解决你的问题,请参考以下文章

1.6 OWIN集成

owin oauth发送附加参数

owin oauth 发送附加参数

owin oauth 发送附加参数

Owin WebAPI上传文件

用Web api /Nancy 通过Owin Self Host简易实现一个 Http 服务器