使用 WCF WebApi 实现工作单元

发布于 2024-12-26 02:06:56 字数 452 浏览 1 评论 0原文

我在使用新的 WCF WebApi 时面临的挑战之一是我无法干净地实现 UnitOfWork 模式。

作为快速背景,该模式的工作原理是在请求开始时启动一个工作单元,执行一些工作,然后回滚或提交该工作单元。

利用 HttpMessageHandler 功能设置“启动”工作单元的代码非常容易。并通过 Task<>库中,我可以编写一个在处理请求后执行的延续。但是,我不能总是确定是否发生了故障以便我可以回滚。

WCF 对故障具有低级支持,在传统的 WCF 服务端点中,您可以检查通道是否出现故障(例如,从 IMessageInspector 内部)。但 WebApi 似乎正在阻止这种行为。

WebApi 确实公开了 HttpErrorHandler 契约。但是,这是非常有限的,因为您无权访问实例上下文或服务实例,因此我无权访问我的工作单元。

我想知道这里还使用了哪些其他方法来实现工作单元模式?

One of the challenges I'm facing with the new WCF WebApi is that I cannot cleanly implement a UnitOfWork pattern.

As a quick background, the pattern works by starting a unit-of-work at the beginning of the request, doing some work, and then afterwards either rolling back or committing the unit of work.

It's pretty easy to setup code which will "start" the unit-of-work, utilizing the HttpMessageHandler functionality. And via Task<> library, I can write a continuation which executes after the request is handled. However, I cannot always determine if a fault occurred so that I can rollback.

WCF has low-level support for faults, and in a traditional WCF service endpoint you can check to see if the channel has faulted (for instance, from inside an IMessageInspector). But the WebApi seems to be preventing this behavior.

The WebApi does expose an HttpErrorHandler contract. However this is very limited since you don't have access to the instance context or service instance, so I don't have access to my unit-of-work.

I wonder what other approaches have been used to implement the unit-of-work pattern here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

固执像三岁 2025-01-02 02:06:56

佩德罗和达雷尔都给出了很好的建议。在我最终想出的解决方案中,我最终使用了一个消息处理程序:

public class UnitOfWorkHandler : DelegatingHandler 
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    {
        var unitOfWork = new UnitOfWork();
        unitOfWork.Begin();
        return base.SendAsync(request, cancellationToken).ContinueWith(result => 
            {
                if (result.Result is HttpResponseMessage && ((HttpResponseMessage)result.Result).IsSuccessStatusCode) 
                {
                    unitOfWork.Commit();
                } 
                else 
                {
                    unitOfWork.Rollback();
                }
                return result.Result;
            });
    }
}

我会使用 Darrel 的建议来存储对 HttpRequestMessage 属性集合上的 UnitOfWork 的引用,但是由于任务延续是作为闭包实现的,所以我可以简单地引用我在其外部范围中创建的工作单元。

Pedro and Darrel both gave great suggestions. In the solution I finally came up with, I ended up using a message handler:

public class UnitOfWorkHandler : DelegatingHandler 
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    {
        var unitOfWork = new UnitOfWork();
        unitOfWork.Begin();
        return base.SendAsync(request, cancellationToken).ContinueWith(result => 
            {
                if (result.Result is HttpResponseMessage && ((HttpResponseMessage)result.Result).IsSuccessStatusCode) 
                {
                    unitOfWork.Commit();
                } 
                else 
                {
                    unitOfWork.Rollback();
                }
                return result.Result;
            });
    }
}

I would have used Darrel's suggestion of storing a reference to the UnitOfWork on the HttpRequestMessage properties collection, however since the Task continuation is implemented as a closure I can simply reference the unit of work I created in its outer scope.

少女七分熟 2025-01-02 02:06:56

有一个挂在 HttpRequestMessage 上的属性集合,您可以将工作单元对象填充到其中,以便可以在返回路径上访问它。

There is a properties collection that hangs off HttpRequestMessage that you can probably stuff your unit of work object in so that you can access it on the return path.

悲凉≈ 2025-01-02 02:06:56

1)您可以使用操作处理程序或消息处理程序来设置和拆除工作单元(UoW)
a) 消息处理程序具有端点范围,因此它们适用于所有操作。
b) 操作处理程序具有操作范围,并且如果只有某些操作需要 UoW,则可能会很有用。

2) 正如 Darrel 所说,有关 HTTP 请求的所有上下文信息都应添加到 HttpRequestMessage 属性包中。

3) 在Web API 模型中,不存在错误(错误是SOAP 构造)。您可能应该依赖 HTTP 响应状态来检查操作是否成功(2xx)或失败(4xx、5xx)。

1) You can use operation handlers or message handlers to set up and tear down the Unit of Work (UoW)
a) Message handlers have endpoint scope, so they apply to all the operations.
b) Operation handlers have operation scope and may be useful if only some operation require the UoW.

2) As Darrel stated, all the contextual information regarding a HTTP request should be added to the HttpRequestMessage property bag.

3) In the Web API model, there aren't faults (fault is a SOAP construct). You probably should rely on the HTTP response status to check if the operation was sucessfull (2xx) or not (4xx, 5xx).

三生殊途 2025-01-02 02:06:56

我的场景:

  • 控制器(需要存储库)
  • 存储库(需要IUnitOfWork
  • 操作过滤器(也需要IUnitOfWork

问题:

DI 动作过滤器已经困扰我一段时间了。我喜欢约瑟夫的方法,我真的很喜欢。在每个请求之前创建(或开始)我的工作单元对我来说似乎很自然。由于操作过滤器可以被缓存,因此它们不使用与控制器相同的依赖范围。 因此,需要 Setter 注入。

解决方案:

以与没有操作过滤器时相同的方式配置结构图。像这样的东西:

public class NHibernateRegistry : Registry
{
    public NHibernateRegistry()
    {
        For<ISessionFactory>).Singleton().Use(
            NhSessionFactoty.Instance.SessionFactory);
        For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<NhUnitOfWork>();
        For(typeof(IBaseRepository<,>)).Use(typeof(BaseRepository<,>));
    }
}

NhSessionFactoty封装了我的休眠配置,并允许获取单个ISessionFactory在我的应用程序中使用。
NhUnitOfWork 实现我的 IUnitOfWork 接口,并处理会话和事务管理。

操作过滤器属性(改编自 Joseph 的回答):

public class UnitOfWorkAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        /* Check this line, it's a problem solver!! */
        var uow = (IUnitOfWork)actionContext.Request.GetDependencyScope()
            .GetService(typeof(IUnitOfWork));
        uow.Begin();
    }

    public override void OnActionExecuted(
        HttpActionExecutedContext actionExecutedContext)
    {
        var uow = (IUnitOfWork)actionExecutedContext.Request.GetDependencyScope()
            .GetService(typeof(IUnitOfWork));

        try
        {
            if (actionExecutedContext.Exception == null)
                uow.Commit();
            else
                uow.Rollback();
        }
        catch
        {
            uow.Rollback();
            throw;
        } 
        /*finally
        {
            uow.Dispose();
        }*/ // Resources are released in Application_EndRequest (Globals.cs)
    }
}

最后是我的基本控制器:

[UnitOfWork]
public class ControllerBaseUow : ApiController {}
/* Then, in my case, I inject Repositories via contructor */

它可以工作,因为在 OnActionExecuting< /code> 我们得到了控制器中使用的相同依赖范围。

然后像平常使用 DI 一样工作:)

My scenario:

  • Controller (needs repo)
  • Repo (needs IUnitOfWork)
  • Action filter (needs IUnitOfWork as well)

The problem:

DI in action filter has been bothering me for a while. I like Joseph's approach, I really do. Having my unit of work created (or started) before each request seems natural to me. Since action filters may be cached, they don't use the same dependency scope as controllers. Hence, Setter injection is need.

The solution:

Configure structuremap the same way as I would if I had no action filters. Something like this:

public class NHibernateRegistry : Registry
{
    public NHibernateRegistry()
    {
        For<ISessionFactory>).Singleton().Use(
            NhSessionFactoty.Instance.SessionFactory);
        For<IUnitOfWork>().HybridHttpOrThreadLocalScoped().Use<NhUnitOfWork>();
        For(typeof(IBaseRepository<,>)).Use(typeof(BaseRepository<,>));
    }
}

NhSessionFactoty encapsulates my hibernate config and allows to get a single ISessionFactory to use in my application.
NhUnitOfWork implements my IUnitOfWork interface, and handles session and transaction management.

The action filter attribute (adapted from Joseph's answer):

public class UnitOfWorkAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        /* Check this line, it's a problem solver!! */
        var uow = (IUnitOfWork)actionContext.Request.GetDependencyScope()
            .GetService(typeof(IUnitOfWork));
        uow.Begin();
    }

    public override void OnActionExecuted(
        HttpActionExecutedContext actionExecutedContext)
    {
        var uow = (IUnitOfWork)actionExecutedContext.Request.GetDependencyScope()
            .GetService(typeof(IUnitOfWork));

        try
        {
            if (actionExecutedContext.Exception == null)
                uow.Commit();
            else
                uow.Rollback();
        }
        catch
        {
            uow.Rollback();
            throw;
        } 
        /*finally
        {
            uow.Dispose();
        }*/ // Resources are released in Application_EndRequest (Globals.cs)
    }
}

and finally my base controller:

[UnitOfWork]
public class ControllerBaseUow : ApiController {}
/* Then, in my case, I inject Repositories via contructor */

It works because in OnActionExecuting we get the same dependency scope used in controllers.

then work like you normally would with DI :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文