I am creating this MVC model and trying to understand best way to do it.How should I get related data when I have relationships between tables.For example in code below I have created a formviewmodel and I am calling function to get all images in Images table for a user. Should I instead keep images property of Users model and fill that within LINQ query in repository instead of calling a method from the Model? Is there a way in LINQ to autofill all images for the contact?
public class UserFormViewModel {
UucsrRepository repository = new UucsrRepository();
public User User { get; set; }
public SelectList States { get; private set; }
public SelectList Genders { get; private set; }
public List<Images> UserImages { get; set; }
public UserFormViewModel(User User) {
User = User;
UserImages = repository.GetUserImages(User.Id).ToList();
States = new SelectList(repository.GetAllStates(), "ShortName", "Name");
Genders = new SelectList(repository.GetAllGenders(), "Gender", "Description");
}
}
Thanks !
First it typically does not make sense to store the actual repository object in the viewmodel. That violates the tenent that you should only send the view exactly what it needs and nothing more. They way i typically create the viewmodels is to ahve a very basic wrapper with almost no code in it. Most of the logic to fill the VM is done in the controller using the repository pattern.
Your example is clear enough to understand how you should or should not be using view models.
Kevin correctly noted, that the ViewModel should only contain the data you use in the view for presentational purposes. In your case, depending on what the view does, you might only send a subset of your User properties. For example, if you just show some user information, but not modify it, you don't need to send the user ID. I would go with smth like:
public class UserFormViewModel
{
public string UserName { get; set; }
public string Email { get; set; }
...
public SelectList States { get; private set; }
public SelectList Genders { get; private set; }
public List<Images> UserImages { get; set; }
}
To populate (prepare) the ViewModel it's rather convenient to use a separate helper method:
private UserFormViewModel PopulateUserFormViewModel(int userId)
{
var model = new UserFormViewModel();
var user = repository.GetUser(userId);
model.UserName = user.Name;
model.Email = user.Email;
...
model.UserImages = repository.GetUserImages(userId).ToList();
...
return model;
}
And in your action you call:
public ActionResult ShowUser(int userId)
{
// maybe do something else
var userFormViewModel = PopulateUserFormViewModel(userId);
return View(userFormViewModel);
}
That's just one of the possible ways, but the general principles are pretty clear: keep your ViewModel simple (just the data you need in the view) and preferably create a reusable method for converting your domain object into your ViewModel.
Related
I am trying to perform a query using Breeze that will return a filtered selection of child entities. I have two custom dtos defined as follows:
#region Dto Models
public class ProductDto {
public int ProductDtoId { get; set; }
public int ProductClassId { get; set; }
public ICollection<ProductRequiredInputDto> RequiredInputs { get; set; }
}
public class ProductRequiredInputDto
{
public int ProductRequiredInputDtoId { get; set; }
public string Product { get; set; }
public string Capacity { get; set; }
public string Electrical { get; set; }
//Navigation properties
public virtual ProductDto ProductDto { get; set; }
}
#endregion
My first query is to simply return a populated ProductDto model.
var query1a = this.entityQuery.from('ProductModel')
return this.entityManager.executeQuery(query1a) // returns a promise
.then(data => { this.product = data.results}
When I make a call to my web api controller everything works as expected as I receive a singular ProductDto model populated with a collection of ProductRequiredInputDto models. Here is a sample:
0: ProductDto__IPE_Data_DtoModels
ProductClassId: 1
ProductDtoId: 1
RequiredInputs: Array[40]
_backingStore: Object
ProductClassId: 1
ProductDtoId: 1
RequiredInputs: Array[40]
Now, what I am trying to achieve is to perform a second query on the ProductDto model that will return a filtered array of ProductRequiredDto models from the RequiredInputs property. I have looked over the Breeze examples and samples but have not been able to find a solution to this particular question.
Short answer: No I don't think you can filter on ICollection Navigation Properties from the EntityQuery.
Longer answer: You can write a custom method on the controller that uses .Include("RequiredInputs") and you can use LINQ to perform the filtering you want on the controller.
Aside: I find it peculiar that you don't have a ProductDtoID property on the ProductRequiredInputDto object.
Is it absolutely necessary to call the function that retrieves ProductDto? Because it doesn't sound logical to me. I would create a controller function:
[HttpGet]
public IQueryable<ProductRequiredInputDto> ProductRequiredInputDtos()
{
return _contextProvider.ProductRequiredInputDto;
}
And use a client side query in the lines of:
var idPredicate = breeze.Predicate.create('id', '==', yourSelectedProductDtoId);
var yourPredicate = breeze.Predicate.create('yourProductRequiredInputDtosProperty, 'yourOperator, 'yourValue');
var query = entityQuery.from('ProductRequiredInputDtos').where(idPredicate).and(yourPredicate);
Jonathan's method would also work, but then you have a specialized controller function for one type of call and those pile up quickly (unless you make them general by receiving params but that's another story). This way you can do any query on this model from your client without cluttering the controller up.
I'm using MVC (for the first time) with Entity framework, Database first
What I want to do is display data from a database in a single view. I created the database first, then I made a ADO.NET Entity Data Model based from the database that contains all the tables. I then created a Index view that was strongly typed with my Entity Data Model as model.
In my Index I have at the top
#model IEnumerable<Forum6.Models.Forum>
This allows me to get the rows from the table "Forum" from my database. If I try to add an extra model I get I get this error message when I run:
Line 1: #model IEnumerable<Forum6.Models.Forum>
Line 2: #model2 IEnumerable<Forum6.Models.Post>
Parser Error Message: Only one 'model' statement is allowed in a file.
After searching for an answer I found this: Two models in one view in ASP MVC 3
The answer was to create a ViewModel (ParentModel) that contained all the Models (Tables).
This is the ViewModel I created:
public class ViewModel
{
public IEnumerable<Forum6.Models.Forum> Forum { get; set; }
public IEnumerable<Forum6.Models.Post> Post { get; set; }
public IEnumerable<Forum6.Models.Topics> Topics { get; set; }
public IEnumerable<Forum6.Models.Users> Users { get; set; }
public IEnumerable<Forum6.Models.PrivMsg> PrivMsg { get; set; }
public IEnumerable<Forum6.Models.Permission> Permission { get; set; }
}
I edited my controller to look like this:
public class HomeController : Controller
{
// ForumDBEntities old_db = new ForumDBEntities();
ViewModel db = new ViewModel();
public ActionResult Index()
{
return View(db);
}
}
Then replaced the old Index view with a new strongly typed view that used the ViewModel as model. Which contains:
#model IEnumerable<Forum6.Models.ViewModel>
Trying to run this gives me this error:
The model item passed into the dictionary is of type 'Forum6.Models.ViewModel', but this dictionary requires a model item
of type
'System.Collections.Generic.IEnumerable`1[Forum6.Models.ViewModel]
How do I make the "ViewModel" enumarable? Or is my error elsewhere?
You'll need to change #model IEnumerable<Forum6.Models.ViewModel> to #model Forum6.Models.ViewModel as you're wrapping your IEnumerables inside a single ViewModel.
A good rule of thumb is to have a 1:1 relationship between your ViewModel and View.
This might be a good read for you: http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/ (just ignore the automapper part if you don't want to go that route)
You'll also need to put in actual data in your ViewModel since
ViewModel db = new ViewModel();
public ActionResult Index()
{
return View(db);
}
will just give your view an empty ViewModel.
One way to do it would be.
public ActionResult Index()
{
var model = new ViewModel
{
Forum = db.GetForum(),
Post = db.GetPost(),
Topic = you get the idea
};
return View(model);
}
One last thing when naming properties or variables in general you should use the plural verb when it contains a list. So your ViewModel would be.
public class ViewModel
{
public IEnumerable<Forum6.Models.Forum> Forums { get; set; }
public IEnumerable<Forum6.Models.Post> Posts { get; set; }
public IEnumerable<Forum6.Models.Topics> Topics { get; set; }
public IEnumerable<Forum6.Models.Users> Users { get; set; }
public IEnumerable<Forum6.Models.PrivMsg> PrivMsgs { get; set; }
public IEnumerable<Forum6.Models.Permission> Permissions { get; set; }
}
Change #model IEnumerable<Forum6.Models.ViewModel> to #model Forum6.Models.ViewModel as you are passing a single instance of a ViewModel class and not a collection of them.
All your collections are passed in a single instance of a view model.
I have a layered application that send commands to the business layer (actually, the application is based on ncqrs framework, but I don't think it's important here).
A command looks like this :
public class RegisterUserCommand : CommandBase
{
public string UserName { get; set; }
public string Email{ get; set; }
public DateTime RegistrationDate { get; set; }
public string ApiKey {get; set;} // edit
}
There is no logic in this class, only data.
I want to have the users type their user name, email and I want the system to use the current date to build the command.
What is best between :
create a strongly typed view based on the RegisterUserCommand, then inject the date and the APi Key just before sending it to the business layer ?
create a RegisterUserViewModel class, create the view with this class and create the command object based on the view input ?
I wrote the following code (for the solution n°2) :
public class RegisterController : Controller
{
//
// GET: /Register/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(RegisterUserViewModel registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
service.Execute(
new RegisterUserCommand
{
RegistrationDate = DateTime.UtcNow,
Email= registrationData.Email,
UserName= registrationData.Name,
ApiKey = "KeyFromConfigSpecificToCaller" // edit
}
);
return View();
}
public class RegisterUserViewModel
{
[Required]
[StringLength(16)]
public string Name { get; set; }
[Required]
[StringLength(64)]
public string Email{ get; set; }
}
}
This code is working... but I wonder if I choose the correct way...
thanks for advises
[Edit] As the Datetime seems to cause misunderstanding, I added another property "ApiKey", that should also be set server side, from the web layer (not from the command layer)
[Edit 2] try the Erik suggestion and implement the 1st solution I imagined :
[HttpPost]
public ActionResult Index(RegisterUserCommand registrationCommand)
{
var service = NcqrsEnvironment.Get<ICommandService>();
registrationCommand.RegistrationDate = DateTime.UtcNow;
registrationCommand.ApiKey = "KeyFromConfigSpecificToCaller";
service.Execute(
registrationCommand
);
return View();
}
... Is it acceptable ?
I think you would be better off with option #2, where you would have a separate ViewModel and a Command. While it may seem redundant (to an extent), your commands are really messages from your web server to your command handler. Those messages may not be formatted the same as your ViewModel, nor should they be. And if you're using NCQRS as is, you would then have to map your commands to your AR methods and constructors.
While it may save you a little bit of time, I think you pigeon-hole yourself in to modeling your domain after your ViewModels, and that should not be the case. Your ViewModels should be a reflection of what your user experiences and sees; your domain should be a reflection of your business rules and knowledge, and are not always reflected in your view.
It may seem like a bit more work now, but do yourself a favor and keep your commands separate from your view models. You'll thank yourself later.
I hope this helps. Good luck!
I would recommend putting this into the constructor of the RegisterUserCommand class. That way the default behavior is always to set it to DateTime.UtcNow, and if you need to set it to something explicitly you can just add it to the object initializer. This will also help in scenarios where you're using this class in other parts of your project, and you forget to set the RegistrationDate explicitly.
public class RegisterUserCommand : CommandBase
{
public string UserName { get; set; }
public string Email{ get; set; }
public DateTime RegistrationDate { get; set; }
public RegisterUserCommand()
{
RegistrationDate = DateTime.UtcNow;
}
}
And the Controller
public class RegisterController : Controller
{
//
// GET: /Register/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(RegisterUserViewModel registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
service.Execute(
new RegisterUserCommand
{
Email= registrationData.Email,
OpenIdIdentifier = registrationData.OpenIdIdentifier
}
);
return View();
}
public class RegisterUserViewModel
{
[Required]
[StringLength(16)]
public string Name { get; set; }
[Required]
[StringLength(64)]
public string Email{ get; set; }
}
}
I would use number 1 and use the system.componentmodel.dataannotations.metadatatype for validation.
I created an example (answer) for another SO question Here.
This allows you to keep your model in another library, validate the fields and show the fields like you would internal/private classes with DataAnnotations. I'm not a big fan of creating a completely separate class for a view that has no additional value while having to ORM the data back to another class. (If you had additional values like dropdown list values, or default values then I think it would make sense).
Instead of
[HttpPost]
public ActionResult Index(RegisterUserViewModel registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
service.Execute(
new RegisterUserCommand
{
RegistrationDate = DateTime.UtcNow,
Email= registrationData.Email,
UserName= registrationData.Name,
ApiKey = "KeyFromConfigSpecificToCaller" // edit
}
);
return View();
}
You can have
[HttpPost]
public ActionResult Index(RegisterUserCommand registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
registrationData.ApiKey = "KeyFromConfigSpecificToCaller";
service.Execute(registrationData);
return View();
}
My simplified domain model looks something like this:
public abstract class Entity<IdK>
{
public virtual IdK Code { get; protected set; }
}
public class Contact : Entity
{
public virtual string Name { get; set; }
public virtual Company Company { get; set; }
}
public class Company : Entity
{
public virtual string Name { get; set; }
}
and I've defined a viewmodel:
public ContactViewModel()
{
public Guid Code { get; set; }
public int Version { get; set; }
public string Name { get; set; }
public string Company { get; set; }
public List<SelectListItem> Companies { get; set; }
}
to manage my contacts in a view.
Since I want the user to be able to choose from a list of companies I've added a list of SelectedListItem which will be rendered in my view like this:
<%=Html.ListBoxFor(m => m.Company, (List<System.Web.Mvc.SelectListItem>)Model.Companies)%>
Now, when the user submits my form I remap my viewmodel with my model before I save it.
I populate my Contact and use the id of the ContactViewModel.Company to create an object of type Company to associate with the property of the Contact class.
Since I don't want to fetch the whole company from the database I just fill the id.
When I persist my contact, though, I get an exception: "not-null property references a null or transient Domain.Contact.Company".
What is the best solution to manage lookups and persistence with MVC + Nhibernate?
Do you have any suggestions from your experience?
Unfortunately with NHibernate and lookups you can't just assign the ID property to a new instance of the Company object and then assign that Company object to the Contact.
Generally what I would do is in my repository, assuming that you can't change the Company information when saving a contact is something like this:
public Contact Save(Contact contact)
{
if(contact.Company.Id > 0)
contact.Company = Session.Load<Company>(contact.Company.Id);
Session.SaveOrUpdate(contact);
}
I generally find this allows you to encapsulate the logic of loading the Company and also allows you to keep it all wrapped up nicely in a single session.
Using Session.Load in this manner avoids hitting the database as described here
If you don't do this, what you're essentially saying to NHibernate is that you have a company object which you have assigned an ID and now want to save it with all the properties set to Null or empty string values or whatever and that is not what you want.
Alternatively you could create a Save specific Domain Object that looks like this:
public abstract class Entity<IdK>
{
public virtual IdK Code { get; protected set; }
}
public class SavableContact : Entity
{
public virtual string Name { get; set; }
public virtual IdK CompanyId { get; set; }
}
Which maps directly to the Contact table in your database so that when you Save this entity you can literally just map back the CompanyId from your view model and NHibernate will only save that value back and not care at all about the company objects.
It's a case of working out what works best for you. I personally prefer the first option as the extra bit of logic helps simplify the domain model, however if you're creating and exposing a public API then the second method might make more sense.
I wonder if there is a way to validate just one of my models in the viewmodel send it to my action? I use the DataAnnotations as validate rules.
Like the if (!ModelState.IsValid)
let me know if the question is unclear and I will edit for a better explination
EDIT
my viewmodel looks like this
public class CompaniesViewModel
{
public Core.Model.Customer Company { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public Core.Model.Meeting Meeting { get; set; }
}
What I want to do in this particular situation is to validate just Customer. I cant do the ModelState.IsValid then all get validated. So how can I do to just validate one of them like customer in this case. Hope this was more clear
There are a number of different ways you can do this. The first is to add a property called IsValid that checks the property. So something like:
public class Company
{
public bool IsValid
{
get { return GetValid() }
}
private bool IsValid()
{
if ( Some check here )
return false;
}
}
[HttpPost]
public ActionResult SomeAction(CompaniesViewModel model)
{
if (model.Company.IsValid)
{
}
}
However a better solution IMO would be just to post the Company to your controller rather than your entire view model. Just because your passing a view model to a view it doesn't mean that you need to post the entire view model back. When you create your HTML form specify only the properties you want to post back to your controller. So for example your controller would become:
[HttpPost]
public ActionResult SomeAction(Company company)
{
if (Model.IsValid)
{
}
}
Now when you check if Model.IsValid it just check company as that is all you've passed back to the controller.
At server side, you can try the ValidateModel(object) method, like in TryValidateModel(CompaniesViewModel.Company).
If you have enabled client sided validation, you need to post only the relevant entity. If you want to post all entities, but you need to validate only one, you can consider the following:
either removing the rules, using javascript ASP .NET MVC Disable Client Side Validation at Per-Field Level
or creating a Data-Transfer-Object, ie a View Model which has NO link to the Model, but reproduces the entities you want with the validation rules you want having applied in this scenario. Of course, then, you'll need in your controller or a model binder some way to bind from your ViewModel to your Model entities.
You can separate Customer Model to another class in your ViewModel and map that in Controller to a existing/new Customer:
public class CompaniesViewModel
{
public Company Company { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
public Core.Model.Meeting Meeting { get; set; }
}
//Validations for Company go here:
public class Company
{
public string CompanyId { get; set; }
[Required]
public string CompanyName { get; set; }
}