This is for an asp.net mvc3 project. I have two views and their own corresponding viewmodels.
Home.aspx has a viewmodel HomeVM
HomeChild.aspx has a viewmodel as HomeChildVM.
Now HomeChildVM is derived from HomeVM and has a few properties more that are used in its own view. My controller has one action method that returns the Home view and another action method that returns the HomeChild view. Both these action methods call one business method that returns a type as HomeVM. Then my action methods return the same to the aspx view.
return View(objHomeVM);
Now, instead of writing another business method and repeating all the code all over again just to return another viewmodel type ie HomeChildVM, I am assigning the properties of objHomeVM to objHomeChildVM one by one like so:
objHomeChildVM.prop1 = objHomeVM.prop1;
objHomeChildVM.prop2 = objHomeVM.prop2;
and then returning it:
return View(objHomeChildVM);
Is there a better way to do this than by assigning properties one by one? I feel this is too primitive a way, unless this is the only way to do it.
HomeChild.aspx currently has this page directive
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<HomeChildVM>" %>
If I change that to HomeVM then I wont be able to use the extra properties that HomeChildVM has which are only specific to HomeChild.aspx page.
Any thoughts on this?
Thanks for your time...
Have you considered creating the required ViewModel outside the business method and then passing a reference of the ViewModel into your business method, as a type of HomeVM, to be populated? ie:
public ActionResult HomeAction()
{
HomeVM objHomeVM = new HomeVM();
BusinessMethod(objHomeVM);
return View(objHomeVM);
}
public ActionResult HomeChildAction()
{
HomeChildVM objHomeChildVM = new HomeChildVM();
BusinessMethod(objHomeChildVM);
return View(objHomeChildVM);
}
private void BusinessMethod(HomeVM objHomeVM)
{
...
objHomeVM.prop1 = prop1;
objHomeVM.prop2 = prop1;
...
}
Remember ViewModels are simply normal objects. The only thing special about them is that they are called ViewModels.
Just a note on your view models. I would create a view model for both views, even though some of the properties are used in both. What are you going to do if you have to remove one of the properties in HomeVM? Then HomeChildVM falls apart.
Getting back to your question on assigning the properties one by one.. I would suggest you look at Auto Mapper. It takes care of mapping the properties between objects for you.
UPDATED
Once you have your types, and a reference to AutoMapper, you can create a map for the two types:
Mapper.CreateMap<Customer, CustomerDto>(); // Create the map
The type on the left is the source type, and the type on the right is the destination type. To perform a mapping, use the Map method:
CustomerDto dto = Mapper.Map<Customer, CustomerDto>(customer);
Here is some sample code that you could write/use to map between 2 objects:
public static Customer Map(CustomerEntity entity)
{
return new Customer
{
CustomerId = entity.CustomerId,
Company = entity.CompanyName,
City = entity.City,
Country = entity.Country
};
}
And then to use it would look something like this:
Mapper.Map(customer);
Related
I am new to MVVM pattern and Caliburn.Micro. I've read some tutorials on how to get started, but I'm confused about the Model part of MVVM in the context of Caliburn.
I want to create my first MVVM application and I have some design questions:
In tutorials, the Model was presented as simple property in
ViewModel. How should I manage more complex models? Is there any
naming convention? Obviously, there should be some external classes
made for my models, but how should I communicate between my models
and the view?
How should I keep references to many instances of one complex model?
For ex. cumtomers (instances of Customer model class)
Is there a possibility to manipulate one model class in many
ViewModels? How should I store my model reference, so it'll be
visible from different ViewModels?
Where should I put my code for more complex model manupulation/file,
database storage? How should I invoke such code? I'm not asking here
about SQLConnections, but MVVM best practices. :)
Thanks in advance for any help :)
EDIT:-------------------------------------------------------
Thank you for your anwser. I uderstand the topic more clearly, but I'm still confused about some details.
For an example, let's assume this little application. I have a form that allows me to add a new Customer. It has a few fields like Name, Surname etc.
After pressing the button, I invoke the addCustomer command in the ViewModel. I want my program to store the newly created customer inside the database.
My view also has the List control (whatever), which displays my customers as raw strings (like "Name: John, Surname: Doe, Address: ..." I know it's dumb to make it like this, but i need an example of model manipulation (like .toString()))
For this example, I've created a bunch of stuff to illustrate my vision of that process:
fields - it's a set of form fields like Name, Surname etc.
customerSet - it's a set of Customer class to store all created
customers
.addToDatabase(fields) - a method which puts newly created customer
to the database
.getStrings - a method which prepares a set of strings to be
displayed by the list in CustomerView
I think about 2 approaches that would be good for a solution:
First approach. I don't like this one. The only advantage is, that
ViewModel handles all the logic inside application. Sharing model
would be a serious problem here, because saving methods are bound to
the ViewModel class.
Second, MVC like approach. To me it's the most intuitive one. But - I
don't know where should I store CustomersModel object, so few
ViewModels could have access to it.
Which is the better one? Or maybe another approach that is more suitable for MVVM?
Another problem is: Where should I put my method that will load all the Customers from the database, so they could be displayes on the list? In "get method" inside viewmodel, or inside a model class?
In tutorials, the Model was presented as simple property in ViewModel.
How should I manage more complex models? Is there any naming
convention? Obviously, there should be some external classes made for
my models, but how should I communicate between my models and the
view?
Your models should represent whatever it is they need to whether it's a customer, account, etc. The view models job is to handle the interaction between the view and models.
How should I keep references to many instances of one complex model?
For ex. cumtomers (instances of Customer model class)
Generally, you will map complex models to more friendly format for display, you can do it manually or use a tool like AutoMapper.
Is there a possibility to manipulate one model class in many
ViewModels? How should I store my model reference, so it'll be visible
from different ViewModels?
If you're working with a local db you can pass IDs around. If it's a service you could persist the model locally for other view models to work with. You could also inject a singleton, ISharedData, into view models that need to work with shared data.
Where should I put my code for more complex model manupulation/file,
database storage? How should I invoke such code? I'm not asking here
about SQLConnections, but MVVM best practices. :)
Create services for more complex model manipulation / business logic. Inject the services into view models that require them. ICustomerService, IAccountService, etc.
EDIT:-------------------------------------------------------
You're first approach is correct. To your point about sharing the model being a serious problem because saving methods are bound to the view model class. The view model will have a SaveCustomerCommand that is fired when the button is clicked, because of its binding.
The SaveCustomerCommand will persist the CustomerModel, regardless of how the CustomerModel is persisted. So if its a database, the view model might have a reference to a context and issue a _db.Save(CustomerModel). If another view model needs to manipulate a CustomerModel, it will do so by using the context. The view model could also have a reference to a CustomerService that handles the crud for the CustomerModel.
Here's how this might look:
public class AddCustomerViewModel : Screen
{
private readonly ICustomerService _customerService;
public AddCustomerViewModel(ICustomerService customerService)
{
_customerService = customerService;
}
//If button is named x:Name="SaveCustomer" CM will
//bind it by convention to this method
public void SaveCustomer(Customer customer)
{
_customerService.Save(customer);
}
}
public class CustomerListViewModel : Screen
{
private readonly ICustomerService _customerService;
private List<CustomerDisplayModel> _customers;
public CustomerListViewModel(ICustomerService customerService)
{
_customerService = customerService;
}
public List<CustomerDisplayModel> Customers
{
get { return _customers; }
set
{
_customers = value;
NotifyOfPropertyChange();
}
}
//only fires once, unlike OnActivate()
protected override void OnInitialize()
{
var customers = _customerService.LoadAllCustomers();
//could just use the model but this shows how one might map from
//the domain model to a display model, AutoMapper could be used for this
Customers = customers.Select(c => new CustomerDisplayModel(c)).ToList();
}
}
public interface ICustomerService
{
List<Customer> LoadAllCustomers();
void Save(Customer customer);
}
//same as button, Label named x:Name="CustomerName" will bind
// to CustomerName
public class CustomerDisplayModel
{
private readonly Customer _customer;
public CustomerDisplayModel(Customer customer)
{
_customer = customer;
}
public string CustomerName
{
get { return _customer.Name; }
set { _customer.Name = value; }
}
public string Surname
{
get { return _customer.Surname; }
set { _customer.Surname = value; }
}
public string Address
{
get { return _customer.Address; }
set { _customer.Address = value; }
}
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Address { get; set; }
}
Whose responsibility is it to populate the values in an ASP MVC 5 architecture (C#, EF), for e.g. if we have PurchaseRecordsViewModel , PurchaseRecords Domain Model , PurchaseController
Does the code to populate data (time, cost etc) go it the viewmodel, right in its very own the viewmodel go in the PurchaseRecordsViewModel ?
Or, does the code go in the Action method of the PurchaseController
View models are typically just dumb collections of properties. Populating a view model typically rests inside of your service layer or, if you don't have one, your action method.
Think of the roles this way.
A domain model is a direct mapping to a database table.
A view model is a collection of properties needed to display a view.
A service layer gets/uses one or more domain models and populates a view model.
A service layer also can take a view model and create/update one or more domain models
A controller action method is the glue between the two. It calls a service layer to get (GET) a view model and passes it to a view. These action methods also take (POST) a view model and pass it to the service layer to do whatever needs to be done to it.
Another question typically asked is why can't I use domain models for a view? You can, but typically you run into things like, needing data from more than one domain model, not needing all the properties that are in the domain model and lastly, you now would have to worry about properties being updated on the domain model that you did not intend.
Expanding upon Tommy's answer, here is some code to go along with his description.
//Controller
public ActionResult Index()
{
List<OrderViewModel>() model = new List<OrderViewModel>();
model = new ServiceClass().GetOrders();
return View(model);
}
//here is your Service Class, this layer transfers the Domain Model into your ViewModel
public List<OrderViewModel> GetOrders()
{
List<OrderDomain> model = new List<OrderDomain>();
model = new DataAccess().GetOrders();
List<OrderViewModel> viewModel = new List<OrderViewModel>();
foreach (var order in model)
{
OrderViewModel vm = new OrderViewModel();
vm.OrderId = order.OrderId;
vm.OrderName = order.OrderName;
viewModel.Add(vm);
}
return viewModel;
}
//some DataAccess class, this class is used for database access
Public List<OrderDomain> GetOrders()
{
List<OrderDomain> model = new List<OrderDomain>();
using (var context = new MyEntities())
{
model = (from x in context.Order
select new OrderDomain
{
OrderId = x.OrderId,
OrderName = x.OrderName
}).ToList();
}
return model;
}
Edit:
This seems to be a mildly popular answer so I would like to mention I no longer follow this pattern. Instead I've been using mediatr and vertical slice architecture.
Ideally, PurchaseRecordViewModel should populate itself by getting PurchaseRecordsDomainModel. It should contain simple mapping of properties, and possibly some formatting of the output you're going to use in your view.
PurchaseRecordsViewModel
public class PurchaseRecordsViewModel
{
public IEnumerable<PurchaseRecordViewModel> PurchaseRecords {get;set;}
}
PurchaseRecordViewModel
public class PurchaseRecordViewModel
{
public DateTime Date {get;set;}
public decimal Cost {get;set;}
// .... some other properties
public PurchaseRecordsViewModel(PurchaseRecordsDomainModel domainModel)
{
Date = domainModel.Date;
Cost = domainModel.Cost;
// .... some other property mappings
}
}
What your action method on PurchaseController should do, is orchestrating the process of getting your PurchaseRecordsDomainModel, creation of PurchaseRecordsViewModel from PurchaseRecordsDomainModel and passing it to the View. Action method itself shouldn't contain any code that deals with connecting and retrieving data from database (in your case querying EF context), or any business logic. You should try to have loosely coupled modules, talking to each other via abstractions, this way you will ensure your application is maintainable, extensible and testable.
Also, try to draw clear separation between various layers of your system. For example, it is not a good idea to have EF entities as Domain Model Entites. You don't want your business logic layer to depend on data access layer, think of it this way, what if at some point of time in the future, you are moving away from EF and using some other ORM or even other technology to store and query data. You don't want to change business logic layer just because you're changing your data access layer. So to go from words to code in your case.
Considering that you already have your view and view model, I would create PurchaseRecordsService class in domain layer(please note depending in your case you might not use Repositories, but some other technique, this example is mainly to illustrate my point)
public class PurchaseRecordsService
{
private readonly IPurchaseRecordsRepository _purchaseRecordsRepository;
public PurchaseRecordsService(IPurchaseRecordsRepository purchaseRecordsRepository)
{
if(purchaseRecordsRepository == null)
{
throw new ArgumentNullException("purchaseRecordsRepository");
}
_purchaseRecordsRepository = purchaseRecordsRepository;
}
public IEnumerable<PurchaseRecordsDomainModel> GetPurchaseRecords()
{
// trivial case, real code can be more complex
return _purchaseRecordsRepository.GetPurchaseRecords();
}
}
Then in your domain layer, you could define IPurchaseRecordsRepository
public interface IPurchaseRecordsRepository
{
IEnumerable<PurchaseRecordsDomainModel > GetPurchaseRecords();
}
The idea is, our PurchaseRecordsService needs a way to communicate with databases, so whoever uses it, must supply implementation of IPurchaseRecordsRepository. Next step is to move to our data access layer and create implementation class of IPurchaseRecordsRepository.
public class EfPurchaseRecordsRepository: IPurchaseRecordsRepository
{
private readonly EfObjectContext _objectContext;
public EfPurchaseRecordsRepository(string connectionString)
{
_objectContext = new EfObjectContext(connectionString);
}
public IEnumerable<PurchaseRecordsDomainModel > GetPurchaseRecords()
{
var purchaseRecords = (from p in _objectContext.PurchaseRecords
....
select p).AsEnumerable();
return purchaseRecords .Select(p => p.ConvertToDomainPurchaseRecord());
}
}
And the last piece - we need to define our Action in PurchaseController
public class PurchaseController: Controller
{
private readonly IPurchaseRecordsRepository _repository;
public PurchaseController(IPurchaseRecordsRepository repository)
{
if(repository == null)
{
throw new ArgumentNullException("repository");
}
_repository = repository;
}
public ActionResult Index()
{
var purchaseRecordsService = new PurchaseRecordsService(_repository);
var purchaseRecordsViewModel = new PurchaseRecordsViewModel();
var purchaseRecords = purchaseRecordsService.GetPurchaseRecords();
foreach(var purchaseRecord in purchaseRecords)
{
var purchaseRecordViewModel = new PurchaseRecordViewModel(purchaseRecord);
purchaseRecordsViewModel.PurchaseRecords.Add(purchaseRecordViewModel);
}
return View(purchaseRecordsViewModel);
}
}
To recap, what we have is loosely coupled code, our Presentation and Data Access Layers don't know about each other, and they depend only on Domain layer. If you need, you can replace MVC front end with WPF for example, move from EF to another technology, your code is testable.
Ideally, your view model should be unaware of your domain model, so I'd say that you put your population logic in your controller, perhaps packed away in some sort of mapping/population utility class.
But remember, when it comes to questions about where to put certain logic, personal preference goes a long way.
I've searched all over the web but not finding the solution for the following problem:
Say I have three ViewModel classes
public class ViewModelNewPerson
{
public string PersonName;
public string Address;
public string EyeColor;
//etc
}
public class ViewModelSelectPerson
{
public int SelectedPersonId;
}
public class ViewModelComposite
{
public ViewModelSelectPerson selectViewModel;
public ViewModelNewPerson newPersonViewModel;
}
and I want to do the following things:
In the Controller I want to create a GET Action which uses the class ViewModelComposite as its Get model, and in the view I want the user choose from the following two available actions: to choose a existed person, and to add a new person as the selected value.
So I need to create two forms in the View, and there would be two POST Actions added to the Controller using the Post model of class ViewModelNewPerson and ViewModelSelectPerson.
My question is, how can I do the manual model binding using a Custom Model Binder that can convert the Composite class of ViewModelComposite to ViewModelNewPerson in the Action of create a new person, and to ViewModelSelectPerson in the Action of select an existing person?
EDIT:
Now I have an idea of decomposing the class ViewModelComposite and declare every property in the two classes into the composite class, and the default model binder will do the trick, I think. But that'll drop the composite pattern, and is not something I wanted.
You would use one single view model in your form, you would have a post action that receives your single view model.
In your Controller code:
public ActionResult GetSomeData(MyCustomViewModel model){
// add the first element
var person = Person.Add(model.Person);
// update the second object in model, with related / needed ID
model.PersonContent.PersonId = person.id;
// add in related content
var AddedContent = PersonContent.Add(model.PersonContent);
}
single form, multiple actions, multiple tables
The goal
Create two "different" methods with the same behavior.
The problem
When someone accesses my application, I want to display a list of items — and this list is the same provided by myapp.com/products/offers/. In other words, I don't want to repeat the same code between the two methods. So I ask: what do I have to do?
What I'm doing now
At HomeController, on Index method whose its type is ActionResult, there is the following code's fragment:
public ActionResult Index()
{
return RedirectToAction("Offers", "Products");
}
Meanwhile, at ProductsController, on Offers method:
public ActionResult Offers()
{
var products = Products.Build.OffersList();
var categories = Categories.Build.Main();
ProductsViewModel viewModel = ProductsViewModel
{
Products = products,
Categories = categories
};
return View(viewModel);
}
Now there are three things to consider:
My application is redirecting the client to another page, generating a second server request wasting bandwidth;
The URL of application is changed from myapp.com/ to myapp.com/Products/Offers/ and I really don't want this;
Will be redundant if I repeat the code — further more that there are things in the ProductsController that by logic should not exist on HomeController.
And, again: What do I have to do?
Move the common logic into a "Service" or "Helper" class:
class ProductListingHelper {
public ProductsViewModel GetProductsViewModel() {
var products = Products.Build.OffersList();
var categories = Categories.Build.Main();
return new ProductsViewModel() {
Products = products,
Categories = categories
};
}
}
Then, in both of your controllers, do this:
return View(new ProductListingHelper().GetProductsViewModel());
Note, that, as Erik points out in the comments, this will require you to create two Views. However, you can also reduce duplication here, by having your ProductListView as a Partial View that the other two views render.
Add a class (usually referred to as a 'Service' class) and move the code you want to reuse into a method in that class (perhaps call the method GetProductsViewModel()) then call that method from each of your controller actions.
Hope that helps.
I am using Prism for navigation in my WPF MVVM application. I register my view as follows.
// MyView is the data type of the view I want to register and "MyView"
// is the name by which I want the data type to be identified within
// the IoC container.
_container.RegisterType<object, MyView>("MyView");
I display this view as follows.
_regionManager.RequestNavigate(
"MyRegion", // This is the name of the Region where the view should be displayed.
"MyView" // This is the registered name of the view in the IoC container.
);
Elsewhere in the application, I need to remove this view in an event handler; however, the following code returns an ArgumentNullException.
_regionManager.Regions["MyRegion"].Remove(
_regionManager.Regions["MyRegion"].GetView("MyView")
);
This indicates that the RequestNavigate method does not add MyView to MyRegion using the name "MyView". I know that if I were to use the _regionManager.Add(MyView, "MyView") method, the GetView method would not return null. Unfortunately, RequestNavigate does not seem to handle the view name in the same way. Is there any way to remove a view (by name) from a region when the view was added using the RequestNavigate method?
It stems from how you add your view, not with your removal. Previously asked answered by adding the view fully, aka including the name.
_regionManager.Regions["MyRegion"].Add(myView, "MyView");
So now you can do your retrieval and removal:
var theView = _regionManager.Regions["MyRegion"].GetView("MyView");
_regionManager.Regions["MyRegion"].Remove(theView);
Without defining name during Regions.Add()
In your View, define a property that is accessible (public if multi-project, internal if all in one project). Use this property in everything, one example would be a public string ViewTitle { get { return "XYZ"; } }. Then retrieve from the Views the item that has the desired ViewTitle. The Views collection is the collection of views in that region, so you can use dynamic in .NET 4.0+ to ignore the type and get the property/function you specify, assuming it is there. Another option is to make your imported ViewModel in the View have a getter rather than just setting the DataContext, then you'd check the property "is" to the ViewModel you're looking for. Removes the string search but exposes the view's datacontext. So probably make an enum like I would do with the region.
I included everything in my View's .cs file so you can see how it works without complicating it or really breaking MVVM.
[ViewSortHint("050")]
[ViewExport(RegionName = RegionNames.WorkspaceTabRegion)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AView : UserControl
{
public AView()
{
InitializeComponent();
}
[Import]
[SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "MEF requires property; never retrieved")]
PrintingViewModel ViewModel { set { this.DataContext = value; } }
public string ViewTitle { get { return "AView"; } }
}
Now in the ViewModel at some point:
var viewToRemove = RegionManager.Regions[RegionNames.WorkspaceTabRegion].Views.FirstOrDefault<dynamic>(v => v.ViewTitle == "AView");
RegionManager.Regions[RegionNames.WorkspaceTabRegion].Remove(viewToRemove);
We recently found ourselves with the same problem; thanks #odysseus.section9 for pointing its root in your comment, it really helped.
We considered making all views implement an interface having a Name property but didn't feel quite right. Then we explored #bland solution but felt uncomfortable about using dynamic so we went for a very similar approach using reflection.
Since we are also already using the ViewExportAttribute to export our views and it contains the desired ViewName property, what we do is querying for each view in a region for its attributes, looking for the ViewExportAttribute and checking the value of the ViewName property. Although in our design all views are annotated with it, the query tolerates views that don't - it simply ignores them.
For convenience we created an extension method for IRegion which searches for the views with the desired name within a region. Also, we added two extension methods to IRegionManager for two common scenarios in our application: re-using an existing view or navigating and removing all existing views (matching a name) and navigating. I think the latter solves your need just by getting rid of the call to
public static IEnumerable<object> FindViews(this IRegion region, string viewName)
{
return from view in region.Views
from attr in Attribute.GetCustomAttributes(view.GetType())
where attr is ViewExportAttribute && ((ViewExportAttribute)attr).ViewName == viewName
select view;
}
public static void ActivateOrRequestNavigate(this IRegionManager regionManager, string regionName, string viewName, UriQuery navigationParams)
{
IRegion region = regionManager.Regions[regionName];
object view = region.FindViews(viewName).FirstOrDefault();
if (view != null)
region.Activate(view);
else
regionManager.RequestNavigate(regionName,
new System.Uri(navigationParams != null ? viewName + navigationParams.ToString() : viewName, UriKind.Relative));
}
public static void RemoveAndRequestNavigate(this IRegionManager regionManager, string regionName, string viewName, UriQuery navigationParams)
{
IRegion region = regionManager.Regions[regionName];
foreach (object view in region.FindViews(viewName))
region.Remove(view);
regionManager.RequestNavigate(regionName,
new System.Uri(navigationParams != null ? viewName + navigationParams.ToString() : viewName, UriKind.Relative));
}