BrandGhost

Autofac - Wire Tap Existing Services

In this video I discuss how to use "Wire Tapping" so that you can wrap an existing service registration with your own implementation. This video is part 1 of this series. You can see part 2 here: https://youtu.be/FV2IjICN_OE My Social: LinkedIn: https://www.linkedin.com/in/nickcosentino Blog: http://www.devleader.ca/ GitHub: https://github.com/ncosentino/ Twitch: https://www.twitch.tv/ncosentino Twitter: https://twitter.com/nbcosentino Facebook: https://www.facebook.com/DevLeaderCa
View Transcript
all right I want to take a video put together today for something in Auto FAC that a friend had asked me about so if you're not familiar with Auto fact this might be maybe a little bit too advanced like it started with but um you might just be interested in following along because you've you've heard of Auto fax so if you're not totally familiar with what Auto fact is it's a dependency injection framework I'm not sure if it's available outside of like the dotnet land maybe there's other languages it's available for but there's lots of different dependency injection frameworks this is just one of them it's my preferred one it does a lot of really cool things and it's really fun to explore but essentially what was being asked was a little bit more than I had thought on the surface so I thought originally the question was if I have a dependency that's part of my main application and I want to introduce something like a plug-in that can override a service and provide a new implementation how do I go about doing that and on the surface I thought that's what it was and I was like okay well I'm pretty sure that's very straightforward an auto FAQ and in fact it is that's not with what was being requested though but just to touch on that first I'm going to kind of jump through some code here so what I have in this solution is console app to just because I forgot to rename it to something more pleasant but sort of your entry point we have a shared project that just has an interface in it something really trivial something super simple it just has an interface it has a single method on it to print something and then I have two other projects and each one of them is sort of an implementation of of that service so basically if we assume that forget that there's a new implementation project here your original setup might be that even this original implementation project might be part of this core application I have it separate just to keep it a little cleaner for conversation but what you might have is sort of you know class I just call it original because it's a not very original the original implementation of this service and all that it does is it writes out a message to sort of the debug output I have it to debug and not console just so that when I'm streaming here I can show you in this little open window down here anyway it so it's essentially call it equivalent to console.writeline there and then what I'm doing in here is I'm also writing out the hash code of the object not for any good reason except that when we talk about some of these details the hash code will tell us what reference of the object we're talking about which as we get into some of the more complex scenarios that might be kind of cool to see what's going on so we have an original implementation and then I have an auto fact module here and all that it does is again if you're not familiar with Auto fact this might be a little bit too much too fast but all that it's doing is registering that type we're gonna make it a single instance just for now so that anytime someone's trying to resolve this they'll keep getting the same instance back and we're going to register it as the implemented interfaces so the only way that someone can get this is when they ask for a service that is I the interface because I couldn't come up with any great names and you can see that when I jump to this program I have some Compton Oh code here so forget that for now it's just I want to build on top of this conversation so you'll see that we have a container builder it is going to register our two modules like I said I'm going to come back to this new implementation one in a second I'm just kind of going over the framework here so we register our assemblies we make a container we make the scope and then we resolve the service right because I was saying that we registered it as the implemented interfaces so the only way we can get that thing out of our dependency container is by resolving it by the interface and then I just call print message and we just have a little at the end so we can press ENTER to to exit so anyway really simple program and what was being asked I thought originally was if I just want to override the implementation by essentially adding in a new module because if we want to assume that the person is unable to sort of touch the main the main body of the application and you just sort of want to override functionality by adding more code how do you go about doing that so essentially the way Auto FAC works is that if you have I'm going to go back to this original module here if I had a another module that looked just like this and instead of registering the original here so we're doing this in the sort of the main module and then if we had a second module that registered another type that also happened to meet the same interface which is I the interface auto FAC will basically the last person to make the registration is the one that will sort of take precedence so that would mean that if we added a new module registered a new type here and then we did it also as implemented interfaces and had the same interface that new module as long as it was registered second would sort of override the behavior so just to illustrate that if I had this order here where I have the original one that gets registered first and then the new implementation and if we assume that it had a very similar looking module and registered a new implementation with the same interface what would happen is that when we resolve it and print the message it would take the new behavior so in fact it's actually really simple to just override existing behavior simply by registering a module after because Autofac will give precedence to the latest registered module so that would be a pretty trivial thing I thought that's what was being asked I was all excited to you know give the answer and say it's you know just this easy but that's not what was being asked it's similar but a little more complicated so what was being requested was not only do we want to do something just like that but the new implementation is actually going to be a rapper and what I mean by that is the new implementation also needs to take in the original implementation okay but that the catch is without knowing or without having a direct dependency on it okay so it's a little bit confusing and it's it's easier to step through the code and do it so what I'm gonna show you over here is this new implementation class and you'll see that it also implements I the interface there's a little bit of extra stuff here I have a magic number and whatever and I'll get to that that's really just to make the debug output look a little bit more telling as we're stepping through some stuff but the new implementation also meets this interface right but it's going to take in another instance of I the interface and it wants to take in the I'm calling it the original one but that's where the trickery comes in and you'll notice that if I check the dependencies here on the left the new implementation the only thing it depends on is this common interface okay so it has no idea that this original implementation exists right so all that it's saying is when I am made I want Auto fact somehow right using some sort of sorcery I want Auto fact to give me an interface that's whatever the original one is right so whatever came from this project down here I want that one passed in and then we're gonna override the behavior so what I've done to make it a little bit more obvious when we override that behavior is we're gonna print a message before then we're gonna call the one that we're wrapping which should be the original functionality and if I jump into what that is remember it just says I am the original implementation with this hashcode so we should see this message then we should see this message and I'm gonna jump back one more time and we're gonna follow up with this third message it says print this magic number after the original okay so the the catch here is that I need the main application without touching it at all right aside from registering a new module that when I resolve this interface here when I resolve it I somehow need to override the original behavior but when I override that new behavior needs to take in the old behavior as well it needs to wrap it and that's something that auto fact can't do like right out of the box super easily it I figured out a way that makes it work without a FAQ and it's pretty cool but I feel like there's a few gotchas that I still have to figure out so I wanted to make a video to talk about it but essentially we can't just I'm gonna jump back to this module we can't just register the type like we did here in the new module and just hope that it works and the reason why we can't just do that you might be able to do it if you had like if you were keying these or using names which is an auto fact feature but the assumption was that I was going by was if we can't touch this module okay so whatever the original code was we can't touch it so that's a constraint we have to work with and if we can't touch it that means if it wasn't named or wasn't keyed I can't do anything magical to to work around that and I somehow need the main application to just do the right thing okay so it's a little weird but it means that in our new module we can't just register a new type and hope it works and again to reiterate why that doesn't work is because what Auto fact will want to do if I go to this new implementation what Auto fact will try to do is when someone requests this right so they're requesting I the interface when this thing goes to get constructed it also needs AI the interface it needs an instance of it so what ends up happening is that I told you that the the latest registered module will take precedence it will go okay well we need to pass in something that meets IB interface and it goes well new implementation does okay so let's make a new one of those what does it need oh it needs AI the interface and if you haven't caught on it has a circular dependency you'll have a stack overflow essentially so it it's not intelligent enough out-of-the-box and for I think good reasons it's not intelligent enough out-of-the-box to know how that you want sort of another implementation how can it possibly decide we have to tell it right so that's where we had to come up with a sort of an alternative solution for registering this and letting it take in sort of the original interface into this so I'm going to show you what that looks like the code right now has been sort of massage a little bit I was trying to to make it a little bit prettier with some extension methods extension methods are heavily used an auto fact to give you a nice nice looking API so I was trying to come up with something that felt pretty cool but not quite there yet but I have it in this new module so I'm going to collapse some of this code for now but essentially from googling I saw an example the guy had referred to this technique is wiretapping which I thought was kind of interesting you're kind of putting a man in the middle right you want to intercept intercept some registration behavior and kind of put a wrapper in place so the interface I'm going to subdue the code that what this actually does but I thought this was a semi-decent looking sort of API for it but essentially we're going to say we want a wiretap when someone's requesting I the interface we actually want to give it this ok so what's cool is that when this happened I actually have this dysfunction here that I don't can't I can't tell if you I'm assuming you guys can't see the hover when I'm hovering over X here but X in this example is actually the original instance that we'll be wrapping of I the interface so I managed to wire it up no pun intended so that we get the original one passed into this function and then we can wrap it and then that way when someone tries to resolve by the interface we sort of put the man in the middle we've done the wire tab okay so how do we go about doing that I'm going to show you this set of extension methods here and talk about some of the things that felt good some that felt bad and what I'm trying to clean up still so essentially I know there's a bunch of code here I just want to I want to highlight the one part in particular that uh this sort of does the magic and essentially what it comes down to is that there's this thing we can hook on to on the component registration and this has a event an event that is called activating okay and what we're doing is we're hooking up an event handler okay so this was something I've never seen before but uh I didn't actually know you could do this in c-sharp but I guess in c-sharp seven you can actually put literally a method body right in here it's not quite the same as an anonymous method and the cool thing that that lets me do is actually unhook this I've never seen this but you can unhook the event handler because when you have this syntax here usually if you had an anonymous method this would be null they would say it's unset but when you have this syntax it actually knows that handler is sort of this method here which is pretty cool anyway this is just an event handler and what we're able to do again there's a bunch of code so don't worry about that too much we're able to check up here we were able to check if the thing that we're trying to work then we're trying to register with is this type of tea and the type of tea I'm scrolling back up here is I the interface so essentially what we're able to do is say go get this registration and we're gonna hook up to this component registration so that when someone tries to activate the instance which is really like they're trying to resolve an instance of this service when it's activating let's sort of intercept that right so what we're able to do is again ignore all of this stuff just for now but what I'm able to do is call this function call that wiretap callback okay and we pass in the instance that it was activating with and instead were able to return back and assign a new instance here to this event handler so that's the the main sort of fancy part of this so it wasn't really a lot of code just to do the interception again I got to touch on some of this code in a sec one of the things about this syntax compared to some of the stuff I was seeing online was that I wanted I wanted an API that felt like you were interacting with the container builder like you'd like you normally do in here for a lot of your setup so usually like in the other module we saw builder register type and then we could have done like new implementation right you have this sort of syntax which feels in my opinion really nice we can use single instance as implemented interfaces so like this feels really good a lot of the modules I've written almost always looks something you know like these lines repeated with subtle differences and I was like when I go to do this again calling a wiretap when I go to do this wiretapping I want to have syntax that looks and feels like this so I started with trying to look on the container builder to see how can I possibly get a component registration I need to be able to pull that off of something and I noticed that the the normal register method on the container builder gives you a component context and then you can do this little bit of magic here so we can go find it's called a type service so we can go find a registered service of the interface we're looking for and then hook onto that so that's how that got started but the really crappy thing about register and this is something I don't like but it's kind of hidden from the API call is that register actually requires that you return a type it's just how that extension method works and I went oh crap like this this thing isn't actually doing a registration I don't have a type that I need to return so I said okay well I'm gonna make a hidden type that no one will ever see it's private to this class and I literally just called a private class ignore and it returns a new one of those and I said great cool this will work but it doesn't work and it doesn't work because this code generally I'm gonna highlight it but this registration code essentially only runs when someone tries to resolve a type so you can see the the problem here is that I said I'm gonna register a type that no one can even see then as a result no one can actually resolve it so I said crap well I'm stuck again but I'm not because there's this thing an auto fat called auto activate an auto activate does exactly what it says you don't have to wait for someone to resolve your type you can auto activate and basically force this registration to run other instances where I've used auto activate might be for like say like a service that were a controller that doesn't actually have anyone dependent on it I just want to like sort of start something up and let it run I use auto activate for things like that sometimes but in this case it was like again a little bit of like hidden magic that's like I don't want anyone to try and resolve this type so it has to be totally hidden and then I have to force the activation so that was a lot of talking let's see what this actually does ok so jumping back to the program here we should see the first thing written out is it says first result in since because we're going to show an example with another instance so it'll print that and what we should see is that we're intercepting the original implementation so we should see the magic number line printed out and saved before we should see then the original message get printed out and then we should have it followed up with magic number after so let's try it out compile faster please and thank you I feel like it should have already been compiled but what do I know there we go okay so we've got this debug stuff running here that the future runs should go a little faster so you'll see it says first resolved instance right so this message here is what we see up here then we hit print one before original and then the original message that has this hash code one after original and then press ENTER to exit so you can see that we did in fact intercept it which is pretty cool and just to prove that that we did an interception if it wasn't already proven in your opinion I'm going to comment out this line that does the wiretap run it again and we shouldn't see any magic number before and after I should just say first resolved the original and then press ENTER to exit ideally if everything works as I expect right so first resolved instance I'm the original and obviously new hash code because it's a new run and then press ENTER to exit so this line does in fact do the wiretap for us which is great and that's mostly what I sort of needed as a basis and then Ike was able to tell my friend like I have you know I have a way that you can get started with this but then I got really curious and I was like okay well artifacts pretty powerful what happens like what does it mean if I don't want a single instance of things you know the one thing I really don't like about this syntax is that it almost gets rid of some of the the magic syntax of auto FAQ because I literally had to new something up that's one of the things about Auto fact that is super nice is that in a perfect world you never have to type new on your your implementations of things you can just register the type and then the dependencies get activated by Auto fact so I didn't like the fact that I had to do something up here the reason why that happens though is again boils down to gonna jump into this that this new implementation does require an interface instance and as a result it can't automatically figure that out so that's something I still want to see if I can explore and get like I'm just going to jump back to this new module the syntax I would love to see here is like forget the magic number thing for just a second but this would feel like super cool if I could do like this like this syntax in my opinion would be awesome because you could say like wiretap when someone asks for this just give them this right and then it would automatically know how to figure out like how to construct this and then like replace I the interface that's really what I'm after this felt close enough it doesn't feel too bad with this example only because there's not a lot of parameters to this if this class had a ton of parameters or the the couple parameters it had were hard to construct this would start to feel bad really fast but at least it gets you over that hurdle so that's something I still want to address but let's talk about some of the the interesting things that you can do with this now so you know what would happen if someone said I want to do a double wiretap right so like what should this is this is where it gets interesting because it's like what should even happen here I like I don't I coded this and I don't actually know what's truly expected and that's I guess one of the fun things above like you make this API and it's like well you know someone might accidentally code this right in a more complex example they might accidentally code this and maybe that's something you want to prevent but maybe there's actually use cases where you want to keep wrapping and doing something else so right now if I go to run this it does work it's not going to throw an exception but when I say it works I'd no if this is truly what people would expect so I'll try to explain when this is printed out but you can see that we have a wire top with one in it and a wire top with two and we never see the wire top with one right so this two overrode this wire tap the first one my opinion on that is like that kind of feels right based on what the rest of auto fact does remember I was saying if you had a registration of a service and you put another registration rate after it it will prioritize the second one and kind of let that override the first one that's what's happening here whether or not that's good or not I mean at least it feels pretty consistent so that's something what if I had this code back in program here and I said I want to do multiple interfaces sorry multiple I said that totally wrong I want to do multiple oh it doesn't like that I just wanted to be like I want to be able to do you know resolve to the services I the interfaces I can't speak all of a sudden so what should happen right and I think before that gets it's already running so so spoiler alert but let's make this a little bigger we can see the first resolved instance we print to print the sash code the second resolved instance we print to this same hashcode print to after enter to exit so in this example I'm just going to jump back to new module this is still overridden right that didn't change and you'll see that the hash code is the same and the reason why that's the case is that I made a single instance of this of original rain so that means when we go to do the wiretap we're always using the same instance okay now what if you don't want that okay so anytime now someone asks for to resolve this we should get a new instance so we should see the hash codes are different this is why I wanted to put the hash code in the output so run this again again like as I'm as I'm talking about this this isn't this is something I was still kind of exploring like I don't know what the right answer should be in terms of using this I don't know what's wrong what's right I'm just trying to figure out where things are different what we can play around with okay so something interesting happened here so to me this feels wrong and you'll see that now we have print one before then print two before and the two one so we actually got double wiretapping okay so we've got double wiretapping and you'll see that this hash code is different and we only this is why the couple things feel wrong about this we only wiretapped the first resolution of this okay so what I mean by that is we said in the program go resolve one of them then print it out which is this part and we got a double wiretap and then we resolved it again we got a new interface but you'll see there's no wiretap right there's nothing that says print before and after so no wiretap occurred on this resolution but like I think we probably would have expected that right so that to me feels kind of not right I don't like I don't know what the expected behavior is but this is something that as I'm walking through a lot of these scenarios I would try to take these scenarios go write up some unit tests for them and then kind of make this matrix of like you know put all the expected behavior and make it work so I got to put single instance back I'm gonna talk about some of the other stuff I was doing with this extension method so I did add this functionality I didn't have a good name for it but I called it wiretap single instance and I made it in V now and what it lets you do by default it inherits so this is this is why we saw some weird functionality there by default it will inherit and it's inheriting the single instance miss if I can make that up the single instance instance miss of this registered service okay so if we made it if the original registration was single instance and we don't want that we can change that right so I'm changing a few parameters here now the wiretaps will always be new instances okay I'm gonna jump back to original oops let's get rid of original module it's still registered as a single instance so original one is single the wiretaps are new instance is and in program were still doing two resolutions so I'm gonna run that and before what we saw was the first instance got wire tapped the second instance was a new instance but it was not wire tapped you can see in this case we got the same original implementation right we got the same one because I made that single instance in the original module and then both of these got double wire tapped to me this one feels right and the reason why to me it feels right or mostly right it gets kind of weird is that the single instance of instance nests of the original implementation was preserved which is what we wanted and I guess where you could argue is like we got both of these are now wiretap so that part to me feels right I think as long as we have a wiretap in place it should always be wiretapped so this feels better but what's interesting is like it's double wiretapped right so is that what we wanted I'm gonna jump back to the new module here is that what we wanted or should we have expected that this line here actually overrides this wiretap I don't know that to me feels a little bit odd I don't know if it's the right answer or not but it doesn't feel totally consistent so I still want to play around with that and just one more example so I'm gonna go back to original module I'm taking this off right so now both wiretap our new instance is and we should see that the hash codes on the original one are now also different because there is no more single instance nacelle and then once I show you this I'm going to try stepping through some of the code to just talk about how I did the single instance nests and cashaan things so you can see like so like it's I feel like it's broken and the reason I feel like it's broken is like we got we lost the wiretap on the second one again to me that's not right I feel like as long as we have these two lines that do the wiretapping anytime we resolve that service we should see a wiretap that's just how I feel about that and then again we still have this double wiretap on the first one arguable I'm not sure I feel like one thing that's kind of misleading right now is like maybe there's a difference between the single instance of the rapt class being cached versus allowing multiple rapping like double triple wire tapping versus overriding I feel like I might need us another parameter to define that like override a wiretap or or wiretap the wiretap if that makes sense so I go gotta still figure that out anyway so it's still TBD I'll probably follow up and maybe do like a unit testing video on on some of the stuff once I arrive it's something that feels good and how I sort of approach the unit test to do that but I'm gonna jump back into some of this code here in the extension method just to talk about some cool things that I was seeing like when I was looking through some of the auto facts stuff so essentially what I was able to find is like I wanted this inherit functionality so like if you wanted to do wiretapping and you didn't want to specify like single instance or not like what's the default behavior and I was like oh I don't know what the default is like maybe and maybe the user doesn't either maybe the the person who programmed the wiretap doesn't know and I'm not totally sold on this yet but maybe they're not sure and what if you just want to do like have the same single instance 'no so that's why i came up with this inherit idea and i said okay well if i'm gonna do that you know how do I know how do I know what the original thing was was marked as single instance or not and it has a flag on the on the registration for the component it actually has this sharing boolean and from at least my understanding so far this sharing boolean just indicates whether or not you have a single instance that will be shared some quick youtubing and googling kind of showed me that I think this can get a little bit more complicated because I think you can do single instance globally I think you can do single instance per lifetime scope I think you can do a single instance per thread or something anyway like I think there's I think there's more to it than just sharing versus not I suspect that probably for most of the use cases I've ever personally said been using Autofac for a few years now but for most instances of this I have seen this probably would suffice but like I said I think there's a few oddities around like lifetime scopes and threads and some other things that maybe this gets a little bit scary to work with so anyway that's how I kind of did like the inherit parameter and then essentially what I was doing was I have a static dictionary I don't like using static stuff extension methods by nature are static to begin with just for context I'm not going to go into detail but I don't like using static stuff except for really really simple extension methods and the reason why I don't is that I've seen I've personally seen code where like you chain a lot of static things together and if you want to get into what I would call a true unit testing of like checking whether or not like every line that you expect is being called the right way you end up getting in this situation we're like you have a lot of nested static methods you have to kind of prove that everything is being exercised or being mocked out properly gets really messy not saying that that's like you know you don't like you should never use static or something like that just from my experience I try to avoid it but don't really have an option here at least how I have this set up I would love to come make this better but I do have this static cache I'm not dealing with thread safety or anything like that a lot of auto fact resolution stuff from what I've seen at least gets kind of Donna like a single thread have not explored auto FAQ in like a multi-threaded environment for for doing resolution so anyway that's something that could be touched on yeah what I'm doing though is saying like if someone has said that this wire tap should be a single instance I'm cashing the the instances based on the container builder okay and the reason why is because when when you're writing an extension method again if it's not something you're familiar with it just changes the syntax to look like you're saying contain your builder dot wiretap but container builder never had a wiretap method you're kind of just adding off like you're pretending to add on this method to a class or an interface but as a result if you want state you need to like have the state save somewhere so I'm kind of pretending that I have you know a cache per container builder and then I check in that cache to see like if someone asked for this type and we've made one before if so assign the cached instance otherwise go make it and then again if we did say single instance let's store that instance in our cache so that's really like the bulk of this other code that was going on it makes it kind of messy but as you saw as I was stepping through some of the stuff I still think there's some weird like obviously bugs just because it's unexpected behavior it's not really defined right now so like I said I'd like to follow up with some like testing video on like how I decide to like what behavior felt right and the cool thing about that will be like as I'm doing that you know I might have it one day where I'm like this feels good everything you know is air quotes right and then maybe after getting myself and maybe some other people trying you know they might say actually that behavior wasn't what I expected like when I write this using this API I expect as an example the wire taps by default override right maybe that's what they expect maybe they expect that they chained I don't know but I might decide on one thing they might decide on another and anyway I'll have these tests in places if I want to change the behavior it'll be really cool is that I can go change the test for the new expected behavior and just correct the code until all the tests pass so it's kind of like some TDD which might be kind of neat to go over but yeah I think that's mostly it I just want to do a quick little summary so the main idea was that we wanted to be able to by adding a new module we wanted to override some existing implementation that was registered in Auto FAC and not only override it but we also wanted to wrap it we wanted to sort of be the man in the middle between the main code calling this and the original implementation so we were able to do that by using a component registration and hooking up to the activated event or activating event and when we do that we get the original instance that it wants to provide like Auto FAC wants to provide and then we can do something with it so we can actually just return a whole new object instead which is kind of cool and then I think just because I saw one thing that's worth mentioning is that when we use this method we we don't have to have these in a particular order okay so because we're not relying on that behavior of Auto FAC taking the last registration between modules we can sort of register these modules in any order we want and still get this wiretapping behavior so just a subtle thing but worth calling it as well so I think that's it Auto facts super-powerful there's a lot of really cool things and I'm kind of excited to see like if I can change this API and the extension methods and is something that feel a little bit better to use a little bit more obvious in terms of the expected behavior so better naming maybe some more clear parameters and stuff like that but yeah that'll be the next steps I think so thanks for watching and hope you enjoy that

Frequently Asked Questions

What is Autofac and how does it relate to dependency injection?

Autofac is a dependency injection framework that I prefer because it offers a lot of cool features. It's primarily used in the .NET ecosystem, but there may be similar frameworks available in other languages. Dependency injection is a design pattern that allows us to create loosely coupled components by injecting dependencies rather than hardcoding them.

How can I override an existing service implementation in Autofac?

To override an existing service implementation in Autofac, you can register a new module that provides the new implementation after the original one. Autofac gives precedence to the last registered module, so as long as you register your new implementation second, it will override the original behavior.

What is wiretapping in the context of Autofac, and how can it be implemented?

Wiretapping in Autofac refers to the technique of intercepting service registrations to wrap existing implementations without directly depending on them. I can implement this by hooking into the component registration's activating event, allowing me to provide a new instance that wraps the original one while keeping the original implementation hidden from the new code.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload