Як я можу передавати значення конструктору на сервісі wcf?


103

Я хотів би передати значення в конструктор класу, який реалізує мою службу.

Однак ServiceHost дозволяє лише мені передавати ім'я типу для створення, а не те, які аргументи передавати його виконавцю.

Мені хотілося б перейти на завод, який створює мій сервісний об’єкт.

Що я знайшов поки що:


6
Я боюся, що складність притаманна WCF, і ви не можете багато чого зробити, щоб полегшити її, за винятком того, як не використовувати WCF або ховати його за більш зручним фасадом, як, наприклад, Windsor's 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 вже зробили це для вас (реп.: Віндзорський фонд WCF).


+1 (Але yuck, #regions, хоча це найменш важкий випадок правопорушення, я
переходжу

5
Як я можу використовувати його для самостійного хостингу? Я отримую виняток після дзвінка в CreateServiceHost. Я можу зателефонувати лише до захищеного методу public override ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); Виняток: Повідомлення про виняток: «ServiceHostFactory.CreateServiceHost» не можна викликати в поточному середовищі хостингу. Цей API вимагає розміщення програми для виклику в IIS або WAS.
Хлопець

2
@Guy У мене є проблема із зразком. Оскільки функція полягає в тому, що protectedя не можу її назвати з Main ()
Андрій Дроздюк

1
У цьому підході є притаманна проблема, і ця ваша залежність створюється лише один раз у середовищі, де розміщується IIS. СервісHostFactory, ServiceHost та InstanceProvider створюються лише один раз, поки пул додатків не буде перероблений, що означає, що залежність не може бути реально оновлена ​​за виклик (наприклад, DbContext), що вводить ненавмисне кешування значень та довший термін служби залежності, що є не хотів. Я не дуже впевнений, як вирішити це, якісь думки?
Девід Андерсон

2
@MarkSeemann Мені просто цікаво, чому ти ввів все це depв Контрактний Ідентифікатор Провідника . Ви можете зробити: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));де IMyService ваш контрактний інтерфейс MyService(IDependency dep). Тому вводьте IDependencyлише в InstanceProvider, який насправді йому потрібен.
войтек

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 тільки в конструкторі атрибутів. Ви не можете надсилати існуючі дані.
Хлопець

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 / ...
Ронні Overby

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/…
Безсмертний блакитний
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.