|
I was trying to watch for file changes in a PowerShell script and got bit. Specifically, I tried to use System.IO.FileSystemWatcher. I think there's a PowerShell thread but my questions are a bit more specific about .NET concurrency. I did something like this:code:
BTW, I was using Monitor statements because there isn't a lock keyword in PowerShell, so I'd basically lock using Monitor.Enter, and then use Pulse/Wait depending on whether I was in that event callback (Pulse) or the invoking parent code (Wait). I can try to post a more complete example, but I would have to fully reconstruct what I was doing. Right now, I'm just polling for the existence of the file, but I always try to make an effort to avoid polling.
|
# ? Jul 23, 2019 00:07 |
|
|
# ? Jun 8, 2024 05:39 |
|
Apparently, I grossly misunderstand how assemblies work. Working on another rewrite of my character portfolio app, this version contains a startup project targeting .NET framework and using WPF, and a data access project targeting .NET standard. My idea was that only the DAL project would have to know anything about the database, EF Core and AutoMapper. The DAL would only expose interfaces for managing data, and a series of observable DTO classes. The DAL project also has a static DAL class which exposes an Initialize method which is responsible for instantiating and registering the AutoMapper Mapper class, as well as registering all of the concrete classes which go with the exposed interfaces. Unfortunately, when I call DAL.Initialize() from the constructor of my MainViewModel, it throws exceptions for every library used by the DAL project. Apparently, I need to install all of the NuGet packages in the DAL in the startup project as well? This seems very counter-intuitive to me. What am I missing? TIA
|
# ? Jul 23, 2019 01:10 |
|
Dependencies are transitive (mostly). What version of the full framework are you using? In my experience, even in versions supposedly compatible with netstandard2.0, there is just a fractal of issues that prevent you from doing anything without sinking hours into workarounds, etc. Maybe consider checking out the Core 3.0 preview, where I believe they are adding support for WPF. e: also, static initializers in your library? WorkerThread fucked around with this message at 01:32 on Jul 23, 2019 |
# ? Jul 23, 2019 01:28 |
|
WorkerThread posted:Dependencies are transitive (mostly). The startup targets .NET Framework 4.7.something. Even though the DAL is in a separate directory, and all the dependent libs exist there, the runtime is expecting to find them in the directory with the startup project. So what’s the point of separating out the code into a separate library? It’s not like this code (in this instance, at least) is reusable, unless I came up with a different program that used the same database. And yes, I am looking forward to 3.0, but I’m not comfortable using preview releases quote:e: also, static initializers in your library? OK, I’ll bite. The sole purpose of the initializer is to instantiate and register the Mapper and to register all the DAL classes. Why shouldn’t it be static? It tracks whether it had already been initialized and won’t do it twice if reinvoked. I mean, functionally, what’s the difference between DAL.Initialize() and new DALInitializer.Initialize()?
|
# ? Jul 23, 2019 02:35 |
|
Rocko Bonaparte posted:I was trying to watch for file changes in a PowerShell script and got bit. Specifically, I tried to use System.IO.FileSystemWatcher. I think there's a PowerShell thread but my questions are a bit more specific about .NET concurrency. I did something like this: If your simplified version isn't too simplified, your problem isn't threading, it's scoping. As written, every time the handler files, it creates a new $found_files, sets it to true, and throws it away. If you initially declare $found_files outside of the handler it should work.
|
# ? Jul 23, 2019 02:39 |
|
LongSack posted:The startup targets .NET Framework 4.7.something. Even though the DAL is in a separate directory, and all the dependent libs exist there, the runtime is expecting to find them in the directory with the startup project. So what’s the point of separating out the code into a separate library? It’s not like this code (in this instance, at least) is reusable, unless I came up with a different program that used the same database. That sounds a little strange. Do you have any funky custom MSBuild stuff? How are you publishing your app or are you running it straight from VS? Also I'm assuming you're using packages.config and not PackageReference? LongSack posted:OK, I’ll bite. The sole purpose of the initializer is to instantiate and register the Mapper and to register all the DAL classes. Why shouldn’t it be static? It tracks whether it had already been initialized and won’t do it twice if reinvoked. I mean, functionally, what’s the difference between DAL.Initialize() and new DALInitializer.Initialize()? I mean, like that, nothing There is value in letting your application control the configuration its dependent libraries. I'm definitely a stupid goon so don't take my word as gospel, but:
My day job is to do software architect garbage, so obviously those points are a lot more important in that context, but I'd argue they're still worthwhile for solo projects, especially if you're using it as part of a portfolio/demo.
|
# ? Jul 23, 2019 03:59 |
|
WorkerThread posted:That sounds a little strange. Do you have any funky custom MSBuild stuff? How are you publishing your app or are you running it straight from VS? Also I'm assuming you're using packages.config and not PackageReference Nope. It’s a standard project built from my MVVM base with an added dotnetstandard2.0 library for the DAL. It’s not published yet, this is running it via F5 in VS. quote:I mean, like that, nothing Point taken. But part of this project was that the main project wouldn’t have any knowledge of the DAL beyond the published interfaces and DTO classes. But, still, at some point the DI container needs to have concrete classes registered for those interfaces, and a static initializer seemed like a place for that. edit: also, if my entity classes are all internal, EF Core won’t find the primary key. I have to use .HasKey to tell EF that a property named Id is the primary key. LongSack fucked around with this message at 05:12 on Jul 23, 2019 |
# ? Jul 23, 2019 05:01 |
|
Did you add the DAL project dependency as a project reference or directly as an assembly reference? The latter is a "dumb" reference that doesn't autoupdate, you normally want the former. Also, every reference has a "Copy to output folder" property (visible both in the .**proj file and in the Visual Studio properties), which should default to yes but might have been turned off for some reason. Also, static and instance initializers are both bad. If your class needs to perform some operations that may or may not succeed before it's ready to fulfill its public APIs, then make the constructor private and expose a static factory method that can return a failure state.
|
# ? Jul 23, 2019 12:40 |
|
NihilCredo posted:Did you add the DAL project dependency as a project reference or directly as an assembly reference? The latter is a "dumb" reference that doesn't autoupdate, you normally want the former. Project reference (specifically using the "Project Dependencies" menu item) quote:Also, every reference has a "Copy to output folder" property (visible both in the .**proj file and in the Visual Studio properties), which should default to yes but might have been turned off for some reason. The references are being copied to the output directory - of the library. For some reason, the runtime is looking for them in the directory of the startup project quote:Also, static and instance initializers are both bad. If your class needs to perform some operations that may or may not succeed before it's ready to fulfill its public APIs, then make the constructor private and expose a static factory method that can return a failure state. The DAL library is 20+ entity classes, 20+ DTO classes, 20+ interfaces, 20+ DAL classes (each of those is one per table in the database) as well as some infrastructure classes (NotifyBase, for example). I wasn't sure where to put the initialization code that's required by the library as a whole, so thus the static initializer. I think I'm going to punt and move everything back into a single project and use namespaces/folders to separate. In this case, there doesn't seem to be an advantage to having this stuff in a separate project and it's causing more problems than it's worth.
|
# ? Jul 23, 2019 14:55 |
|
LongSack posted:The references are being copied to the output directory - of the library. For some reason, the runtime is looking for them in the directory of the startup project If your dependencies are set up right, with the startup project depending on the library project, then when it's built, the library plus its dependencies should be copied into the startup project's output folder, i.e. if your library depends on some additional stuff, you'd have Library\bin\Debug\Library.dll Library\bin\Debug\dependency1.dll Library\bin\Debug\dependency2.dll and Startup\bin\Debug\Startup.exe Startup\bin\Debug\Library.dll Startup\bin\Debug\dependency1.dll Startup\bin\Debug\dependency2.dll with Library.dll, dependency1.dll and dependency2.dll being copied from Library's output. If you have Startup\bin\Debug\Startup.exe loading up Library\bin\Debug\Library.dll then something is not right.
|
# ? Jul 23, 2019 16:06 |
|
LongSack posted:The references are being copied to the output directory - of the library. For some reason, the runtime is looking for them in the directory of the startup project * Of course, that is not the whole truth: see here.
|
# ? Jul 23, 2019 17:33 |
|
SirViver posted:This is pretty much the expected/standard .NET assembly load behavior. All your assemblies must* be placed next to the executable respectively executing assembly. Anything else requires configuration or manual dynamic loading of assemblies or implementing your own assembly resolve logic. Good link, thanks. What was happening was that the library DLL was being copied to the startup project directory, but the library's dependent libraries weren't being copied. All moot now, anyway, since I moved everything back into a single assembly. In this case, There's no real advantage to having a separate assembly versus just using folders / namespaces.
|
# ? Jul 23, 2019 17:57 |
|
LongSack posted:Project reference (specifically using the "Project Dependencies" menu item) This is going to be part of your pain. All this says to VS is 'build Project X before Project Y'. It doesn't involve actual code references at all - how are you even accessing those classes without having added the DAL project as an assembly reference (in Solution Explorer, the References group under the Project root), you'll not only ensure that the build order is handled automatically, but also that the output gets copied over (by default). Alternately, you could change the settings of both projects to output to a common folder, rather than the default output location.
|
# ? Jul 23, 2019 17:58 |
|
I must not have ever understood .net core. I always thought of it as completely cross platform. Hearing that wpf is coming in core 3.0 makes me think I was wrong. What exactly does wpf in core mean?
|
# ? Jul 23, 2019 18:53 |
|
bobua posted:I must not have ever understood .net core. I always thought of it as completely cross platform. Hearing that wpf is coming in core 3.0 makes me think I was wrong. What exactly does wpf in core mean? .NET Core is cross-platform. But WPF is a library that is rooted in .NET Framework, which is not cross-platform. Or, for a little more detail: .NET Framework has an enormous API surface area. .NET Core only covers *part* of that. And with Core 3.0, WinForms and WPF - previously Framework-only libraries - will be offered in Core. *BUT* only for Windows-run Core.
|
# ? Jul 23, 2019 19:07 |
|
raminasi posted:If your simplified version isn't too simplified, your problem isn't threading, it's scoping. As written, every time the handler files, it creates a new $found_files, sets it to true, and throws it away. If you initially declare $found_files outside of the handler it should work. Not a problem with scoping. I've already obsessively copy-pasted the variable from outside to all places of use just to make sure my beady eyes didn't mess it up somewhere. I tidied things a bit and put it up on Pastebin: https://pastebin.com/hkY5SMBL I also have the Monitor statements in there, but commented out. If you run this and put some xxx_returncode.txt file in C:\temp\poll_folder, then you'll see the event listener kick off, which should set $found_files to true. However, it's just a parade of false in the poll loop. At any rate, I'd prefer not to poll and would rather just block and get kicked on either a timeout or a stimulus from the event. I can't figure out how to make that work. Monitor locking up like it did implies to me the event runs on the same thread as the main loop. When I call Monitor.Wait, I think I starve the event handler since the events don't fire at all any more. I'm assuming it's coroutine/async shenanigans, but I'm not familiar enough with FileSystemWatcher to know what how its events work.
|
# ? Jul 23, 2019 19:30 |
|
NihilCredo posted:Also, static and instance initializers are both bad. If your class needs to perform some operations that may or may not succeed before it's ready to fulfill its public APIs, then make the constructor private and expose a static factory method that can return a failure state. Going back to this, let me ask ... Given a library that has over 100 classes (each table in the database has a POCO entity class, an observable DTO class, a config class (for use in ApplyConfiguration), a DAL class, and a DAL interface), where would you put code that is needed to Initialize the library as a whole, rather than any individual class? Before the library is usable, I need to configure, instantiate and register the AutoMapper Mapper class, and register the concrete DAL classes that serve the interfaces. I recognize that an exception in a static constructor renders the class unusable, but honestly, an error during the initialization of the DAL library is not recoverable anyway. So, if static initializers are bad, what’s the alternative? Factory methods are out, since I’m initializing the library as a whole, not any individual class. Also, keep in mind that class instantiation is not done by a framework (like in ASP.NET Core), so I can’t do DI just by putting parameters in a constructor. I have to go out and grab dependencies from the container, like C# code:
|
# ? Jul 24, 2019 04:32 |
Adventures in making a local out-of-process COM server in C#/Framework 4.6 Continuing my previous question about local RPC, I did implement a working call mechanism over named pipes, only missing a way to start the server if it isn't running yet. However I also kept looking into COM interop in .NET Framework, and came upon this blog post. It turns out creating a local COM server isn't that hard after all? So I go about to declare an interface, give it an IID, make it ComVisible and dual. I can build my assembly, getting a typelib from it is more difficult. After seeing various warnings about regasm.exe and then trying tlbexp.exe, I figure out that the proper way is after all regasm.exe /tlb, to build the typelib TLB file and get it registered. Next is implementing a class implementing the interface, give it a CLSID, and calling RegisterTypeForComClients on startup, and it fails? Okay the class needs to be marked public. Now it registers without errors. Can I then instantiate it? Try with PowerShell: [System.Activator]::CreateInstance([System.Type]::GetTypeFromCLSID([guid]'C4086A64-17AB-48DC-AA1D-0650329210B5')) It doesn't error! Can I then call the returned object? Nope, no useful methods on it. Trying to call Invoke on the Type object returned by GetTypeFromCLSID, nope the best I get is an error about wrong number of arguments to the call. I try calling both by name and by DispID. I can't query anything about the object's actual type. Maybe the class registration is incomplete, and COM isn't associating the typelib with the object? I try manually adding the CLSID to registry, pointing LocalServer32 to my EXE file, and referencing the TypeLib by GUID. Nope, even restarting PowerShell doesn't get me any type information for the dispatch interface. Well, can I at least have it start the server process when it's not running with class factory registered? Uh, it hangs. It does start the process indicated in LocalServer32, but never detects the class factory registration. After lots of searching and experimentation, it turns out this only works if the assembly is compiled as x86 or x64, AnyCPU does not work. So far so good. Okay then let's try calling it straight. I'll load up another C# project and add my typelib as a reference. Oh, that fails because it's produced from a .NET asssembly, it tells me to add the assembly directly. But I don't want to do that, I don't get out-of-process activation then. Try to do it the classic way, get a Type object by CLSID, then instantiate it with Activator. Copy the interface declaration from the server project, and cast the instantiated object to the interface type. I can create the object, the server starts, and the interface cast succeeds. Great! Call the method? Fails. Error indicates something about wrong thread context. But it's an out-of-process object, thread-affinity should be a non issue?!? Just marshal the call and send it over. This is where I give up and decide to just go ahead with the named pipe and do my own, custom server activation. Classic answer: "How hard can it be?" Very hard.
|
|
# ? Jul 24, 2019 09:37 |
|
Yeah, that sounds about right. The best part in all of this that no one at MS who also had to do this took a step back and said: "How the gently caress did we end up here? Let's fix it."
|
# ? Jul 24, 2019 17:30 |
|
LongSack posted:Going back to this, let me ask ... It's less about the individual class and more about the workflow. The idea is that you have to perform several steps to get your data, some of which may fail, and we're trying to make it as explicit and enforceable as possible that you should handle these failures. Here's a rough guess at how your workflow may look like for some basic CRUD: 0) Get the connection strings and other options from environment variables or config files or whatnot. This isn't part of your library, but it's still external data that may or may not be successfully loaded. 1) Initialize the library, i.e. get the Context online. This may or may not succeed for reasons other than bugs, eg. database unavailable/borked. 2) Get an individual DAL object. The DAL classes are basically (I assume) a bundle of functions operating over the Context, so if the Context is valid then instantiating the DAL class can't really fail. 3) Get a POCO object with the content of a database row. This may or may not succeed for reasons other than bugs, eg. database suddenly unavailable/borked, or a primary key suddenly vanished. 4) Get a DTO object that can update/delete the database row. Again, the DTO class is just a bundle of functions and events wrapped around the POCO class, so you can't really fail to instantiate it. Its functions, of course, may fail. 5) Use the DTO object's methods to update/delete the original row. This may or may not succeed, of course. 6) You're done. All you need at this point is to know if the update/delete operation succeeded, or why it failed. The above workflow could look like this in code: code:
The extra step is to encode in the function calls themselves the fact that this particular operation may fail in that particular way. This removes the need for you to manually wire up those catch() blocks, which in the example above are absolutely unenforced - I didn't handle the WrongPocoColumnsException, for example, but it still compiles and can bite me at runtime. That's what functional-style error handling does, and I can write a post about that too if you want. Java's checked exceptions are another way to achieve the same safety, but unfortunately they can't be implemented in C#. NihilCredo fucked around with this message at 20:43 on Jul 24, 2019 |
# ? Jul 24, 2019 20:41 |
|
NihilCredo posted:words Initializing the library itself has nothing to do with the library workflow (and the above is fairly accurate) except for the fact that the initialization is required for the library to work at all. It needs to happen once at application startup. Here is what happens in the (formerly static) initializer:
Since I moved everything back into a single assembly, I’m handling it in the constructor of my MainViewModel, although I suppose it could go in App.xaml.cs, but I’m curious to learn if there is a better way.
|
# ? Jul 25, 2019 01:21 |
|
IMapper is in the constructor of every class that needs it and DI resolves it. Under normal circumstances there is just one global IMapper object containing all the mappings for every type. Assuming you have assemblies/namespaces for various layers (request, controller, logic, dal etc ) the mapping config is setup so that you have a main rule file in each namespace and they each call the rule file in the assembly "below" it, keeping a clear heriachy of dependencies. It doesn't matter if your DAL is one huge monolothic class or hundreds of classes, you just need that IMapper as a constructor dependency wherever it's needed, there is no extra overhead for you as a dev. For testing, you just test the mappings seperatly. When testing every other part of the app you can inject a mock mapper.
|
# ? Jul 25, 2019 01:38 |
|
For where it goes, you usually put all that configuration and registration in your main assembly where it will execute once on application startup, somewhere sane people don't unit test. In a WPF application, that might be an overridden Application.OnStartup(StartupEventArgs). You very probably do not want to put it in the constructor of a viewmodel, since that's not a viewmodel's responsibility, and it makes that viewmodel less testable.
|
# ? Jul 25, 2019 02:24 |
|
Xik posted:IMapper is in the constructor of every class that needs it and DI resolves it. Under normal circumstances there is just one global IMapper object containing all the mappings for every type. Except that this isn’t ASP.NET where the framework instantiates everything and DI happens by magic. My DAL classes all start out like C# code:
quote:For where it goes, you usually put all that configuration and registration in your main assembly where it will execute once on application startup, somewhere sane people don't unit test. In a WPF application, that might be an overridden Application.OnStartup(StartupEventArgs). OK, so App.xaml.cs is a good place. Thank you.
|
# ? Jul 25, 2019 03:53 |
|
LongSack posted:Except that this isn’t ASP.NET where the framework instantiates everything and DI happens by magic. My DAL classes all start out like What prevents you from using constructor injection? You don't need a container at all. Back before I settled on Microsoft.Extensions.DependencyInjection, I would create/configure all my classes in the application root (Program.cs/App.xaml.cs/etc) manually. No magic required I'm not saying that was especially smart, as using containers is actually very simple, but it's certainly possible.
|
# ? Jul 25, 2019 04:25 |
|
Rocko Bonaparte posted:Not a problem with scoping. I've already obsessively copy-pasted the variable from outside to all places of use just to make sure my beady eyes didn't mess it up somewhere. Oh, Powershell closures apparently capture by value. One way around this is something called a reference object (apparently, I don't really know Powershell): PowerShell code:
|
# ? Jul 25, 2019 04:32 |
|
WorkerThread posted:What prevents you from using constructor injection? You don't need a container at all. Back before I settled on Microsoft.Extensions.DependencyInjection, I would create/configure all my classes in the application root (Program.cs/App.xaml.cs/etc) manually. No magic required I’m using my own homebrew DI container which doesn’t support it. I guess it’s time to look at Microsoft’s DI. Does it support “object registration” if that’s the correct term? Like, my DI class supports “register” (a new instance is created every call), “register singleton” (the first call caches the created object which is returned on subsequent calls) and “register object” which adds an instantiated object directly to the cache, so it’s like “register singleton” except that the object is provided at registration time).
|
# ? Jul 25, 2019 04:43 |
|
LongSack posted:I’m using my own homebrew DI container which doesn’t support it. I guess it’s time to look at Microsoft’s DI. Got it. Microsoft's container should feel pretty familiar, then, except it supports constructor injection. You've got three scopes available-- Singleton, Scoped, and Transient (like what you called "register" above). You can also provide pre-built instances as well for tricky to construct types. I prefer that container because it doesn't really do a lot of magic for you in the background: no auto-registration or assembly scanning, etc. I like to see the registrations for each type in use by my app.
|
# ? Jul 25, 2019 05:00 |
|
WorkerThread posted:Got it. Microsoft's container should feel pretty familiar, then, except it supports constructor injection. You've got three scopes available-- Singleton, Scoped, and Transient (like what you called "register" above). You can also provide pre-built instances as well for tricky to construct types. Thanks! I’ll take a look at it tomorrow.
|
# ? Jul 25, 2019 05:11 |
|
LongSack posted:Thanks! I’ll take a look at it tomorrow. Rewrote the (thankfully only five I had already written) DALs to use Microsoft’s DI using constructor injection and finished the other 22 today. It was surprisingly (to me) easy to do. Then, in App.xaml.cs I created the ServiceCollection and registered all my services, and flipped that into a ServiceProvider. Now what do I do with that to make it available to the rest of the program? Ended up registering it with my original DI container. Haven’t got to the point where I can actually run any of the code yet, so hopefully it all works. Also had a bit of a breakthrough. My DAL classes have standard Insert, Update and Delete methods, plus 2 types of read methods: Get methods return an IEnumerable always. If the read returns no rows, it returns an empty collection, but they always return a collection. In contrast, Read methods return either an object, or null, since Read is used to grab a single item, where Get is used to get a collection. I realized, in a blinding flash of the obvious, that these various methods were basically the same except for the Where clause. I realized that I could write a single Get method and pass it an Expression<Func<foo, bool>> parameter with a default value of null. So now my GetForCharacter(int cid) method can simply be Get(x => x.CharacterId == cid); Even the reads can use the same method. Read(int id) becomes Get(x => x.Id == id).SingleOrDefault(); This localizes all of the read code into a single method, which helps with maintainability. Maybe this is obvious to y’all, but it was a breakthrough for me. Oh, and if you’re wondering why use Expression<Func<foo, bool>> rather than just Func<foo, bool>, it’s because Where with the latter returns an IEnumerable<foo>, so you can’t add, say, an .AsNoTracking(). Where with the former returns an IQueryable<foo>, so you can.
|
# ? Jul 26, 2019 03:36 |
|
LongSack posted:Oh, and if you’re wondering why use Expression<Func<foo, bool>> rather than just Func<foo, bool>, it’s because Where with the latter returns an IEnumerable<foo>, so you can’t add, say, an .AsNoTracking(). Where with the former returns an IQueryable<foo>, so you can. Just so you know, there are .AsQueryable<T> and .AsEnumerable<T> methods that are available from entity framework that will transform from one to the other.
|
# ? Jul 26, 2019 05:10 |
|
LongSack posted:Rewrote the (thankfully only five I had already written) DALs to use Microsoft’s DI using constructor injection and finished the other 22 today. It was surprisingly (to me) easy to do. Then, in App.xaml.cs I created the ServiceCollection and registered all my services, and flipped that into a ServiceProvider. Now what do I do with that to make it available to the rest of the program? Ended up registering it with my original DI container. What do you mean 'the rest of the program'? You've setup your container at the application root. Everything your program needs should be configured and ready to go, shouldn't it?
|
# ? Jul 26, 2019 09:36 |
|
beuges posted:Just so you know, there are .AsQueryable<T> and .AsEnumerable<T> methods that are available from entity framework that will transform from one to the other. Be very very careful with these, however! An IQueryable<T> from Entity Framework represents a database query being constructed. If you do .AsEnumerable() it stops being a database query - whatever you had before that will be executed on enumeration and any further .Where() etc statements will not reach the database and will be executed in memory. The same applies if you pass an IQueryable<T> to a function taking IEnumerable<T> as input. You cannot undo this effect with .AsQueryable() (that just means "pretend there is a fake query being constructed but really all we have is this in memory list"). This is the main reason why many programmers create database reading code that downloads the entire table into app memory on every query. Don't let IQueryable become IEnumerable!
|
# ? Jul 26, 2019 09:54 |
|
LongSack posted:Rewrote the (thankfully only five I had already written) DALs to use Microsoft’s DI using constructor injection and finished the other 22 today. It was surprisingly (to me) easy to do. Then, in App.xaml.cs I created the ServiceCollection and registered all my services, and flipped that into a ServiceProvider. Now what do I do with that to make it available to the rest of the program? Ended up registering it with my original DI container. Sounds like you've more-or-less discovered the Repository Pattern https://deviq.com/repository-pattern/
|
# ? Jul 26, 2019 13:59 |
|
Cuntpunch posted:What do you mean 'the rest of the program'? Well, constructor injection only works with classes being instantiated by DI, right? This isn't ASP.NET Core where everything is basically instantiated by the framework. Let's take my MainViewModel. It needs access to services in DI, so how does it get access to the ServiceProvider? It gets instantiated in the code-behind of the main Window as C# code:
I suppose I could move the DI initialization code into the code-behind of the main Window and then it could pass the provider to the VM, but that feels like the wrong place for that (that code is currently in App.xaml.cs)
|
# ? Jul 26, 2019 15:26 |
|
LongSack posted:Well, constructor injection only works with classes being instantiated by DI, right? This isn't ASP.NET Core where everything is basically instantiated by the framework. Let's take my MainViewModel. It needs access to services in DI, so how does it get access to the ServiceProvider? It gets instantiated in the code-behind of the main Window as Constructor injection just means "an implementation is passed in via the constructor". DataContext = new MainViewModel(someRequiredServiceImplementation); However, the way it typically works with a DI container is that you tell the DI container "give me a MainViewModel that's constructed correctly" and it handles creating it for you with all of the appropriate dependencies already injected. New Yorp New Yorp fucked around with this message at 16:27 on Jul 26, 2019 |
# ? Jul 26, 2019 16:24 |
|
LongSack posted:Well, constructor injection only works with classes being instantiated by DI, right? This isn't ASP.NET Core where everything is basically instantiated by the framework. Let's take my MainViewModel. It needs access to services in DI, so how does it get access to the ServiceProvider? It gets instantiated in the code-behind of the main Window as You've referenced ASP.NET a few times now and I think you may need to peek behind the curtain and see the real Wizard of Oz - because ASP.NET has some standard behavior around its DI stuff, but there's really nothing magic about it. It's just hiding the boilerplate from you. In the case of XAML based stuff, I've tended to follow Bugnion's ViewModelLocator pattern. Have a Locator class that, at construction time, registers all of your Interfaces, Implementations, AND ViewModels with your DI container. This class should also expose each ViewModel as a get-only property, resolved out of the DI container. In your App.xaml, add an instance of this class to a resource dictionary. Then in any other XAML view you've got, you'll be able to just do something like DataContext={FooViewModel, Source={StaticResource Locator}} Bang, you've 'wired up' your entire application effectively at application start. Bonus: you can now setup Design-Time implementations of your services so that the UI designer well be more useful.
|
# ? Jul 26, 2019 17:07 |
|
New Yorp New Yorp posted:Constructor injection just means "an implementation is passed in via the constructor". Right, but in that instance - unless I am fundamentally misunderstanding things (always a possibility) - the code-behind still need access to the ServiceProvider in order to pass it to the MainViewModel. So either I create and initialize the DI in the code-behind for the main window (which feels wrong), or I do it in the application startup code and find a way to make the provider available to the code-behind so it can pass the provider to the view model. I suppose i could add it to Application.Resources as a way of doing it, but currently i'm registering it with my old DI registrar.
|
# ? Jul 26, 2019 17:10 |
|
Cuntpunch posted:You've referenced ASP.NET a few times now and I think you may need to peek behind the curtain and see the real Wizard of Oz - because ASP.NET has some standard behavior around its DI stuff, but there's really nothing magic about it. It's just hiding the boilerplate from you. This looks interesting, and since I haven't really started coding the view models yet, I'll look into it. I Hadn't planned on putting the view models into DI, but I can see the advantages of the above. As for the bonus, that's always been available using {d:DesignInstance Type=FooViewModel, IsDesignTimeCreatable=False}, hasn't it?
|
# ? Jul 26, 2019 17:18 |
|
|
# ? Jun 8, 2024 05:39 |
|
LongSack posted:This looks interesting, and since I haven't really started coding the view models yet, I'll look into it. I Hadn't planned on putting the view models into DI, but I can see the advantages of the above. As for the bonus, that's always been available using {d:DesignInstance Type=FooViewModel, IsDesignTimeCreatable=False}, hasn't it? Sure - but if FooViewModel *requires* an IBarService, that starts to get messy.
|
# ? Jul 26, 2019 17:21 |