How do I provide a generic child property - c#

We have an Entity framework model, which contains classes named QuoteStatus and SystemStatus, which model the status of a quote and a system respectively. Each of these classes has a navigation property, which is a collection that contains the emails of people who are to be notified when the status changes. The QuoteStatus class looks like this (simplified)...
public class QuoteStatus {
public int ID { get; set; }
public string Name { get; set; }
public List<QuoteStatusNotification> QuoteStatusNotifications;
}
public class QuoteStatusNotification {
public int ID { get; set; }
public string Email { get; set; }
}
The SystemStatus and SystemStatusNotification classes are remarkably similar.
Now, we want to have a WPF window that can be used to maintain both types of statuses (and any more that come along in the future). The idea is to have a dropdown control at the top of the window, where the user specifies the type of status to be shown (quote or system), and the value is sent to the view model.
The view model would have private variables for the data...
private List<QuoteStatus> _quoteStatuses;
private List<SystemStatus> _systemStatuses;
We want the view model to have a public Statuses property, which can be bound to a grid on the view. Depending on what value the user chooses in the dropdown, the Statuses property would contain either the _quoteStatuses collection, or the _systemStatuses collection.
We did that by creating a base Status class, and having the QuoteStatus and SystemStatus classes inherit from it. That was fine.
We ran into a problem with the child collections. We want the Status base class to have a StatusNotifications collection, which will be a collection of either the QuoteStatusNotification class, or the SystemStatusNotification class. We couldn't work out how to create that StatusNotifications collection.
From another thread here (see the second suggestion in the accepted answer), it looks like I might be able to do this with covariance, but I can't get my head round how to do it.
Anyone able to explain this?

Simple inheritance:
public class StatusBaseClass
{
public List<StatusNotification> StatusNotifications;
}
public class QuoteStatus : StatusBaseClass
{
}
public class SystemStatus : StatusBaseClass
{
}
public class StatusNotification
{
}
public class QuoteStatusNotification : StatusNotification
{
}
public class SystemtatusNotification : StatusNotification
{
}
You can add a QuoteStatusNotificatio or SystemStatusNotification to the base class list
What you can do that is really neat in your xaml is provide different UI for the two classes in a list view for example. See here: DataTemplate for each DataType in a GridViewColumn CellTemplate
For example:
<ListView ItemsSource="{Binding Statuses}">
<ListView.Resources>
<DataTemplate DataType="{x:Type viewModel:SystemStatus}">
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:QuoteStatus}">
</DataTemplate>
</ListView.Resources>
</ListView>
A bit more detail of what you're trying to do and may be able to help a bit more.

Create a base class that has the properties that are common across both types. Then you can pass
List<StatusBaseClass> statuses;
You can put either type into this list.
If it is a mixed list then you can get the individual types out by:
var quoteStatuses = statuses.OfType<QuoteStatus>();
var systemStatuses = statuses.OfType<SystemStatus>();

Related

Model binder for abstract class reference within abstract class asp.net core mvc 3

I'm following along at Model binder for abstract class in asp.net core mvc 2, but my model doesn't bind the HostedControls in ConcreteControlHost (see below). If I change the type of HostedControls to ConcreteControlText[], it does bind. I'd like to keep it as AbstractControl[] so that I can host multiple types of control.
I know the abstract binder is working because MainModel.Controls binds.
While debugging the binding of ConcreteControlHost, binder._propertyBinders has an entry for HostedControls as follows:
{[ModelMetadata (Property: 'ConcreteControlHost.HostedControls' Type: 'AbstractControl[]'), {Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinder<MyProject.AbstractControl>}]}
Following that property (Value.ElementBinder.Inner) eventually leads to AbstractModelBinder.
Breakpoints in AbstractModelBinder are not hit when binding the properties of ConcreteControlHost, but are when binding the properties of MainModel (as in, I get the hits for ConcreteControlHost, but not for ConcreteControlText).
This isn't related to In an Editor Template call another Editor Template with the same Model because it isn't the same model, and because everything renders correctly, it just doesn't bind. None of the ConcreteControlTexts referenced by HostedControls are referenced directly by MainModel.Controls.
public class MainModel {
public AbstractControl[] Controls;
}
public abstract class AbstractControl {
public string TypeName { get; set;}
}
public class ConcreteControlText: AbstractControl {
public string Text { get; set; }
}
public class ConcreteControlHost: AbstractControl {
public AbstractControl[] HostedControls { get; set; }
}
Does anyone see what I need to change to allow model-binding to work on ConcreteControlHost.HostedControls?
Turns out in my actual code, HostedControls had { get; private set; }. Removing the private modifier on the setter made it work.
Jeremy, thanks for looking into this.

Sending back a different model from the view

I want to send details of 2 different models to the view so I do this with a view model.
namespace Apps.Models
{
public class viewmodel1
{
public App app { get; set; }
public List<Colleague> colleague { get; set; }
}
}
app being a model that stores a variety of properties about an application, and colleague being a model that stores a variety of properties about colleagues.
Originally I was only passing in app, but I want to be able to display a drop down list of the colleagues first names in this view when creating an app (because the two are linked) so i need to pass in all the current colleagues to get their first names. This part is working.
However, my action result needs to change when the form is submitted to take in this new viewmodel type, and I don't know how to access the properties from the action result. Normally when you bind include there are just properties in the model, but i want to include the properties that are WITHIN the objects the viewmodel has. I don't know how to do this or of i am just doing something wrong.
I would obviously like to retain the validation that is already written for the app properties, but I'm not sure I can do that this way.
Not sure if this is the answer to your question without seeing the xaml binding you are trying to use, but you can use bindings that dive into objects.
So if you have an Employee object that is your DataContext (Employee would be your ViewModel although it has no controller logic, so I guess it's just a model here):
public class Employee
{
public string Name {get; set;}
public EmpAddress Address {get; set;}
}
public class EmpAddress
{
public string Street {get; set;}
public string Zip {get; set;
}
You can do bindings that look like this:
<TextBlock Text={Binding Address.Street}/>
Let me know if this isn't what you were after and I'll try to help.

Direct binding between UI and Model issue within MVVM application

I have a WPF application with MVVM.As I understood, the main goal of MVVM is to separate between logic layer and UI layer.
I have this Model class :
public class User
{
public string Login{get;set;}
public string Pwd{get;set;}
public List<User> GetUsers()
{
//
}
}
in my ViewModel, I instanciate a User object and an ObservableCollection of User
public class UserVM
{
public User _User{get;set;}
public ObservableCollection<User> liste{get; private set;}
public UserVM()
{
_User = new User("TODO","PWD2");
liste = new ObservableCollection(_User.GetUsers);
}
}
I feel that I bind directly a UI properties to a model object,So I need To know :
When I bind UI properties to the object _User properties, did I respect the MVVM architecture?
When I bind a listview datasource to liste, did I respect the MVVM architecture?
For the first question, if it is not suitable for MVVM, is it better to expose the model's properties instead of declaring the class?
For the second question, if it is not suitable for MVVM, How can I fix it ?
Thanks,
It looks like your User class has a tree-like structure in that it contains a List of User objects which themselves may contain a List of User objects...
The problem here is that your view model class contains User objects. Only the UserVM model would contain an ObservableCollection for example.
A simple fix would be: EDIT user.GetUsers() doesn't return a List<UserVM>
public class UserVM
{
public string Login { get; set; }
public string Pwd { get; set; }
public ObservableCollection<UserVM> Users { get; private set; }
public UserVM(User user)
{
Login = user.Login;
Pwd = user.Pwd;
Users = new ObservableCollection<UserViewModel>(
user.GetUsers().Select(subUser => new UserViewModel(subUser)));
}
}
You may also want to implement INotifyPropertyChanged so that the view gets notifications that the view model has changed.

MVC 3 2 models in a view

I am new to MVC (i.e. the last few days) and i would like to know if what i have done is the best way.
I have a view that is a combination of an insert item form with a list details form underneath for a particular entity. As such i kind of need 2 models for the page in order to avoid doing things like #Html.LabelFor(model => model.FirstOrDefault().EventTypeID, "Event Type").
What i have done is set the model to be Tuple<IEnumerable<Event>,Event> that way i have both the single item and the collection of items. Can anyone suggest a less hacky alternative or is this the best way of doing this?
There are 2 solutions.
You should create a different View Model Class (a simple class with both models as properties)
You can assign it to the ViewBag.Model1 ... ViewBag.Model2 ... But this is dynamic so you will have no intellisense and you can get errors at runtime.
You should use a ViewModel like this
public class ViewModel
{
public TypeOfYourModel MyModel1 { get; set; }
public TypeOfYourModel MyModel2 { get; set; }
}
I suggest you create a ViewModel that would contain both objects you want to pass.
public class NewEventViewModel
{
public Event NewEvent { get; set; }
public Event EventDetails { get; set; }
}
You could also use ViewBag, but it is not strongly typed so you would not get IntelliSense.
I would create a Model object just for the view, with 2 properties, one for the single entity and one for the collection, and then you can pass this composed object as the model for the view

MVP and rich user interface

I want to implement MVP pattern for my application.
MVP Passive View actually. So I came to a problem, it's easy one,
but can't decide which path should I take, so
I want to ask you guru's, how to properly work with MVP and display rich UI.
I mean, let's assume we need to display some data, and customer wants it to be TreeView.
There is requirement, that if user select different treenode, then the application updates itself with
new data or something like that.
At this point, i'm not sure how to implement View.
(All view logic goes to presenter)
I don't think that it is a good idea, to expose WinForms class
ISomeForm : IView {
//Presenter will take control of this TreeView.
TreeView Host {
get;
}
}
or exposing my data models
ISomeForm : IView {
//View knows how to display this data
List<MyDataNodes> Items {
get;
set;
}
}
or using other View interfaces.
ISomeForm : IView {
//Presenter knows what Views presenter should display.
List<IDataView> Items {
get;
set;
}
}
Any suggestions?
I would go with the View Interfaces.
In WPF MVVM, the more view separation I have, the easier it is to manage the UI/Logic interaction along the way.
I had to solve this problem using a MVC pattern. You could expose the TreeView as you suggested in your first example. Then the presenter could subscribe some events of the TreeView. But if you go this way your presenter will probably have to subscribe a lot of events of differents controls on your form. I have chosen to have a single event on the form that sends messages to the controller (in my case). The messages are represented as a class and can have any information you need. This is how my message looks:
public class MvcMessage
{
public object Source { get; private set; }
public MessageType MessageType { get; private set; }
public Type EntityType { get; private set; }
public IList InvolvedItems { get; set; }
public int NumAffected { get; set; }
public EventArgs SourceEventArgs { get; internal set; }
/// <summary>
/// Name of property who changed its value. Applies to models implementing INotifyPropertyChanged.
/// </summary>
public string PropertyName { get; set; }
public MvcMessage(object source, MessageType messageType, Type entityType)
{
this.Source = source;
this.MessageType = messageType;
this.EntityType = entityType;
}
public void Reroute(Type newEntityType)
{
MvcMessage reroutedMessage = (MvcMessage)MemberwiseClone();
reroutedMessage.EntityType = newEntityType;
Controller.NotifyAll(reroutedMessage);
}
}
... where MessageType is a enum containing a lot of common commands and requests.
My IView interface then defines the event like this:
public delegate void ViewEventHandler(MvcMessage message);
public interface IView : IViewPage, IWin32Window
{
event ViewEventHandler ViewEvent;
...
}
You should go more along the lines of the two latter examples; the view shouldn't expose WinForm-ish details to the presenter. See this answer for details on handling exactly your problem with TreeView updating - especially item 5.

Categories

Resources