Làm cách nào để chuyển giá trị cho hàm tạo trên dịch vụ wcf của tôi?


103

Tôi muốn chuyển các giá trị vào hàm tạo trên lớp triển khai dịch vụ của tôi.

Tuy nhiên ServiceHost chỉ cho phép tôi truyền vào tên của kiểu cần tạo, chứ không phải những đối số nào để chuyển tới trình cấu trúc của nó.

Tôi muốn có thể vượt qua nhà máy tạo đối tượng dịch vụ của tôi.

Những gì tôi đã tìm thấy cho đến nay:


6
Tôi e rằng sự phức tạp vốn có đối với WCF và bạn không thể làm gì để giảm bớt nó, ngoài việc không sử dụng WCF hoặc ẩn nó sau mặt tiền thân thiện với người dùng hơn, như Cơ sở WCF của Windsor nếu bạn đang sử dụng Windsor
Krzysztof Kozmic

Câu trả lời:


122

Bạn sẽ cần phải thực hiện một sự kết hợp của các tùy chỉnh ServiceHostFactory, ServiceHostIInstanceProvider.

Đưa ra một dịch vụ có chữ ký hàm tạo này:

public MyService(IDependency dep)

Đây là một ví dụ có thể tạo ra 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
}

Đăng ký MyServiceHostFactory trong tệp MyService.svc của bạn hoặc sử dụng MyServiceHost trực tiếp trong mã cho các tình huống tự lưu trữ.

Bạn có thể dễ dàng tổng quát hóa cách tiếp cận này và trên thực tế, một số Container DI đã làm điều này cho bạn (gợi ý: Cơ sở WCF của Windsor).


+1 (Nhưng yuck, #regions mặc dù nó là trường hợp nghiêm trọng nhất của hành vi phạm tội, tôi chuyển sang giao diện rõ ràng impl bản thân mình: P)
Ruben Bartelink

5
Làm thế nào tôi có thể sử dụng nó để tự lưu trữ? Tôi nhận được một ngoại lệ sau khi gọi đến CreateServiceHost. Tôi chỉ có thể gọi phương thức được bảo vệ ghi đè công khai ServiceHostBase CreateServiceHost (string constructorString, Uri [] baseAddresses); Ngoại lệ là Thông báo ngoại lệ là: 'ServiceHostFactory.CreateServiceHost' không thể được gọi trong môi trường lưu trữ hiện tại. API này yêu cầu ứng dụng gọi điện phải được lưu trữ trong IIS hoặc WAS.
Guy

2
@Guy Tôi đang gặp sự cố mẫu. Bởi vì chức năng là protectedtôi không thể gọi nó là bản thân mình từ Main ()
Andriy Drozdyuk

1
Có một vấn đề cố hữu với cách tiếp cận này và đó là sự phụ thuộc của bạn thực sự chỉ được tạo một lần trong môi trường được lưu trữ IIS. ServiceHostFactory, ServiceHost và InstanceProvider đều chỉ được tạo một lần cho đến khi nhóm ứng dụng được tái chế, có nghĩa là phần phụ thuộc của bạn thực sự không thể được làm mới mỗi lần gọi (ví dụ: DbContext), điều này giới thiệu bộ nhớ đệm không mong muốn của các giá trị và thời gian tồn tại lâu hơn của phần phụ thuộc đó không muốn. Tôi không thực sự chắc chắn làm thế nào để giải quyết điều này, bất kỳ suy nghĩ?
David Anderson

2
@MarkSeemann Tôi chỉ tự hỏi, tại sao bạn lại đưa depvào mỗi Trình cung cấp dịch vụ của Hợp đồng . Bạn có thể làm: ImplementedContracts.Values.First(c => c.Name == "IMyService").ContractBehaviors.Add(new MyInstanceProvider(dep));đâu IMyService là một giao diện hợp đồng của bạn MyService(IDependency dep). Vì vậy, IDependencychỉ tiêm vào InstanceProvider thực sự cần nó.
voytek

14

Bạn có thể chỉ cần tạo và thể hiện của bạn Servicevà chuyển thể hiện đó cho ServiceHostđối tượng. Điều duy nhất bạn phải làm là thêm một [ServiceBehaviour]thuộc tính cho dịch vụ của mình và đánh dấu tất cả các đối tượng trả về bằng [DataContract]thuộc tính.

Đây là một mô hình:

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

và cách sử dụng:

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

host.Open();

Tôi hy vọng điều này sẽ làm cho cuộc sống của ai đó dễ dàng hơn.


5
Điều đó chỉ hoạt động cho những người độc thân (như được chỉ ra bởi InstanceContextMode.Single).
John Reynolds

11

Câu trả lời của Mark với IInstanceProviderlà đúng.

Thay vì sử dụng ServiceHostFactory tùy chỉnh, bạn cũng có thể sử dụng một thuộc tính tùy chỉnh (giả sử MyInstanceProviderBehaviorAttribute). Bắt nguồn từ nó Attribute, làm cho nó triển khai IServiceBehaviorvà thực hiện IServiceBehavior.ApplyDispatchBehaviorphương pháp như

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

Sau đó, áp dụng thuộc tính cho lớp triển khai dịch vụ của bạn

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

Tùy chọn thứ ba: bạn cũng có thể áp dụng một hành vi dịch vụ bằng cách sử dụng tệp cấu hình.


2
Về mặt kỹ thuật, đây cũng giống như một giải pháp, nhưng với cách tiếp cận đó, bạn kết hợp chặt chẽ IInstanceProvider với dịch vụ.
Mark Seemann

2
Chỉ là một lựa chọn thứ hai, không có đánh giá nào tốt hơn. Tôi đã sử dụng ServiceHostFactory tùy chỉnh một vài lần (đặc biệt khi bạn muốn đăng ký một số hành vi).
dalo

1
Vấn đề là bạn có thể khởi tạo DI container ví dụ chỉ trong hàm tạo thuộc tính .. bạn không thể gửi dữ liệu tồn tại.
Guy

5

Tôi đã làm việc từ câu trả lời của Mark, nhưng (ít nhất là đối với kịch bản của tôi), nó không cần thiết phải phức tạp. Một trong các hàm ServiceHosttạo chấp nhận một phiên bản của dịch vụ mà bạn có thể chuyển trực tiếp vào từ việc ServiceHostFactorytriển khai.

Để lấy lại ví dụ của Mark, nó sẽ như thế này:

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
Điều này sẽ hoạt động nếu dịch vụ của bạn và tất cả các phụ thuộc được đưa vào đều an toàn theo luồng. Sự quá tải cụ thể đó của phương thức khởi tạo ServiceHost về cơ bản vô hiệu hóa quản lý vòng đời của WCF. Thay vào đó, bạn đang nói rằng tất cả các yêu cầu đồng thời sẽ được xử lý bởi instance. Điều đó có thể ảnh hưởng đến hiệu suất hoặc không. Nếu bạn muốn có thể xử lý các yêu cầu đồng thời, toàn bộ biểu đồ đối tượng đó phải an toàn theo chuỗi, nếu không bạn sẽ nhận được hành vi không xác định, không chính xác. Nếu bạn có thể đảm bảo an toàn cho luồng, thì giải pháp của tôi thực sự là phức tạp. Nếu bạn không thể đảm bảo điều đó, giải pháp của tôi là bắt buộc.
Mark Seemann

3

Vặn nó… Tôi đã pha trộn các mẫu định vị dịch vụ và tiêm phụ thuộc (nhưng chủ yếu nó vẫn là tiêm phụ thuộc và nó thậm chí diễn ra trong phương thức khởi tạo, nghĩa là bạn có thể có trạng thái chỉ đọc).

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

Các phần phụ thuộc của dịch vụ được chỉ định rõ ràng trong hợp đồng của Dependencieslớp lồng nhau của nó . Nếu bạn đang sử dụng vùng chứa IoC (một vùng chứa chưa sửa lỗi WCF cho bạn), bạn có thể định cấu hình nó để tạo Dependenciescá thể thay vì dịch vụ. Bằng cách này, bạn sẽ có được cảm giác mờ ảo ấm áp mà vùng chứa của bạn mang lại trong khi cũng không phải nhảy qua quá nhiều vòng do WCF áp đặt.

Tôi sẽ không mất ngủ vì cách tiếp cận này. Ai khác cũng vậy. Rốt cuộc, vùng chứa IoC của bạn là một tập hợp lớn, béo, tĩnh các đại biểu tạo ra nội dung cho bạn. Thêm một cái nữa là gì?


Một phần của vấn đề là tôi muốn công ty sử dụng phương pháp tiêm phụ thuộc và nếu nó trông không sạch sẽ và đơn giản đối với một lập trình viên chưa bao giờ sử dụng tiêm phụ thuộc, thì tiêm phụ thuộc sẽ không bao giờ được sử dụng bởi bất kỳ lập trình viên nào khác. Tuy nhiên, tôi đã không sử dụng WCF trong nhiều năm, và tôi không bỏ lỡ nó!
Ian Ringrose

Đây là cách tiếp cận của tôi đối với thuộc tính ghi một lần stackoverflow.com/questions/839788/…
Ronnie Overby

0

Chúng tôi đã đối mặt với cùng một vấn đề này và đã giải quyết nó theo cách sau. Đó là một giải pháp đơn giản.

Trong Visual Studio, chỉ cần tạo một ứng dụng dịch vụ WCF bình thường và xóa giao diện của nó. Giữ nguyên vị trí của tệp .cs (chỉ cần đổi tên nó) và mở tệp cs đó và thay thế tên của giao diện bằng tên lớp ban đầu của bạn để triển khai logic dịch vụ (theo cách này lớp dịch vụ sử dụng kế thừa và thay thế triển khai thực tế của bạn). Thêm một hàm tạo mặc định gọi các hàm tạo của lớp cơ sở, như sau:

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

Lớp cơ sở MyService là phần triển khai thực tế của dịch vụ. Lớp cơ sở này không nên có một phương thức khởi tạo không tham số, mà chỉ có những phương thức khởi tạo có tham số chấp nhận các phụ thuộc.

Dịch vụ nên sử dụng lớp này thay vì MyService ban đầu.

Đó là một giải pháp đơn giản và hoạt động như một sự quyến rũ :-D


4
Bạn chưa tách Service1 khỏi các phụ thuộc của nó, đó là một vấn đề. Bạn vừa khởi tạo các phụ thuộc trong phương thức khởi tạo cho Service1, điều này bạn có thể thực hiện mà không cần lớp cơ sở.
saille

0

Đây là một giải pháp rất hữu ích - đặc biệt là đối với những người mới làm quen với WCF coder. Tôi muốn đăng một mẹo nhỏ cho bất kỳ người dùng nào có thể đang sử dụng phần mềm này cho một dịch vụ được lưu trữ trên IIS. MyServiceHost cần kế thừa WebServiceHost , không chỉ ServiceHost.

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

Điều này sẽ tạo tất cả các ràng buộc cần thiết, v.v. cho các điểm cuối của bạn trong IIS.


-2

Tôi sử dụng các biến tĩnh thuộc loại của tôi. Không chắc liệu đây có phải là cách tốt nhất hay không, nhưng nó phù hợp với tôi:

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

Khi tôi khởi tạo máy chủ dịch vụ, tôi làm như sau:

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

    ...

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

    ....
}

5
Static / Singletons là ác! - xem stackoverflow.com/questions/137975/…
Màu xanh bất tử
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.