ฉันจะส่งค่าไปยังตัวสร้างบนบริการ wcf ของฉันได้อย่างไร


103

ฉันต้องการส่งผ่านค่าไปยังตัวสร้างในคลาสที่ใช้บริการของฉัน

อย่างไรก็ตาม ServiceHost อนุญาตให้ฉันส่งในชื่อของประเภทที่จะสร้างเท่านั้นไม่ใช่อาร์กิวเมนต์ใดที่จะส่งผ่านไปยังตัวสร้าง

ฉันต้องการที่จะผ่านในโรงงานที่สร้างวัตถุบริการของฉัน

สิ่งที่ฉันพบจนถึงตอนนี้:

  • WCF Dependency Injection Behaviorซึ่งเป็นมากกว่าสิ่งที่ฉันกำลังมองหาและดูเหมือนจะซับซ้อนเกินความต้องการของฉัน

6
ฉันกลัวว่าความซับซ้อนนั้นมีอยู่ใน WCF และไม่มีอะไรที่คุณสามารถทำได้เพื่อบรรเทามันนอกจากการไม่ใช้ WCF หรือซ่อนมันไว้หลังซุ้มที่เป็นมิตรกับผู้ใช้มากกว่าเช่น WCF Facility ของวินด์เซอร์หากคุณใช้ Windsor
Krzysztof Kozmic

คำตอบ:


122

คุณจะต้องใช้การรวมกันของที่กำหนดเองServiceHostFactory, และServiceHostIInstanceProvider

ให้บริการพร้อมลายเซ็นตัวสร้างนี้:

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 Containers บางตัวได้ทำสิ่งนี้ให้คุณแล้ว (cue: Windsor's WCF Facility)


+1 (แต่yuck #regionsแม้ว่าจะเป็นกรณีที่รุนแรงน้อยที่สุดของความผิด แต่ฉันก็เปลี่ยนเป็นอินเทอร์เฟซที่ชัดเจนโดยนัยตัวเอง: P)
Ruben Bartelink

5
ฉันจะใช้โฮสติ้งตัวเองได้อย่างไร? ฉันได้รับข้อยกเว้นหลังจากโทรไปที่ CreateServiceHost ฉันสามารถเรียกใช้เมธอดที่ได้รับการป้องกันเท่านั้นที่จะแทนที่ ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); ข้อยกเว้นคือข้อความยกเว้นคือ: ไม่สามารถเรียกใช้ 'ServiceHostFactory.CreateServiceHost' ภายในสภาพแวดล้อมการโฮสต์ปัจจุบัน API นี้ต้องการให้แอปพลิเคชันการโทรโฮสต์อยู่ใน IIS หรือ WAS
Guy

2
@Guy ฉันมีปัญหาตัวอย่าง เนื่องจากฟังก์ชั่นคือprotectedฉันไม่สามารถเรียกมันเองได้จาก Main ()
Andriy Drozdyuk

1
มีปัญหาโดยธรรมชาติกับแนวทางนี้และนั่นคือการพึ่งพาของคุณถูกสร้างขึ้นเพียงครั้งเดียวในสภาพแวดล้อมที่โฮสต์ IIS ServiceHostFactory, ServiceHost และ InstanceProvider จะถูกสร้างขึ้นเพียงครั้งเดียวจนกว่าพูลแอปพลิเคชันจะถูกรีไซเคิลซึ่งหมายความว่าการอ้างอิงของคุณไม่สามารถรีเฟรชต่อการโทรได้ (เช่น DbContext) ซึ่งแนะนำการแคชค่าโดยไม่ได้ตั้งใจและอายุการใช้งานที่ยาวนานขึ้นของการอ้างอิงที่เป็น ไม่ต้องการ. ฉันไม่แน่ใจว่าจะแก้ไขปัญหานี้อย่างไรมีความคิดอย่างไรบ้าง?
David Anderson

2
@MarkSeemann ฉันแค่สงสัยว่าทำไมคุณถึงฉีดdepเข้าไปในInstanceProvider ของทุกสัญญา คุณสามารถทำ: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));ที่เป็นอินเตอร์เฟซสัญญาของคุณIMyService MyService(IDependency dep)ดังนั้นฉีดIDependencyลงใน InstanceProvider ที่จำเป็นจริงๆเท่านั้น
voytek

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)
John Reynolds

11

คำตอบของ Mark กับ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 กับบริการนี้อย่างแน่นหนา
Mark Seemann

2
เป็นเพียงตัวเลือกที่สองไม่มีการประเมินว่าอะไรดีกว่า ฉันใช้ ServiceHostFactory แบบกำหนดเองสองสามครั้ง (โดยเฉพาะเมื่อคุณต้องการลงทะเบียนพฤติกรรมหลายอย่าง)
dalo

1
ปัญหาคือคุณสามารถเริ่มต้นตัวอย่างเช่นคอนเทนเนอร์ DI ในตัวสร้างแอตทริบิวต์เท่านั้น .. คุณไม่สามารถส่งข้อมูลที่มีอยู่ได้
Guy

5

ฉันทำงานจากคำตอบของ Mark แต่ (อย่างน้อยสำหรับสถานการณ์ของฉัน) มันซับซ้อนโดยไม่จำเป็น หนึ่งในผู้ServiceHostสร้างยอมรับอินสแตนซ์ของบริการซึ่งคุณสามารถส่งผ่านได้โดยตรงจากการServiceHostFactoryใช้งาน

หากต้องการ piggyback จากตัวอย่างของ Mark จะมีลักษณะดังนี้:

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

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 มาหลายปีแล้วและฉันก็ไม่พลาด!
Ian Ringrose

นี่คือแนวทางของฉันในการเขียนครั้งเดียวคุณสมบัติstackoverflow.com/questions/839788/…
Ronnie 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
Static / Singletons ชั่วร้าย! - ดูstackoverflow.com/questions/137975/…
Immortal Blue
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.