ViewModel instance will always be recreated when switching between pages - unity-container

I'm using Prism 6, UWP with Unity.
The ViewModels will be automatically injected into the datacontext of the page. However, when I navigate between the pages the viewmodels will always be recreated. Is this behaviour desired by Prism and Unity?
Imagine the following scenario, a user enter some data into a page therefore the proper properties of the viewmodel will be set. When the user is switching back to another page and revisits the page all entered data are lost, because a new viewmodel instance is created.
At the moment my workaround is to override OnNavigatedTo and OnNavigatingFrom to save all properties of the viewmodel with the SessionStateService manual. I'm not sure if this is the correct way?
You can reproduce this behaviour with the following example:
https://github.com/PrismLibrary/Prism-Samples-Windows/tree/master/SplitViewSample/SplitViewSample

I am not using Prism, I'm using modified version of Template 10.
I just had a quick look at the Prism source code. Looks like Template 10 borrowed a lot of ideas from Prism.
I'll try to answer your question from 2 perspectives:
1) AFAIK, in Prism there is a static class with which you can set how to create/resolve you view model when it is automatically looked up for the corresponding View. The class is ViewModelLocationProvider, in file ViewModelLocationProvider.cs you can use following methods to setup the 'view model factories'
/// <summary>
/// Sets the default view model factory.
/// </summary>
/// <param name="viewModelFactory">The view model factory which provides the ViewModel type as a parameter.</param>
public static void SetDefaultViewModelFactory(Func<Type, object> viewModelFactory)
{
_defaultViewModelFactory = viewModelFactory;
}
/// <summary>
/// Sets the default view model factory.
/// </summary>
/// <param name="viewModelFactory">The view model factory that provides the View instance and ViewModel type as parameters.</param>
public static void SetDefaultViewModelFactory(Func<object, Type, object> viewModelFactory)
{
_defaultViewModelFactoryWithViewParameter = viewModelFactory;
}
/// <summary>
/// Registers the view model factory for the specified view type name.
/// </summary>
/// <param name="viewTypeName">The name of the view type.</param>
/// <param name="factory">The viewmodel factory.</param>
public static void Register(string viewTypeName, Func<object> factory)
{
_factories[viewTypeName] = factory;
}
then all the logic for getting view model instance is in the following, pay attention to the comment summary here, it describes the logic/strategy
/// <summary>
/// Automatically looks up the viewmodel that corresponds to the current view, using two strategies:
/// It first looks to see if there is a mapping registered for that view, if not it will fallback to the convention based approach.
/// </summary>
/// <param name="view">The dependency object, typically a view.</param>
/// <param name="setDataContextCallback">The call back to use to create the binding between the View and ViewModel</param>
public static void AutoWireViewModelChanged(object view, Action<object, object> setDataContextCallback)
{
// Try mappings first
object viewModel = GetViewModelForView(view);
// Fallback to convention based
if (viewModel == null)
{
var viewModelType = _defaultViewTypeToViewModelTypeResolver(view.GetType());
if (viewModelType == null)
return;
viewModel = _defaultViewModelFactoryWithViewParameter != null ? _defaultViewModelFactoryWithViewParameter(view, viewModelType) : _defaultViewModelFactory(viewModelType);
}
setDataContextCallback(view, viewModel);
}
on line 87 and 96 you get you view model instance for the corresponding view.
So that means, if you don't call any of those methods to setup the factories, it will fall back to the default factory which is
/// <summary>
/// The default view model factory whic provides the ViewModel type as a parameter.
/// </summary>
static Func<Type, object> _defaultViewModelFactory = type => Activator.CreateInstance(type);
that is pretty clear you will always get a new instance.
Regarding Unity, I didn't see anything special, the only clue is that in the PrismApplication class in PrismApplication.cs, it sets up the factory like following:
/// <summary>
/// Configures the <see cref="ViewModelLocator"/> used by Prism.
/// </summary>
protected virtual void ConfigureViewModelLocator()
{
ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Resolve(type));
}
that means the factory is now using
/// <summary>
/// Resolves the specified type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A concrete instance of the specified type.</returns>
protected virtual object Resolve(Type type)
{
return Activator.CreateInstance(type);
}
which you can override with your own implementation.
In PrismUnityApplication class, PrismUnityApplication.cs, it offers a default implementation to resolve the instance with Unity
/// <summary>
/// Implements the Resolves method to be handled by the Unity Container.
/// Use the container to resolve types (e.g. ViewModels and Flyouts)
/// so their dependencies get injected
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A concrete instance of the specified type.</returns>
protected override object Resolve(Type type)
{
return Container.Resolve(type);
}
and yeah, like other guys mentioned, you can control the lifetime of your view models yourself via Unity.
2) sorry for the long answer,
but I feel it's better to show you some code that will make things clear.
I'll keep the 2nd one short.
In my opinion, you don't need a view model when your view is gone.
I am not sure how the Frame stack is implemented in UWP and how they manage the view/page instances. I would assume once you navigate to a different page, the previous view/page should be released or can be released at GC, and you have the parameter and page type to be able to navigate back, but it will be a new instance and you restore the state of your view by restoring your view model.
so really, i think you're on the right track. and you should save/persist your user data whenever you can, and your solution works when the app is suspended and then is resumed, you can still recover the state of your view.
Thanks for reading.

It should be solved when you register your ViewModels as singletons (ContainerControlledLifetimeManager) in the UnityContainer. The best place for this would be the App.xaml.cs in method OnInitializeAsync
protected override Task OnInitializeAsync(IActivatedEventArgs args)
{
Container.RegisterType<MyViewModel>(new ContainerControlledLifetimeManager());
// rest of the method
}

Related

MiniProfiler MVC SQL Timings Not Displaying

I am using MiniProfiler.Mvc5 v4.2.1 with C# for an ASP.NET MVC5 website. I am implementing MiniProfiler based on the Samples.Mvc5 project included in the source code repo and am having an issue with the display of SQL timings. I am curious if something might be off in my setup, but I am not sure exactly what that might be.
Here is an example of loading the homepage, and I am confused why the SQL timings and percentage all show as 0.0:
However, if I actually click on the sql timings I get this view, which does seem to indicate that each SQL call does have timings associated with it:
The DataConnection class I am using to define ProfileDbConnection and other related objects is in a separate CSPROJ, here are some relevant configuration methods:
/// <summary>
/// Creates a new native connection
/// </summary>
protected override IDbConnection CreateNativeConnection()
{
var connection = new SqlConnection(ConnectionString);
return new ProfiledDbConnection(connection, MiniProfiler.Current);
}
/// <summary>
/// Creates a new SQL command
/// </summary>
/// <param name="cmdText">Command text</param>
protected override DbCommand CreateCommand(string cmdText)
{
var command = new SqlCommand(cmdText, null, (SqlTransaction)Transaction);
return new ProfiledDbCommand(command, (DbConnection)NativeConnection, MiniProfiler.Current);
}
/// <summary>
/// Creates a new command parameter
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="value">Parameter value</param>
protected override DbParameter CreateParameter(string name, object value)
{
return new SqlParameter(name, value);
}
/// <summary>
/// Creates a data adapter
/// </summary>
protected override DbDataAdapter CreateDataAdapter()
{
return new ProfiledDbDataAdapter(new SqlDataAdapter(), MiniProfiler.Current);
}
In the MVC app's Global.asax.cs:
public MvcApplication()
{
AuthenticateRequest += (sender, e) =>
{
var app = (HttpApplication) sender;
if (Request.IsLocal || app.User != null && app.User.Identity.IsAuthenticated && app.User.Identity.Name == "administrator")
{
MiniProfiler.StartNew();
}
};
EndRequest += (sender, e) =>
{
MiniProfiler.Current?.Stop();
};
}
Can anyone help direct me as to why I might not be seeing them aggregated in the initial view, or where I might start looking to gather more info?
I'm not sure exactly why Mini Profiler would behave like that, as I am not an expert in it. I would, however, wager it's because the Kentico API calls use their own DBContext inside of Kentico, and your DataConnection class does not share the same exact context as Kentico's. The strange thing is that you do see some on the individual level...But it is kinda of hard to tell with out more source code being shared.
But with that being said, Kentico offers automatic integration with Glimpse. Kentico's customized version of Glimpse does show SQL timings and many other profiling options. Check out my blog on how to use that. https://www.mcbeev.com/Blog/January-2018/Why-Kentico-Glimpse-is-a-Must-Have-Tool-for-Kentico-MVC-Developers and a follow up post on adding more memory debugging information at https://www.mcbeev.com/Blog/September-2019/KenticoCacheDoctor-2-Now-With-Kentico-Glimpse.
In the MVC5 world I think Glimpse is still a viable option.

.NET Core equivalent of CallContext.LogicalGet/SetData

I am trying to move into .net core an existing .net application that is using CallContext.LogicalGet/SetData.
When a web request hits the application I save a CorrelationId in the CallContext and whenever I need to log something later down the track I can easily collect it from the CallContext, without the need to transfer it everywhere.
As CallContext is no longer supported in .net core since it is part of System.Messaging.Remoting what options are there?
One version I have seen is that the AsyncLocal could be used (How do the semantics of AsyncLocal differ from the logical call context?) but it looks as if I would have to transmit this variable all over which beats the purpose, it is not as convenient.
Had this problem when we switched a library from .Net Framework to .Net Standard and had to replace System.Runtime.Remoting.Messaging CallContext.LogicalGetData and CallContext.LogicalSetData.
I followed this guide to replace the methods:
http://www.cazzulino.com/callcontext-netstandard-netcore.html
/// <summary>
/// Provides a way to set contextual data that flows with the call and
/// async context of a test or invocation.
/// </summary>
public static class CallContext
{
static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>();
/// <summary>
/// Stores a given object and associates it with the specified name.
/// </summary>
/// <param name="name">The name with which to associate the new item in the call context.</param>
/// <param name="data">The object to store in the call context.</param>
public static void SetData(string name, object data) =>
state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data;
/// <summary>
/// Retrieves an object with the specified name from the <see cref="CallContext"/>.
/// </summary>
/// <param name="name">The name of the item in the call context.</param>
/// <returns>The object in the call context associated with the specified name, or <see langword="null"/> if not found.</returns>
public static object GetData(string name) =>
state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null;
}
You can use a dictionary of AsyncLocal to simulate exactly the API and behavior of the original CallContext. See http://www.cazzulino.com/callcontext-netstandard-netcore.html for a complete implementation example.

Revalidate Model When Using WebAPI (TryValidateModel equivalent)

Using vanilla MVC I can revalidate my model with TryValidateModel. The TryValidateModel method doesn't seem to be applicable to WebAPI. How can I revalidate my model when using WebAPI?
I know it has been a while since this has been asked, but the problem is still valid. Thus i thought i should share my solution to this problem.
I decided to implement the TryValidateModel(object model) myself, based on the implementation in the System.Web.Mvc.Controller.cs
The problem is that the mvc's TryValidateModel internally used their own HttpContext and ModelState. If you go and compaire the two, they are very similar....
The be able to use our own HttpContext there exists a HttpContextWrapper that can be used for that.
And Since we have to clear our model state, it doesn't really matter that we use a different type of ModelState , as long as we get the desired result, thus i create a new ModelState object from the correct type...
I did add the error to the ModelState of the controller and not to the model state to the newly created ModelState , This seems to work just fine for me :)
Here is my code, that i just added to the controller...
do not forget to import the library...
using System.Web.ModelBinding;
protected internal bool TryValidateModel(object model)
{
return TryValidateModel(model, null /* prefix */);
}
protected internal bool TryValidateModel(object model, string prefix)
{
if (model == null)
{
throw new ArgumentNullException("model");
}
ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
var t = new ModelBindingExecutionContext(new HttpContextWrapper(HttpContext.Current), new System.Web.ModelBinding.ModelStateDictionary());
foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(metadata, t).Validate(null))
{
ModelState.AddModelError(validationResult.MemberName, validationResult.Message);
}
return ModelState.IsValid;
}
I don't know when was it added but now there is Validate method on api controller.
ApiController.Validate Method (TEntity)
https://msdn.microsoft.com/en-us/library/dn573258%28v=vs.118%29.aspx
Based from rik-vanmechelen original answer, here is my version that relies on the services container exposed by Web API.
/// <summary>
/// Tries to validate the model.
/// </summary>
/// <param name="model">The model.</param>
/// <returns>Whether the model is valid or not.</returns>
protected internal bool TryValidateModel(object model)
{
if (model == null)
{
throw new ArgumentNullException("model");
}
var metadataProvider = Configuration.Services.GetService<System.Web.Http.Metadata.ModelMetadataProvider>();
var validatorProviders = Configuration.Services.GetServices<System.Web.Http.Validation.ModelValidatorProvider>();
var metadata = metadataProvider.GetMetadataForType(() => model, model.GetType());
ModelState.Clear();
var modelValidators = metadata.GetValidators(validatorProviders);
foreach (var validationResult in modelValidators.SelectMany(v => v.Validate(metadata, null)))
{
ModelState.AddModelError(validationResult.MemberName, validationResult.Message);
}
return ModelState.IsValid;
}
This uses the following simple extension methods to access the services :
/// <summary>
/// Services container extension methods.
/// </summary>
public static class ServicesContainerExtensions
{
/// <summary>
/// Gets the service.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <param name="services">The services.</param>
/// <returns>The service.</returns>
/// <exception cref="System.ArgumentNullException">services</exception>
public static TService GetService<TService>(this ServicesContainer services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
return (TService)((object)services.GetService(typeof(TService)));
}
/// <summary>
/// Gets the services.
/// </summary>
/// <typeparam name="TService">The type of the service.</typeparam>
/// <param name="services">The services.</param>
/// <returns>The services.</returns>
/// <exception cref="System.ArgumentNullException">services</exception>
public static IEnumerable<TService> GetServices<TService>(this ServicesContainer services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
return services.GetServices(typeof(TService)).Cast<TService>();
}
}
The advantage of using this method is that it reuses the MetadataProvider and ValidatorProvider(s) you have configured for your Web API application while the previous answer is retrieving the one configured in ASP.NET MVC.
ASP.NET MVC and WebAPI run through different pipelines.
Turns out TryValidateModel is not supported in WebAPI. There's a feature request over on CodePlex.

EntityFramework: Update single field with detached entity

Unlike normal, I have code that actually works, but I'm wondering if it's the only (or best approach).
The basic Idea is I have an existing application that's handmade data layer is being ported to Entity Framework. As a compromise to minimize code changes, I'm working with existing methods, which tend to take a more disconnected approach. For example I have a lot of things like this:
UpdateNote(int noteId, string note)
I seem to have a method that works for this type of update without requiring a re-fetch:
var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note{ Id = noteId, Note = ""};
context.Notes.Attach(note);
note.Note = "Some Note";
context.SaveChanges();
It's a little ugly (though concise enough), so I would like to know if there is there a better approach to use with EF? Any downsides to this method, other than loosing built-in validation?
This is a pattern that will be used all over my app.
The following extension method for DbContext is an approach which would avoid to initialize your entities with some values different to the values you want to change it to.
public static class EFExtensions
{
public static void MarkAsModified(this DbContext context, object entity,
params string[] properties)
{
foreach (var property in properties)
context.Entry(entity).Property(property).IsModified = true;
}
}
You could then use it this way:
var context = new MyEntities();
context.Configuration.ValidateOnSaveEnabled = false;
var note = new Model.Note { Id = noteId }; // only key properties required to set
note.Note = "Some Note";
note.SomeOtherProperty = 1234;
note.AndAnotherProperty = "XYZ";
context.Notes.Attach(note);
context.MarkAsModified(note, "Note", "SomeOtherProperty" , "AndAnotherProperty");
context.SaveChanges();
Note: This only works for scalar properties, not navigation properties.
Besides validation I could imagine that this approach is problematic for a proper concurrency checking.
Edit
According to #Adam Tuliper's comment below concurrency is likely not a problem because the concurrency check is skipped when an entity is attached manually to the context (without reading it from the database) and marked as modified to send an UPDATE command to the database. It just overwrites the lastest version in the DB. Thanks to Adam for pointing this out!
See the following code I use to easily attach a disconnected object back to the graph, assuming we're now going to save it.
public static class EntityFrameworkExtensions
{
/// <summary>
/// This class allows you to attach an entity.
/// For instance, a controller method Edit(Customer customer)
/// using ctx.AttachAsModified(customer);
/// ctx.SaveChanges();
/// allows you to easily reattach this item for udpating.
/// Credit goes to: http://geekswithblogs.net/michelotti/archive/2009/11/27/attaching-modified-entities-in-ef-4.aspx
/// </summary>
public static void AttachAsModified<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
/// <summary>
/// This marks an item for deletion, but does not currently mark child objects (relationships).
/// For those cases you must query the object, include the relationships, and then delete.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="objectSet"></param>
/// <param name="entity"></param>
public static void AttachAsDeleted<T>(this ObjectSet<T> objectSet, T entity) where T : class
{
objectSet.Attach(entity);
objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Deleted);
}
public static void AttachAllAsModified<T>(this ObjectSet<T> objectSet, IEnumerable<T> entities) where T : class
{
foreach (var item in entities)
{
objectSet.Attach(item);
objectSet.Context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
}
}
}

Using Unity IoC Container

Can someone help me with this? I'm trying to use Unity IoC for injection. Here's the code:
IUnityContainer container = new UnityContainer()
.RegisterType<IBaseModel, EmployeeModel>()
.RegisterType<IProxyObjectAdapter, EmployeeProxyObjectAdapter>()
.RegisterType<IAdventureWorksRepository<IProxyObjectAdapter>,AdventureWorksWCFRepository<IProxyObjectAdapter>>();
Unity is complaining at that last line. Here's the error message:
'AdventureWorksManagement.ServiceAgents.AdventureWorksWCFRepository'
cannot be used as type parameter 'TTo'
in the generic type or method
'Microsoft.Practices.Unity.UnityContainerExtensions.RegisterType(Microsoft.Practices.Unity.IUnityContainer,
params
Microsoft.Practices.Unity.InjectionMember[])'.
There is no implicit reference
conversion from
'AdventureWorksManagement.ServiceAgents.AdventureWorksWCFRepository'
to
'AdventureWorksManagement.ServiceAgents.IAdventureWorksRepository'. C:\My Development\My Testing
Ground\MyTesting
Ground\AdventureWorksManagement\ModelView\EmployeeViewModel.cs 94 33 AdventureWorksManagement
Here's the class definition:
/// <summary>
/// Repository for WCF agents
/// </summary>
public class AdventureWorksWCFRepository<T> : IAdventureWorksRepository<IBaseModel> where T: IProxyObjectAdapter
{
/// <summary>
/// WCF service end
/// </summary>
private readonly AdvWorksManagementService.EmployeeServiceContractClient servicePoint;
/// <summary>
/// Adapter used to translate WCF proxy objects to domain objects
/// </summary>
private T proxyobjectAdapter;
.....
Please advice.
dormantroot
The error is correct. You're mapping:
IAdventureWorksRepository<IProxyObjectAdapter> -> AdventureWorksWCFRepository<IProxyObjectAdapter>
However, in the code definition, AdventureWorkdsWCFRepository<T> implements IAdventureWorksRepository<IBaseModel>. Which is not the interface you're trying to map from. I suspect that IBaseModel was supposed to be "T" in the code.

Resources