Cách giải quyết tốt nhất cho ứng dụng WCF `sử dụng vấn đề chặn 'là gì?


404

Tôi thích khởi tạo các máy khách dịch vụ WCF của mình trong một usingkhối vì đó là cách tiêu chuẩn để sử dụng các tài nguyên triển khai IDisposable:

using (var client = new SomeWCFServiceClient()) 
{
    //Do something with the client 
}

Nhưng, như đã lưu ý trong bài viết MSDN này , việc gói một ứng dụng khách WCF trong một usingkhối có thể che giấu bất kỳ lỗi nào dẫn đến việc máy khách bị chuyển sang trạng thái bị lỗi (như thời gian chờ hoặc sự cố giao tiếp). Câu chuyện dài, khi Dispose () được gọi, phương thức Close () của máy khách sẽ kích hoạt, nhưng đưa ra một lỗi vì nó ở trạng thái bị lỗi. Ngoại lệ ban đầu sau đó bị che bởi ngoại lệ thứ hai. Không tốt.

Cách giải quyết được đề xuất trong bài viết MSDN là hoàn toàn tránh sử dụng một usingkhối và thay vào đó, khởi tạo ứng dụng khách của bạn và sử dụng chúng như thế này:

try
{
    ...
    client.Close();
}
catch (CommunicationException e)
{
    ...
    client.Abort();
}
catch (TimeoutException e)
{
    ...
    client.Abort();
}
catch (Exception e)
{
    ...
    client.Abort();
    throw;
}

So với usingkhối, tôi nghĩ đó là xấu xí. Và rất nhiều mã để viết mỗi khi bạn cần một khách hàng.

May mắn thay, tôi đã tìm thấy một vài cách giải quyết khác, chẳng hạn như cách giải quyết này trên IServiceOrients. Bạn bắt đầu với:

public delegate void UseServiceDelegate<T>(T proxy); 

public static class Service<T> 
{ 
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock) 
    { 
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel(); 
        bool success = false; 
        try 
        { 
            codeBlock((T)proxy); 
            proxy.Close(); 
            success = true; 
        } 
        finally 
        { 
            if (!success) 
            { 
                proxy.Abort(); 
            } 
        } 
     } 
} 

Mà sau đó cho phép:

Service<IOrderService>.Use(orderService => 
{ 
    orderService.PlaceOrder(request); 
}); 

Điều đó không tệ, nhưng tôi không nghĩ nó biểu cảm và dễ hiểu như usingkhối.

Cách giải quyết hiện tại tôi đang cố gắng sử dụng Tôi lần đầu tiên đọc về blog.davidbarret.net . Về cơ bản, bạn ghi đè Dispose()phương thức của khách hàng bất cứ nơi nào bạn sử dụng nó. Cái gì đó như:

public partial class SomeWCFServiceClient : IDisposable
{
    void IDisposable.Dispose() 
    {
        if (this.State == CommunicationState.Faulted) 
        {
            this.Abort();
        } 
        else 
        {
            this.Close();
        }
    }
}

Điều này dường như có thể cho phép usingkhối một lần nữa mà không có nguy cơ che giấu một ngoại lệ trạng thái bị lỗi.

Vì vậy, có bất kỳ vấn đề nào khác mà tôi phải chú ý khi sử dụng các cách giải quyết này không? Có ai nghĩ ra cái gì tốt hơn không?


42
Cái cuối cùng (kiểm tra cái này. State) là một cuộc đua; nó có thể không bị lỗi khi bạn kiểm tra boolean, nhưng có thể bị lỗi khi bạn gọi Close ().
Brian

15
Bạn đọc trạng thái; nó không có lỗi. Trước khi bạn gọi Đóng (), lỗi kênh. Đóng () ném. Trò chơi kết thúc.
Brian

4
Thơi gian trôi. Nó có thể là một khoảng thời gian rất ngắn, nhưng về mặt kỹ thuật, trong khoảng thời gian giữa việc kiểm tra trạng thái của kênh và yêu cầu đóng, trạng thái của kênh có thể thay đổi.
Eric King

8
Tôi sẽ sử dụng Action<T>thay vì UseServiceDelegate<T>. diễn viên phụ.
hIpPy

2
Tôi thực sự không thích trình trợ giúp tĩnh này Service<T>vì nó làm phức tạp việc kiểm tra đơn vị (như hầu hết mọi thứ tĩnh đều làm). Tôi thích nó là không tĩnh để nó có thể được đưa vào lớp đang sử dụng nó.
Fabio Marreco

Câu trả lời:


137

Trên thực tế, mặc dù tôi đã viết blog (xem câu trả lời của Luke ), tôi nghĩ rằng điều này tốt hơn so với trình bao bọc IDis Dùng của tôi. Mã tiêu biểu:

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
}); 

(chỉnh sửa mỗi bình luận)

Usetrả về void, cách dễ nhất để xử lý các giá trị trả về là thông qua một biến được bắt:

int newOrderId = 0; // need a value for definite assignment
Service<IOrderService>.Use(orderService=>
  {
    newOrderId = orderService.PlaceOrder(request);
  });
Console.WriteLine(newOrderId); // should be updated

2
@MarcGravell Tôi có thể tiêm khách hàng đó ở đâu? Tôi giả sử rằng ChannelFactory tạo máy khách và đối tượng nhà máy được tạo mới bên trong lớp Service, điều đó có nghĩa là mã phải được cấu trúc lại một chút để cho phép một nhà máy tùy chỉnh. Điều này có đúng không, hay tôi đang thiếu một cái gì đó rõ ràng ở đây?
Anttu

16
Bạn có thể dễ dàng sửa đổi trình bao bọc để bạn không cần một biến chụp cho kết quả. Một cái gì đó như thế này: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
chris

3
Có thể hữu ích https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/https://devzone.channeladam.com/articles/2014/09/how-to-easily-call-wcf-service-properly/http://dzimchuk.net/post/wcf-error-helpers
PreguntonCojoneroCabrón

Làm cách nào để thêm thông tin xác thực bằng cách này?
Hippasus

2
Theo tôi, giải pháp đúng nhất sẽ là: 1) Thực hiện mô hình Đóng / Hủy bỏ mà không có điều kiện cuộc đua 2) Xử lý tình huống khi hoạt động dịch vụ ném ngoại lệ 3) Xử lý các tình huống khi cả hai phương pháp Đóng và Hủy bỏ ngoại lệ 4) Xử lý các trường hợp ngoại lệ không đồng bộ, chẳng hạn như https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
ThreadAdortException

88

Đưa ra lựa chọn giữa giải pháp được ủng hộ bởi IServiceOrients.com và giải pháp được ủng hộ bởi blog của David Barret , tôi thích sự đơn giản được cung cấp bằng cách ghi đè phương thức Dispose () của khách hàng. Điều này cho phép tôi tiếp tục sử dụng câu lệnh use () như người ta mong đợi với một đối tượng dùng một lần. Tuy nhiên, như @Brian đã chỉ ra, giải pháp này chứa một điều kiện chủng tộc trong đó Nhà nước có thể không bị lỗi khi được kiểm tra nhưng có thể là vào thời điểm Close () được gọi, trong trường hợp đó là CommunicationException vẫn xảy ra.

Vì vậy, để giải quyết vấn đề này, tôi đã sử dụng một giải pháp kết hợp tốt nhất của cả hai thế giới.

void IDisposable.Dispose()
{
    bool success = false;
    try 
    {
        if (State != CommunicationState.Faulted) 
        {
            Close();
            success = true;
        }
    } 
    finally 
    {
        if (!success) 
            Abort();
    }
}

2
không phải là rủi ro khi sử dụng câu lệnh 'Thử cuối cùng' (hoặc đường cú pháp - "bằng cách sử dụng () {}") với các tài nguyên không được quản lý? Trong trường hợp cụ thể, nếu tùy chọn "Đóng" không thành công, ngoại lệ sẽ không bị bắt và cuối cùng có thể không chạy. Ngoài ra, nếu có một ngoại lệ trong tuyên bố cuối cùng, nó có thể che dấu các ngoại lệ khác. Tôi nghĩ đó là lý do tại sao Try-Catch được ưa thích.
Zack Jannsen

Zack, không rõ ràng về đối tượng của bạn; tôi đang thiếu gì Nếu phương thức Đóng ném một ngoại lệ, khối cuối cùng sẽ thực thi trước khi ngoại lệ được ném lên. Đúng?
Patrick Szalapski

1
@jmoreno, tôi đã gỡ bỏ chỉnh sửa của bạn. Nếu bạn chú ý, không có khối bắt nào trong phương thức. Ý tưởng là bất kỳ ngoại lệ nào xảy ra (thậm chí cuối cùng) nên được ném, không được âm thầm bắt.
Matt Davis

5
@MattDavis Tại sao bạn cần successcờ? Tại sao không try { Close(); } catch { Abort(); throw; }?
Konstantin Spirin

Còn về việc thử / bắt xung quanh Close(); success = true;thì sao? Tôi sẽ không muốn ném ngoại lệ nếu tôi có thể hủy bỏ nó thành công trong khối cuối cùng. Tôi chỉ muốn ném ngoại lệ nếu Abort () thất bại trong trường hợp đó. Bằng cách này, thử / bắt sẽ ẩn ngoại lệ điều kiện cuộc đua tiềm năng và vẫn cho phép bạn hủy bỏ () kết nối trong khối cuối cùng.
goku_da_master

32

Tôi đã viết một hàm bậc cao hơn để làm cho nó hoạt động đúng. Chúng tôi đã sử dụng điều này trong một số dự án và nó dường như hoạt động rất tốt. Đây là cách mọi thứ nên được thực hiện ngay từ đầu, mà không cần mô hình "sử dụng" hay như vậy.

TReturn UseService<TChannel, TReturn>(Func<TChannel, TReturn> code)
{
    var chanFactory = GetCachedFactory<TChannel>();
    TChannel channel = chanFactory.CreateChannel();
    bool error = true;
    try {
        TReturn result = code(channel);
        ((IClientChannel)channel).Close();
        error = false;
        return result;
    }
    finally {
        if (error) {
            ((IClientChannel)channel).Abort();
        }
    }
}

Bạn có thể thực hiện cuộc gọi như thế này:

int a = 1;
int b = 2;
int sum = UseService((ICalculator calc) => calc.Add(a, b));
Console.WriteLine(sum);

Điều này là khá nhiều giống như bạn có trong ví dụ của bạn. Trong một số dự án, chúng tôi viết các phương thức trợ giúp được gõ mạnh, vì vậy cuối cùng chúng tôi viết những thứ như "Wcf.UseFooService (f => f ...)".

Tôi thấy nó khá thanh lịch, tất cả mọi thứ xem xét. Có một vấn đề cụ thể bạn gặp phải?

Điều này cho phép các tính năng tiện lợi khác được cắm vào. Ví dụ, trên một trang web, trang web xác thực dịch vụ thay mặt cho người dùng đã đăng nhập. (Trang web không có thông tin xác thực.) Bằng cách viết trình trợ giúp phương thức "UseService" của riêng chúng tôi, chúng tôi có thể định cấu hình nhà máy kênh theo cách chúng tôi muốn, v.v. Chúng tôi cũng không bị ràng buộc sử dụng proxy được tạo - mọi giao diện sẽ làm .


Tôi đang nhận được ngoại lệ: Thuộc tính Địa chỉ trên ChannelFactory.Endpoint là null. Điểm cuối của ChannelFactory phải có Địa chỉ hợp lệ được chỉ định . GetCachedFactoryPhương pháp là gì?
Marshall

28

Đây là cách được Microsoft khuyến nghị để xử lý các cuộc gọi khách WCF:

Để biết thêm chi tiết, xem: Ngoại lệ dự kiến

try
{
    ...
    double result = client.Add(value1, value2);
    ...
    client.Close();
}
catch (TimeoutException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}
catch (CommunicationException exception)
{
    Console.WriteLine("Got {0}", exception.GetType());
    client.Abort();
}

Thông tin bổ sung Vì vậy, nhiều người dường như đang hỏi câu hỏi này trên WCF rằng Microsoft thậm chí đã tạo một mẫu chuyên dụng để trình bày cách xử lý các trường hợp ngoại lệ:

c: \ WF_WCF_Samples \ WCF \ Basic \ Client \ ExpectedExceptions \ CS \ client

Tải xuống mẫu: C # hoặc VB

Xem xét rằng có rất nhiều vấn đề liên quan đến tuyên bố sử dụng , (nóng lên?) Các cuộc thảo luậnchủ đề nội bộ về vấn đề này, tôi sẽ không lãng phí thời gian của mình để cố gắng trở thành một cao bồi mã và tìm cách sạch hơn. Tôi sẽ chỉ sử dụng nó và triển khai các ứng dụng khách WCF theo cách dài dòng (chưa đáng tin cậy) này cho các ứng dụng máy chủ của tôi.

Thất bại bổ sung tùy chọn để bắt

Nhiều trường hợp ngoại lệ xuất phát từ CommunicationExceptionvà tôi không nghĩ rằng hầu hết các ngoại lệ đó nên được thử lại. Tôi đã tìm hiểu từng ngoại lệ trên MSDN và tìm thấy một danh sách ngắn các trường hợp ngoại lệ có thể thử lại (ngoài các điều TimeOutExceptiontrên). Hãy cho tôi biết nếu tôi bỏ lỡ một ngoại lệ nên được thử lại.

  // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
catch (ChannelTerminatedException cte)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
// reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
catch (EndpointNotFoundException enfe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

// The following exception that is thrown when a server is too busy to accept a message.
catch (ServerTooBusyException stbe)
{
secureSecretService.Abort();
// todo: Implement delay (backoff) and retry
}

Phải thừa nhận rằng đây là một chút mã trần tục để viết. Tôi hiện đang thích câu trả lời này và không thấy bất kỳ "hack" nào trong mã đó có thể gây ra sự cố.


1
Là mã từ mẫu vẫn gây ra vấn đề? Tôi đã thử chạy dự án Sử dụng (VS2013) nhưng dòng với "Hope this code wasn't important, because it might not happen."vẫn được thực thi ...
janv8000

14

Cuối cùng tôi đã tìm thấy một số bước vững chắc hướng tới một giải pháp sạch cho vấn đề này.

Công cụ tùy chỉnh này mở rộng WCFProxyGenerator để cung cấp proxy xử lý ngoại lệ. Nó tạo ra một proxy bổ sung được gọi là ExceptionHandlingProxy<T>kế thừa ExceptionHandlingProxyBase<T>- cái sau thực hiện chức năng của proxy. Kết quả là bạn có thể chọn sử dụng proxy mặc định kế thừa ClientBase<T>hoặc ExceptionHandlingProxy<T>đóng gói việc quản lý vòng đời của nhà máy và kênh của kênh. ExceptionHandlingProxy tôn trọng các lựa chọn của bạn trong hộp thoại Thêm tham chiếu dịch vụ đối với các phương thức và loại bộ sưu tập không đồng bộ.

Codeplex có một dự án gọi là Exception Xử lý Trình tạo Proxy WCF . Về cơ bản, nó cài đặt một công cụ tùy chỉnh mới cho Visual Studio 2008, sau đó sử dụng công cụ này để tạo proxy dịch vụ mới (Thêm tham chiếu dịch vụ) . Nó có một số chức năng tốt để xử lý các kênh bị lỗi, thời gian chờ và xử lý an toàn. Có một video tuyệt vời ở đây được gọi là ExceptionHandlingProxyWrapper giải thích chính xác cách thức hoạt động của nó.

Bạn có thể sử dụng lại Usingcâu lệnh một cách an toàn và nếu kênh bị lỗi theo bất kỳ yêu cầu nào (TimeoutException hoặc CommunicationException), Wrapper sẽ khởi tạo lại kênh bị lỗi và thử lại truy vấn. Nếu thất bại thì nó sẽ gọi Abort()lệnh và loại bỏ proxy và lấy lại Ngoại lệ. Nếu dịch vụ ném FaultExceptionmã, nó sẽ dừng thực thi và proxy sẽ bị hủy bỏ một cách an toàn, ném ngoại lệ chính xác như mong đợi.


@Shimmy Trạng thái Beta. Ngày: Thứ bảy 11 tháng 7 năm 2009 bởi Michele Bustamante . Dự án chết?
Kiquenet

11

Dựa trên câu trả lời của Marc Gravell, MichaelGG và Matt Davis, các nhà phát triển của chúng tôi đã đưa ra những điều sau đây:

public static class UsingServiceClient
{
    public static void Do<TClient>(TClient client, Action<TClient> execute)
        where TClient : class, ICommunicationObject
    {
        try
        {
            execute(client);
        }
        finally
        {
            client.DisposeSafely();
        }
    }

    public static void DisposeSafely(this ICommunicationObject client)
    {
        if (client == null)
        {
            return;
        }

        bool success = false;

        try
        {
            if (client.State != CommunicationState.Faulted)
            {
                client.Close();
                success = true;
            }
        }
        finally
        {
            if (!success)
            {
                client.Abort();
            }
        }
    }
}

Ví dụ sử dụng:

string result = string.Empty;

UsingServiceClient.Do(
    new MyServiceClient(),
    client =>
    result = client.GetServiceResult(parameters));

Càng gần với cú pháp "sử dụng" càng tốt, bạn không phải trả về giá trị giả khi gọi phương thức void và bạn có thể thực hiện nhiều cuộc gọi đến dịch vụ (và trả lại nhiều giá trị) mà không phải sử dụng bộ dữ liệu.

Ngoài ra, bạn có thể sử dụng điều này với ClientBase<T>con cháu thay vì ChannelFactory nếu muốn.

Phương thức tiện ích mở rộng được hiển thị nếu nhà phát triển muốn loại bỏ proxy / kênh theo cách thủ công thay thế.


Việc sử dụng điều này có ý nghĩa nếu tôi đang sử dụng PoolingDuplex và không đóng kết nối sau một cuộc gọi để dịch vụ khách hàng của tôi có thể tồn tại thậm chí vài ngày và xử lý các cuộc gọi lại máy chủ. Theo như tôi hiểu giải pháp được thảo luận ở đây có ý nghĩa cho một cuộc gọi mỗi phiên?
sll

@sll - đây là để đóng kết nối ngay sau khi cuộc gọi trở lại (một cuộc gọi mỗi phiên).
TrueWill

@cacho Làm cho DisposeSafelyriêng tư chắc chắn là một lựa chọn, và sẽ tránh nhầm lẫn. Có thể có những trường hợp sử dụng mà ai đó muốn gọi trực tiếp, nhưng tôi không thể đưa ra một cách trực tiếp.
TrueWill

@truewill chỉ để làm tài liệu, điều quan trọng là phải đề cập rằng phương pháp này là an toàn chủ đề phải không?
Cacho Santa

1
Theo tôi, giải pháp đúng nhất sẽ là: 1) Thực hiện mô hình Đóng / Hủy bỏ mà không có điều kiện cuộc đua 2) Xử lý tình huống khi hoạt động dịch vụ ném ngoại lệ 3) Xử lý các tình huống khi cả hai phương pháp Đóng và Hủy bỏ ngoại lệ 4) Xử lý các trường hợp ngoại lệ không đồng bộ, chẳng hạn như https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
ThreadAdortException

8

@Marc Gravell

Sẽ không ổn khi sử dụng cái này:

public static TResult Using<T, TResult>(this T client, Func<T, TResult> work)
        where T : ICommunicationObject
{
    try
    {
        var result = work(client);

        client.Close();

        return result;
    }
    catch (Exception e)
    {
        client.Abort();

        throw;
    }
}

Hoặc, điều tương tự (Func<T, TResult>)trong trường hợpService<IOrderService>.Use

Điều này sẽ làm cho các biến trở lại dễ dàng hơn.


2
+1 @MarcGravell Tôi nghĩ rằng câu trả lời của bạn 'cũng có thể làm tốt hơn': P (và hành động có thể được thực hiện theo thuật ngữ Func với trả về null). Toàn bộ trang này là một mớ hỗn độn - Tôi sẽ xây dựng một trang thống nhất và nhận xét về các dups nếu tôi dự tính sử dụng WCF bất cứ lúc nào trong thập kỷ này ...
Ruben Bartelink

7

Cái này là cái gì?

Đây là phiên bản CW của câu trả lời được chấp nhận nhưng bao gồm (những gì tôi cho là hoàn chỉnh) Bao gồm xử lý ngoại lệ.

Câu trả lời được chấp nhận tham khảo trang web này không còn tồn tại . Để cứu bạn, tôi bao gồm những phần có liên quan nhất ở đây. Ngoài ra, tôi đã sửa đổi nó một chút để bao gồm xử lý thử lại ngoại lệ để xử lý các thời gian chờ mạng phiền phức đó.

Cách sử dụng máy khách WCF đơn giản

Khi bạn tạo proxy phía máy khách của mình, đây là tất cả những gì bạn cần để thực hiện nó.

Service<IOrderService>.Use(orderService=>
{
  orderService.PlaceOrder(request);
});

ServiceDelegate.cs

Thêm tập tin này vào giải pháp của bạn. Không có thay đổi nào là cần thiết cho tệp này, trừ khi bạn muốn thay đổi số lần thử lại hoặc ngoại lệ nào bạn muốn xử lý.

public delegate void UseServiceDelegate<T>(T proxy);

public static class Service<T>
{
    public static ChannelFactory<T> _channelFactory = new ChannelFactory<T>(""); 

    public static void Use(UseServiceDelegate<T> codeBlock)
    {
        IClientChannel proxy = (IClientChannel)_channelFactory.CreateChannel();
        bool success = false;


       Exception mostRecentEx = null;
       int millsecondsToSleep = 1000;

       for(int i=0; i<5; i++)  // Attempt a maximum of 5 times 
       {
           try
           {
               codeBlock((T)proxy);
               proxy.Close();
               success = true; 
               break;
           }

           // The following is typically thrown on the client when a channel is terminated due to the server closing the connection.
           catch (ChannelTerminatedException cte)
           {
              mostRecentEx = cte;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep  * (i + 1)); 
           }

           // The following is thrown when a remote endpoint could not be found or reached.  The endpoint may not be found or 
           // reachable because the remote endpoint is down, the remote endpoint is unreachable, or because the remote network is unreachable.
           catch (EndpointNotFoundException enfe)
           {
              mostRecentEx = enfe;
               proxy.Abort();
               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }

           // The following exception that is thrown when a server is too busy to accept a message.
           catch (ServerTooBusyException stbe)
           {
              mostRecentEx = stbe;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch (TimeoutException timeoutEx)
           {
               mostRecentEx = timeoutEx;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           } 
           catch (CommunicationException comException)
           {
               mostRecentEx = comException;
               proxy.Abort();

               //  delay (backoff) and retry 
               Thread.Sleep(millsecondsToSleep * (i + 1)); 
           }
           catch(Exception )
           {
                // rethrow any other exception not defined here
                // You may want to define a custom Exception class to pass information such as failure count, and failure type
                proxy.Abort();
                throw ;  
           }
       }
       if (success == false && mostRecentEx != null) 
       { 
           proxy.Abort();
           throw new Exception("WCF call failed after 5 retries.", mostRecentEx );
       }

    }
}

PS: Tôi đã tạo bài đăng này trên wiki cộng đồng. Tôi sẽ không thu thập "điểm" từ câu trả lời này, nhưng tốt hơn là bạn nâng cấp nó nếu bạn đồng ý với việc thực hiện hoặc chỉnh sửa nó để làm cho nó tốt hơn.


Tôi không chắc chắn tôi đồng ý với đặc điểm của bạn về câu trả lời này. Đó là phiên bản CW với ý tưởng xử lý ngoại lệ của bạn được thêm vào.
John Saunders

@JohnSaunders - Đúng (khái niệm xử lý ngoại lệ của tôi). Hãy cho tôi biết bất kỳ trường hợp ngoại lệ nào tôi đang thiếu hoặc đang xử lý sai.
goodguys_activate

Điều gì về biến thành công? Nó cần thêm vào mã nguồn: if (thành công) return; ??
Kiquenet

Nếu cuộc gọi đầu tiên ném và cuộc gọi thứ 2 thành công thì mostRecentEx sẽ không có giá trị vì vậy bạn sẽ ném một ngoại lệ không thành công trong 5 lần thử lại. hoặc tôi đang thiếu một cái gì đó? Tôi không thấy nơi bạn xóa mostRecentEx nếu lần thử thứ 2, 3, 4 hoặc 5 thành công. Cũng không thấy sự trở lại o thành công. Tôi nên thiếu một cái gì đó ở đây, nhưng mã này sẽ không chạy luôn 5 lần nếu không có ngoại lệ nào được ném?
Bart Calix đến

@Bart - Tôi đã thêm vào success == falsecâu lệnh if cuối cùng
goodguys_activate

7

Dưới đây là phiên bản nâng cao của nguồn từ câu hỏi và được mở rộng để lưu trữ nhiều nhà máy kênh và cố gắng tra cứu điểm cuối trong tệp cấu hình theo tên hợp đồng.

Nó sử dụng .NET 4 (cụ thể: chống chỉ định, LINQ, var):

/// <summary>
/// Delegate type of the service method to perform.
/// </summary>
/// <param name="proxy">The service proxy.</param>
/// <typeparam name="T">The type of service to use.</typeparam>
internal delegate void UseServiceDelegate<in T>(T proxy);

/// <summary>
/// Wraps using a WCF service.
/// </summary>
/// <typeparam name="T">The type of service to use.</typeparam>
internal static class Service<T>
{
    /// <summary>
    /// A dictionary to hold looked-up endpoint names.
    /// </summary>
    private static readonly IDictionary<Type, string> cachedEndpointNames = new Dictionary<Type, string>();

    /// <summary>
    /// A dictionary to hold created channel factories.
    /// </summary>
    private static readonly IDictionary<string, ChannelFactory<T>> cachedFactories =
        new Dictionary<string, ChannelFactory<T>>();

    /// <summary>
    /// Uses the specified code block.
    /// </summary>
    /// <param name="codeBlock">The code block.</param>
    internal static void Use(UseServiceDelegate<T> codeBlock)
    {
        var factory = GetChannelFactory();
        var proxy = (IClientChannel)factory.CreateChannel();
        var success = false;

        try
        {
            using (proxy)
            {
                codeBlock((T)proxy);
            }

            success = true;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }

    /// <summary>
    /// Gets the channel factory.
    /// </summary>
    /// <returns>The channel factory.</returns>
    private static ChannelFactory<T> GetChannelFactory()
    {
        lock (cachedFactories)
        {
            var endpointName = GetEndpointName();

            if (cachedFactories.ContainsKey(endpointName))
            {
                return cachedFactories[endpointName];
            }

            var factory = new ChannelFactory<T>(endpointName);

            cachedFactories.Add(endpointName, factory);
            return factory;
        }
    }

    /// <summary>
    /// Gets the name of the endpoint.
    /// </summary>
    /// <returns>The name of the endpoint.</returns>
    private static string GetEndpointName()
    {
        var type = typeof(T);
        var fullName = type.FullName;

        lock (cachedFactories)
        {
            if (cachedEndpointNames.ContainsKey(type))
            {
                return cachedEndpointNames[type];
            }

            var serviceModel = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).SectionGroups["system.serviceModel"] as ServiceModelSectionGroup;

            if ((serviceModel != null) && !string.IsNullOrEmpty(fullName))
            {
                foreach (var endpointName in serviceModel.Client.Endpoints.Cast<ChannelEndpointElement>().Where(endpoint => fullName.EndsWith(endpoint.Contract)).Select(endpoint => endpoint.Name))
                {
                    cachedEndpointNames.Add(type, endpointName);
                    return endpointName;
                }
            }
        }

        throw new InvalidOperationException("Could not find endpoint element for type '" + fullName + "' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.");
    }
}

1
Tại sao sử dụng UseServiceDelegate<T>thay vì Action<T>?
Mike Mayer

1
Lý do duy nhất tôi có thể nghĩ rằng tác giả ban đầu đã làm như vậy là để có một đại biểu được đánh máy mạnh mẽ mà nhà phát triển sẽ biết thuộc về việc gọi một dịch vụ. Nhưng, theo như tôi thấy, Action<T>hoạt động tốt như vậy.
Jesse C. Choper 30/03/13

5

Một trình bao bọc như thế này sẽ hoạt động:

public class ServiceClientWrapper<ServiceType> : IDisposable
{
    private ServiceType _channel;
    public ServiceType Channel
    {
        get { return _channel; }
    }

    private static ChannelFactory<ServiceType> _channelFactory;

    public ServiceClientWrapper()
    {
        if(_channelFactory == null)
             // Given that the endpoint name is the same as FullName of contract.
            _channelFactory = new ChannelFactory<ServiceType>(typeof(T).FullName);
        _channel = _channelFactory.CreateChannel();
        ((IChannel)_channel).Open();
    }

    public void Dispose()
    {
        try
        {
            ((IChannel)_channel).Close();
        }
        catch (Exception e)
        {
            ((IChannel)_channel).Abort();
            // TODO: Insert logging
        }
    }
}

Điều đó sẽ cho phép bạn viết mã như:

ResponseType response = null;
using(var clientWrapper = new ServiceClientWrapper<IService>())
{
    var request = ...
    response = clientWrapper.Channel.MyServiceCall(request);
}
// Use your response object.

Trình bao bọc tất nhiên có thể bắt được nhiều ngoại lệ hơn nếu điều đó là bắt buộc, nhưng nguyên tắc vẫn giữ nguyên.


Tôi nhớ cuộc thảo luận về việc Vứt bỏ không được gọi trong một số điều kiện nhất định ... dẫn đến rò rỉ bộ nhớ w / WCF.
goodguys_activate

Tôi không chắc nó đã dẫn đến rò rỉ bộ nhớ nhưng vấn đề là ở đây. Khi bạn gọi Disposetrên IChannel, nó có thể ném ngoại lệ nếu kênh ở trạng thái bị lỗi, đây là một vấn đề vì Microsoft chỉ định rằng Disposekhông bao giờ nên ném. Vì vậy, những gì mã ở trên là xử lý trường hợp khi Closeném một ngoại lệ. Nếu Abortném nó có thể là một cái gì đó sai nghiêm trọng. Tôi đã viết một bài đăng trên blog về nó vào tháng 12 năm ngoái: blog.tomasjansson.com/2010/12/disposeible-wcf-client-wrapper
Tomas Jansson

4

Tôi đã sử dụng proxy động Castle để giải quyết vấn đề Dispose () và cũng đã triển khai tự động làm mới kênh khi nó ở trạng thái không sử dụng được. Để sử dụng điều này, bạn phải tạo một giao diện mới kế thừa hợp đồng dịch vụ và IDis Dùng của bạn. Proxy động thực hiện giao diện này và bao bọc kênh WCF:

Func<object> createChannel = () =>
    ChannelFactory<IHelloWorldService>
        .CreateChannel(new NetTcpBinding(), new EndpointAddress(uri));
var factory = new WcfProxyFactory();
var proxy = factory.Create<IDisposableHelloWorldService>(createChannel);
proxy.HelloWorld();

Tôi thích điều này vì bạn có thể tiêm dịch vụ WCF mà không cần người tiêu dùng phải lo lắng về bất kỳ chi tiết nào của WCF. Và không có thêm hành trình như các giải pháp khác.

Hãy nhìn vào mã, nó thực sự khá đơn giản: WCF Dynamic Proxy


4

Sử dụng phương pháp mở rộng:

public static class CommunicationObjectExtensions
{
    public static TResult MakeSafeServiceCall<TResult, TService>(this TService client, Func<TService, TResult> method) where TService : ICommunicationObject
    {
        TResult result;

        try
        {
            result = method(client);
        }
        finally
        {
            try
            {
                client.Close();
            }
            catch (CommunicationException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (TimeoutException)
            {
                client.Abort(); // Don't care about these exceptions. The call has completed anyway.
            }
            catch (Exception)
            {
                client.Abort();
                throw;
            }
        }

        return result;
    }
}

4

Nếu bạn không cần IoC hoặc đang sử dụng ứng dụng khách được tạo tự động (Tham chiếu dịch vụ), thì bạn có thể đơn giản sử dụng trình bao bọc để quản lý việc đóng và để cho GC lấy cơ sở khách khi nó ở trạng thái an toàn sẽ không ném bất kỳ ngoại lệ nào. GC sẽ gọi Dispose in serviceclient, và điều này sẽ gọi Close. Vì nó đã được đóng lại, nó không thể gây ra bất kỳ thiệt hại nào. Tôi đang sử dụng mà không có vấn đề trong mã sản xuất.

public class AutoCloseWcf : IDisposable
{

    private ICommunicationObject CommunicationObject;

    public AutoDisconnect(ICommunicationObject CommunicationObject)
    {
        this.CommunicationObject = CommunicationObject;
    }

    public void Dispose()
    {
        if (CommunicationObject == null)
            return;
        try {
            if (CommunicationObject.State != CommunicationState.Faulted) {
                CommunicationObject.Close();
            } else {
                CommunicationObject.Abort();
            }
        } catch (CommunicationException ce) {
            CommunicationObject.Abort();
        } catch (TimeoutException toe) {
            CommunicationObject.Abort();
        } catch (Exception e) {
            CommunicationObject.Abort();
            //Perhaps log this

        } finally {
            CommunicationObject = null;
        }
    }
}

Sau đó, khi bạn đang truy cập vào máy chủ, bạn tạo ứng dụng khách và sử dụng usingtrong phần tự động phát hiện:

var Ws = new ServiceClient("netTcpEndPointName");
using (new AutoCloseWcf(Ws)) {

    Ws.Open();

    Ws.Test();
}

3

Tóm lược

Sử dụng các kỹ thuật được mô tả trong câu trả lời này, người ta có thể sử dụng dịch vụ WCF trong một khối sử dụng với cú pháp sau:

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Tất nhiên bạn có thể điều chỉnh điều này hơn nữa để đạt được một mô hình lập trình ngắn gọn hơn cụ thể cho tình huống của bạn - nhưng vấn đề là chúng ta có thể tạo ra một triển khai IMyServicelặp lại kênh thực hiện chính xác mô hình dùng một lần.


Chi tiết

Tất cả các câu trả lời được đưa ra cho đến nay đều giải quyết được vấn đề khắc phục "lỗi" trong việc triển khai Kênh WCF IDisposable. Câu trả lời dường như cung cấp mô hình lập trình ngắn gọn nhất (cho phép bạn sử dụng usingkhối để xử lý các tài nguyên không được quản lý) là câu hỏi này - nơi proxy được sửa đổi để thực hiện IDisposablevới triển khai không có lỗi. Vấn đề với cách tiếp cận này là khả năng bảo trì - chúng tôi phải triển khai lại chức năng này cho từng proxy mà chúng tôi sử dụng. Trên một biến thể của câu trả lời này, chúng ta sẽ thấy làm thế nào chúng ta có thể sử dụng thành phần thay vì kế thừa để làm cho kỹ thuật này chung chung.

Lần thử đầu tiên

Dường như có nhiều cách triển khai khác nhau để IDisposablethực hiện, nhưng để tranh luận, chúng tôi sẽ sử dụng một sự thích ứng của câu trả lời được sử dụng bởi câu trả lời hiện được chấp nhận .

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    void DoWork();
}

public class ProxyDisposer : IDisposable
{
    private IClientChannel _clientChannel;


    public ProxyDisposer(IClientChannel clientChannel)
    {
        _clientChannel = clientChannel;
    }

    public void Dispose()
    {
        var success = false;
        try
        {
            _clientChannel.Close();
            success = true;
        }
        finally
        {
            if (!success)
                _clientChannel.Abort();
            _clientChannel = null;
        }
    }
}

public class ProxyWrapper : IMyService, IDisposable
{
    private IMyService _proxy;
    private IDisposable _proxyDisposer;

    public ProxyWrapper(IMyService proxy, IDisposable disposable)
    {
        _proxy = proxy;
        _proxyDisposer = disposable;
    }

    public void DoWork()
    {
        _proxy.DoWork();
    }

    public void Dispose()
    {
        _proxyDisposer.Dispose();
    }
}

Được trang bị các lớp trên bây giờ chúng ta có thể viết

public class ServiceHelper
{
    private readonly ChannelFactory<IMyService> _channelFactory;

    public ServiceHelper(ChannelFactory<IMyService> channelFactory )
    {
        _channelFactory = channelFactory;
    }

    public IMyService CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return new ProxyWrapper(channel, channelDisposer);
    }
}

Điều này cho phép chúng tôi sử dụng dịch vụ của mình bằng cách sử dụng usingkhối:

ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Làm cái này chung chung

Tất cả những gì chúng tôi đã làm cho đến nay là cải tổ giải pháp của Tomas . Điều ngăn chặn mã này là chung chung là thực tế là ProxyWrapperlớp phải được triển khai lại cho mọi hợp đồng dịch vụ mà chúng tôi muốn. Bây giờ chúng ta sẽ xem xét một lớp cho phép chúng ta tạo kiểu này một cách linh hoạt bằng IL:

public class ServiceHelper<T>
{
    private readonly ChannelFactory<T> _channelFactory;

    private static readonly Func<T, IDisposable, T> _channelCreator;

    static ServiceHelper()
    {
        /** 
         * Create a method that can be used generate the channel. 
         * This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
         * */
        var assemblyName = Guid.NewGuid().ToString();
        var an = new AssemblyName(assemblyName);
        var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);

        var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));

        var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
            new[] { typeof(T), typeof(IDisposable) });

        var ilGen = channelCreatorMethod.GetILGenerator();
        var proxyVariable = ilGen.DeclareLocal(typeof(T));
        var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
        ilGen.Emit(OpCodes.Ldarg, proxyVariable);
        ilGen.Emit(OpCodes.Ldarg, disposableVariable);
        ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
        ilGen.Emit(OpCodes.Ret);

        _channelCreator =
            (Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));

    }

    public ServiceHelper(ChannelFactory<T> channelFactory)
    {
        _channelFactory = channelFactory;
    }

    public T CreateChannel()
    {
        var channel = _channelFactory.CreateChannel();
        var channelDisposer = new ProxyDisposer(channel as IClientChannel);
        return _channelCreator(channel, channelDisposer);
    }

   /**
    * Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
    * This method is actually more generic than this exact scenario.
    * */
    private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
    {
        TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            TypeAttributes.Public | TypeAttributes.Class);

        var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
            tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));

        #region Constructor

        var constructorBuilder = tb.DefineConstructor(
            MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
            MethodAttributes.RTSpecialName,
            CallingConventions.Standard,
            interfacesToInjectAndImplement);

        var il = constructorBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i);
            il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
        }
        il.Emit(OpCodes.Ret);

        #endregion

        #region Add Interface Implementations

        foreach (var type in interfacesToInjectAndImplement)
        {
            tb.AddInterfaceImplementation(type);
        }

        #endregion

        #region Implement Interfaces

        foreach (var type in interfacesToInjectAndImplement)
        {
            foreach (var method in type.GetMethods())
            {
                var methodBuilder = tb.DefineMethod(method.Name,
                    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
                    MethodAttributes.Final | MethodAttributes.NewSlot,
                    method.ReturnType,
                    method.GetParameters().Select(p => p.ParameterType).ToArray());
                il = methodBuilder.GetILGenerator();

                if (method.ReturnType == typeof(void))
                {
                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);
                    il.Emit(OpCodes.Callvirt, method);
                    il.Emit(OpCodes.Ret);
                }
                else
                {
                    il.DeclareLocal(method.ReturnType);

                    il.Emit(OpCodes.Nop);
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldfld, typeFields[type]);

                    var methodParameterInfos = method.GetParameters();
                    for (var i = 0; i < methodParameterInfos.Length; i++)
                        il.Emit(OpCodes.Ldarg, (i + 1));
                    il.Emit(OpCodes.Callvirt, method);

                    il.Emit(OpCodes.Stloc_0);
                    var defineLabel = il.DefineLabel();
                    il.Emit(OpCodes.Br_S, defineLabel);
                    il.MarkLabel(defineLabel);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ret);
                }

                tb.DefineMethodOverride(methodBuilder, method);
            }
        }

        #endregion

        return tb.CreateType();
    }
}

Với lớp người trợ giúp mới của chúng tôi, bây giờ chúng tôi có thể viết

var channelFactory = new ChannelFactory<IMyService>("");

var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
    proxy.DoWork();
}

Lưu ý rằng bạn cũng có thể sử dụng cùng một kỹ thuật (với sửa đổi nhỏ) cho các máy khách được tạo tự động kế thừa cho ClientBase<>(thay vì sử dụng ChannelFactory<>) hoặc nếu bạn muốn sử dụng một triển khai khác IDisposableđể đóng kênh của mình.


2

Tôi thích cách kết nối này:

var client = new ProxyClient();
try
{
    ...
    client.Close();
}
finally
{
    if(client.State != CommunicationState.Closed)
        client.Abort();
}

1

Tôi đã viết một lớp cơ sở đơn giản xử lý việc này. Nó có sẵn dưới dạng gói NuGet và nó khá dễ sử dụng.

//MemberServiceClient is the class generated by SvcUtil
public class MemberServiceManager : ServiceClientBase<MemberServiceClient>
{
    public User GetUser(int userId)
    {
        return PerformServiceOperation(client => client.GetUser(userId));
    }

    //you can also check if any error occured if you can't throw exceptions       
    public bool TryGetUser(int userId, out User user)
    {
        return TryPerformServiceOperation(c => c.GetUser(userId), out user);
    }
}

Có bản cập nhật nào cho VS2013-.net 4.5.1 không? có tùy chọn nào cho Thử lại như stackoverflow.com/a/9370880/206730 không? -
Kiquenet

@Kiquenet Tôi không làm việc trên WCF nữa. Nếu bạn gửi cho tôi một yêu cầu kéo, tôi có thể hợp nhất nó và cập nhật gói.
Ufuk Hacıoğulları

1
public static class Service<TChannel>
{
    public static ChannelFactory<TChannel> ChannelFactory = new ChannelFactory<TChannel>("*");

    public static TReturn Use<TReturn>(Func<TChannel,TReturn> codeBlock)
    {
        var proxy = (IClientChannel)ChannelFactory.CreateChannel();
        var success = false;
        try
        {
            var result = codeBlock((TChannel)proxy);
            proxy.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success)
            {
                proxy.Abort();
            }
        }
    }
}

Vì vậy, nó cho phép viết các câu trả lời độc đáo:

return Service<IOrderService>.Use(orderService => 
{ 
    return orderService.PlaceOrder(request); 
}); 

1

Tôi muốn thêm triển khai Dịch vụ từ câu trả lời của Marc Gravell cho trường hợp sử dụng ServiceClient thay vì ChannelFactory.

public interface IServiceConnector<out TServiceInterface>
{
    void Connect(Action<TServiceInterface> clientUsage);
    TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage);
}

internal class ServiceConnector<TService, TServiceInterface> : IServiceConnector<TServiceInterface>
    where TServiceInterface : class where TService : ClientBase<TServiceInterface>, TServiceInterface, new()
{
    public TResult Connect<TResult>(Func<TServiceInterface, TResult> channelUsage)
    {
        var result = default(TResult);
        Connect(channel =>
        {
            result = channelUsage(channel);
        });
        return result;
    }

    public void Connect(Action<TServiceInterface> clientUsage)
    {
        if (clientUsage == null)
        {
            throw new ArgumentNullException("clientUsage");
        }
        var isChanneldClosed = false;
        var client = new TService();
        try
        {
            clientUsage(client);
            client.Close();
            isChanneldClosed = true;
        }
        finally
        {
            if (!isChanneldClosed)
            {
                client.Abort();
            }
        }
    }
}

1

Đối với những người quan tâm, đây là bản dịch VB.NET của câu trả lời được chấp nhận (bên dưới). Tôi đã tinh chỉnh nó một chút cho ngắn gọn, kết hợp một số lời khuyên của những người khác trong chủ đề này.

Tôi thừa nhận nó không có chủ đề cho các thẻ gốc (C #), nhưng vì tôi không thể tìm thấy phiên bản VB.NET của giải pháp tốt này, tôi cho rằng những người khác cũng sẽ tìm kiếm. Bản dịch Lambda có thể hơi rắc rối, vì vậy tôi muốn cứu ai đó gặp rắc rối.

Lưu ý rằng việc triển khai cụ thể này cung cấp khả năng định cấu hình ServiceEndpointkhi chạy.


Mã số:

Namespace Service
  Public NotInheritable Class Disposable(Of T)
    Public Shared ChannelFactory As New ChannelFactory(Of T)(Service)

    Public Shared Sub Use(Execute As Action(Of T))
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Sub



    Public Shared Function Use(Of TResult)(Execute As Func(Of T, TResult)) As TResult
      Dim oProxy As IClientChannel

      oProxy = ChannelFactory.CreateChannel

      Try
        Use = Execute(oProxy)
        oProxy.Close()

      Catch
        oProxy.Abort()
        Throw

      End Try
    End Function



    Public Shared ReadOnly Property Service As ServiceEndpoint
      Get
        Return New ServiceEndpoint(
          ContractDescription.GetContract(
            GetType(T),
            GetType(Action(Of T))),
          New BasicHttpBinding,
          New EndpointAddress(Utils.WcfUri.ToString))
      End Get
    End Property
  End Class
End Namespace

Sử dụng:

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Disposable(Of IService).Use(Sub(Client) Jobs = Client.GetJobs(Me.Status))
  End Get
End Property

Public ReadOnly Property Jobs As List(Of Service.Job)
  Get
    Return Disposable(Of IService).Use(Function(Client) Client.GetJobs(Me.Status))
  End Get
End Property

1

Kiến trúc hệ thống của chúng tôi thường sử dụng khung Unity IoC để tạo các phiên bản của ClientBase, vì vậy không có cách nào chắc chắn để thực thi rằng các nhà phát triển khác thậm chí sử dụngusing{} các khối. Để làm cho nó trở nên rõ ràng nhất có thể, tôi đã tạo lớp tùy chỉnh này mở rộng ClientBase và xử lý việc đóng kênh theo cách xử lý hoặc hoàn thiện trong trường hợp ai đó không loại bỏ rõ ràng trường hợp được tạo ra của Unity.

Ngoài ra còn có những thứ cần được thực hiện trong hàm tạo để thiết lập kênh cho thông tin xác thực và nội dung tùy chỉnh, vì vậy đó cũng ở đây ...

public abstract class PFServer2ServerClientBase<TChannel> : ClientBase<TChannel>, IDisposable where TChannel : class
{
    private bool disposed = false;

    public PFServer2ServerClientBase()
    {
        // Copy information from custom identity into credentials, and other channel setup...
    }

    ~PFServer2ServerClientBase()
    {
        this.Dispose(false);
    }

    void IDisposable.Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            try
            {
                    if (this.State == CommunicationState.Opened)
                        this.Close();
            }
            finally
            {
                if (this.State == CommunicationState.Faulted)
                    this.Abort();
            }
            this.disposed = true;
        }
    }
}

Sau đó, một khách hàng có thể chỉ đơn giản là:

internal class TestClient : PFServer2ServerClientBase<ITest>, ITest
{
    public string TestMethod(int value)
    {
        return base.Channel.TestMethod(value);
    }
}

Và người gọi có thể thực hiện bất kỳ trong số này:

public SomeClass
{
    [Dependency]
    public ITest test { get; set; }

    // Not the best, but should still work due to finalizer.
    public string Method1(int value)
    {
        return this.test.TestMethod(value);
    }

    // The good way to do it
    public string Method2(int value)
    {
        using(ITest t = unityContainer.Resolve<ITest>())
        {
            return t.TestMethod(value);
        }
    }
}

Bạn không bao giờ sử dụng tham số xử lý trong phương thức Vứt bỏ của mình
CaffGeek

@Chad - Tôi đã theo dõi mẫu thiết kế Hoàn thiện / Loại bỏ chung của Microsoft: msdn.microsoft.com/en-us/l Library / b1yfkh5e% 28VS.71% 29.aspx Đúng là tôi không sử dụng biến này, vì tôi không sử dụng biến này Không cần thực hiện bất kỳ việc dọn dẹp nào khác nhau giữa việc xử lý thông thường và hoàn thiện. Nó có thể được viết lại để hoàn thành cuộc gọi Dispose () và chuyển mã từ Dispose (bool) sang Dispose ().
Mã hóaWithSpike

Hoàn thiện thêm chi phí, và không xác định. Tôi tránh chúng bất cứ khi nào có thể. Bạn có thể sử dụng các nhà máy tự động của Unity để tiêm cho các đại biểu và đưa những người đó vào sử dụng các khối hoặc (tốt hơn) ẩn hành vi dịch vụ tạo / gọi / loại bỏ đằng sau một phương thức trên giao diện được chèn. Mỗi cuộc gọi đến sự phụ thuộc sẽ tạo ra proxy, gọi nó và loại bỏ nó.
TrueWill

0

Tôi đã giới thiệu một vài câu trả lời trên bài đăng này và tùy chỉnh nó theo nhu cầu của tôi.

Tôi muốn có khả năng làm một cái gì đó với WCF client trước khi sử dụng nó DoSomethingWithClient().

public interface IServiceClientFactory<T>
{
    T DoSomethingWithClient();
}
public partial class ServiceClient : IServiceClientFactory<ServiceClient>
{
    public ServiceClient DoSomethingWithClient()
    {
        var client = this;
        // do somthing here as set client credentials, etc.
        //client.ClientCredentials = ... ;
        return client;
    }
}

Đây là lớp người trợ giúp:

public static class Service<TClient>
    where TClient : class, ICommunicationObject, IServiceClientFactory<TClient>, new()
{
    public static TReturn Use<TReturn>(Func<TClient, TReturn> codeBlock)
    {
        TClient client = default(TClient);
        bool success = false;
        try
        {
            client = new TClient().DoSomethingWithClient();
            TReturn result = codeBlock(client);
            client.Close();
            success = true;
            return result;
        }
        finally
        {
            if (!success && client != null)
            {
                client.Abort();
            }
        }
    }
}

Và tôi có thể sử dụng nó như:

string data = Service<ServiceClient>.Use(x => x.GetData(7));

Điều gì về constructor của khách hàng bằng cách sử dụng ràng buộc và endpoing? TClient (ràng buộc, endpoing)
Kiquenet

0

Tôi có trình bao bọc riêng cho một kênh thực hiện Loại bỏ như sau:

public void Dispose()
{
        try
        {
            if (channel.State == CommunicationState.Faulted)
            {
                channel.Abort();
            }
            else
            {
                channel.Close();
            }
        }
        catch (CommunicationException)
        {
            channel.Abort();
        }
        catch (TimeoutException)
        {
            channel.Abort();
        }
        catch (Exception)
        {
            channel.Abort();
            throw;
        }
}

Điều này dường như hoạt động tốt và cho phép một khối sử dụng được sử dụng.


0

Trình trợ giúp sau đây cho phép gọi voidvà các phương thức không trống. Sử dụng:

var calculator = new WcfInvoker<CalculatorClient>(() => new CalculatorClient());
var sum = calculator.Invoke(c => c.Sum(42, 42));
calculator.Invoke(c => c.RebootComputer());

Bản thân lớp là:

public class WcfInvoker<TService>
    where TService : ICommunicationObject
{
    readonly Func<TService> _clientFactory;

    public WcfInvoker(Func<TService> clientFactory)
    {
        _clientFactory = clientFactory;
    }

    public T Invoke<T>(Func<TService, T> action)
    {
        var client = _clientFactory();
        try
        {
            var result = action(client);
            client.Close();
            return result;
        }
        catch
        {
            client.Abort();
            throw;
        }
    }

    public void Invoke(Action<TService> action)
    {
        Invoke<object>(client =>
        {
            action(client);
            return null;
        });
    }
}

0

Ghi đè lên Dispose () của khách hàng mà không cần tạo lớp proxy dựa trên ClientBase, mà không cần phải quản lý việc tạo và lưu vào bộ đệm kênh ! (Lưu ý rằng WcfClient không phải là lớp ABSTRACT và dựa trên ClientBase)

// No need for a generated proxy class
//using (WcfClient<IOrderService> orderService = new WcfClient<IOrderService>())
//{
//    results = orderService.GetProxy().PlaceOrder(input);
//}

public class WcfClient<TService> : ClientBase<TService>, IDisposable
    where TService : class
{
    public WcfClient()
    {
    }

    public WcfClient(string endpointConfigurationName) :
        base(endpointConfigurationName)
    {
    }

    public WcfClient(string endpointConfigurationName, string remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) :
        base(endpointConfigurationName, remoteAddress)
    {
    }

    public WcfClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) :
        base(binding, remoteAddress)
    {
    }

    protected virtual void OnDispose()
    {
        bool success = false;

        if ((base.Channel as IClientChannel) != null)
        {
            try
            {
                if ((base.Channel as IClientChannel).State != CommunicationState.Faulted)
                {
                    (base.Channel as IClientChannel).Close();
                    success = true;
                }
            }
            finally
            {
                if (!success)
                {
                    (base.Channel as IClientChannel).Abort();
                }
            }
        }
    }

    public TService GetProxy()
    {
        return this.Channel as TService;
    }

    public void Dispose()
    {
        OnDispose();
    }
}

0

Phương pháp của tôi để làm điều này là tạo ra một lớp kế thừa thực hiện rõ ràng IDis Dùng. Điều này hữu ích cho những người sử dụng gui để thêm tham chiếu dịch vụ (Thêm tham chiếu dịch vụ). Tôi chỉ bỏ lớp này trong dự án làm tham chiếu dịch vụ và sử dụng nó thay vì máy khách mặc định:

using System;
using System.ServiceModel;
using MyApp.MyService; // The name you gave the service namespace

namespace MyApp.Helpers.Services
{
    public class MyServiceClientSafe : MyServiceClient, IDisposable
    {
        void IDisposable.Dispose()
        {
            if (State == CommunicationState.Faulted)
            {
                Abort();
            }
            else if (State != CommunicationState.Closed)
            {
                Close();
            }

            // Further error checks and disposal logic as desired..
        }
    }
}

Lưu ý: Đây chỉ là một triển khai đơn giản của xử lý, bạn có thể triển khai logic xử lý phức tạp hơn nếu bạn muốn.

Sau đó, bạn có thể thay thế tất cả các cuộc gọi của mình bằng ứng dụng khách dịch vụ thông thường bằng các máy khách an toàn, như thế này:

using (MyServiceClientSafe client = new MyServiceClientSafe())
{
    var result = client.MyServiceMethod();
}

Tôi thích giải pháp này vì nó không yêu cầu tôi phải có quyền truy cập vào các định nghĩa Giao diện và tôi có thể sử dụng using câu lệnh như tôi mong đợi trong khi cho phép mã của tôi trông giống hoặc ít hơn.

Bạn vẫn sẽ cần xử lý các trường hợp ngoại lệ có thể được ném ra như được chỉ ra trong các bình luận khác trong chủ đề này.


-2

Bạn cũng có thể sử dụng một DynamicProxyđể mở rộng Dispose()phương thức. Bằng cách này bạn có thể làm một cái gì đó như:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.