tworzenie kanałów WCF

Próbuję przekonwertować istniejącą aplikację. NET do WCF. Zarówno serwer, jak i klient mają wspólny interfejs, a wszystkie obiekty są aktywowane przez serwer.

W świecie WCF byłoby to podobne do tworzenia usługi per-call i używania ChannelFactory<T> do tworzenia proxy. Trochę się męczę z tym jak poprawnie stworzyć ChannelFactory<T> dla ASP.NET klient.

Ze względu na wydajność, chcę buforować ChannelFactory<T> obiekty i po prostu tworzyć kanał za każdym razem, gdy wywołuję usługę. W. NET remoting days, there used to RemotingConfiguration.GetRegisteredWellknownClientTypes() method to get a collection of client objects that I could then cache. Wygląda na to, że w świecie WCF czegoś takiego nie ma, chociaż udało mi się pobrać zbiór punktów końcowych z pliku konfiguracyjnego.

Oto, co myślę, że zadziała. Mogę stworzyć coś takiego:

public static ProxyHelper
{
    static Dictionary<Type, object> lookup = new Dictionary<string, object>();  

    static public T GetChannel<T>()
    {
        Type type = typeof(T);
        ChannelFactory<T> factory;

        if (!lookup.ContainsKey(type))
        {
            factory = new ChannelFactory<T>();
            lookup.Add(type, factory);
        }
        else
        {
            factory = (ChannelFactory<T>)lookup[type];
        }

        T proxy = factory.CreateChannel();   
        ((IClientChannel)proxy).Open();

        return proxy;
    }    
}

Wydaje mi się, że powyższy kod zadziała, ale obawiam się, że wiele wątków próbuje dodać nowe obiekty ChannelFactory<T>, jeśli nie znajduje się w wyszukiwarce. Ponieważ używam. NET 4.0, I myślałem o użyciu metody ConcurrentDictionary i użyciu metody GetOrAdd() lub najpierw użyj metody TryGetValue(), aby sprawdzić, czy ChannelFactory<T> istnieje, a nie istnieje, następnie użyj metody GetOrAdd(). Nie jestem pewien wydajności chociaż ConcurrentDictionary.TryGetValue() i ConcurrentDictionary.GetOrAdd() metody.

Kolejnym drobnym pytaniem jest Czy muszę wywoływać metodę ChannelFactory.Close() na obiektach channel factory po ASP.NET aplikacja się kończy lub mogę pozwolić. NET framework pozbyć się obiektów channel factory na własną rękę. Kanał proxy będzie zawsze zamykany po wywołaniu metody service przez za pomocą metody ((IChannel)proxy).Close().

Author: Jim Aho, 2010-07-08

4 answers

Tak, jeśli chcesz stworzyć coś takiego - statyczną klasę do przechowywania wszystkich instancji ChannelFactory<T> - zdecydowanie musisz się upewnić, że ta klasa jest w 100% bezpieczna dla wątku i nie może potknąć się, gdy jest dostępna jednocześnie. Nie używałem jeszcze zbyt wiele funkcji. NET 4, więc nie mogę ich konkretnie skomentować - ale zdecydowanie polecam, aby było to jak najbardziej bezpieczne.

Jeśli chodzi o twoje drugie (drobne) pytanie: sama ChannelFactory jest klasą statyczną - więc tak naprawdę nie można nazwać .Close() Metoda na nim. Jeśli chcesz zapytać, czy chcesz wywołać metodę .Close() Na Rzeczywistej IChannel, to jeszcze raz: tak, staraj się być dobrym obywatelem i zamknij te kanały, jeśli kiedykolwiek możesz. Jeśli przegapisz jeden,. NET zajmie się nim - ale nie wyrzucaj nieużywanych kanałów na podłogę i idź dalej-posprzątaj po sobie! :-)

 13
Author: marc_s,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-08 05:22:48

Oto Klasa pomocnicza, której używam do obsługi fabryk kanałów:

public class ChannelFactoryManager : IDisposable
{
    private static Dictionary<Type, ChannelFactory> _factories = new Dictionary<Type,ChannelFactory>();
    private static readonly object _syncRoot = new object();

    public virtual T CreateChannel<T>() where T : class
    {
        return CreateChannel<T>("*", null);
    }

    public virtual T CreateChannel<T>(string endpointConfigurationName) where T : class
    {
        return CreateChannel<T>(endpointConfigurationName, null);
    }

    public virtual T CreateChannel<T>(string endpointConfigurationName, string endpointAddress) where T : class
    {
        T local = GetFactory<T>(endpointConfigurationName, endpointAddress).CreateChannel();
        ((IClientChannel)local).Faulted += ChannelFaulted;
        return local;
    }

    protected virtual ChannelFactory<T> GetFactory<T>(string endpointConfigurationName, string endpointAddress) where T : class
    {
        lock (_syncRoot)
        {
            ChannelFactory factory;
            if (!_factories.TryGetValue(typeof(T), out factory))
            {
                factory = CreateFactoryInstance<T>(endpointConfigurationName, endpointAddress);
                _factories.Add(typeof(T), factory);
            }
            return (factory as ChannelFactory<T>);
        }
    }

    private ChannelFactory CreateFactoryInstance<T>(string endpointConfigurationName, string endpointAddress)
    {
        ChannelFactory factory = null;
        if (!string.IsNullOrEmpty(endpointAddress))
        {
            factory = new ChannelFactory<T>(endpointConfigurationName, new EndpointAddress(endpointAddress));
        }
        else
        {
            factory = new ChannelFactory<T>(endpointConfigurationName);
        }
        factory.Faulted += FactoryFaulted;
        factory.Open();
        return factory;
    }

    private void ChannelFaulted(object sender, EventArgs e)
    {
        IClientChannel channel = (IClientChannel)sender;
        try
        {
            channel.Close();
        }
        catch
        {
            channel.Abort();
        }
        throw new ApplicationException("Exc_ChannelFailure");
    }

    private void FactoryFaulted(object sender, EventArgs args)
    {
        ChannelFactory factory = (ChannelFactory)sender;
        try
        {
            factory.Close();
        }
        catch
        {
            factory.Abort();
        }
        Type[] genericArguments = factory.GetType().GetGenericArguments();
        if ((genericArguments != null) && (genericArguments.Length == 1))
        {
            Type key = genericArguments[0];
            if (_factories.ContainsKey(key))
            {
                _factories.Remove(key);
            }
        }
        throw new ApplicationException("Exc_ChannelFactoryFailure");
    }

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

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            lock (_syncRoot)
            {
                foreach (Type type in _factories.Keys)
                {
                    ChannelFactory factory = _factories[type];
                    try
                    {
                        factory.Close();
                        continue;
                    }
                    catch
                    {
                        factory.Abort();
                        continue;
                    }
                }
                _factories.Clear();
            }
        }
    }
}

Następnie definiuję service invoker:

public interface IServiceInvoker
{
    R InvokeService<T, R>(Func<T, R> invokeHandler) where T: class;
}

I realizacja:

public class WCFServiceInvoker : IServiceInvoker
{
    private static ChannelFactoryManager _factoryManager = new ChannelFactoryManager();
    private static ClientSection _clientSection = ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;

    public R InvokeService<T, R>(Func<T, R> invokeHandler) where T : class
    {
        var endpointNameAddressPair = GetEndpointNameAddressPair(typeof(T));
        T arg = _factoryManager.CreateChannel<T>(endpointNameAddressPair.Key, endpointNameAddressPair.Value);
        ICommunicationObject obj2 = (ICommunicationObject)arg;
        try
        {
            return invokeHandler(arg);
        }
        finally
        {
            try
            {
                if (obj2.State != CommunicationState.Faulted)
                {
                    obj2.Close();
                }
            }
            catch
            {
                obj2.Abort();
            }
        }
    }

    private KeyValuePair<string, string> GetEndpointNameAddressPair(Type serviceContractType)
    {
        var configException = new ConfigurationErrorsException(string.Format("No client endpoint found for type {0}. Please add the section <client><endpoint name=\"myservice\" address=\"http://address/\" binding=\"basicHttpBinding\" contract=\"{0}\"/></client> in the config file.", serviceContractType));
        if (((_clientSection == null) || (_clientSection.Endpoints == null)) || (_clientSection.Endpoints.Count < 1))
        {
            throw configException;
        }
        foreach (ChannelEndpointElement element in _clientSection.Endpoints)
        {
            if (element.Contract == serviceContractType.ToString())
            {
                return new KeyValuePair<string, string>(element.Name, element.Address.AbsoluteUri);
            }
        }
        throw configException;
    }

}

Teraz za każdym razem, gdy musisz zadzwonić do usługi WCF, możesz użyć tego:

WCFServiceInvoker invoker = new WCFServiceInvoker();
SomeReturnType result = invoker.InvokeService<IMyServiceContract, SomeReturnType>(
    proxy => proxy.SomeMethod()
);

Zakłada to, że zdefiniowano punkt końcowy klienta dla umowy o świadczenie usług IMyServiceContract w pliku konfiguracyjnym:

<client>
    <endpoint 
        name="myservice" 
        address="http://example.com/" 
        binding="basicHttpBinding" 
        contract="IMyServiceContract" />
</client>
 59
Author: Darin Dimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-08 08:17:25

Nie podobała mi się konstrukcja wywołująca:

WCFServiceInvoker invoker = new WCFServiceInvoker();
var result = invoker.InvokeService<IClaimsService, ICollection<string>>(proxy => proxy.GetStringClaims());

Również nie można użyć tego samego kanału dwa razy.

Stworzyłem To rozwiązanie:

using(var i = Connection<IClaimsService>.Instance)
{           
   var result = i.Channel.GetStringClaims();
}

Teraz możesz ponownie użyć tego samego kanału, dopóki Instrukcja using nie wywoła dispose.

Metoda GetChannel jest zasadniczo ChannelFactory.CreateChannel () z dodatkowymi konfiguracjami, których używam.

Możesz zbudować buforowanie dla ChannelFactory, tak jak robią to inne rozwiązania.

Kod połączenia klasa:

public static class Connection<T>
   {
      public static ChannelHolder Instance
      {
         get
         {
            return new ChannelHolder();
         }
      }

      public class ChannelHolder : IDisposable
      {
         public T Channel { get; set; }

         public ChannelHolder()
         {
            this.Channel = GetChannel();
         }

         public void Dispose()
         {
            IChannel connection = null;
            try
            {
               connection = (IChannel)Channel;
               connection.Close();
            }
            catch (Exception)
            {
               if (connection != null)
               {
                  connection.Abort();
               }
            }
         }
      }
}
 2
Author: Max,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-29 14:34:47

@NelsonRothermel, yes I went down the road of nie używa metody try catch w module obsługi zdarzenia ChannelFaulted Channelfaulted. Tak więc ChannelFaulted stałby się

 private void ChannelFaulted(object sender, EventArgs e)
    {
        IClientChannel channel = (IClientChannel)sender;            
        channel.Abort();
    }

Wydaje się, że pozwala oryginalnemu wyjątkowi na bańkę. Zdecydował się również nie używać kanału.close as it seems to throw an exception ponieważ kanał jest już w stanie uszkodzonym. FactoryFaulted event handler może mieć podobne problemy. Btw @ Darin, dobry kawałek kodu. [odpowiedz]..

 0
Author: PhilMc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-30 09:14:05