WildGums / Orchestra

Orchestra is a composable shell and WPF framework built on top of Catel
Other
492 stars 77 forks source link

Dynamically Loading modules #49

Closed mikeprag closed 8 years ago

mikeprag commented 8 years ago

Hi, This framework looks very interesting. Do you have any examples of dynamically loading modules? Also, do you have a project that shows Prism integration? What's the best way to learn the framework (I can't see any documentation although picking my way through the LogViewer app.

Thanks in advance for the great work

Mike

GeertvanHorrik commented 8 years ago

Hi Mike,

Thanks for your interest!

  1. You can either use Prism (Catel.Extensions.Prism) for the extensibility or use Orc.Extensibility.
  2. For now the examples are the best way (LogViewer app, examples in Orchestra, etc)

If you have any questions, feel free to ping us.

mikeprag commented 8 years ago

Hi Geert, Thanks for the reply. Our application modules perform very different functions (almost like different apps) so would Orc.Extensibility work in this case? It would be like reloading a new app into the base shell. Can I dynamically load modules in that other developers write by configuration?

Also, I'm working through the WPF examples and have downloaded the WPF.GettingStarted project. I upgraded this to version 4.5.0.0 via NuGet but now it won't compile as I get this error in 3 places:

Severity Code Description Project File Line Suppression State Error CS0618 'CollectionExtensions.ReplaceRange(ObservableCollection, IEnumerable)' is obsolete: 'Please use ReplaceRange(this ICollection<T>, IEnumerable<T>) instead. Will be treated as an error from version 5.0.0. Will be removed in version 5.0.0.' WPF.GettingStarted C:\Development\TestProjects\WPF\Catel\Catel.GettingStarted.WPF-master\WPF.GettingStarted\Services\FamilyService.cs 38 Active

Can you advise?

Thanks

Mike

JugoRadonic commented 8 years ago

Hello Geert,

First of, thank you and anyone else involved for the great effort to make these sold platforms (Catel and Orchestra) available to the rest of us. It is really well put together and makes developers life simpler.

Unfortunately, much like Mr. Mike, I am also having some dilemma about what would be the right way to implement modules with Orchestra shell (assuming that host-shell project is not aware of module assemblies and does not reference them)?

I have already made some Prism test work last few days on dynamically monitoring and loading assemblies from the folders. But when Orchestra comes in the mix (and I would really like to use it if possible for my next project) things get a bit messy.

Everything I do regarding modules ends up feeling to me like a hack rather then a proper way to go about using Orchestra. Deleted two test projects already (my MEF and Unity modular attempts) that actually worked but I was not happy about how they turned out :).

Not expecting or asking you for full working example with modules, I know the time is always the issue. But if I may suggest at least one small example to be made that implement custom bootstrapper instead of the default Orchestra one and still keep Orchestra shell to behave as is?

It would mean a lot, since having custom bootstrapping done by the platform creators would not feel hackish...

Thank you again for the work done, Jugo

Allann commented 8 years ago

We tried many attempts too, and not until we took all the PRISM code and changed it considerably to use the Catel Viewmodel and view managers instead of the internal PRISM collections did we get this even remotely work. Our biggest issue was that when views or viewmodels were created by prism they were not wired up correctly within the Catel framework therefore missing out on some of the finer aspects of the awesome work the Catel guys have done.

JugoRadonic commented 8 years ago

Hi Allann,

You just described my main issues and concerns. And it is not encouraging since rewriting things makes us incompatible with future Orchestra versions and upgrades. It's something I'd really like to avoid if possible.

Still hope there is another way of doing it without making my own version of Orchestra...

Jugo

GeertvanHorrik commented 8 years ago

Some general info:

In some apps, the plugins provide only a small bit of extensibility. On other apps, they take care of the complete app. There is of course always some sort of Api library involved in order to automate. We deploy this api to MyGet (private nuget) and our extensions use this api. Then we deploy the extensions to myget and combine the usage of Orc.NuGetExplorer and Orc.Extensibility. The user can select the current plugin and it will automatically be loaded.

We don't have a full example with plugins yet, but we could write such an app if the demand is high enough. On the other hand, we can't focus on community only, we need to make some money somehow as well ;-)


@mikeprag: Severity Code Description Project File Line Suppression State Error CS0618 'CollectionExtensions.ReplaceRange(ObservableCollection, IEnumerable)' is obsolete: 'Please use ReplaceRange(this ICollection, IEnumerable) instead. Will be treated as an error from version 5.0.0. Will be removed in version 5.0.0.' WPF.GettingStarted C:\Development\TestProjects\WPF\Catel\Catel.GettingStarted.WPF-master\WPF.GettingStarted\Services\FamilyService.cs 38 Active

This is indeed a fix. Just cast the collection like this:

((ICollection<T>)myCollection).ReplaceRange

We will try to keep the samples up to date, but our team is relatively small. And with over 50 components open-source, it's a lot of work.


@JugoRadonic

We use the regular bootstrapper. We do have our own internal components on top of all Orc components (an advanced implementation with all the components combined into an app framework). There is no need to hack a custom bootstrapper together. You can use the samples and use Orc.Extensibility to search for plugins and instantiate them (Orc.Extensibility takes care of this for you). It's one of the latest components we made open source. If you need help, ping us.


@Allann

I recommend only to use prism if you want to use the view region stuff. If you don't use it, prism is overkill. I'd like to think the Orc.* components are well set up and you can just pick whatever you need for your apps. Need extensibility, use Orc.Extensibility. Want to deploy extensions via NuGet / MyGet, use Orc.NuGetExplorer as well.

Catel does offer a prism extension that should take care of the wiring for you, but you should consider if you really need prism at all.

Orcomp commented 8 years ago

Does anyone want to build a basic example of what they are trying to achieve as a sample application inside the Orchestra repo (Examples folder) and send us a PR? We can then have something concrete to work on and clarify the issues you are running into.

We are also planning to release a Csv text editor application in the coming months, which will be open source (MIT) and use Orchestra and many Orc.* libraries. If anyone is interested in contributing please let us know.

Allann commented 8 years ago

Thanks for the quick answer Geert. We are actually using nearly all of the prism library, the module parts to make our shell (loosely based on the Orchestra shell) extensible. We currently have an implementation which allows the shell to be running whilst new assemblies (plug-in dlls) to be installed and automatically picked up and integrate into the shell. We are also using the region parts to host the various ViewModels. Using ViewModel first, another change to prism as it generally does view first. We started with the Prism extension but quickly found issues when using regions especially in ItemControls. This was mainly due to the fact that prism would keep it's own instances of Views and not use the Catel managers, therefore not wiring up many of the handlers used for inter-viewmodel communication. Our version of prism is now a mix of prism 5 and 6, catel prism extension and our own code so not really sure we can still call it prism ;). @Orcomp - we started with the Prism modularity example in the catel/orchestra repository, and then used a bare bones version of the shell to attempt to load the module dlls into various regions in the shell. All has to be loosely coupled as the shell doesn't know about the modules and vice versa. That was our first issue, because the modularity example isn't loosely coupled. the shell has references to modules. The shell should have a ContentControl region, a tab region, and then a view needs to be injected into a hosted tab view. This is the general gist of what we were trying to achieve, all whilst maintaining viewmodel first and the instantiation thought catel's mvvm core, not prism's activator.createinstance.

JugoRadonic commented 8 years ago

Hello,

Thank you all for the information provided, and as I feared, I've been going at Orchestra modules implementation wrong. They way I did it is how it could be looked at with just Catel...

I've looked into Orc.Extensibility just now (and still looking :)), but apart from a few implementation classes and interfaces (that are now revolving around Plugin instead of Module or ModuleBase) I did not see any use case. Example seems empty for now.

Let me be concrete with what would benefit my proof of concept I am tasked with:

  1. I have main part of the application build around Orchestra shell already and extended it with some of my custom extras (tabs, docking, etc...). For sake of this problem, lets make it a pure Orchestra shell with just empty FluentRibbon.
  2. Every other project with the actual working parts of the LOB app must be separated from the host project in as loose way as we can have it. Again, for the sake of discussion lets forget about dynamic folder discovery at app runtime and keep it just at ModuleA.dll with some simple View and ViewModel sitting in the .\Module folder of the app root.
  3. This is the part I need some aid with if you can point me the right way. Shell needs to be able to load this ModuleA.dll at start without having it referenced in the solution beforehand, then add a button to a Ribbon and it's command to display ModuleA's simple View and it's ViewModel when button is clicked.

This 1,2,3 and much more is very clear how to do with Orchestra if project with the shell has a reference on module assembly. But I am not sure yet how to inject it to the shell using Orc.Extensibility and make shell and module comunicate when module (extension) is resolved at runtime. Normally I would MEF it and that 'hack' works, but I wish to keep Orchestra source alone and just use it as is via Nuget. Can this be done with your latest Orc.Extensibility plugins?

With regards, Jugo

GeertvanHorrik commented 8 years ago

@JugoRadonic So this is where the power of Orc.Extensibility comes into play. It automatically searches for plugins (without having them referenced). Then it can instantiate them for you. For example, we have ProductX.Api what implements an IPlugin interface:

public interface IPlugin
{
    void Initialize();
    void ShellActivated();
}

Then any of your extensions (without being referenced) can be instantiated and can be initialized (since we do reference the Api assembly in both the host and extensions).

We use this in lots of our apps and some apps allow customization of the ribbon, some allow everything (dynamic tabs, workspaces, filtering, project management, just everything).

So to answer your question: you must define some sort of contract between the host and the extensions. In our case, that's always ProductX.Api.

alexfdezsauco commented 8 years ago

@JugoRadonic https://github.com/JugoRadonic:

Shell needs to be able to load this ModuleA.dll at start without having it referenced in the solution beforehand

This is a very common use case with the Catel extension for Prism

Take a look into https://github.com/Catel/Catel.Examples ModularityWithCatel.Desktop

then add a button to a Ribbon and it's command to display ModuleA's simple View and it's ViewModel when button is clicked.

When the module is initialized, using the right service you should be able to add buttons the ribbon and do whatever you want to do when the button is clicked

On Wed, Jul 13, 2016 at 8:31 AM, Geert van Horrik notifications@github.com wrote:

@JugoRadonic https://github.com/JugoRadonic So this is where the power of Orc.Extensibility comes into play. It automatically searches for plugins (without having them referenced). Then it can instantiate them for you. For example, we have ProductX.Api what implements an IPlugin interface:

public interface IPlugin { void Initialize(); void ShellActivated(); }

Then any of your extensions (without being referenced) can be instantiated and can be initialized (since we do reference the Api assembly in both the host and extensions).

We use this in lots of our apps and some apps allow customization of the ribbon, some allow everything (dynamic tabs, workspaces, filtering, project management, just everything).

So to answer your question: you must define some sort of contract between the host and the extensions. In our case, that's always ProductX.Api.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/WildGums/Orchestra/issues/49#issuecomment-232340658, or mute the thread https://github.com/notifications/unsubscribe/ABs_QHgsDcDHU6NLXxxaMdpXdV8UvRGnks5qVNqOgaJpZM4JKgQH .

JugoRadonic commented 8 years ago

Hi Geert and Alex,

Thank you both for the time taken to help me out. Fog is starting to clear out (did some more code as we spoke). And having an implementation of IPlugin that sits in between other two assemblies is loose enough for me.

Just one final question, is Orc.Extensibility replacing they way modularity was handled in Catel as far as Orchestra goes? Because Catels modules is what I used in the code I made work yesterday, but was not happy with how it looked with Orchestra platform involved and scraped it...

Cheers, Jugo

GeertvanHorrik commented 8 years ago

There are 2 ways of modularity:

  1. Catel.Extensions.Prism
  2. Orc.Extensibility

Both sit on top of Catel, and if we had to re-implement, Catel.Extensions.Prism would probably be Orc.Prism. But... since we never use the region manager in prism and wanted to be able to customize the plugin search functionality, we decided to implement our own (being in control is a big plus for us). We prefer Orc.Extensibility, but that's because we don't use the other stuff in Prism and it allows us to be in full control about future updates.

Orchestra itself does not support modularity. You can implement it either via Prism, Extensibility or something your write yourself (but wouldn't recommend that, Orc.Extensibility does a lot of the heavy lifting (including the reflection-only analyzing of the extensions)).

JugoRadonic commented 8 years ago

That clarifies it.

I will try to stick with Orc.Extensibility since I am also sure regions are not going to be used, at least not in the way they exist in Prism.

With what is being said about Orc.Extensibility in last few posts, it seem enough for my needs and more.

Cheers, Jugo

mikeprag commented 8 years ago

Thanks for the info everyone - Also becoming clearer to me as well. @GeertvanHorrik

We use this in lots of our apps and some apps allow customization of the ribbon, some allow everything (dynamic tabs, workspaces, filtering, project management, just everything).

Geert....Do you have any demo of the above that I can use to see how this works? Also, is there any demo anywhere using any form of real Data Access (specifically, SQL Server) - Sorry for the newbie questions. Thanks Mike

GeertvanHorrik commented 8 years ago

No, we don't have a demo of that unfortunately, it would require a major example app (for which we don't have time / resources to write at the moment).

mikeprag commented 8 years ago

Hi Geert, Sure, I understand. Two things: Regarding the Data Access example, we use Telerik's Data Access (a free ORM but it could be EntityFrameowrk or any of the others) so just at a high level, what do I need to do to make this work with Orchestra/Catel? Also, picking up on @JugoRadonic point, there does not seem to be any real use case in the Orc.Extensibility example. Do you have a simple example (I completely realise you are doing this on a voluntary basis so not in any way having a go!)?

Thanks in advance for any help

Mike

GeertvanHorrik commented 8 years ago

What ORM you use is irrelevant for Catel / Orchestra. It's just an app-layer. Catel does provide extensions for EF, but can easily work without.

About Orc.Extensibility: we recently moved this from our internal libs into the open source world so we can use it in any project we want (even open source ones). We just haven't found time to create an example. If we have a good idea for an app (that includes extensibility), we could write something, but again, both money and resources are limited :-(

JugoRadonic commented 8 years ago

Hello,

Just a quick info to the @mikeprag and all those that might find this idea helpful. I am sorry for not being able to provide working sample since my implementation is very specific, but I'll try my best to explain some key pointers in case it helps you set it up.

I've solved that 1,2,3 scenario from my previous posts with something similar to what Mr. Igr Alexánder (@alexfdezsauco) suggested. It's a mix of ideas from Catel Prism examples (but without any well known module names or references) and Unity DI made so it feels like nothing changes in the way one would use Orc without all this modularity involved.

Made my own ModularShellService that overtakes Orchestras ShellService, keeping all the things I liked in Ochestra implementation (well I like pretty much everything :), just altered some small details to fit my specific needs).

In doing this, one gets the place (kind of a custom bootstraper) where he can choose when and where to discover and initialize new assemblies that represent modules. For example during Orchestra splash screen while shell is constructed, before or after, however you like... Now you can implement some sort of ModuleCatalog (basic load from static folder does not work for me due to ClickOnce use, but for many scenarios it would suffice). Just make sure to invoke InitializeModules() AFTER modules are loaded in the catalog so all their ModuleInitializer.Initialize() will happen like they would when modules are referenced by the starting project. And from there, it's business as usual, same as if you had everything within one project like in Orchestra example. Big thanks to @alexfdezsauco once again for giving me a nudge I needed to find the solution that is good enough for now.

@GeertvanHorrik I would (and hopefully will in time) replace this solution to the Orc.Extensibility one if I fully understood how it was meant to be used, but I feel like I do not. Maybe instead of making whole time consuming examples, you can just extend your existing one (LogViewer) with something frequently used by many projects. For example, add a plugin that display user login before main app (without an actual login implementation, just a dummy viewmodel to showcase Orc.Extensibility).

mikeprag commented 8 years ago

@GeertvanHorrik Thanks again for the reply. I understand the ORM technology is irrelevant but I was trying to ask (badly!) is whether it matters how this manifests itself in the ViewModel. i.e. Exposing the model objects via the ViewModel (so it has model object references) or wrapping them - Just some advice really on what you think is the best approach

@JugoRadonic Thanks for your explanation - I will try and follow it through. Just to second your idea, I was also not looking for a full-blown app to demonstrate e.g. Add a tab or group and some buttons to the ribbon that have come from an external module. Completely appreciate it is a matter of time, money and resources!

Thanks for all the help

Mike

GeertvanHorrik commented 8 years ago

I have written an example in the Orc.Extensibility repository (develop branch). It shows you how to set up:

  1. Host app
  2. PluginFinder (the only thing you really have to implement yourself)
  3. Extensions api
  4. Extensions using the api

@mikeprag We do try to help everyone out, but again resources are limited. You'll have to figure out how to do the data access yourself for now. Catel uses the repository pattern, but as with every architecture, there are 1000 opinions on what's right. Check out the Catel docs on how to use the EF extensions.

@JugoRadonic See the example. If you have questions about the example, feel free to post it here.

JugoRadonic commented 8 years ago

@JugoRadonic See the example. If you have questions about the example, feel free to post it here.

Example is spot on, clear as day.

Did not expect it that fast to be honest. Thank you very much Geert!

Aitania commented 7 years ago

Hallo Allan,

"...we started with the Prism modularity example in the catel/orchestra repository, and then used a bare bones version of the shell to attempt to load the module dlls into various regions in the shell. All has to be loosely coupled as the shell doesn't know about the modules and vice versa. That was our first issue, because the modularity example isn't loosely coupled. the shell has references to modules. The shell should have a ContentControl region, a tab region, and then a view needs to be injected into a hosted tab view. This is the general gist of what we were trying to achieve, all whilst maintaining viewmodel first and the instantiation thought catel's mvvm core, not prism's activator.createinstance.!

have you a sample how to do this from ribbon backstage commands?

GeertvanHorrik commented 7 years ago

It's better to start a new discussion.

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.