ما هو أفضل الحل البديل لعميل WCF "باستخدام" كتلة "؟

StackOverflow https://stackoverflow.com/questions/573872

سؤال

أنا أحب إنشاء عملاء خدمة WCF بلدي داخل using كتلة كما انها إلى حد كبير الطريقة القياسية لاستخدام الموارد التي تنفذ IDisposable:

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

ولكن، كما لوحظ في هذه المادة MSDN, ، لف عميل WCF في using يمكن أن يقنع Block أي أخطاء تؤدي إلى ترك العميل في حالة مخيبة (مثل مشكلة مهلة أو اتصال). قصة قصيرة طويلة، عند التخلص منها () تسمى، حرائق طريقة العميل الإغلاق ()، ولكن يلقي خطأ لأنه في حالة مخيبة. الاستثناء الأصلي ثم ملثم من الاستثناء الثاني. ليست جيدة.

الحل المقترح في مقالة MSDN هو تجنب تماما استخدام using Block، وللثبات بدلا من ذلك لعملائك واستخدمهم شيئا مثل هذا:

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

مقارنة مع using كتلة، أعتقد أن هذا قبيح. وكثير من التعليمات البرمجية للكتابة في كل مرة تحتاج إلى عميل.

لحسن الحظ، وجدت بعض الحلول الأخرى، مثل هذا واحد على iservicegicoriented. تبدأ مع:

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 آخر يجب أن أبحث عن استخدام هذه الحلول؟ هل وصل أي شخص إلى أي شيء أفضل؟

هل كانت مفيدة؟

المحلول

في الواقع، على الرغم من أنني مدون (يرى إجابة لوقا)، أظن هذه أفضل من المجمع الخاص بي. رمز نموذجي:

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

نصائح أخرى

بالنظر إلى الاختيار بين المحلول الذي يدعو إليه iserviceoriented.com والحلول الذي يدعو إليه بلوق ديفيد باريت, أنا أفضل البساطة المعروضة عن طريق تجاوز طريقة تنفيذه العميل (). هذا يتيح لي الاستمرار في استخدام عبارة الاستخدام () كما يتوقع المرء مع كائن يمكن التخلص منها. ومع ذلك، كما أشار Babrian، يحتوي هذا الحل على شرط سباق في حالة عدم وجود الحالة في حالة التحقق منها ولكن يمكن أن يكون بحلول وقت إغلاق الوقت () يسمى، وفي هذه الحالة لا يزال يتعين على التواصل.

لذلك، للتجول في هذا، لقد استخدمت حل يمزج أفضل ما في العالمين.

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

كتبت أ وظيفة الطلب العالي لجعلها تعمل بشكل صحيح. لقد استخدمنا هذا في العديد من المشاريع ويبدو أنه يعمل بشكل رائع. هذه هي الطريقة التي كان يجب القيام بها من البداية، دون "استخدام" نموذج أو نحو ذلك.

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.USFOOSERVICE (F => F ...)".

أجد أنه أنيق للغاية، كل الأشياء التي تم النظر فيها. هل هناك مشكلة معينة واجهتها؟

يتيح ذلك توصيل ميزات Nifty الأخرى. على سبيل المثال، على موقع واحد، يصادق الموقع على الخدمة نيابة عن المستخدم المسجل. (الموقع ليس له أوراق اعتماد بحد ذاته.) من خلال كتابة المساعد الأسلوب الخاص ب "Incasservice" الخاص بنا، يمكننا تكوين مصنع القناة بالطريقة التي نريدها، وما إلى ذلك. نحن لسنا ملزمنا أيضا باستخدام الوكلاء المستندين - أي واجهة وبعد

هذه هي طريقة مايكروسوفت الموصى بها للتعامل مع مكالمات عميل 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 Basic Client ClientPtions Cs Client Client

قم بتنزيل العينة:ج # أو ب

النظر في أن هناك العديد من القضايا تنطوي على بيان الاستخدام, (ساخنة؟) المناقشات الداخلية و الخيوط في هذه المسألة، لن أضيع وقتي في محاولة لتصبح كود رعاة البقر والعثور على طريقة نظافة. سوف تمتص الأمر فقط، وتنفذ عملاء WCF هذه الطريقة المطلة على تطبيقات الخادم الخاصة بي.

فشل إضافي اختياري للقبض

استثناءات كثيرة تستمد من 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
}

من المسلم به أن هذا هو القليل من الكود الدنيوي للكتابة. أنا أفضل حاليا هذه الإجابة, ولا ترى أي "اختراق" في هذا الرمز الذي قد يسبب مشكلات أسفل الطريق.

لقد وجدت أخيرا بعض الخطوات الصلبة نحو حل نظيف لهذه المشكلة.

تمتد هذه الأداة المخصصة WCFProxyGenerator لتوفير وكيل معالجة استثناء. يولد وكيل إضافي يسمى ExceptionHandlingProxy<T> الذي يرث ExceptionHandlingProxyBase<T> - الأخير الذي ينفذ لحم وظائف الوكيل. والنتيجة هي أنه يمكنك اختيار استخدام الوكيل الافتراضي الذي يرث ClientBase<T> أو ExceptionHandlingProxy<T> الذي يغلف إدارة عمر مصنع القناة والقناة. استثناء SPROXY يحترم اختياراتك في مربع الحوار إضافة مرجع خدمة مع احترام الأساليب غير المتزامنة وأنواع التجميع.

codeplex. لديه مشروع يسمى استثناء مناولة مولد وكيل WCF. وبعد إنه يقوم بشكل أساسي بتثبيت أداة مخصصة جديدة إلى Visual Studio 2008، ثم استخدم هذه الأداة لتوليد وكيل الخدمة الجديد (إضافة مرجع الخدمة). وبعد لديها بعض الوظائف الجميلة للتعامل مع القنوات المختمة والمهالج والتخلص الآمن. هناك فيديو ممتاز هنا اتصل استثناءاللينجبروكسيوبر شرح بالضبط كيف يعمل هذا.

يمكنك استخدام بأمان Using بيان مرة أخرى، وإذا كانت القناة مختارة على أي طلب (تتبع التترفيه أو التواصل)، فسيقوم التفاف بإعادة تهيئة القناة المختارة وإعادة محاولة الاستعلام. إذا فشل ذلك، فسوف يطلق عليه Abort() أمر وتخلص من وكيل و Rethrow الاستثناء. إذا ارمي الخدمة FaultException الرمز سوف يتوقف عن التنفيذ، وسيتم إحباط الوكيل بأمان رمي الاستثناء الصحيح كما هو متوقع.

استنادا إلى إجابات Marc Gravell، Michaelgg، ومات ديفيس، جاء مطورينا مع ما يلي:

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

إنه قريب من بناء جملة "استخدام" قدر الإمكان، لا يتعين عليك إرجاع قيمة وهمية عند استدعاء طريقة الفراغ، ويمكنك إجراء مكالمات متعددة إلى الخدمة (وإرجاع قيم متعددة) دون الحاجة إلى استخدام Tuples.

أيضا، يمكنك استخدام هذا مع ClientBase<T> أحفاد بدلا من channelfactory إذا رغبت في ذلك.

يتعرض طريقة التمديد إذا كان المطور يريد أن يتخلص يدويا من وكيل / قناة بدلا من ذلك.

@ 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

هذه ستجعل المتغيرات العائدة أسهل.

ما هذا؟

هذه هي نسخة 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 );
       }

    }
}

ملاحظة: لقد صنعت هذا المنشور مجتمع Wiki. لن أحصل على "نقاط" من هذه الإجابة، لكنها تفضل رفع الرسوم إذا كنت توافق على التنفيذ أو تحريرها لجعلها أفضل.

أدناه هي نسخة محسنة من المصدر من السؤال وتمتد لذاكرة التخزين المؤقت لمصانع القنوات متعددة ومحاولة البحث عن نقطة النهاية في ملف التكوين حسب اسم العقد.

يستخدم .NET 4 (على وجه التحديد: متناقض، لينك، 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.");
    }
}

غلاف مثل هذا سيعمل:

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:

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 الوكيل الديناميكي

إذا كنت لا تحتاج IOC. أو تستخدم عميل Autogenerated (مرجع خدمة)، ثم يمكنك استخدام مجمع لإدارة الإغلاق والسماح gc. خذ ClientBase عندما يكون في حالة آمنة لن يرمي أي استثناء. سيتصل GC بالتخلص من الخدمة في الخدمة، وهذا سوف يسميه Close. وبعد لأنه مغلق Alread، لا يمكن أن يسبب أي ضرر. أنا أستخدم هذا دون مشاكل في رمز الإنتاج.

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 في autodisconect:

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

    Ws.Open();

    Ws.Test();
}

استخدم طريقة تمديد:

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

ملخص

يمكن أن يستهلك استخدام التقنيات الموضحة في هذه الإجابة خدمة WCF في كتلة باستخدام بناء الجملة التالي:

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

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

يمكنك بالطبع أن تتكيف هذا الأمر أكثر لتحقيق نموذج برمجة أكثر إيجازا خاصة بموقفك - لكن النقطة هي أننا نستطيع إنشاء تنفيذ IMyService روبيتها القناة التي تنفذ بشكل صحيح النمط المتاح.


تفاصيل

جميع الإجابات المقدمة إلى حد بعيد معالجة مشكلة التجول في "الخطأ" في انفجار قناة 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 لإغلاق قناتك.

أنا أحب هذه الطريقة لإغلاق الاتصال:

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

لقد كتبت فئة قاعدة بسيطة هذا يعالج هذا. انها متوفرة ك حزمة 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);
    }
}
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); 
}); 

أود إضافة تنفيذ الخدمة من إجابة ماركيل لحالة استخدام الخدمة 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();
            }
        }
    }
}

بالنسبة للمهتمين، إليك ترجمة VB.NET للإجابة المقبولة (أدناه). لقد قمت بتنفيذها قليلا للإيجاز، حيث أجمع بين بعض النصائح من قبل الآخرين في هذا الموضوع.

أعترف أنه خارج موضوع العلامات الأصلية (C #)، ولكن كما لم أكن قادرا على العثور على نسخة VB.NET من الحل المناسب، أفترض أن الآخرين سوف يبحثون كذلك. يمكن أن تكون الترجمة Lambda صعبة بعض الشيء، لذلك أود حفظ شخص ما المشكلة.

لاحظ أن هذا التنفيذ الخاص يوفر القدرة على تكوين ServiceEndpoint في وقت التشغيل.


رمز:

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

غالبا ما تستخدم بنية النظام لدينا وحدة IOC. إطار لإنشاء مثيلات العميل بحيث لا توجد طريقة مؤكدة لفرض أن المطورين الآخرين يستخدمون using{} كتل. من أجل جعلها مقاومة للمخفز قدر الإمكان، قمت بإنشاء هذه الفئة المخصصة التي تمدد العميل، وتؤدي إلى إغلاق القناة على التخلص منها، أو عند الانتهاء من ذلك في حالة عدم التخلص من ذلك صراحة من الوحدة التي تم إنشاؤها مثيل.

هناك أيضا أشياء تحتاج إلى القيام به في المنشئ لإعداد القناة لأوراق اعتماد مخصصة وأشياء، لذلك هذا هنا أيضا ...

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

أحيلت بعض الإجابات على هذا المنصب وتخصيصها حسب احتياجاتي.

أردت القدرة على القيام بشيء مع عميل 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));

لدي غلاف الخاص بي للقناة التي تنفذ الناشط على النحو التالي:

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

يبدو أن هذا يعمل بشكل جيد ويسمح باستخدام كتلة باستخدام.

المساعد التالي يسمح للاتصال 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;
        });
    }
}

تجاوز ناشط العميل () دون الحاجة إلى إنشاء فئة وكيل بناء على ClientBase، أيضا دون الحاجة إلى إدارة إنشاء القناة والتخزين المؤقتفي (لاحظ أن WCFClient ليس فئة مجردة ويستند إلى 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();
    }
}

لقد كان طريقتي للقيام بذلك لإنشاء فئة موروثة تنفذ صراحة. هذا مفيد للناس الذين يستخدمون واجهة المستخدم الرسومية لإضافة مرجع الخدمة (إضافة مرجع الخدمة). أنا فقط أسقط هذه الفئة في المشروع مما يجعل مرجع الخدمة واستخدامه بدلا من العميل الافتراضي:

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 بيان كما أتوقع مع السماح بمزددي أن يبدو نفسه أكثر أو أقل.

ستظل بحاجة إلى التعامل مع الاستثناءات التي يمكن إلقاؤها على النحو المشار إليه في تعليقات أخرى في هذا الموضوع.

يمكنك أيضا استخدام DynamicProxy لتمديد Dispose() طريقة. بهذه الطريقة يمكنك القيام بشيء ما:

using (var wrapperdProxy = new Proxy<yourProxy>())
{
   // Do whatever and dispose of Proxy<yourProxy> will be called and work properly.
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top