วิธีแก้ปัญหาที่ดีที่สุดสำหรับลูกค้า WCF `using` ปัญหาบล็อกคืออะไร?


404

ฉันชอบอินสแตนซ์ไคลเอ็นต์บริการ WCF ของฉันภายในusingบล็อกเพราะมันเป็นวิธีมาตรฐานในการใช้ทรัพยากรที่ใช้IDisposable:

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

แต่ดังที่กล่าวไว้ในบทความ MSDN นี้การตัดไคลเอ็นต์ WCF ในusingบล็อกอาจปิดบังข้อผิดพลาดใด ๆ ที่ทำให้ไคลเอ็นต์อยู่ในสถานะบกพร่อง (เช่นหมดเวลาหรือปัญหาการสื่อสาร) เรื่องสั้นสั้น ๆ เมื่อเรียกใช้ Dispose () เมธอด Close () ของไคลเอ็นต์จะเริ่มทำงาน แต่จะโยนข้อผิดพลาดเนื่องจากอยู่ในสถานะบกพร่อง ข้อยกเว้นดั้งเดิมถูกหลอกลวงโดยข้อยกเว้นที่สอง ไม่ดี.

วิธีแก้ปัญหาที่แนะนำในบทความ MSDN คือการหลีกเลี่ยงการใช้usingบล็อกอย่างสมบูรณ์และเพื่อยกตัวอย่างลูกค้าของคุณและใช้สิ่งเหล่านี้แทน:

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

เทียบกับusingบล็อกฉันคิดว่ามันน่าเกลียด และมีรหัสมากมายให้เขียนทุกครั้งที่คุณต้องการไคลเอนต์

โชคดีที่ฉันพบวิธีแก้ปัญหาอื่นสองสามอย่างเช่นนี้ใน IServiceOriented คุณเริ่มต้นด้วย:

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(); 
            } 
        } 
     } 
} 

ซึ่งจะช่วยให้:

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

นั่นไม่เลว แต่ฉันไม่คิดว่ามันเป็นเรื่องที่แสดงออกและเข้าใจได้ง่ายเหมือนกับusingบล็อก

วิธีแก้ปัญหาที่ฉันกำลังพยายามที่จะใช้ครั้งแรกที่ผมอ่านเกี่ยวกับในblog.davidbarret.net โดยทั่วไปคุณจะแทนที่Dispose()วิธีการของลูกค้าทุกที่ที่คุณใช้ สิ่งที่ต้องการ:

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

ดูเหมือนว่าจะสามารถอนุญาตการusingบล็อกอีกครั้งโดยไม่มีอันตรายจากการปิดบังข้อยกเว้นสถานะที่ผิดพลาด

ดังนั้นมี gotchas อื่น ๆ ที่ฉันต้องระวังการใช้วิธีแก้ปัญหาเหล่านี้หรือไม่ มีใครมากับอะไรดีกว่ากันบ้าง


42
คนสุดท้าย (ซึ่งตรวจสอบเรื่องนี้สถานะ) เป็นการแข่งขัน มันอาจไม่ผิดพลาดเมื่อคุณตรวจสอบบูลีน แต่อาจผิดพลาดเมื่อคุณโทรปิด ()
ไบรอัน

15
คุณอ่านสถานะ มันไม่ใช่ความผิด ก่อนที่คุณจะโทรหาปิด () ความผิดพลาดของช่อง ปิด () พ่น จบเกม.
Brian

4
เวลาผ่านไป. อาจเป็นช่วงเวลาสั้น ๆ แต่ในทางเทคนิคในช่วงเวลาระหว่างการตรวจสอบสถานะของช่องและขอให้ปิดสถานะของช่องอาจเปลี่ยนแปลง
Eric King เมื่อ

8
ฉันต้องการใช้แทนAction<T> UseServiceDelegate<T>ผู้เยาว์.
hIpPy

2
ฉันไม่ชอบผู้ช่วยแบบคงที่จริงๆService<T>เพราะมันทำให้การทดสอบหน่วยเป็นเรื่องที่ยุ่งยาก ฉันต้องการให้ไม่คงที่ดังนั้นจึงสามารถฉีดเข้าไปในชั้นเรียนที่ใช้งานได้
Fabio Marreco

คำตอบ:


137

ที่จริงแล้วถึงแม้ว่าฉันblogged (ดูคำตอบของลุค ) ฉันคิดว่านี่ดีกว่าเสื้อคลุม IDisposable ของฉัน รหัสทั่วไป:

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

(แก้ไขต่อความคิดเห็น)

ตั้งแต่Useคืนโมฆะวิธีที่ง่ายที่สุดในการจัดการค่าส่งคืนคือผ่านตัวแปรที่บันทึกไว้:

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 ฉันจะฉีดไคลเอ็นต์นั้นได้ที่ไหน ฉันสมมติว่า ChannelFactory สร้างไคลเอนต์และวัตถุจากโรงงานจะถูกสร้างใหม่ภายในคลาสบริการซึ่งหมายความว่ารหัสควรได้รับการปรับโครงสร้างใหม่เพื่อให้โรงงานแบบกำหนดเอง สิ่งนี้ถูกต้องหรือไม่หรือฉันพลาดอะไรบางอย่างที่นี่
Anttu

16
คุณสามารถปรับเปลี่ยนเสื้อคลุมได้อย่างง่ายดายดังนั้นคุณไม่จำเป็นต้องใช้ตัวแปรจับภาพสำหรับผลลัพธ์ บางอย่างเช่นนี้: public static TResult Use<TResult>(Func<T, TResult> codeBlock) { ... }
คริส

3
อาจมีประโยชน์ 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

ฉันจะเพิ่มข้อมูลรับรองโดยใช้วิธีนี้ได้อย่างไร
Hippasus

2
ในความเห็นของฉันทางออกที่ถูกต้องที่สุดคือ: 1) ดำเนินการรูปแบบปิด / ยกเลิกโดยไม่มีเงื่อนไขการแข่งขัน 2) จัดการสถานการณ์เมื่อการดำเนินการบริการมีข้อยกเว้น 3) จัดการสถานการณ์เมื่อทั้งวิธีปิดและยกเลิกยกเลิกข้อยกเว้น 4) ข้อยกเว้นแบบอะซิงโครนัสเช่น ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

88

ด้วยตัวเลือกระหว่างโซลูชันที่สนับสนุนโดย IServiceOriented.com และโซลูชันที่สนับสนุนโดยบล็อกของ David Barretฉันชอบความเรียบง่ายที่มีให้โดยการแทนที่เมธอด Dispose () ของลูกค้า สิ่งนี้ทำให้ฉันสามารถใช้คำสั่ง use () ได้อย่างที่คาดไว้กับวัตถุที่ใช้แล้วทิ้ง อย่างไรก็ตามตามที่ @Brian ชี้ให้เห็นวิธีการแก้ปัญหานี้มีสภาพการแข่งขันในที่ที่รัฐอาจไม่ผิดพลาดเมื่อมีการตรวจสอบ แต่อาจเป็นเวลาที่เรียกว่าปิด () ซึ่งในกรณีนี้ CommunicationException ยังคงเกิดขึ้น

ดังนั้นเพื่อให้ได้สิ่งนี้ฉันได้ใช้วิธีแก้ปัญหาที่ผสมผสานสิ่งที่ดีที่สุดของโลกทั้งสอง

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

2
การเสี่ยงกับการใช้คำสั่ง 'ลอง - ท้ายที่สุด' (หรือน้ำตาลเชิงประโยค - "การใช้ () {}") กับทรัพยากรที่ไม่มีการจัดการใช่หรือไม่ ในกรณีที่จุดถ้าตัวเลือก "ปิด" ล้มเหลวข้อยกเว้นจะไม่ถูกจับและในที่สุดก็อาจไม่ทำงาน นอกจากนี้หากมีข้อยกเว้นในคำสั่งในที่สุดก็สามารถปกปิดข้อยกเว้นอื่น ๆ ฉันคิดว่านี่เป็นเหตุผลว่าทำไมถึงเป็นที่นิยมสำหรับ Try-Catch
Zack Jannsen

แซคไม่ชัดเจนในวัตถุของคุณ ฉันหายไปอะไร หากวิธีการปิดส่งข้อยกเว้นบล็อกสุดท้ายจะดำเนินการก่อนที่ข้อผิดพลาดจะถูกส่งออกไป ขวา?
แพทริค Szalapski

1
@jmoreno ฉันยกเลิกการแก้ไขของคุณ หากคุณสังเกตเห็นว่าไม่มีวิธีบล็อก catch เลย ความคิดคือควรจะโยนข้อยกเว้นใด ๆที่เกิดขึ้น (แม้ในที่สุด) ไม่ได้ถูกจับในความเงียบ
Matt Davis

5
@MattDavis ทำไมคุณถึงต้องติดsuccessธงเลยล่ะ? ทำไมtry { Close(); } catch { Abort(); throw; }ล่ะ
Konstantin Spirin

ลองใส่ / ลองดูClose(); success = true;ล่ะ ฉันไม่ต้องการให้มีข้อยกเว้นถ้าฉันสามารถยกเลิกได้ในบล็อกในที่สุด ฉันต้องการให้มีข้อยกเว้นถ้า Abort () ล้มเหลวในกรณีนั้น วิธีนี้การลอง / จับจะซ่อนข้อยกเว้นสภาพการแข่งขันที่อาจเกิดขึ้นและยังอนุญาตให้คุณยกเลิก () การเชื่อมต่อในบล็อกสุดท้าย
goku_da_master

32

ฉันเขียนฟังก์ชันคำสั่งซื้อที่สูงขึ้นเพื่อให้ทำงานได้ถูกต้อง เราใช้มันในหลายโครงการและดูเหมือนว่าจะทำงานได้ดี นี่คือสิ่งที่ควรทำตั้งแต่เริ่มต้นโดยไม่มีกระบวนทัศน์ "ใช้" หรืออื่น ๆ

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();
        }
    }
}

คุณสามารถโทรแบบนี้:

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

นี่มันเหมือนที่คุณมีในตัวอย่าง ในบางโครงการเราเขียนวิธีใช้ตัวช่วยอย่างมากดังนั้นเราจึงเขียนสิ่งต่างๆเช่น "Wcf.UseFooService (f => f ... )"

ฉันคิดว่ามันค่อนข้างหรูหราและทุกสิ่งถือว่า คุณพบปัญหาเฉพาะหรือไม่?

นี่อนุญาตให้เชื่อมต่อฟีเจอร์ที่ดีอื่น ๆ ได้ตัวอย่างเช่นในไซต์หนึ่งไซต์จะตรวจสอบสิทธิ์กับบริการในนามของผู้ใช้ที่ล็อกอิน (เว็บไซต์ไม่มีข้อมูลรับรองด้วยตัวเอง) โดยการเขียนผู้ช่วยเมธอด "UseService" ของเราเองเราสามารถกำหนดค่าช่องทางโรงงานตามที่เราต้องการและเราไม่ผูกพันกับการใช้พรอกซีที่สร้างขึ้น - อินเตอร์เฟสใด ๆ จะทำ .


ฉันได้รับการยกเว้น: คุณสมบัติที่อยู่ใน ChannelFactory.Endpoint เป็นโมฆะ ปลายทางของ ChannelFactory ต้องมีที่อยู่ที่ถูกต้องระบุ GetCachedFactoryวิธีการคืออะไร?
มาร์แชลล์

28

นี่คือวิธีที่ Microsoft แนะนำในการจัดการการโทรของลูกค้า WCF:

สำหรับรายละเอียดเพิ่มเติมดู: ข้อยกเว้นที่คาดไว้

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();
}

ข้อมูลเพิ่มเติม ผู้คนจำนวนมากดูเหมือนจะถามคำถามนี้ใน WCF ซึ่ง Microsoft ได้สร้างตัวอย่างเฉพาะขึ้นมาเพื่อสาธิตวิธีการจัดการข้อยกเว้น:

C: \ WF_WCF_Samples \ WCF \ พื้นฐาน \ ไคลเอ็นต์ \ ExpectedExceptions \ CS \ ลูกค้า

ดาวน์โหลดตัวอย่าง: C #หรือ VB

พิจารณาว่ามีปัญหาจำนวนมากที่เกี่ยวข้องกับการใช้คำสั่ง , (ร้อน?) การอภิปรายภายในและหัวข้อเกี่ยวกับเรื่องนี้ผมไม่อยากจะเสียเวลาของฉันพยายามที่จะกลายเป็นคาวบอยรหัสและหาวิธีทำความสะอาด ฉันจะดูดมันขึ้นมาและใช้ลูกค้า WCF วิธีนี้ (ยังเชื่อถือได้) verbose สำหรับแอปพลิเคชันเซิร์ฟเวอร์ของฉัน

ตัวเลือกเพิ่มเติมความล้มเหลวในการจับ

มีข้อยกเว้นมากมายที่มาจากCommunicationExceptionและฉันไม่คิดว่าข้อยกเว้นเหล่านั้นส่วนใหญ่ควรลองใหม่ ฉันทำงานผิดพลาดไปในแต่ละข้อยกเว้นบน MSDN และพบรายการข้อยกเว้นสั้น ๆ ที่สามารถลองใหม่ได้ (นอกเหนือจากTimeOutExceptionด้านบน) แจ้งให้เราทราบหากฉันพลาดข้อยกเว้นที่ควรลองใหม่

  // 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
}

เป็นที่ยอมรับว่าเป็นรหัสทางโลกที่จะเขียน ขณะนี้ฉันต้องการคำตอบนี้และไม่เห็น "แฮ็ก" ในรหัสนั้นที่อาจทำให้เกิดปัญหาตามมา


1
รหัสจากตัวอย่างยังคงเป็นสาเหตุของปัญหาหรือไม่ ฉันพยายามเรียกใช้ UsingUsing project (VS2013) แต่บรรทัดที่"Hope this code wasn't important, because it might not happen."ยังดำเนินการอยู่ ...
janv8000

14

ในที่สุดฉันก็ได้พบขั้นตอนที่เป็นของแข็งเพื่อแก้ปัญหานี้ให้สะอาด

เครื่องมือแบบกำหนดเองนี้ขยาย WCFProxyGenerator เพื่อให้ข้อยกเว้นการจัดการพร็อกซี มันสร้างพร็อกซี่เพิ่มเติมที่เรียกว่าExceptionHandlingProxy<T>สืบทอดExceptionHandlingProxyBase<T>- หลังซึ่งใช้เนื้อของฟังก์ชั่นของพร็อกซี่ ผลลัพธ์คือคุณสามารถเลือกใช้พร็อกซีเริ่มต้นที่รับช่วงClientBase<T>หรือExceptionHandlingProxy<T>จัดการแค็ปซูลอายุการใช้งานของโรงงานช่องและช่อง ExceptionHandlingProxy เคารพสิ่งที่คุณเลือกในกล่องโต้ตอบเพิ่มการอ้างอิงบริการที่เกี่ยวข้องกับวิธีการแบบอะซิงโครนัสและประเภทการรวบรวม

Codeplexมีโครงการที่เรียกว่าข้อยกเว้นการจัดการ WCF พร็อกซีปั่นไฟ มันเป็นพื้นติดตั้งเครื่องมือที่กำหนดเองใหม่เพื่อ Visual Studio 2008 จากนั้นใช้เครื่องมือนี้ในการสร้างบริการใหม่พร็อกซี่(เพิ่มการอ้างอิงค่าบริการแล้ว) มีฟังก์ชั่นที่ดีในการจัดการกับช่องสัญญาณผิดพลาดหมดเวลาและกำจัดอย่างปลอดภัย มีวิดีโอยอดเยี่ยมที่นี่เรียกว่าExceptionHandlingProxyWrapperอธิบายว่าวิธีนี้ทำงานอย่างไร

คุณสามารถใช้Usingคำสั่งได้อย่างปลอดภัยอีกครั้งและหากช่องสัญญาณมีข้อบกพร่องในคำขอใด ๆ (TimeoutException หรือ CommunicationException) Wrapper จะเริ่มต้นช่องสัญญาณที่ผิดพลาดอีกครั้งและลองสืบค้นอีกครั้ง หากล้มเหลวจะมีการเรียกAbort()คำสั่งและกำจัดพร็อกซีและสร้างข้อยกเว้นใหม่ ถ้าบริการพ่นFaultExceptionรหัสมันจะหยุดดำเนินการและพร็อกซีจะถูกยกเลิกอย่างปลอดภัยโยนข้อยกเว้นที่ถูกต้องตามที่คาดไว้


@Shimmy Status Beta วันที่: วันเสาร์ที่ 11 กรกฎาคม 2009โดยMichele Bustamante โครงการที่ตายแล้ว?
Kiquenet

11

จากคำตอบของ Marc Gravell, MichaelGG และ Matt Davis นักพัฒนาของเราก็มีสิ่งต่อไปนี้:

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();
            }
        }
    }
}

ตัวอย่างการใช้งาน:

string result = string.Empty;

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

มันใกล้เคียงกับไวยากรณ์ "ใช้" มากที่สุดคุณไม่จำเป็นต้องส่งคืนค่าดัมมี่เมื่อเรียกใช้วิธีการโมฆะและคุณสามารถทำการโทรหลายครั้งไปยังบริการ (และคืนค่าหลายค่า) โดยไม่ต้องใช้สิ่งอันดับ

นอกจากนี้คุณสามารถใช้สิ่งนี้กับClientBase<T>ลูกหลานแทน ChannelFactory หากต้องการ

วิธีการขยายถูกเปิดเผยหากนักพัฒนาต้องการกำจัดพร็อกซี / ช่องด้วยตนเองแทน


การใช้สิ่งนี้เหมาะสมถ้าฉันใช้ PoolingDuplex และไม่ปิดการเชื่อมต่อหลังจากการโทรเพื่อให้บริการลูกค้าของฉันอาจใช้งานได้สองสามวันและจัดการกับการเรียกกลับของเซิร์ฟเวอร์ เท่าที่ฉันเข้าใจวิธีแก้ปัญหาซึ่งกล่าวถึงที่นี่เหมาะสมสำหรับหนึ่งสายต่อเซสชัน?
sll

@sll - ใช้สำหรับปิดการเชื่อมต่อทันทีหลังจากที่โทรกลับมา (โทรหนึ่งครั้งต่อเซสชัน)
TrueWill

@cacho การทำให้DisposeSafelyเป็นส่วนตัวเป็นตัวเลือกแน่นอนและจะหลีกเลี่ยงความสับสน อาจมีกรณีการใช้งานที่ใครบางคนต้องการโทรหามันโดยตรง แต่ฉันไม่สามารถคิดได้ทันที
TrueWill

@tewewill สำหรับเอกสารเป็นสิ่งสำคัญที่ต้องพูดถึงว่าวิธีการนี้ปลอดภัยต่อเธรดหรือไม่
Cacho Santa

1
ในความเห็นของฉันทางออกที่ถูกต้องที่สุดคือ: 1) ทำรูปแบบการปิด / ยกเลิกโดยไม่มีเงื่อนไขการแข่งขัน 2) จัดการกับสถานการณ์เมื่อการดำเนินการบริการมีข้อยกเว้น 3) จัดการสถานการณ์เมื่อทั้งวิธีการปิดและยกเลิกยกเลิกข้อยกเว้น 4) ข้อยกเว้นแบบอะซิงโครนัสเช่น ThreadAbortException https://devzone.channeladam.com/articles/2014/07/how-to-call-wcf-service-properly/
Kiquenet

8

@Marc Gravell

จะไม่เป็นการดีถ้าใช้สิ่งนี้:

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;
    }
}

หรือสิ่งเดียวกัน(Func<T, TResult>)ในกรณีของService<IOrderService>.Use

สิ่งเหล่านี้จะทำให้ตัวแปรกลับมาง่ายขึ้น


2
+1 @MarcGravell ฉันคิดว่าคำตอบของคุณ 'สามารถทำได้ดีกว่า' เช่นกัน: P (และการกระทำหนึ่งสามารถนำไปใช้ในแง่ของ Func ด้วยผลตอบแทนที่เป็นโมฆะ) หน้านี้ทั้งเป็นระเบียบ - ฉันต้องการไปกำหนดแบบครบวงจรหนึ่งและแสดงความคิดเห็นเกี่ยวกับ dups ถ้าผมวาดภาพโดยใช้ WCF เวลาใด ๆ ทศวรรษที่ผ่านมานี้ ...
Ruben Bartelink

7

นี่คืออะไร?

นี่เป็นเวอร์ชัน CW ของคำตอบที่ยอมรับ แต่มีการจัดการข้อยกเว้น (สิ่งที่ฉันพิจารณาว่าสมบูรณ์)

คำตอบที่ได้รับการยอมรับอ้างอิงถึงเว็บไซต์นี้ซึ่งไม่ได้มีอยู่แล้ว เพื่อช่วยคุณแก้ปัญหาฉันได้รวมส่วนที่เกี่ยวข้องมากที่สุดไว้ที่นี่ นอกจากนี้ฉันแก้ไขมันเล็กน้อยเพื่อรวมการจัดการข้อยกเว้นลองอีกครั้งเพื่อจัดการกับการหมดเวลาของเครือข่ายที่น่ารำคาญ

การใช้งาน WCF ลูกค้าอย่างง่าย

เมื่อคุณสร้างพร็อกซีฝั่งไคลเอ็นต์ของคุณนี่คือทั้งหมดที่คุณต้องนำไปใช้

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

ServiceDelegate.cs

เพิ่มไฟล์นี้ลงในโซลูชันของคุณ ไฟล์นี้ไม่จำเป็นต้องทำการเปลี่ยนแปลงนอกจากว่าคุณต้องการแก้ไขจำนวนครั้งในการลองใหม่หรือข้อยกเว้นใดที่คุณต้องการจัดการ

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: ฉันทำโพสต์นี้เป็นวิกิชุมชน ฉันจะไม่รวบรวม "คะแนน" จากคำตอบนี้ แต่ต้องการให้คุณลงคะแนนถ้าคุณเห็นด้วยกับการใช้งานหรือแก้ไขเพื่อให้ดีขึ้น


ฉันไม่แน่ใจว่าฉันเห็นด้วยกับลักษณะของคุณของคำตอบนี้ มันเป็นรุ่น CW กับคุณความคิดในการจัดการข้อยกเว้นเพิ่ม
John Saunders

@JohnSaunders - True (แนวคิดการจัดการข้อยกเว้นของฉัน) แจ้งให้เราทราบถึงข้อยกเว้นใด ๆ ที่ฉันพลาดไปหรือกำลังจัดการผิด
goodguys_activate

ตัวแปรความสำเร็จคืออะไร มันต้องการเพิ่มไปยังซอร์สโค้ด: ถ้า (สำเร็จ) ส่งคืน; ??
Kiquenet

หากการโทรครั้งแรกเกิดขึ้นและการโทรลำดับที่สองประสบความสำเร็จมากที่สุด RecentEx จะไม่เป็นโมฆะดังนั้นคุณจึงส่งข้อยกเว้นที่ล้มเหลว 5 ครั้งต่อไป หรือฉันหายไปบางอย่าง ฉันไม่เห็นตำแหน่งที่คุณล้างข้อมูลมากที่สุด RegentEx หากลองครั้งที่ 2, 3, 4 หรือ 5 สำเร็จ ยังไม่เห็นผลตอบแทน o สำเร็จ ฉันควรจะหายไปบางสิ่งบางอย่างที่นี่ แต่รหัสนี้จะไม่ทำงานตลอดเวลา 5 ครั้งหากไม่มีข้อยกเว้นถูกโยน?
Bart Calixto

@Bart - ฉันเพิ่มsuccess == falseไปยังขั้นสุดท้ายถ้ามีคำสั่ง
goodguys_activate

7

ด้านล่างเป็นรุ่นที่ปรับปรุงแล้วของแหล่งที่มาจากคำถามและขยายไปยังแคชหลายโรงงานที่ช่องและพยายามค้นหาจุดสิ้นสุดในไฟล์การกำหนดค่าตามชื่อสัญญา

มันใช้. NET 4 (โดยเฉพาะ: contravariance, 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
ทำไมต้องใช้UseServiceDelegate<T>แทนAction<T>?
Mike Mayer

1
เหตุผลเดียวที่ฉันสามารถคิดได้ว่าผู้เขียนต้นฉบับทำเช่นนั้นก็คือให้มีผู้ได้รับมอบหมายประเภทที่ผู้พัฒนาต้องการทราบว่าเป็นของบริการโทรศัพท์ แต่เท่าที่ฉันเห็นมันAction<T>ก็ใช้ได้เช่นกัน
Jesse C. Slicer

5

เสื้อคลุมแบบนี้จะได้ผล:

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
        }
    }
}

ที่ควรให้คุณเขียนโค้ดเช่น:

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

แน่นอนว่าเสื้อคลุมสามารถจับข้อยกเว้นมากขึ้นถ้าจำเป็น แต่หลักการยังคงเหมือนเดิม


ฉันจำการสนทนาเกี่ยวกับการทิ้งไม่ได้ถูกเรียกภายใต้เงื่อนไขบางประการ ... ส่งผลให้หน่วยความจำรั่วด้วย WCF
goodguys_activate

ฉันไม่แน่ใจว่ามันส่งผลให้เกิดการรั่วไหลของหน่วยความจำ แต่ปัญหาคือ เมื่อคุณโทรหาDisposeช่อง IChannel มันอาจส่งข้อยกเว้นหากช่องสัญญาณอยู่ในสถานะบกพร่องซึ่งเป็นปัญหาเนื่องจาก Microsoft ระบุว่าDisposeไม่ควรส่งสัญญาณ ดังนั้นสิ่งที่รหัสด้านบนจะจัดการกรณีเมื่อCloseมีข้อยกเว้น หากAbortโยนมันอาจเป็นสิ่งที่ผิดอย่างร้ายแรง ฉันเขียนโพสต์บล็อกเกี่ยวกับเรื่องนี้เมื่อเดือนธันวาคมที่แล้ว: blog.tomasjansson.com/2010/12/disposible-wcf-client-wrapper
Tomas Jansson

4

ฉันใช้พร็อกซีไดนามิกของ Castle เพื่อแก้ปัญหา Dispose () และใช้การรีเฟรชช่องอัตโนมัติเมื่ออยู่ในสถานะใช้งานไม่ได้ ในการใช้สิ่งนี้คุณต้องสร้างอินเทอร์เฟซใหม่ที่สืบทอดสัญญาการบริการและ IDisposable ของคุณ ไดนามิกพร็อกซีใช้อินเตอร์เฟสนี้และล้อมรอบช่อง WCF:

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

ฉันชอบสิ่งนี้เนื่องจากคุณสามารถฉีดบริการ WCF โดยที่ผู้บริโภคไม่ต้องกังวลเกี่ยวกับรายละเอียดของ WCF และไม่มี cruft เพิ่มเติมเหมือนโซลูชันอื่น ๆ

ลองดูรหัสดูแล้วมันค่อนข้างง่าย: WCF Dynamic Proxy


4

ใช้วิธีการขยาย:

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

หากคุณไม่ต้องการIoCหรือใช้ไคลเอนต์ที่สร้างอัตโนมัติ (อ้างอิงบริการ) คุณสามารถใช้ wrapper เพื่อจัดการกับการปิดและปล่อยให้GCใช้ฐานลูกค้าเมื่ออยู่ในสถานะที่ปลอดภัยซึ่งจะไม่ทำให้เกิดข้อยกเว้นใด ๆ GC จะเรียกใช้ Dispose ใน serviceclient และจะเรียกCloseใช้ เนื่องจากมันถูกปิดอ่านแล้วมันไม่สามารถสร้างความเสียหายใด ๆ ฉันใช้สิ่งนี้โดยไม่มีปัญหาในรหัสการผลิต

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;
        }
    }
}

จากนั้นเมื่อคุณเข้าถึงเซิร์ฟเวอร์คุณสร้างไคลเอนต์และใช้usingในการตัดต่ออัตโนมัติ:

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

    Ws.Open();

    Ws.Test();
}

3

สรุป

การใช้เทคนิคที่อธิบายไว้ในคำตอบนี้สามารถใช้บริการ WCF ในบล็อกการใช้ที่มีไวยากรณ์ต่อไปนี้:

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

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

แน่นอนว่าคุณสามารถปรับให้เข้ากับรูปแบบการเขียนโปรแกรมที่กระชับยิ่งขึ้นเฉพาะกับสถานการณ์ของคุณ - แต่ประเด็นก็คือเราสามารถสร้างการดำเนินการของIMyServiceการรีช่องสัญญาณซึ่งใช้รูปแบบการทิ้งอย่างถูกต้อง


รายละเอียด

ทุกคำตอบที่กำหนดป่านนี้แก้ไขปัญหาการรับรอบ "ข้อผิดพลาด" ใน implemention WCF IDisposableช่องทางของ คำตอบที่ดูเหมือนว่าจะนำเสนอรูปแบบการเขียนโปรแกรมที่รัดกุมที่สุด (ช่วยให้คุณใช้usingบล็อกเพื่อกำจัดทรัพยากรที่ไม่มีการจัดการ) คือคำตอบนี้ - โดยที่พร็อกซีนั้นได้รับการแก้ไขเพื่อนำไปใช้IDisposableงาน ปัญหาของวิธีนี้คือการบำรุงรักษา - เราต้องนำฟังก์ชั่นนี้กลับมาใช้ใหม่สำหรับพร็อกซีที่เราเคยใช้ ในรูปแบบของคำตอบนี้เราจะเห็นว่าเราสามารถใช้การจัดองค์ประกอบภาพมากกว่าการสืบทอดเพื่อสร้างเทคนิคนี้แบบทั่วไป

ความพยายามครั้งแรก

ดูเหมือนจะใช้งานต่าง ๆ สำหรับIDisposableการใช้งาน แต่เพื่อประโยชน์ของอาร์กิวเมนต์เราจะใช้การปรับตัวที่ใช้โดยคำตอบที่ได้รับการยอมรับในขณะนี้

[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();
    }
}

อาวุธที่มีคลาสข้างต้นเราสามารถเขียนได้

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);
    }
}

สิ่งนี้ทำให้เราสามารถใช้บริการของเราโดยใช้usingบล็อก:

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

ทำให้ทั่วไปนี้

ทั้งหมดที่เราได้ทำเพื่อให้ห่างไกลคือการกำหนดใหม่แก้ปัญหาโทมัส สิ่งที่ป้องกันไม่ให้รหัสนี้เป็นรหัสทั่วไปคือความจริงที่ว่าProxyWrapperชั้นจะต้องมีการใช้งานอีกครั้งสำหรับทุกสัญญาบริการที่เราต้องการ ตอนนี้เราจะดูคลาสที่ให้เราสร้างประเภทนี้แบบไดนามิกโดยใช้ 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();
    }
}

ด้วยคลาสผู้ช่วยใหม่ของเราเราสามารถเขียนได้

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

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

โปรดทราบว่าคุณสามารถใช้เทคนิคเดียวกัน (ด้วยการปรับเปลี่ยนเล็กน้อย) สำหรับลูกค้าที่สร้างขึ้นโดยอัตโนมัติที่สืบทอดClientBase<>(แทนที่จะใช้ChannelFactory<>) หรือหากคุณต้องการใช้การนำไปใช้ที่แตกต่างกันIDisposableเพื่อปิดช่องของคุณ


2

ฉันชอบวิธีการปิดการเชื่อมต่อนี้:

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

1

ฉันได้เขียนคลาสพื้นฐานอย่างง่ายที่จัดการสิ่งนี้ สามารถใช้เป็นแพ็คเกจ NuGetและใช้งานง่าย

//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);
    }
}

มีการอัพเดตสำหรับ VS2013-.net 4.5.1 หรือไม่ ตัวเลือกใด ๆ สำหรับการลองใหม่เช่นstackoverflow.com/a/9370880/206730 ? -
Kiquenet

@ Kiquenet ฉันไม่ได้ทำงานกับ WCF อีกต่อไป หากคุณส่งคำขอการดึงฉันสามารถผสานและอัปเดตแพ็คเกจได้
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();
            }
        }
    }
}

ดังนั้นจึงอนุญาตให้เขียนข้อความสั่งคืนได้อย่างดี:

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

1

ฉันต้องการเพิ่มการใช้งานบริการจากคำตอบของ Marc Gravellสำหรับกรณีการใช้ ServiceClient แทน 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

สำหรับผู้ที่สนใจนี่คือการแปล VB.NET ของคำตอบที่ยอมรับได้ (ด้านล่าง) ฉันได้ขัดเกลาเรื่องเล็ก ๆ น้อย ๆ โดยรวมเคล็ดลับบางอย่างของคนอื่น ๆ ในกระทู้นี้

ฉันยอมรับว่าเป็นหัวข้อที่ไม่เกี่ยวข้องกับแท็กต้นทาง (C #) แต่เนื่องจากฉันไม่สามารถหาโซลูชันที่ดีนี้ VB.NET รุ่นฉันคิดว่าคนอื่นจะมองเช่นกัน การแปลของแลมบ์ดาค่อนข้างยุ่งยากดังนั้นฉันจึงต้องการช่วยผู้อื่นให้เดือดร้อน

โปรดทราบว่าการใช้งานเฉพาะนี้ให้ความสามารถในการกำหนดค่าServiceEndpointat runtime


รหัส:

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

การใช้งาน:

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

สถาปัตยกรรมระบบของเรามักใช้Unity IoC framework เพื่อสร้างอินสแตนซ์ของ ClientBase ดังนั้นจึงไม่มีวิธีที่แน่นอนในการบังคับให้ผู้พัฒนารายอื่นใช้งานusing{}บล็อก เพื่อให้เป็นที่เข้าใจผิดที่สุดฉันได้สร้างคลาสที่กำหนดเองนี้ซึ่งขยาย ClientBase และจัดการปิดช่องทางในการกำจัดหรือจบในกรณีที่มีคนไม่ได้กำจัดอินสแตนซ์ที่สร้างความสามัคคีอย่างชัดเจน

นอกจากนี้ยังมีสิ่งที่ต้องทำในตัวสร้างเพื่อตั้งค่าช่องทางสำหรับข้อมูลประจำตัวที่กำหนดเองและสิ่งที่ดังนั้นที่อยู่ที่นี่ด้วย ...

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;
        }
    }
}

จากนั้นลูกค้าสามารถ:

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

และผู้โทรสามารถทำสิ่งเหล่านี้ได้:

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);
        }
    }
}

คุณไม่เคยใช้พารามิเตอร์การกำจัดในวิธีการกำจัดของคุณ
CaffGeek

@Chad - ฉันติดตามรูปแบบการออกแบบสุดท้าย / กำจัดแบบทั่วไปของ Microsoft: msdn.microsoft.com/en-us/library/b1yfkh5e%28VS.71%29.aspx มันเป็นความจริงที่ฉันไม่ได้ใช้ตัวแปรเพราะฉันไม่ได้ใช้ ไม่จำเป็นต้องทำการล้างข้อมูลที่แตกต่างกันระหว่างการกำจัดแบบปกติและการสรุป มันอาจจะถูกเขียนใหม่เพียงแค่มีการโทรออกสุดท้าย () และย้ายรหัสจากการกำจัด (บูล) เพื่อการกำจัด ()
CodingWithSpike

Finalizers เพิ่มค่าใช้จ่ายและไม่ได้กำหนดไว้ ฉันหลีกเลี่ยงพวกเขาเมื่อใดก็ตามที่เป็นไปได้ คุณสามารถใช้โรงงานอัตโนมัติของ Unity เพื่อฉีดผู้ได้รับมอบหมายและใส่พวกเขาในการใช้บล็อกหรือ (ดีกว่า) ซ่อนพฤติกรรมการสร้าง / โทร / ทิ้งบริการหลังวิธีการในอินเทอร์เฟซที่ถูกฉีด การเรียกไปยังการพึ่งพาแต่ละครั้งจะสร้างพร็อกซีเรียกมันและปิดใช้งาน
TrueWill

0

ฉันส่งคำตอบเล็ก ๆ น้อย ๆ เกี่ยวกับโพสต์นี้และปรับแต่งตามความต้องการของฉัน

ฉันต้องการความสามารถในการทำบางสิ่งบางอย่างกับลูกค้า WCF ก่อนที่จะใช้มันดังนั้น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;
    }
}

นี่คือคลาสตัวช่วย:

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();
            }
        }
    }
}

และฉันสามารถใช้มันเป็น:

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

ตัวสร้างไคลเอนต์เกี่ยวกับการใช้การเชื่อมโยงและการ endpoing คืออะไร TClient (มีผลผูกพัน, endpoing)
Kiquenet

0

ฉันมี wrapper ของฉันเองสำหรับช่องที่ใช้งานการกำจัดดังนี้:

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;
        }
}

ดูเหมือนว่าจะทำงานได้ดีและอนุญาตให้ใช้บล็อกที่จะใช้


0

ผู้ช่วยต่อไปนี้อนุญาตให้มีวิธีการโทรvoidและไม่เป็นโมฆะ การใช้งาน:

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

ชั้นเรียนคือ:

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

แทนที่การกำจัดของลูกค้า () โดยไม่จำเป็นต้องสร้างคลาสพร็อกซีตาม ClientBase โดยไม่จำเป็นต้องจัดการการสร้างช่องและแคช ! (โปรดทราบว่า WcfClient ไม่ใช่คลาส ABSTRACT และขึ้นอยู่กับ 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

วิธีการของฉันในการทำเช่นนี้คือการสร้างคลาสที่สืบทอดมาซึ่งใช้ IDisposable อย่างชัดเจน สิ่งนี้มีประโยชน์สำหรับผู้ที่ใช้ gui เพื่อเพิ่มการอ้างอิงบริการ (เพิ่มการอ้างอิงบริการ) ฉันแค่ปล่อยคลาสนี้ในโครงการที่ทำการอ้างอิงบริการและใช้แทนไคลเอนต์เริ่มต้น:

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..
        }
    }
}

หมายเหตุ: นี่เป็นเพียงการนำไปใช้อย่างง่ายของการกำจัดคุณสามารถใช้ตรรกะการกำจัดที่ซับซ้อนมากขึ้นหากคุณต้องการ

จากนั้นคุณสามารถแทนที่การโทรทั้งหมดที่ทำกับไคลเอ็นต์บริการปกติด้วยไคลเอ็นต์ที่ปลอดภัยเช่นนี้:

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

ฉันชอบโซลูชันนี้เนื่องจากไม่ต้องการให้ฉันเข้าถึงคำจำกัดความของอินเทอร์เฟซและฉันสามารถใช้usingคำสั่งได้ตามที่คาดหวังในขณะที่อนุญาตให้โค้ดของฉันดูมากขึ้นหรือน้อยลง

คุณยังจะต้องจัดการกับข้อยกเว้นที่สามารถโยนได้ตามที่ระบุไว้ในความคิดเห็นอื่น ๆ ในหัวข้อนี้


-2

คุณสามารถใช้ a DynamicProxyเพื่อขยายDispose()วิธีการได้ วิธีนี้คุณสามารถทำสิ่งที่ชอบ:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.