SHORT
When it comes to MVVM, is it legal to have a ViewModel in a Model class due to polymorphism?
DETAILED
Imagine you have the following construct of 2 classes:
public class ArticleModel
{
public string Name { get; set; }
}
public class TransactionModel
{
public ArticleModel ArticleModel { get; set; }
}
So basically every transaction has an associated article. Therefore, if you wrap this in a ViewModel, it may look as follows:
ArticleViewModel
public class ArticleModelView
{
private ArticleModel _ArticleModel;
public ArticleModel ArticleModel
{
// basic get and set + notify
}
public string Name
{
// basic get and set + notify
}
}
TransactionViewModel
public class TransactionViewModel
{
private TransactionModel _TransactionModel;
public TransactionModel TransactionModel
{
// basic get and set + notify
}
public ArticleModel ArticleModel
{
// basic get and set + notify
}
}
The reason I'm asking is, if you declare a TransactionViewModel with a TransactionModel, you won't get the updates of the underlying ArticleModel. For example:
TransactionViewModel transactionViewModel = new TransactionViewModel
(new TransactionModel(new ArticleModel("sometestname")));
Now, when changing the name of the underlying ArticleModel:
transactionViewModel.ArticleModel.Name = "hi";
nothing will be notified of the changes made since I did not assign a new ArticleModel but instead just changed the name attribute.
If the ArticleModel property in TransactionModel would be an ArticleViewModel, the changes would have been reported. I could even implement a ArticleViewModel in the TransactionViewModel but then there could be still the chance that due to wrong access the changes may not be reported.
Any thoughts on this?
So it looks like you are trying to change the articlemodels name property but also notifyproperty change, which the way you are doing it won't work unless your model implements the inotifypropertychange. What you could do is somehting like:
public class TransactionViewModel
{
public ArticleModel CurrentArticleModel { get; set; }
public String Name
{
get { return CurrentArticleModel.Name; }
set
{
CurrentArticleModel.Name = value;
NotifyPropertyChange("Name");
}
}
Also if necessary, I don't see anything wrong with your TransactionViewModel having an instance of a ArticleViewModel. I would assume that the ArticleViewModel would be bound to its own usercontrol or something though
Related
I am trying to find some 'best practice' sample how to use Xamarin.Forms, ReactiveUI and Akavache in realworld scenario.
Lets say there is simple page representing Customer Detail. It should retrieve data from server when activated (navigated to). I like the idea of GetAndFetchLatest extension method from Akavache so I would like to use it.
I ended up with something like this:
public class CustomerDetailViewModel : ViewModelBase //(ReactiveObject, ISupportsActivation)
{
private readonly IWebApiClient webApiClient;
public Customer Customer { get; }
public ReactiveCommand<Unit, Unit> GetDataCommand { get; }
public CustomerDetailViewModel(Customer customer, IWebApiClient webApiClient = null)
{
this.Customer = customer;
this.webApiClient = webApiClient ?? Locator.Current.GetService<IWebApiClient>();
GetDataCommand = ReactiveCommand.CreateFromTask(GetData);
}
private Task GetData()
{
BlobCache.LocalMachine.GetAndFetchLatest($"customer_{Customer.Id.ToString()}",
() => webApiClient.GetCustomerDetail(Customer.Id))
.Subscribe(data =>
{
CustomerDetail = data;
});
return Task.CompletedTask;
}
private CustomerDetail customerDetail;
public CustomerDetail CustomerDetail
{
get => customerDetail;
set => this.RaiseAndSetIfChanged(ref customerDetail, value);
}
}
DTOs
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class CustomerDetail
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
View binding
this.WhenActivated(disposables =>
{
this.OneWayBind(this.ViewModel, x => x.Customer.Name, x => x.nameLabel.Text)
.DisposeWith(disposables);
this.OneWayBind(this.ViewModel, x => x.CustomerDetail.Description, x => x.descriptionLabel.Text)
.DisposeWith(disposables);
this.ViewModel?.GetDataCommand.Execute().Subscribe();
}
But I think this is not 100% bullet proof. There are some possible problems with this:
Is it ok to call this.ViewModel?.GetDataCommand.Execute().Subscribe(); in this.WhenActivated(d => ...) on the view when I want to load data on activation?
Binding to CustomerDetail.Description can cause NullReferenceException am I right? Or is it safe?
I want to do somethin like: "If there is CustomerDetail, show CustomerDetail.Name. When its not loaded yet, show Customer.Name". Do I need to make specific Property on ViewModel because of it?
How to indicate loading?
Am I missing something important here? Some other problems I can have with this?
You could use the WhenActivated in your ViewModel, there is a interface you can implement ISupportActivation. You can then invoke or run GetData from your ViewModel. There is also a helper extension method called InvokeCommand()
We deliberately don't propogate down. We use our own form of null propagation.
You could potentially set the text on your control in that case is one way. The WhenActivated won't happen until your View is shown.
I usually have done this as a boolean property on the ViewModel, the ViewModel can take into account different commands etc. You could potentially do a ObservableAsPropertyHelper to a command calling StartsWith(false)
Potentially I would use a ObservableAsPropertyHelper on your BlobCache but looks reasonable code.
Brief: I'm creating an MVC application in which I need to display a variety of types documents, some containing more author information than others.
What I wanna do: My approach is to have a generic "view document" view, which dynamically displays the document in a format dictated by the shape/type of the object passed to it.
Example: A simple document would be loaded into a SimpleDocumentViewModel, and display as such. However I'd like to load a larger type of document into an ExtendedDocumentViewModel, bringing with it additional information about both the document and the author. The view(s) would then display the appropriate data based on the object it receives.
Where I'm at now: In this vein I've created the following interfaces and classes, but I'm stuck as to how to return/identify the more specific return types in their derived classes.
abstract class BaseDocumentViewModel : DocumentViewModel, IDocumentViewModel
{
public int DocumentId { get; set; }
public string Body { get; set; }
public IAuthorViewModel Author { get; set; }
}
class SimpleDocumentViewModel : BaseDocumentViewModel
{
}
class ExtendedDocumentViewModel : BaseDocumentViewModel
{
public new IAuthorExtendedViewModel Author { get; set; }
}
interface IAuthorViewModel
{
int PersonId { get; set; }
string Name { get; set; }
}
interface IAuthorExtendedViewModel : IAuthorViewModel
{
int ExtraData { get; set; }
int MoreExtraData { get; set; }
}
Question: So my question is; how best can I get the specific types from the fully implemented classes, or do I need to return the base types and query it all in the view? Or am I off my head and need to go back to the drawing board?
Edits:
I know that c# doesn't support return type covarience, but hoped that there may be another way of returning/identifying the derived types so that I don't have to query them all in the view.
My current solution would be to always return the base types, and have a separate view for each concrete type that simply casts each object to the correct type, only querying those that could differ. Perhaps this is the best solution end of, but it feels very inelegant.
Usually you can do a simple "is" check. So you can have conditional rendering in your views, for example:
#if(Model is ExtendedDocumentViewModel)
{
// render ExtendedDocumentViewModel html here
}
Type checking is usually considered an anti pattern, however I am not sure if there is a much better approach to this problem. If you are using .NET Core you can also check the subclass tag here http://examples.aspnetcore.mvc-controls.com/InputExamples/SubClass .
Possible cleaner option is to just have a signature in the interface called GetView that each document has to implement. This way each document type has their own way of implementing the function and the calling function knows that each document has a function GetView. This method will work well if every document has a unique way of viewing the document. However if some documents share the same way of getting views, then may I suggest creating each View type into their own class and you can assign the views types to each document. I suggest looking into the strategy pattern.
First suggestion:
class SimpleDocumentViewModel : IAuthorViewModel
{
view GetView()
{
... do document specific stuff
... return view
}
}
class ExtendedDocumentViewModel : IAuthorViewModel
{
int ExtraData { get; set; }
int MoreExtraData { get; set; }
view GetView()
{
... do document specific stuff
... return view
}
}
interface IAuthorViewModel
{
view GetView();
}
Second suggestion:
class SimpleDocumentViewModel : IAuthorViewModel
{
public viewType1 view {get;set;}
public SimpleDocumentViewModel(viewType1 viewIn,etc...)
{
view = viewIn;
}
view GetView()
{
return view.GetView();
}
}
class ExtendedDocumentViewModel : IAuthorViewModel
{
int ExtraData { get; set; }
int MoreExtraData { get; set; }
public viewType2 view {get;set;}
public ExtendedDocumentViewModel(viewType2 viewIn,etc...)
{
view = viewIn;
}
view GetView()
{
return view.GetView(ExtraData,MoreExtraData);
}
}
interface IAuthorViewModel
{
view GetView();
}
I may be way off base here, but as I understand your question... why not just throw the return types in an object and pass that to your view?
You could look at the desired method and use reflection to pull out whatever info you want. Modify this and the object class hold whatever you want it to.
public class DiscoverInternalClass
{
public List<InternalClassObject> FindClassMethods(Type type)
{
List<InternalClassObject> MethodList = new List<InternalClassObject>();
MethodInfo[] methodInfo = type.GetMethods();
foreach (MethodInfo m in methodInfo)
{
List<string> propTypeList = new List<string>();
List<string> propNameList = new List<string>();
string returntype = m.ReturnType.ToString();
foreach (var x in m.GetParameters())
{
propTypeList.Add(x.ParameterType.Name);
propNameList.Add(x.Name);
}
InternalClassObject ICO = new InternalClassObject(c.Name, propNameList, propTypeList);
MethodList.Add(ICO);
}
return MethodList;
}
}
he object class could be something like this or modify it however you want:
public class InternalClassObject
{
public string Name { get; set; }
public List<string> ParameterNameList { get; set; }
public List<string> ParameterList { get; set; }
public InternalClassObject(string iName,List<string> iParameterNameList, List<string> iParameterList)
{
Name = iName;
ParameterNameList = iParameterNameList;
ParameterList = iParameterList;
}
}
You could call the method like this with the desired class.
public static List<InternalClassObject> MethodList = new List<InternalClassObject>();
DiscoverInternalClass newDiscover= new DiscoverInternalClass();
MethodList = newDiscover.FindClassMethods(typeof(ExtendedDocumentViewModel));
Now you can have your GetView build based on what is in MethodList
Hope this helps!
When implementing INotifyPropertyChanged (using Prisim) the code below makes sense, you'd want to know when a property changes.
[DisplayName("Media Type Id"), Display(Name = "Media Type Id")]
public int MediaTypeId
{
get { return this._MediaTypeId; }
set { this.SetProperty(ref this._MediaTypeId, value); }
}
private int _MediaTypeId;
But im a but confused when it comes to navigation properties.
Do I implement it? To me this would make sense if i was to do something like artist.Album = new Album();
But what if only needed to change a property like artist.Album.name = "NEW_NAME" (assuming Album.name implements INotifyPropertyChanged)
Would the code below still be necessary?
[DisplayName("Album"), Display(Name = "Album")]
public Album Album
{
get { return this._Album; }
set { this.SetProperty(ref this._Album, value); }
}
private Album _Album;
Or will this work just as well
public virtual Album Album { get; set; }
Same for navigation collections.
[DisplayName("Playlists"), Display(Name = "Playlists")]
public ICollection<Playlist> Playlists
{
get { return this._Playlists; }
set { this.SetProperty(ref this._Playlists, value); }
}
private ICollection<Playlist> _Playlists
Or
public virtual ICollection<Playlist> Playlists { get; set; }
As you understand, you implement INotifyPropertyChanged (INPC) in order for the UI to update when a property on the model changes. So in your case, if you have something that is data binding to the Album property, it must implement INPC if there is a chance that it might change. Instead of using regular collection, you have a class called ObservableCollection that already implements INPC for you so you don't have to.
I have a ShoppingCart listView with items that is bound to ShopingCartViewModel. When I click to an item it takes me to the ItemInfoFragment which is bound to ItemInfoViewModel.
In ItemInfoFragment I have a button which deletes the item and removes it from the ShoppingCart listview.
My problem is; After i delete the item and press backbutton to return to my previously activity, the ShoppingCart listView still shows the Item that I deleted.
My Question is; How to RaisePropertyChange in ShoppingCartViewModel when i exit the ItemInfoFragment?
I believe you have a few options:
Shared Persistent Storage
If you use a storage/caching solution like SQLite or Realm etc. Which can be used to read and modify the same shopping cart data between pages. You can then use view life cycle events (OnResume[Android] or ViewWillAppear[iOS]) to retrieve the latest from the cache.
Alternatively if the shopping cart data size is small you could read/write it to MvvmCross Settings Plugin. You will just have to serialize and deserialize your objects as you can only save basic types like strings, bools, int etc.
Dependency Injection Shared Instance
You can create an in memory cache via using a shared class instance the can be shared between multiple ViewModels. This classes properties can bind directly to your various views. Any changes to the list will update all views that bind to it. One thing to note is that you will have to manually handle clean up if you require the memory space occupied by the this instance class.
Example:
Example model
public class ItemInfo
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
Shared class instance and interface
public interface ISharedShoppingCart
{
MvxObservableCollection<ItemInfo> ShoppingCartItems { get; set; }
}
public class SharedShoppingCart : MvxNotifyPropertyChanged, ISharedShoppingCart
{
MvxObservableCollection<ItemInfo> _shoppingCartItems;
public MvxObservableCollection<ItemInfo> ShoppingCartItems
{
get { return _shoppingCartItems; }
set { SetProperty(ref _shoppingCartItems, value); }
}
}
Make sure to register the class and interface
public class App : MvxApplication
{
public override void Initialize()
{
/* Other registerations*/
Mvx.LazyConstructAndRegisterSingleton<ISharedShoppingCart, SharedShoppingCart>();
}
}
Example usage in shared ViewModels
public class ShopingCartViewModel : MvxViewModel
{
readonly ISharedShoppingCart _sharedShoppingChart;
public ShopingCartViewModel(ISharedShoppingCart sharedShoppingChart)
{
_sharedShoppingChart = sharedShoppingChart;
}
public MvxObservableCollection<ItemInfo> ShoppingCartItems
{
get { return _sharedShoppingChart.ShoppingCartItems; }
set { _sharedShoppingChart.ShoppingCartItems = value; }
}
}
public class ItemInfoViewModel : MvxViewModel
{
readonly ISharedShoppingCart _sharedShoppingCart;
public ItemInfoViewModel(ISharedShoppingCart sharedShoppingCart)
{
_sharedShoppingCart = sharedShoppingCart;
}
void RemoveItemFromCart(int id)
{
_sharedShoppingCart.ShoppingCartItems
.Remove(_sharedShoppingCart.ShoppingCartItems.Single(x => x.Id == id));
}
}
Pub/Sub
You could send messages back to the shopping cart ViewModel using the MvvmCross Messenger Plugin.
Is it possible to add some additional attributes to my components which are then set/hydrated using some custom logic/perhaps from a data store? Similar to adding some custom builder strategy in cab/unity ?
UPDATE
e.g.
assuming a class has these properties
[MyImport] string name1 { get; set }
[MyImport] MyType name2 { get; set }
[MyGuid] Guid { get; set; }
with custom attributes MyImport and MyGuid which are resolved by an "extension" to MEF ( which gets executed after the [imports] are resolved ) and has code along these lines
// property SET
var valu = myDBStore.GetValue( instanceGUID, propertyInfo.Name);
propertyInfo.SetValue( instance, TypeDescripter.GetConverter(valu).ConvertTo(propertyType), null);
// property GET - for example only, used during dehydration outside of MEF !
var valu = propertyInfo.GetValue( instance, null);
myDBStore.SetValue( instanceGUID, propertyInfo.Name, TypeDescripter.GetConverter(valu).ConvertTo(typeof(string));
// the above is pseudo code only, pls no comments on correct args/syntax :)
EDIT
components which are then set/hydrated using some custom logic/perhaps from a data store
One can do this via an "ExportFactory".
// "ExportFactory"
public sealed class DataStoreProvider
{
[Export(typeof(Model))]
public Model Item
{
get
{
return [custom logic];
}
}
}
public class NeedsModel
{
[Import(typeof(Model))]
public Model Item { get; set; }
}
Initial Answer
This is possible through MEF's Lazy<T, TMetadata>.
public interface ISomeMetadata
{
string UsefulInfo { get; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class ExportBaseAttribute : ExportAttribute, ISomeMetadata
{
public ExportBaseAttribute(string usefulInfo)
:base(typeof(BaseExport))
{
UsefulInfo = usefulInfo;
}
public string UsefulInfo { get; private set; }
}
// BaseExport class is not needed.. just showing advanced attribute usage.
public abstract class BaseExport { }
[ExportBase("Useful Filter Information")]
public class SomeExport : BaseExport
{
}
Then, in your host (composer), you can
[ImportMany(typeof(BaseExport))]
Lazy<BaseExport, ISomeMetadata>[] _baseExports
After you compose, you can run a LINQ filter using .Metadata
var goodExports = from export in _baseExports
where export.Metadata.UsefulInfo ...
select export;