0

In developing a WPF application that allows editing of article and carrier (pallet, racking) data (in a CRUD-fashion) I'm looking how to manage the lifecycle of the WCF clients connecting to the service that contains the actual data.

I prefer to use an MVVM approach using Caliburn Micro and StructureMap or Castle Windsor.

My main issue is not the creation of WCF client channels or factories, but more important the cleanup after use. I intend to use per-request lifecycle on the server side, as such I will need to create and dispose my clients on a per-request basis. As such I have the following in mind:

public class Article
{
    public int Id { get; set; }
    public string ArticleId { get; set; }
}

[ServiceContract]
public interface IArticleCrud
{
    [OperationContract]
    Article CreateArticle(string articleId);
    [OperationContract]
    void Delete(int articleId);
}

public class ArticlesViewModel
{
    private readonly Func<IArticleCrud> articleCrudFactory;

    public ArticlesViewModel(Func<IArticleCrud> articleCrudFactory)
    {
        this.articleCrudFactory = articleCrudFactory;
    }

    public void Delete(int articleId)
    {
        // Doesn't work since IArticleCrud is not IDisposable
        using (var crud = articleCrudFactory())
        {
            crud.Delete(articleId);
        }
    }
}

As noted in the comment this won't work because IArticleCrud is not IDisposable. IArticleCrud is used to create a ChannelFactory on the client side to generate proxies for the service implementing the same interface. I'd happily swap out this code for the following:

public class DeleteArticleCommand : IRequest
{
    public int Id { get; set; }
}

public class ArticlesViewModel
{
    private readonly IMediator mediator;

    public ArticlesViewModel(IMediator mediator)
    {
        this.mediator = mediator;
    }

    public void Delete(int articleId)
    {
        mediator.Send(new DeleteArticleCommand {Id = articleId});
    }
}

public class DeleteArticleCommandHandler : RequestHandler<DeleteArticleCommand>
{
    private readonly IArticleCrud articleCrud;

    public DeleteArticleCommandHandler(IArticleCrud articleCrud)
    {
        this.articleCrud = articleCrud;
    }

    protected override void HandleCore(DeleteArticleCommand message)
    {
        articleCrud.Delete(message.Id);
    }
}

However, this doesn't solve my problem as I'm still not dealing with the disposal of the WCF client. I could however make the IMediator create a new nested container on the Send action and have it disposed after the Send action completes, but it seems like a lot of hassle.

Am I getting it all wrong, or does it just require a lot of effort just to perform a WCF call from a WPF application?

As a sidenote, I will be having more services than just these few CRUD services, so the perhaps pragmatic solution of fixing this in my CRUD services is not an option.

mycroes
  • 645
  • 8
  • 20

2 Answers2

0

I've dealt with the same Problem (WCF-Service used in a WPF Application) and wanted to use the ServiceInterface instead the ServiceClient (which is IDisposable and can be used in a using-block).

One of the solutions to Close the Connection is to cast the Interface to the Client-type and call the .Close()-Method:

public class Article
{
    public int Id { get; set; }
    public string ArticleId { get; set; }
}

public interface IArticleCrud
{
    Article CreateArticle(string articleId);
    void Delete(int articleId);
}

public class ArticlesViewModel
{
    private readonly Func<IArticleCrud> articleCrudFactory;

    public ArticlesViewModel(Func<IArticleCrud> articleCrudFactory)
    {
        this.articleCrudFactory = articleCrudFactory;
    }

    public void Delete(int articleId)
    {
        //Using-Block doesn't work since IArticleCrud is not IDisposable
        var crud = articleCrudFactory();
        crud.Delete(articleId);

        if (crud is ArticleCrud)
            (crud as ArticleCrud).Close();
    }
}

You can also create a static method in your articleCrudFactory that will Close your IArticleCrud:

public static void CloseInterface(IArticleCrud crud)
{
    if (crud is ArticleCrud)
        (crud as ArticleCrud).Close();
    else { ... } 
}
Marcel B
  • 508
  • 2
  • 9
  • I think you perhaps mean casting to `IChannel` or `ICommunicationObject` and calling close on that, I don't have `Close()` on `IArticleCrud` and I don't even have `ArticleCrud` in the client (remember, it's just a WCF proxy). Also, `Close()` is not safe enough for me (need to try/catch and abort on error). – mycroes Mar 01 '16 at 14:02
  • Please edit your Question and specify that you are using just a WCF Proxy or else other People may think you are implementing the Service with "rightclick -> add new ServiceReference" which then generates the Interface and the Client (IArticleCrud and ArticleCrudClient in your case). – Marcel B Mar 02 '16 at 05:50
0

I've done it already with WCF and MVVM and its really easy (if I get your problem right):

public interface IRequest
{
}

public interface IRequestHandler<in TCommand> where TCommand : IRequest
{
    void HandleCore(TCommand command);
}

public class DeleteArticleCommand : IRequest
{
    public int Id { get; set; }
}

public class ArticlesViewModel
{
    private readonly IRequestHandler<DeleteArticleCommand> _handler;

    public ArticlesViewModel(IRequestHandler<DeleteArticleCommand> handler)
    {
        _handler = handler;
    }

    public void Delete(int articleId)
    {
        _handler.HandleCore(new DeleteArticleCommand { Id = articleId });
    }
}

//On client side
public sealed class WcfServiceCommandHandlerProxy<TCommand> 
    : IRequestHandler<TCommand> where TCommand : IRequest
{
    public void HandleCore(TCommand command)
    {
        using (var service = new ActuaclWcfServiceClient())
        {
            service.Send(command); //Or however you are working with you WCF client
        }
    }
}

//Somewhere on server side
public class DeleteArticleCommandHandler : IRequestHandler<DeleteArticleCommand>
{
    private readonly IArticleCrud _articleCrud;

    public DeleteArticleCommandHandler(IArticleCrud articleCrud)
    {
        _articleCrud = articleCrud;
    }

    public void HandleCore(DeleteArticleCommand message)
    {
        articleCrud.Delete(message.Id);
    }
}

Just register your IRequestHandler interface to be implemented with WcfServiceCommandHandlerProxy type and that's it:

//May vary :)
Register(typeof (ICommandHandler<>), typeof (WcfServiceCommandHandlerProxy<>))
Szer
  • 3,426
  • 3
  • 16
  • 36
  • I intended `IArticleCrud` as the ServiceContract for the WCF service, but more importantly your implementation of the WcfServiceCommandHandlerProxy is controlling the service creation, which I'd rather get from IoC to control it from one place, which brings me back to the original problem. – mycroes Mar 01 '16 at 14:22
  • @mycroes This `WcfServiceCommandHandlerProxy` doing only one thing - creating and disposing WCF client. If you need more responsibilities decorate it and wrap this one with another class. `WcfServiceCommandHandlerProxyclass` should not be change after that implementation. IoC doing nothing with lifetime right now. IoC only knows that `WcfServiceCommandHandlerProxyclass` implements `IRequestHandler`. – Szer Mar 01 '16 at 14:28
  • `new ActualWcfServiceClient()` imposes constraints elsewhere, for instance I need to do XML configuration for the addresses/bindings or put them in here as well. Also, I don't use generated WCF clients but I use the service interface to generate a proxy. – mycroes Mar 01 '16 at 14:39
  • @mycroes I don't get what is your problem really. If you don't want create it with `new` just do it through DI as usual. And if you have 5 different services just implement 5 different clients with one interface. What are you trying to achieve? – Szer Mar 01 '16 at 14:42
  • The problem would be that if I would depend on the client proxy itself then again I lose control over disposing it. However, this made me think that I might be able to DI the `ChannelFactory` and replace `new ActualWcfServiceClient()` with `channelFactory.CreateChannel()`, except for the minor annoyance that channels are not properly disposable (can throw exceptions in `Dispose()`) – mycroes Mar 02 '16 at 07:29