Как передать значения конструктору в моей службе wcf?

104

Я хотел бы передать значения в конструктор класса, реализующего мою службу.

Однако ServiceHost позволяет мне передавать только имя создаваемого типа, а не аргументы, передаваемые его конструктору.

Я хотел бы иметь возможность передать фабрику, которая создает мой объект службы.

Что я нашел на данный момент:

Ян Рингроуз
источник
6
Я боюсь, что сложность присуща WCF, и вы мало что можете сделать, чтобы ее облегчить, кроме как не использовать WCF или скрывать его за более удобным для пользователя фасадом, например, Windsor WCF Facility, если вы используете Windsor
Krzysztof Kozmic

Ответы:

122

Вам необходимо реализовать комбинацию обычая ServiceHostFactory, ServiceHostиIInstanceProvider .

Для службы с этой подписью конструктора:

public MyService(IDependency dep)

Вот пример, который может развернуть MyService:

public class MyServiceHostFactory : ServiceHostFactory
{
    private readonly IDependency dep;

    public MyServiceHostFactory()
    {
        this.dep = new MyClass();
    }

    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        return new MyServiceHost(this.dep, serviceType, baseAddresses);
    }
}

public class MyServiceHost : ServiceHost
{
    public MyServiceHost(IDependency dep, Type serviceType, params Uri[] baseAddresses)
        : base(serviceType, baseAddresses)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        foreach (var cd in this.ImplementedContracts.Values)
        {
            cd.Behaviors.Add(new MyInstanceProvider(dep));
        }
    }
}

public class MyInstanceProvider : IInstanceProvider, IContractBehavior
{
    private readonly IDependency dep;

    public MyInstanceProvider(IDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    #region IInstanceProvider Members

    public object GetInstance(InstanceContext instanceContext, Message message)
    {
        return this.GetInstance(instanceContext);
    }

    public object GetInstance(InstanceContext instanceContext)
    {
        return new MyService(this.dep);
    }

    public void ReleaseInstance(InstanceContext instanceContext, object instance)
    {
        var disposable = instance as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    #endregion

    #region IContractBehavior Members

    public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }

    public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime)
    {
        dispatchRuntime.InstanceProvider = this;
    }

    public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)
    {
    }

    #endregion
}

Зарегистрируйте MyServiceHostFactory в файле MyService.svc или используйте MyServiceHost непосредственно в коде для сценариев самостоятельного размещения.

Вы можете легко обобщить этот подход, и на самом деле некоторые контейнеры DI уже сделали это за вас (реплика: Windsor WCF Facility).

Марк Симанн
источник
+1 (Но фу, #regions, хотя это наименее серьезный случай нарушения, я конвертирую в явный интерфейс, подразумевающий себя: P)
Рубен Бартелинк
5
Как я могу использовать его для самостоятельного хостинга? Я получаю исключение после вызова CreateServiceHost. Я могу вызвать только защищенный метод публичного переопределения ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); Исключением является сообщение об исключении: «ServiceHostFactory.CreateServiceHost» не может быть вызван в текущей среде размещения. Этот API требует, чтобы вызывающее приложение размещалось в IIS или WAS.
Guy
2
@Guy У меня проблема с образцом. Потому что функция такова, что protectedя не могу вызвать ее сам из Main ()
Андрей Дроздюк
1
У этого подхода есть неотъемлемая проблема, и это то, что ваша зависимость действительно создается только один раз в среде, размещенной на IIS. ServiceHostFactory, ServiceHost и InstanceProvider создаются только один раз, пока пул приложений не будет переработан, что означает, что ваша зависимость не может быть обновлена ​​за один вызов (например, DbContext), что приводит к непреднамеренному кэшированию значений и увеличению срока службы зависимости, которая является нежелательный. Я не совсем уверен, как это решить, есть мысли?
Дэвид Андерсон
2
@MarkSeemann Мне просто интересно, зачем вы вводили depкаждый контракт InstanceProvider. Вы можете сделать: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));где IMyService контрактный интерфейс вашего MyService(IDependency dep). Поэтому вводите IDependencyтолько в InstanceProvider, который действительно в этом нуждается.
Войтек 02
14

Вы можете просто создать свой экземпляр Serviceи передать этот экземпляр ServiceHostобъекту. Единственное, что вам нужно сделать, это добавить [ServiceBehaviour]атрибут для вашей службы и пометить все возвращаемые объекты [DataContract]атрибутом.

Вот макет:

namespace Service
{
    [ServiceContract]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class MyService
    {
        private readonly IDependency _dep;

        public MyService(IDependency dep)
        {
            _dep = dep;
        }

        public MyDataObject GetData()
        {
            return _dep.GetData();
        }
    }

    [DataContract]
    public class MyDataObject
    {
        public MyDataObject(string name)
        {
            Name = name;
        }

        public string Name { get; private set; }
    }

    public interface IDependency
    {
        MyDataObject GetData();
    }
}

и использование:

var dep = new Dependecy();
var myService = new MyService(dep);
var host = new ServiceHost(myService);

host.Open();

Надеюсь, это кому-то облегчит жизнь.

Керим
источник
5
Это работает только для синглтонов (как указано InstanceContextMode.Single).
Джон Рейнольдс
11

Ответ Марка IInstanceProviderправильный.

Вместо использования настраиваемого ServiceHostFactory вы также можете использовать настраиваемый атрибут (скажем MyInstanceProviderBehaviorAttribute). Получите его Attribute, заставьте его реализовать IServiceBehaviorи реализовать такой IServiceBehavior.ApplyDispatchBehaviorметод, как

// YourInstanceProvider implements IInstanceProvider
var instanceProvider = new YourInstanceProvider(<yourargs>);

foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
    foreach (var epDispatcher in dispatcher.Endpoints)
    {
        // this registers your custom IInstanceProvider
        epDispatcher.DispatchRuntime.InstanceProvider = instanceProvider;
    }
}

Затем примените атрибут к классу реализации службы.

[ServiceBehavior]
[MyInstanceProviderBehavior(<params as you want>)]
public class MyService : IMyContract

Третий вариант: вы также можете применить поведение службы с помощью файла конфигурации.

дало
источник
2
Технически это тоже похоже на решение, но при таком подходе вы плотно связываете IInstanceProvider с сервисом.
Марк Земанн
2
Просто второй вариант, без оценки, что лучше. Я пару раз использовал настраиваемый ServiceHostFactory (особенно когда вы хотите зарегистрировать несколько вариантов поведения).
dalo
1
Проблема в том, что вы можете инициировать, например, контейнер DI только в конструкторе атрибутов .. вы не можете отправлять существующие данные.
Guy
5

Я работал с ответом Марка, но (по крайней мере, для моего сценария) это было излишне сложным. Один из ServiceHostконструкторов принимает экземпляр службы, который можно передать непосредственно из ServiceHostFactoryреализации.

Если взять пример Марка, это будет выглядеть так:

public class MyServiceHostFactory : ServiceHostFactory
{
    private readonly IDependency _dep;

    public MyServiceHostFactory()
    {
        _dep = new MyClass();
    }

    protected override ServiceHost CreateServiceHost(Type serviceType,
        Uri[] baseAddresses)
    {
        var instance = new MyService(_dep);
        return new MyServiceHost(instance, serviceType, baseAddresses);
    }
}

public class MyServiceHost : ServiceHost
{
    public MyServiceHost(MyService instance, Type serviceType, params Uri[] baseAddresses)
        : base(instance, baseAddresses)
    {
    }
}
МакГарнагл
источник
12
Это будет работать, если ваш сервис и все внедренные зависимости являются потокобезопасными. Эта конкретная перегрузка конструктора ServiceHost по существу отключает управление жизненным циклом WCF. Вместо этого вы говорите, что все параллельные запросы будут обрабатываться instance. Это может повлиять или не повлиять на производительность. Если вы хотите иметь возможность обрабатывать параллельные запросы, весь этот граф объектов должен быть потокобезопасным, иначе вы получите недетерминированное неправильное поведение. Если вы можете гарантировать безопасность потоков, мое решение действительно излишне сложное. Если вы не можете этого гарантировать, требуется мое решение.
Марк Земанн
3

К черту… Я смешал шаблоны внедрения зависимостей и локатора сервисов (но в основном это все еще внедрение зависимостей, и оно даже происходит в конструкторе, что означает, что вы можете иметь состояние только для чтения).

public class MyService : IMyService
{
    private readonly Dependencies _dependencies;

    // set this before creating service host. this can use your IOC container or whatever.
    // if you don't like the mutability shown here (IoC containers are usually immutable after being configured)
    // you can use some sort of write-once object
    // or more advanced approach like authenticated access
    public static Func<Dependencies> GetDependencies { get; set; }     
    public class Dependencies
    {
        // whatever your service needs here.
        public Thing1 Thing1 {get;}
        public Thing2 Thing2 {get;}

        public Dependencies(Thing1 thing1, Thing2 thing2)
        {
            Thing1 = thing1;
            Thing2 = thing2;
        }
    }

    public MyService ()
    {
        _dependencies = GetDependencies(); // this will blow up at run time in the exact same way your IoC container will if it hasn't been properly configured up front. NO DIFFERENCE
    }
}

Зависимости службы четко указаны в контракте ее вложенного Dependenciesкласса. Если вы используете контейнер IoC (тот, который еще не исправляет беспорядок WCF для вас), вы можете настроить его для создания Dependenciesэкземпляра вместо службы. Таким образом вы получите теплое нечеткое ощущение, которое дает вам ваш контейнер, и при этом вам не придется перепрыгивать через слишком много препятствий, наложенных WCF.

Я не собираюсь терять сон из-за такого подхода. Никто не должен. В конце концов, ваш контейнер IoC - это большая, толстая статическая коллекция делегатов, которая создает для вас вещи. Что добавляете еще?

Ронни Оверби
источник
Частично проблема заключалась в том, что я хотел заставить компанию использовать внедрение зависимостей, и если бы это не выглядело чистым и простым для программиста, который никогда не использовал внедрение зависимостей, то внедрение зависимостей никогда не было бы использовано никаким другим программистом. Однако я не использую WCF много лет и не скучаю по нему!
Ян Рингроуз,
Вот мой подход к свойству с однократной записью stackoverflow.com/questions/839788/…
Ронни Оверби,
0

Мы столкнулись с этой же проблемой и решили ее следующим образом. Это простое решение.

В Visual Studio просто создайте обычное приложение-службу WCF и удалите его интерфейс. Оставьте файл .cs на месте (просто переименуйте его), откройте этот файл cs и замените имя интерфейса своим исходным именем класса, который реализует логику службы (таким образом, класс службы использует наследование и заменяет вашу фактическую реализацию). Добавьте конструктор по умолчанию, который вызывает конструкторы базового класса, например:

public class Service1 : MyLogicNamespace.MyService
{
    public Service1() : base(new MyDependency1(), new MyDependency2()) {}
}

Базовый класс MyService - это фактическая реализация службы. Этот базовый класс не должен иметь конструктора без параметров, а только конструкторы с параметрами, которые принимают зависимости.

Служба должна использовать этот класс вместо исходного MyService.

Это простое решение и работает как шарм :-D

Рон Дейкерс
источник
4
Вы не отделили Service1 от его зависимостей, что было своего рода сутью. Вы только что создали экземпляры зависимостей в конструкторе для Service1, что можно обойтись без базового класса.
Saille
0

Это было очень полезным решением, особенно для начинающих программистов WCF. Я действительно хотел опубликовать небольшой совет для всех пользователей, которые могут использовать это для службы, размещенной в IIS. MyServiceHost должен наследовать WebServiceHost , а не только ServiceHost.

public class MyServiceHost : WebServiceHost
{
    public MyServiceHost(MyService instance, Type serviceType, params Uri[] baseAddresses)
        : base(instance, baseAddresses)
    {
    }
}

Это создаст все необходимые привязки и т. Д. Для ваших конечных точек в IIS.

Эрик Дикман
источник
-2

Я использую статические переменные своего типа. Не уверен, что это лучший способ, но у меня он работает:

public class MyServer
{   
    public static string CustomerDisplayName;
    ...
}

Когда я создаю экземпляр хоста службы, я делаю следующее:

protected override void OnStart(string[] args)
{
    MyServer.CustomerDisplayName = "Test customer";

    ...

    selfHost = new ServiceHost(typeof(MyServer), baseAddress);

    ....
}
Борис
источник
5
Статические / Синглтоны - это зло! - см. stackoverflow.com/questions/137975/…
Immortal Blue