Prism / MEF new view not getting a new viewmodel from MEF import - prism

I have a tabbed application where I want the user to be able to search for a person and then, in a new view, show the person's details. The user should be able to have multiple person detail views open for different people.
I was a little unsure if I am following the correct procedure for creating my new view. Using Unity (which I am not) it seems you would call Container.Resolve(view) however I am doing the following, the satisfyImports was necessary in order for my imports in the view / viewmodel to be created.
PersonDetailView view = new PersonDetailView();
_container.SatisfyImportsOnce(view);
_regionManager.Regions["MainRegion"].Add(view, this.SelectedPerson.Name);
_regionManager.RequestNavigate("MainRegion", new Uri("PersonDetailView", UriKind.Relative));
In the code for my PersonDetailView I have the following property to set the data context.
[Import]
public PersonDetailsViewModel ViewModel
{
set
{
this.DataContext = value;
}
}
This seems to work but the trouble I am having is that when I create a second person view, the new view is getting the same instance of the datacontext as the view that is already created.
Is this because I am creating my views incorrectly or is there a way that I tell MEF to create a new oject when it fulfills the imports for my new view?

When you export a part, by default it used a CreationPolicy of Shared. This essentially makes the exported instance a singleton in the container. With your export, add another attribute:
[Export, PartCreationPolicy(CreationPolicy.NonShared)]
public class Foo { }
This will ensure a new instance is created each time you call to compose the consumer instance.

Related

How to set global viewmodels in ASP .NET Core 3.1?

I am new to ASP.NET Core and have some trouble with binding global models and viewmodels to razor views. My application is a mixture of Web API and MVC with razor views.
My goal
When I open an ASP.NET MVC page, I need to instantiate a model by loading it from the database (DbContext service) based on an id received in a cookie. I want to use this model object globally in every view or partial view.
Current Implementation
I can access the cookies in action methods of page controllers, so that I have to load the model from the DbContext in every action method and bind it as viewmodel to target view. This is not practical, because I have to do this in every page controller, because I need that model object on all pages in my navigation pane.
Idea
I think it should be possible to access to Cookies and dbcontext within Startup.cs and bind the model object to _ViewStart.cshtml, so that it is accessible globally in every view or partial view. Even this approach were correct, I do not have any idea how the code would look like. Because I am learning Web-Apps with .NET Core by learning by doing and try and error at the moment. :(
UPDATE
I have a layout page _Layout.cshtml, which includes partial views like the _NavPane.cshtml. My goal is to pass a Model object to the _Layout, which is instantiated via loading from the database (I have a service IMandantRepository for this purpose) and dependent on a cookie.
That model object is needed on every page request. That's why it would be a better practice to load the model object outside the MVC page controllers and pass it to them (what I can not implement technically).
I tried to find a solution by myself and ended up in following interim ugly solution. Following is the content of the _ViewStart file. On the bottom I assign the needed global variables, which I can use in every view or partial view.
This solution has at least two disadvantages:
The model object is possibly loaded redundantly.
Too many program logic in a view file.
#inject MyProject.Data.IMandantRepository mandantRepo
#{
// Main layout template
Layout = "_Layout";
// Define default values
bool showAdminSection = false;
string src = "/images/logos/nologo.png";
// Read cookie value
string currentMandantUid;
Context.Request.Cookies.TryGetValue("currentMandant", out currentMandantUid);
// Load mandant from the database
var thisMandant = mandantRepo.GetMandantByUid(currentMandantUid);
if(thisMandant is Mandant){
src = "data:image/*;base64," + thisMandant.GetBase64Logo();
showAdminSection = thisMandant.Abbr == "AdminMandant";
}
// Assing global variables to ViewData
ViewData["CurrentMandant"] = thisMandant;
ViewData["logoSrc"] = src;
ViewData["showAdminSection"] = showAdminSection;
}
This is an example code in ConfigureService() of Startup.cs. You can register your dbContext class in this way.
services.AddDbContext<BookStoreContext>( options =>
options.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")));

MVVMCross MvxDialogFragment Restore Issue - Does not have MvxFragmentPresentationAttribute

I have upgraded to the latest version of MvvmCross (6.4.1) from 4.2.3. I and using Xamarin Android not Xamarin forms
In the view which initiates the dialog I do the following
Create dialog fragment derived from MvxDialogFragment
Assign a view model to it
Then call ShowView on the fragment
However when I rotate the device it fails in OnCreate with the message
Your fragment is not generic and it does not have MvxFragmentPresentationAttribute attribute set!
This did not happen in 4.2.3. The reason I create dialog this way is that I want it to use different view models depending on where I need this dialog. For example I want to show a different list of data, but in the same format in the dialog.
It seems this will only work if we apply the MvxFragmentPresentationAttribute which needs the type of view model to be defined at design time rather than run time.
Is there anything I can do to achieve this
Any help will be appreciated
If you somehow need to specify the ViewModel type at runtime, you can instead of decorating the class with the MvxFragmentPresentationAttribute let it implement, IMvxOverridePresentationAttribute and return it there with the appropriate ViewModel to be presented in.
Something like:
public class MyDialog : MvxDialogFragment, IMvxOverridePresentationAttribute
{
public MvxBasePresentationAttribute PresentationAttribute(MvxViewModelRequest request)
{
return new MvxFragmentPresentationAttribute
{
ActivityHostViewModelType = myDynamicType
};
}
}
Where you implement some kind of logic to get the myDynamicType somewhere.
However, you should be able to use MvxDialogFragmentPresentationAttribute instead though and the presenter will attempt to use the topmost Android Activity to present it in if you provide a null ref as the ActivityHostViewModelType.

Sharing controller JavaFX [duplicate]

I have created a root FXML which is a BorderPane and it has his own root controller.
I want to dynamicly add FXML's to the center of this borderpane.
Each of these fxml's share the same controller, root controller. I have done this in netbeans by choosing an exsisting controller when creating an empty FXML file.
I also have gave the nodes different id names, but the root controller does not recognize the nodes in these fxml's.
Is it possible to share the same controller for different fxml's?
Thanks in advance
Background
I don't know that sharing a controller instance is really recommended, at least I've never seen it done before.
Even if you set the controller class in each of the fxml's you are loading to the same value, it isn't going to share the same controller instance, because every time you load a controller, it will create a new instance (object) of the controller class (which doesn't seem to be what you want).
Potential Solutions
I haven't tried either of these solutions, but believe they will work.
The initialize method will probably be called each time you load a new fxml file. So you will want to account for that in your logic by making initialize idempotent.
A. Manually set the controller instance.
Remove all of the references to your controller class from your fxml files.
Manually create an instance of your controller class.
MyController controller = new MyController();
Set the controller to your controller instance before you load each fxml.
FXMLLoader loader = new FXMLLoader();
loader.setController(controller);
Panel panel = (Panel) loader.load("myfxml.fxml");
Repeat step 3 for each of your fxml files, using the same controller reference each time.
B. Use a controller factory.
You can set a controller factory on your fxml loaders and have the controller factory always return the same controller instance.

Associating ViewModels with Fragments in MVVMCross

We have a couple of Fragments that we use as common controls:
MyCommonHeaderA
MyCommonHeaderB
In our common View class we call base.OnCreate(bundle) and once that has returned we fish out the fragment instances and set their ViewModels
var commonHeaderAFragment = (MyCommonHeaderA)this.SupportFragmentManager.FindFragmentById(Resource.Id.header1banner);
if (commonHeaderAFragment != null)
{
commonHeaderAFragment.ViewModel = this.ViewModel;
}
var commonHeaderBFragment = (MyCommonHeaderB)this.SupportFragmentManager.FindFragmentById(Resource.Id.header2banner);
if (commonHeaderBFragment != null)
{
commonHeaderBFragment.ViewModel = this.ViewModel;
}
Until recently this has been working with no problem. Recently we have upgraded Xamarin and MVVMCross.
Now whenever we rotate the device OnCreate is called and the execution path ends up in MvxFragmentExtensions.OnCreate where it tries to lookup a type for the Fragment using FindAssociatedViewModelTypeOrNull. There is no associated ViewModel type for the Fragment. We never needed to, should we have associated a type?
I did try MvxViewForAttribute and concrete typed ViewModel property but neither of those worked as they wanted to create new VM instances.
I have a solution which is that in the base OnCreate, if we have a bundle try and find the Fragments and set their ViewModel property before base.onCreate and when there is no bundle we set the ViewModel property after OnCreate. It is clunky but works. I just wanted to check if we should have been setting up our Fragments differently so that we would not have hit this issue
There is an example available that uses the MvxCachingFragmentActivity: https://github.com/MvvmCross/MvvmCross-AndroidSupport/tree/master/Samples
In there you don't need to worry about those kind of problems anymore.

How do you access the MainViewModel in ViewModelLocator from code behind?

Building a WP7 app using MVVM light for my view models. I'm using the ViewModelLocator that gets added when you add the library through NuGet. Works great but now I need to get access to a ViewModel from code.
In my code the user clicks a button and I need to search the MainViewModel (which contains several view models) and find one based on the criteria the user entered.
Normally I would just response to the Click event of the button but I don't have an instance variable of the ViewModelLocator class to get a hold of the MainViewModel to perform the search. With the default template (non-MVVMLight) for Windows Phone 7, the App class has a static variable to the main view model so you can access it anytime with App.ViewModel.
There's some talk from twitter about using commands which would be good, but at some point I have to perform a code search across multiple vms to get the results I need. Probably need to inject a ISearchViewModel service into the View or something to make this work.
Here's the implementation of ViewModelLocator that is provided:
public class ViewModelLocator
{
private static MainViewModel _main;
public ViewModelLocator()
{
_main = new MainViewModel();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return _main;
}
}
}
So from the code behind of another view, how do you get access to Main (MainViewModel contains all the lists of data and has a search method I call)? Or Should you?
Just wondering how people are solving this type of problem?
Thanks.
In MVVM-Light the ViewModelLocator is provided as an application resource. Therefore you can still directly access it, but the syntax is different. If you look at your App.xaml you should see this piece of code somewhere.
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator"
d:IsDataSource="True" />
</Application.Resources>
From anywhere in your application you can access the App's resources and therefore also the MainViewModel with this piece of code:
(App.Current.Resources["Locator"] as ViewModelLocator).Main
This works for any application resource.
If you created the ViewModelLocator as in the template you have static references to the ViewModels. The mvvmlocatorproperty-snippet creates ViewModel-properties like this.
This means that you could just instantiate a new ViewModelLocator to locate the ViewModels in your code behind button click. It will always be the same viewmodels independent of the different instances of the ViewModelLocator
To access the MainViewModel from your code you can add this property to your class:
public ViewModel.MainViewModel myContext { get { return (DataContext as ViewModel.MainViewModel); } }
Then you can just use myContext.[whatever]
You can just use ViewModelLocator.MainViewModelStatic. Default template for MVVMLight have a static property for each your viewmodel.

Resources