【问题标题】:Is there a way to pass an instance of DbContext from the controller to an ActionFilter?有没有办法将 DbContext 的实例从控制器传递到 ActionFilter?
【发布时间】:2018-02-09 22:18:39
【问题描述】:

我有一个在 ASP.NET MVC 5 框架之上使用 c# 编写的应用程序。

我通过继承ActionFilterAttribute 类创建了一个自定义操作过滤器(即ValidateCookieValueAction)。我正在为我的多个操作方法使用ValidateCookieValueAction 属性。

过滤器的目的是确保用户在允许他们进行操作之前拥有 cookie 值。尽管存在安全问题,但过滤器效果很好。但是,cookie 值本身需要在用户进入之前进行验证。

要验证 cookie 值,我需要一个 DbContext 的实例,以便查询数据库并验证 cookie 值。

我知道我可以直接在 ActionFilter 内创建 DbContext 的新实例。但是,我想这样做。我希望能够传递我已经从控制器创建的DbContext 实例,以允许我重用我已经在控制器中建立的连接。

这是我的控制器的设置方式

public class BaseController 
{
    protected IDbContext Context { get; private set; }

    protected override void Initialize(RequestContext requestContext)
    {
        base.Initialize(requestContext);

        Context = new DbContext();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            Context.Dispose();
        }

        base.Dispose(disposing);
    }
}

public class TestController : BaseController 
{
    [ValidateCookieValueAction]
    public ActionResult Index()
    {
        // the uses is in!
        return View();
    }
}

这是我的动作过滤器类

public class ValidateCookieValueAction : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        var cookie = new CookieJar(filterContext.HttpContext);
        if (!cookie.Has(ContactListCookieName))
        {
            var Url = new UrlHelper(filterContext.RequestContext);
            var url = Url.Action("Guest", "Test");

            filterContext.Result = new RedirectResult(url);
        }
    }
}

如何将Context 的实例传递给ValidateCookieValueAction

【问题讨论】:

  • 如果除了连接重用之外没有其他问题,那么继续并在您的过滤器中启动一个新的 DbContext。 EF 保留一个与数据库的开放连接池,并将获取其中一个来为新上下文提供服务。
  • @SamAxe 但框架随后会发出 2 个不同的 DbContaxt 实例,我也试图避免这种情况。
  • DbContexts 便宜并且应该有很短的寿命。您应该在您的操作过滤器中创建一个新实例。
  • @Mike:我认为将它包装在 using 块中比保留实例并传递它更可取(如果在过滤器中抛出异常,你将如何处理它,如果你这样做在过滤器本身中,控制器将如何检测到 dbcontext 已被处置...)。此外:我非常同意戴。
  • 如果你想为每个请求使用一个 dbcontext,你将需要一个带有每个请求生命周期管理器的 DI 容器。与此类似:stackoverflow.com/questions/27065538/… 将数据访问逻辑隔离到“数据层”也是一个好主意。

标签: c# asp.net-mvc asp.net-mvc-5 actionfilterattribute custom-action-filter


【解决方案1】:

这与原始问题没有直接关系,但您似乎正在自己创建一个身份验证机制。如果没有,请告诉我;我将删除此答案。

ASP.NET 已经有了 ASP.NET Identity,它比我们自己创建更安全可靠。但是,如果您想使用现有的自定义表,您可以使用 OWIN 中间件,它基本上是 ASP.NET 标识的子集。

使用 OWIN 中间件的主要优点是您可以使用 ASP.NET MVC 附带的 Authorize 属性。实施比您想象的要容易得多。

示例代码 -

OWIN Authentication Middle-ware

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = "ApplicationCookie",
            LoginPath = new PathString("/Account/Login")
        });
    }
}

Store access as role claim in Principle object

public void SignIn(User user, IList<string> roleNames)
{
    IList<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.Sid, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.GivenName, user.FirstName),
        new Claim(ClaimTypes.Surname, user.LastName),
    };

    foreach (string roleName in roleNames)
    {
        claims.Add(new Claim(ClaimTypes.Role, roleName));
    }

    ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationType);

    IOwinContext context = _context.Request.GetOwinContext();
    IAuthenticationManager authenticationManager = context.Authentication;

    authenticationManager.SignIn(identity);
}

用法

[Authorize(Roles = "CanViewHome")]
public class IndexController : Controller
{
    [Authorize(Roles = "CanEditHome")]
    public ActionResult Edit()
    {
        return View();
    }
}

【讨论】:

  • 有没有办法在登录后向用户添加新的声明?如果我可以向用户现有声明添加 a.l 新声明(身份验证后的方式),我正在做的事情可以更改为使用声明
  • 是的,您只需先调用SignOut方法退出,然后再次调用SignIn方法为同一请求中的同一用户创建新的身份验证cookie。
  • 虽然,这并不能回答我关于“有没有办法将 DbContext 的实例从控制器传递到 ActionFilter?”的问题?但确实导致了一个解决方案可能比我想要做的更好。谢谢你的回答
  • @MikeA 您可以添加声明而无需再次调用此登录/注销,但我认为这样做相当简单。如果您想要替代方案,请参阅stackoverflow.com/questions/24587414/…
【解决方案2】:

默认情况下,当前上下文中已经存储了一个上下文。

在 Startup.Configuration(IAppBuilder 应用程序)中

app.CreatePerOwinContext(ApplicationDbContext.Create); // this already stores a DbContext

你可以用

var dbContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>();

或者,您可以将上下文存储在 [TempData] 或 [Session] 中。
如果您在同一个 Request 中重新获取它,那么只需将 DbContext 扔到 Context.Items[] 中,这些只会持续到当前 Request。

在 OnActionExecuting 中:

filterContext.Controller.TempData

在控制器中:

this.TempData

【讨论】:

    猜你喜欢
    • 2021-12-28
    • 1970-01-01
    • 2020-03-02
    • 2010-11-07
    • 2019-10-23
    • 1970-01-01
    • 2023-02-18
    • 1970-01-01
    • 2021-02-17
    相关资源
    最近更新 更多