Can Caliburn.Micro be used for Visual Studio 2017 Extension Development? - c#

I have been trying to use Caliburn.Micro in my extension that I am developing for Visual Studio 2017. I created my own custom bootstrapper. I used the example listed here Customer Bootstrapper. I kickstart my bootstrapper right after the extension's toolwindowpane is initialized. I have tried everything and it seems like I cannot get the Caliburn.Micro conventions to work. I am wondering if Caliburn.Micro can even work in an extension for Visual Studio!? The ToolWindowPane can host a WPF UserControl so I didn't think there would be an issue, but I cannot even get the ShellView to even see the ShellViewModel. Can someone let me know if Caliburn.Micro can work in this context???
Thanks!
Here is my BootStrapper:
public class ClassBootStrapper : BootstrapperBase<IShellViewModel>
{
private CompositionContainer container;
private static BootstrapperBase bootstrapper;
public static void Initialise()
{
if (null == bootstrapper)
{
bootstrapper = new ClassBootStrapper();
}
}
private ClassBootStrapper()
{
Initialize();
}
protected override IEnumerable<Assembly> SelectAssemblies()
{
var baseAssemblies = new List<Assembly>(base.SelectAssemblies());
var thisAssembly = Assembly.GetAssembly(typeof(ClassBootStrapper));
if (!baseAssemblies.Contains(thisAssembly))
{
baseAssemblies.Add(thisAssembly);
}
foreach (var assembly in baseAssemblies.ToList().Where(newAssembly => AssemblySource.Instance.Contains(newAssembly)))
{
baseAssemblies.Remove(assembly);
}
return baseAssemblies;
}
}

I test it in my side, it doesn't support the VS2017, you'd better to post the "Q & A" here:
https://marketplace.visualstudio.com/items?itemName=TheWinDev.CaliburnMicroWindows10Template#overview
So the extension team could provide much more helpful information for this extension.

Related

How is Setup class instantiated in MVVMCross in Xamarin?

I'm starting learning MVVM cross, In the android app, I have a splash screen class:
[Activity(MainLauncher = true,
Label = "#string/app_name",
Theme = "#style/Theme.Splash",
NoHistory = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
ScreenOrientation = ScreenOrientation.Portrait)]
public class SplashScreen : MvxSplashScreenActivity
{
public SplashScreen() : base(Resource.Layout.SplashScreen)
{
}
}
and this is the Setup class:
public class Setup : MvxAndroidSetup
{
protected Setup(Context applicationContext) : base(applicationContext)
{
}
protected override IMvxApplication CreateApp()
{
return null;
}
}
the problem is that the debugger doesn't hit the constructor of the Setup Class, instead I get "An unhandled exception" after the constructor of the splash screen
EDIT
I've already defined the App class in the PCL project:
public class App : MvxApplication
{
public override void Initialize()
{
base.Initialize();
}
also defined the AppStart:
public class AppStart : MvxNavigatingObject, IMvxAppStart
{
public async void Start(object hint = null)
{
//hardcoded login for this demo
//var userService = Mvx.Resolve<IUserDataService>();
//await userService.Login("gillcleeren", "123456");
ShowViewModel<MainViewModel>();
}
}
The main reason behind this project is to understand the sequence of code required and executed by MVVM Cross, so I provide the minimum code till it runs successfully without runtime errors.
Update
I have read your code again more thoroughly and I can see the issue now. You defined the constructor of the Setup class as protected, which makes it invisible for activation.
On MvvmCross for Android the magic happens inside MvxAndroidSetupSingleton class (see the source code here) which searches for the Setup type you defined. The FindSetupType method looks for your defined Setup class first and then inside the CreateSetup method Activator.CreateInstance is used to build the Setup instance. The CreateInstance method variant used however searches only for public constructors, which means it doesn't find your protected one. The result is that it cannot build the Setup class and crashes.
Original answer
The reason this happens is that you have no Core libary that would define the MvvmCross App class and would initialize other required setup. I suggest you to start with a simple tutorial or to look into the official sample projects to see what is necessary to make MvvmCross work in a Xamarin.Android app.

MEF: Creating a ViewModel in my UserControls

I am currently converting my WPF/MVVM application from Ninject to MEF to take advantage of some plugin architecture. There is no Prism or Unity, nor do I want to go down that path. I'm using VS2015 and .Net 4.6.
I was using the technique that was popular with MVVM Light where you instantiate the ViewModel inside of the XAML with Ninject.
public static ImageViewModel ImageVM => Kernel.Get<ImageViewModel>();
But now that I'm moving to MEF, I would like to see if there are some alternatives. Most of the currently answered posts on Stack Exchange are fairly old and I'm hoping there are some new alternatives available now that .NET 4.5 is available.
tl;dr
I have a Window that contains 10 UserControls. Each user control has a new instance of a ViewModel attached to it. Since the window is creating the user controls in xaml, how can I get a unique instance of my ViewModel into each control?
public partial class FingerprintControl{
public FingerprintControl() {
InitializeComponent();
}
[Import]
public FingerprintControlViewModel ViewModel
{
get { return DataContext as FingerprintControlViewModel; }
set { DataContext = value; }
}
One suggestion that I saw said to add
CompositionInitializer.SatisfyImports(this);
after the InitializeComponent(). But that is a Silverlight only class.
I looked at https://www.nuget.org/packages/Microsoft.Composition/ but the documentation for MEF 2 is just incredibly non-existent on the site.
I also saw that ExportFactory was added to MEF 2, but not sure if that would help either.
I did find in MEF 2 the static method CompositionContextExtensions.SatisfyImports, but I don't know what to do with it. The documentation only says, "Satisfies the imports of the specified object from the specified context." (Not real useful...)
We use a class wrapper for Mef with static methods for all our apps:
public class Mef
{
private static CompositionContainer container = null;
public static CompositionContainer Container { get { return container; } }
private static AggregateCatalog catalog;
public static void Initialize()
{
catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.exe"));
catalog.Catalogs.Add(new DirectoryCatalog(path: ".", searchPattern: "*.dll"));
container = new CompositionContainer(catalog);
StartWatch();
}
private static void StartWatch()
{
var watcher = new FileSystemWatcher() { Path = ".", NotifyFilter = NotifyFilters.LastWrite };
watcher.Changed += (s, e) =>
{
string lName = e.Name.ToLower();
if (lName.EndsWith(".dll") || lName.EndsWith(".exe"))
Refresh();
};
watcher.EnableRaisingEvents = true;
}
public static void Refresh()
{
foreach (DirectoryCatalog dCatalog in catalog.Catalogs)
dCatalog.Refresh();
}
}
(Note: We use the above to dynamically load plugins on-demand into our app as required by the user. You may want to use a different catalog system - several to choose from)
Then we initialize the class early on in the app life-cycle, usually in the App.Xaml code-behind constructor:
public App()
{
this.InitializeComponent();
Mef.Initialize();
}
and when I have a base-level mef-import, in the class/code-behind constructor call:
Mef.Container.SatisfyImports(this);
Hope this helps.

cannot build Caliburn Micro tutorial code. No definition for 'AssemblySource.Select'

I'm trying to get started with Caliburn Micro following the tutorial at http://www.mindscapehq.com/blog/index.php/2012/2/1/caliburn-micro-part-4-the-event-aggregator/
However, the code in the tutorial produces errors. On that page, the following Bootstraper code is provided:
using Caliburn.Micro;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
public class AppBootstrapper : Bootstrapper<AppViewModel>
{
private CompositionContainer container;
protected override void Configure()
{
container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);
container.Compose(batch);
}
protected override object GetInstance(Type serviceType, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = container.GetExportedValues<object>(contract);
if (exports.Count() > 0)
{
return exports.First();
}
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
}
I can find no class (generic or otherwise) called Bootstrapper, but I've been able to extend BootstrapperBase instead.
However, I am running into trouble on the line where container is declared. AssemblySource does not have a method named Select.
So what gives? Is this a difference between version 2.0 and 1.0? If so, are there some updated learning materials I can follow?
There has been some breaking changes between 1.5.2 and 2.0.0. Most of which are outlined in the documentation. Although it appears the change to Bootstrapper is missing. That should be fixed soon.
Instead of inheriting Bootstrapper<T> you should inherit Bootstrapper and add a method like
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<AppViewModel>();
}
to your Bootstrapper.
The missing method Select is because AssemblySource.Instance is an IObservableCollection and the Select method is the LINQ extension method. Adding a using for System.Linq will correct that error.

WinRT 8.1 settings in Caliburn.Micro

I'm trying to open a settings view in a Caliburn.Micro WinRT 8.1 app using VS2013 RC, but I keep getting an unhandled exception when opening it with the following message:
Value cannot be null. Parameter name: Could not parse the VisualElements from the app manifest.
I can reproduce the issues with the following steps:
create a new Windows Store app from VS2013 RC using the Blank app template.
add Caliburn.Micro via NuGet.
in App.xaml, change the base class to caliburn:CaliburnApplication (the namespace is declared as xmlns:caliburn="using:Caliburn.Micro").
in App.xaml.cs, change the class like this (for the CM-based settings I follow http://compiledexperience.com/blog/posts/settings-caliburn)
Code below:
public sealed partial class App
{
private WinRTContainer _container;
public App()
{
InitializeComponent();
}
protected override void Configure()
{
_container = new WinRTContainer();
_container.RegisterWinRTServices();
_container.PerRequest<MainViewModel>();
_container.PerRequest<SettingsViewModel>();
ISettingsService settings = _container.RegisterSettingsService();
settings.RegisterCommand<SettingsViewModel>("Test settings");
}
protected override object GetInstance(Type service, string key)
{
var instance = _container.GetInstance(service, key);
if (instance != null) return instance;
throw new Exception("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override void PrepareViewFirst(Frame rootFrame)
{
_container.RegisterNavigationService(rootFrame);
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
DisplayRootView<MainView>();
}
}
Finally, create folders for Views and ViewModels in the solution add add to them the required items: MainViewModel, SettingsViewModel, MainView, SettingsView. The views just include a TextBlock with some text. MainViewModel derives from Screen, while SettingsViewModel derives from PropertyChangedBase. There is no relevant code in any of them.
When launching the app, I can see the main view; then I open the charms bar and click settings, and I find the label leading to my app settings; when I click it, I get the exception quoted above. Any hint?
You can find a full repro solution here: http://sdrv.ms/18GIMvB .
If you aren't ready to move to the alpha version of CM, you can update Callisto to 1.4.0 via NuGet. That fixed the error for me.
It seems that the new CM release (alpha 2) fixed the issue, so I'm adding some more information here to help other newcomers like me. Here is what I'm doing now:
In app's Configure I have some bootstrap code like:
...
ResourceLoader loader = ResourceLoader.GetForViewIndependentUse("Resources");
ISettingsService settings = _container.RegisterSettingsService();
settings.RegisterFlyoutCommand<ContentSettingsViewModel>(loader.GetString("SettingsContent"));
The ContentSettingsViewModel is a viewmodel for filtering some contents. The string got from resources is the label which will appear in the settings flyout (be sure there is an entry for this string, as passing an empty or null string triggers an exception). This VM is derived from CM Screen as I'm overriding OnActivate and OnDeactivate to load and save settings when the user opens or dismisses the settings page.

MVC4 MEF-based dynamically loaded plugins

updated: read below in this post for a minimal solution
I have some newbie questions about an MVC4 solution with plugins. I googled around a bit and found
some good stuff, but it does not exactly fit my requirements, so I'm asking here for some advice.
It seems that the best solution for widget-like plugins in MVC is portable areas (in the MvcContrib package). I found the basic guidance here:
http://lostechies.com/erichexter/2009/11/01/asp-net-mvc-portable-areas-via-mvccontrib/
and some useful tips here:
http://geekswithblogs.net/michelotti/archive/2010/04/05/mvc-portable-areas-ndash-web-application-projects.aspx
More stuff in this post:
How to create ASP.NET MVC area as a plugin DLL?
That's all cool but sadly my requirements are a bit different:
unfortunately, I need a system where plugins are added and discovered dynamically, and this is not the case with portable areas, which must be referenced by the main MVC site project. I'd like to just upload something to the site and get it discover and use new components, so I'm going to use MEF for this.
fortunately, my plugins will not be like widgets, which might be very complex and heterogeneous; rather, they are components which must follow a common, shared pattern. Think of them like specialized editors: for each data type I'll offer a component with editing functions: new, edit, delete. So I was thinking of plugin-controllers which implement a common interface and provide actions like New, Edit, Delete and the like.
I must use MVC4 and in the future I'll have to add localization and mobile customizations.
I must avoid dependencies from complex frameworks and keep the code as simple as possible.
So, whenever I want to add a new data type for editing in this website I'd just like to drop a DLL in its plugins folder for the logic stuff (controller etc), and some views in the correct locations, to get the site discover and use the new editor.
Eventually I could include the views in the DLL itself (I found this: http://razorgenerator.codeplex.com , and this tutorial: http://www.chrisvandesteeg.nl/2010/11/22/embedding-pre-compiled-razor-views-in-your-dll/, which I suppose I could use with the codeplex razorgenerator as the code it refers to is not compatible with VS2012), but probably I'll have better keep them separated (also because of the localization and mobile-awareness requirements); I was thinking of adding an upload mechanism to my site admin area, where you can upload a single zip with the DLL with controllers and folders with views, and then let the server unzip and store files where required. This would allow me to easily modify views without having to deploy again the whole add-in.
So I started looking for MEF and MVC, but most of the posts refer to MVC2 and are not compatible. I had better luck with this, which is mainly focused on web API, but looks promising and simple enough:
http://kennytordeur.blogspot.it/2012/08/mef-in-aspnet-mvc-4-and-webapi.html
This essentially adds a MEF-based dependency resolver and controller factory to the "standard" MVC application. Anyway the sample in the post refers to a single-assembly solution, while I need to deploy several different plugins. So I slightly modified the code to use a MEF DirectoryCatalog (rather than an AssemblyCatalog) pointing to my plugins folder and then created a test MVC solution, with a single plugin in a class library.
Anyway, when I try loading the plugin controller the framework calls my factory GetControllerInstance with a null type, so that of course MEF cannot proceed to composition. Probably I'm missing something obvious, but I'm new to MVC 4 and any suggestion or useful (MVC4-compliant) link are welcome. Thanks!
Here is the essential code:
public static class MefConfig
{
public static void RegisterMef()
{
CompositionContainer container = ConfigureContainer();
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver =
new MefDependencyResolver(container);
}
private static CompositionContainer ConfigureContainer()
{
//AssemblyCatalog assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
DirectoryCatalog catalog = new DirectoryCatalog(
HostingEnvironment.MapPath("~/Plugins"));
CompositionContainer container = new CompositionContainer(catalog);
return container;
}
}
public class MefDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
_container = container;
}
public IDependencyScope BeginScope()
{
return this;
}
public object GetService(Type serviceType)
{
var export = _container.GetExports(serviceType, null, null).SingleOrDefault();
return (export != null ? export.Value : null);
}
public IEnumerable GetServices(Type serviceType)
{
var exports = _container.GetExports(serviceType, null, null);
List createdObjects = new List();
if (exports.Any())
createdObjects.AddRange(exports.Select(export => export.Value));
return createdObjects;
}
public void Dispose()
{
}
}
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _compositionContainer;
public MefControllerFactory(CompositionContainer compositionContainer)
{
_compositionContainer = compositionContainer;
}
protected override IController GetControllerInstance(
System.Web.Routing.RequestContext requestContext, Type controllerType)
{
if (controllerType == null) throw new ArgumentNullException("controllerType");
var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();
IController result;
if (null != export) result = export.Value as IController;
else
{
result = base.GetControllerInstance(requestContext, controllerType);
_compositionContainer.ComposeParts(result);
} //eelse
return result;
}
}
You can download the full test solution from here:
http://www.filedropper.com/mvcplugins
Edit: a first working minimal solution
Here are my findings, hope they can be useful for some other newbie starting with this stuff: I did not manage to succesfully run the framework quoted in the above reply, I suppose there must be something to be updated for VS2012 and MVC4. Anyway, I looked at the code and googled a bit more:
1) first of all, a source of confusion for me were the 2 different interfaces with the same name: IDependencyResolver. If I understand well, one (System.Web.Http.Dependencies.IDependencyResolver) is used for webapi, and another (System.Web.Mvc.IDependencyResolver) for generic DI. This post helped me here: http://lucid-nonsense.co.uk/dependency-injection-web-api-and-mvc-4-rc/.
2) also, a third component is the DefaultControllerFactory-derived controller factory, which is crucial to this post because it is the factory used for plugin-hosted controllers.
Here are my implementations for all these, slightly modified from several samples: first the HTTP resolver:
public sealed class MefHttpDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefHttpDependencyResolver(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
string name = AttributedModelServices.GetContractName(serviceType);
try
{
return _container.GetExportedValue(name);
}
catch
{
return null;
}
}
public IEnumerable GetServices(Type serviceType)
{
if (serviceType == null) throw new ArgumentNullException("serviceType");
string name = AttributedModelServices.GetContractName(serviceType);
try
{
return _container.GetExportedValues(name);
}
catch
{
return null;
}
}
public IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{
}
}
Then the MVC resolver, which is very similar, even if strictly not necessary for the dummy sample in this scenario:
public class MefDependencyResolver : IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public object GetService(Type type)
{
if (type == null) throw new ArgumentNullException("type");
string name = AttributedModelServices.GetContractName(type);
try
{
return _container.GetExportedValue(name);
}
catch
{
return null;
}
}
public IEnumerable GetServices(Type type)
{
if (type == null) throw new ArgumentNullException("type");
string name = AttributedModelServices.GetContractName(type);
try
{
return _container.GetExportedValues(name);
}
catch
{
return null;
}
}
}
And finally the controller factory:
[Export(typeof(IControllerFactory))]
public class MefControllerFactory : DefaultControllerFactory
{
private readonly CompositionContainer _container;
[ImportingConstructor]
public MefControllerFactory(CompositionContainer container)
{
if (container == null) throw new ArgumentNullException("container");
_container = container;
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controller = _container
.GetExports()
.Where(c => c.Metadata.Name.Equals(controllerName, StringComparison.OrdinalIgnoreCase))
.Select(c => c.Value)
.FirstOrDefault();
return controller ?? base.CreateController(requestContext, controllerName);
}
}
As for the sample controller, I created it into a class library project:
[Export(typeof(IController))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[ExportMetadata("Name", "Alpha")]
public sealed class AlphaController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Hello, this is the PLUGIN controller!";
return View();
}
}
In the main project (the MVC site) I have a Plugins folder where I copy this DLL, plus a "standard" set of views in their folders for this controller's views.
This is the simplest possible scenario, and probably there is much more to find out and refine, but I needed to be simple to start with. Anyway, any suggestion is welcome.
I'm currently working on the same issue. I've found this solution:
Blog post: http://blog.longle.net/2012/03/29/building-a-composite-mvc3-application-with-pluggable-areas/
Source code: https://bitbucket.org/darincreason/mvcapplication8.web
Basically it loads assemblies from specified location and with some name pattern on web application startup:
AssemblyInfo.cs:
[assembly: PreApplicationStartMethod(
typeof(PluginAreaBootstrapper), "Init")]
PluginAreaBootstrapper.cs:
public class PluginAreaBootstrapper
{
public static readonly List<Assembly> PluginAssemblies = new List<Assembly>();
public static List<string> PluginNames()
{
return PluginAssemblies.Select(
pluginAssembly => pluginAssembly.GetName().Name)
.ToList();
}
public static void Init()
{
var fullPluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Areas");
foreach (var file in Directory.EnumerateFiles(fullPluginPath, "*Plugin*.dll", SearchOption.AllDirectories))
PluginAssemblies.Add(Assembly.LoadFile(file));
PluginAssemblies.ForEach(BuildManager.AddReferencedAssembly);
// Add assembly handler for strongly-typed view models
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
}
private static Assembly AssemblyResolve(object sender, ResolveEventArgs resolveArgs)
{
var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies();
// Check we don't already have the assembly loaded
foreach (var assembly in currentAssemblies)
{
if (assembly.FullName == resolveArgs.Name || assembly.GetName().Name == resolveArgs.Name)
{
return assembly;
}
}
return null;
}
}
But I believe you can create some directory observer which can dynamically load assemblies, so you don't even need to restart your web application.
In my opinion it meets your 1, 2 and 4 needs. It's very simple, doesn't require any frameworks, has minimal configuration, allows dynamic loading of the plugins and works with MVC 4.
This solution plugs assemblies into Area directory, but I believe you can quite easily tune it to play as you like using routing.
You can find more complete solutions, here, and more background here. Our requirements were also a DI so this will help a bit too, although not needed (first links provide solution for DI as well. Good luck with this, it's not hard. Also note that those are for MVC3 but easily convert/migrate to MVC 4

Categories

Resources