question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Introduce dependency injection of services from a custom parameter attribute in Service implementation

See original GitHub issue

Introduce a parameter attribute allowing to inject services from DI directly in Service implementation similar to ASP.NET Core [FromServices] attribute. https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.fromservicesattribute?view=aspnetcore-5.0

[ServiceContract]
public interface IService
{
    [OperationContract]
    Task<Customer> GetCustomerAsync(int customerId, [Injected]DbContext dbContext);
}

This would mean the customer ID comes in as part of the service call, and CoreWCF will fetch from the InstanceContext scoped ServiceProvider the dbContext and add it as a parameter. What are your thoughts?

_Originally posted by @mconnew in https://github.com/CoreWCF/CoreWCF/issues/473#issuecomment-949130495_

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
mconnewcommented, Oct 26, 2021

I was thinking of having the option in the service contract. It wouldn’t show up in the WSDL. We already have the concept of the service contract not being identical between the client and server. Here’s 3 different contracts which produce the exact same on the wire SOAP message:

[ServiceContract]
public interface IService
{
    [OperationContract]
    string Echo(string echo);
}
[ServiceContract]
public interface IService
{
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginEcho(string echo, AsyncCallback cb, AsyncState s);

    [OperationContract]  
    string EndEcho(IAsyncResult iar);
}
[ServiceContract]
public interface IService
{
    [OperationContract]
    Task<string> EchoAsync(string echo);
}

So there is precedence for having a mismatch in the contracts between the client and the server. I also understand the contract reuse issue. I have some other ideas which I’m not saying are better or preferred, just adding ideas to the conversation.

We could create a helper method to create a ServiceEndpoint for the client to use. One of the constructors of ChannelFactory takes a ServiceEndpoint. We would then need some mechanism to have the client ignore the extra parameters on the call, so that would probably involve a custom IClientMessageFormatter. This option is still a little messy because the client code would need to ignore the extra stuff on the contract and pass null/default in the call. Ideally the client shouldn’t be aware of server side injection dependencies so I’m not a big fan of this idea.

We could create a source generator which scans for any ServiceContract interfaces and checks if they have any [FromServices] attributes. If they do, generate a copy of the contract with either a modified name or in a different namespace without the extra parameters. This way you can still write your interface once, so no copy/paste maintenance problems, and have a clean client side contract. It does pull in a client side dependency on the [FromServices] attribute, but now that asp.net core is a part of .NET, I don’t think that’s as big an issue today as it was a few years ago.

We could keep it out of the contract completely. There’s a good argument for this because the contract should be about what’s sent over the wire. This hasn’t been cleanly implemented even today with the ability to apply an attribute which derives from IContractBehavior to the contract itself, or the fact that the .NET sync/async method shape is built in to the contract. We would ideally like to have compile time errors if the interface doesn’t match the implementation. This way if you make contract changes, you will get a compile error if you don’t also make those changes to the implementation. One way to achieve this is with a source generator which generates contract methods based on the implementation methods and not the interface (which would still be used to find candidate methods as not all methods are service implementation methods). This is my favorite idea so far, and wouldn’t need any runtime support in CoreWCF. We would need a source analyzer which enforces needing to make you implementation class partial, but that’s all the changes you would need other than adding the methods. You would write code like this:

[ServiceContract]
public interface ISimpleService
{
    [OperationContract]
    string Echo(string echo);
}

public partial class SimpleService : ISimpleService
{
    public string Echo(string echo, [FromServices] Func<string, string> identity) => identity(echo);
}

What the source analyzer would do is find all ServiceContract interfaces, look for any classes which implement them, then check each method to see if the use [FromService] in their method signature, and if they do, require the class to be partial. If it isn’t, that becomes a compilation error. What the source generator would do is emit the following code:

public partial class SimpleService : ISimpleService
{
    public string Echo(string echo)
    {
        IServiceProvider serviceProvider = OperationContext.Current.Extensions.Find<IServiceProvider>();
        if (serviceProvider == null) throw new InvalidOperationException("Missing IServiceProvider in InstanceContext extensions"); // Need to work on error, maybe link to documentation
        Func<string, string> identity = serviceProvider.GetService<Func<string, string>>();
        return Echo(echo, identity);
    }
}

The advantage of doing this with a source analyzer/source generator is there’s no runtime changes needed. There are a LOT of places in the code that we would need to touch to implement it in the CoreWCF runtime packages, whereas this is a lot simpler.

0reactions
mconnewcommented, Nov 3, 2021

Yes, they should be distributed in the CoreWCF.Primitives package. As it’s a build time tool, it won’t increase the application footprint at runtime. It will need to be a separate project which is referenced by the Primitives build but won’t produce its own package.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Injecting services into ValidationAttributes in ASP.NET Core
In my first attempt to inject a service I thought I would have to take a similar approach to the ServiceFilter and TypeFilter...
Read more >
Dependency injection in ASP.NET Core
ASP.NET Core supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) ...
Read more >
Dependency Injection in ASP.NET Core Attributes
The idea is to use service location, in the constructor to retrieve the required services and set them to class level variables, ...
Read more >
c# - Dependency Injection in attributes
extract all logic from the attribute into a custom service that contains all dependencies. Register that service in your container. let the ...
Read more >
.NET Dependency Injection With Constructor Parameters
In this article, we'll look at different ways to use dependency injection with constructor parameters in .NET Core.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found