I have two models; one holding the values of the fields that are displayed on the page and the other holding the required fields for that page depending on a user variable.(think of it as a combo box that for on each variable, different fields are rendered for the page). My question is what is the most efficient and manageable way to use those two models in a single view/controller? I tried to tuple them but I get that I need to create a parameter-less constructor error. Thank you.
As tarzanbappa said, one of the best approaches would be for you to add the extra fields in each ViewModel.
In order for you to not go into each ViewModel and add all the properties you need, you can add a variable instance of your other ViewModel.
For example: (Your ViewModels)
public class MyViewModel()
{
public T PropertyOne { get; set; }
public T PropertyTwo { get; set; }
public MyOtherViewModel PropertyThree { get; set; }
}
public class MyOtherViewModel()
{
public T PropertyOneCheck { get; set; }
public T PropertyTwoCheck { get; set; }
}
Then in your DataModel call, set your variable.
For Example: (LINQ)
...
PropertyThree = (from r in context.Table
where conditionIsMet
select new MyOtherViewModel
{
PropertyOneCheck = r.PropertyOneCheck,
PropertyTwoCheck = r.PropertyTwoCheck
}).FirstOrDefault();
...
And in your View You can toggle the visibility of fields as follows:
#if(Model.PropertyThree.PropertyOneCheck)
{
//Show Field
}else
{
//Hide Field
}
The best approach would be to have a view model designed specifically for the UI which, by the sounds of things, would be a hybrid of your two current models.
You can use DataAnnotations to add metadata to each property and then Validate your model e.g.
public class ViewModel
{
[Required]
public string PropertyOne { get; set; }
[Required]
public string PropertyTwo { get; set; }
}
...
var model = new ViewModel();
var results = new List<ValidationResult>();
if (!Validator.TryValidateObject(model, new ValidationContext(model), results)) {
Console.WriteLine("Model is not valid:");
foreach (var r in results) {
Console.WriteLine(r.ErrorMessage);
}
} else {
Console.WriteLine("Model is valid");
}
Live Example
Related
I have created a class and I'm putting a list of same type as a property of that class.
Is it good or bad practice?
I am putting the same type of list because of I want to manage everything by only one object.
I don't want to create a single object and a list of object of the same type.
Any help is highly appreciated!
class AssetSection
{
public string Code { get; set; }
public string Description { get; set; }
public string SITEID { get; set; }
public string PlantID { get; set; }
public string User { get; set; }
public string UpDateTime { get; set; }
public List<AssetSection> AssetSections { get; set; }
public AssetSection(string des, string code)
{
Description = des;
Code = code;
}
}
That's ok. If you can imagine, you can design and use it.
Let's talk about entity framework. We create 2 entities like this:
public class User : IdentityUser
{
[Key]
public string Id { get; set; }
public UserProfile Profile { get; set; }
}
public class UserProfile
{
[Key]
public string UserId { get; set; }
public User User { get; set; }
}
Now, when we try to get current user:
User user = await _userManager.GetUserAsync(User);
user becomes an instance of User class now. This instance has a property name Profile, and this property has another property name User which has a type User.
It's called mapping. So, to answer your question: You can use it. But I'm not saying it's good or not based on the way to design the model.
As a general observation, such a structure is known as a rose tree, or just a tree. It enables you to write code like this:
var t = new AssetSection("foo", "bar")
{
AssetSections = new List<AssetSection>
{
new AssetSection("baz", "qux")
{
new AssetSection("corge", "garply"),
new AssetSection("fred", "plugh")
{
AssetSections = new List<AssetSection>
{
new AssetSection("xyzzy", "thud")
}
}
},
new AssetSection("quux", "quuz")
{
new AssetSection("grault", "waldo")
}
}
};
If what you want to model is a tree-like structure like that, then it's fine. On the other hand, if such a hierarchy is not what you're trying to model, then it's likely to be confusing.
By the way, the code as proposed violates the .NET framework design guidelines:
DO NOT provide settable collection properties.
DO NOT use ArrayList or List<T> in public APIs
I'm trying to create a common complex object for my models (action parameters) and reuse it in many places.
Here is some sample code:
[HttpGet("/api/values")]
public ActionResult<string> Get([FromQuery] MyModel model) {
var sb = new StringBuilder();
sb.AppendLine(model.Id);
sb.AppendLine($"{model.Id}-{model.Generated?.DateStart}-{model.Generated?.DateEnd}");
sb.AppendLine($"{model.Id}-{model.Reference?.DateStart}-{model.Reference?.DateEnd}");
return sb.ToString();
}
public class MyModel {
public string Id { get; set; }
public DateInfo Generated { get; set; } = new DateInfo();
public DateInfo Reference { get; set; } = new DateInfo();
}
public class DateInfo {
public DateTime? DateStart { get; set; }
public DateTime? DateEnd { get; set; }
public RelativeTime? RelativeTime { get; set; }
}
Imagine the DateInfo class would have validation and common properties to be used in many models.
Adding [FromQuery(Name = "Something")] to the nested properties does the trick for swagger, but it makes it impossible to have two nested properties with the same type.
UDPATE:
I understand that adding the fully qualified property name (.../values?Id=1&Generated.DateInfo=2&Reference.DateInfo=3) would make it work, but that would be a really ugly way to call any API. Hyphens are the way, not dots.
I would like to map the binding in the same way as mapping a regular property.
How to achieve that?
I see two options.
Option 1: Just create a new, flattened class {Id, Foo, Bar} to use as the parameter of your action method. You can then map that to MyModel. That's the approach I would recommend as most maintainable.
Option 2: Custom model binding, as follows:
[ModelBinder(BinderType = typeof(MyModelBinder))]
public class MyModel
{
public string Id { get; set; }
[FromQuery]
public Info ComplexNestedProperty { get; set; }
}
public class AuthorEntityBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var model = new MyModel
{
Id = bindingContext.ValueProvider.GetValue("id"),
ComplexNestedProperty = new Info
{
Foo = bindingContext.ValueProvider.GetValue("foo"),
Bar = bindingContext.ValueProvider.GetValue("bar")
}
};
bindingContext.Result = ModelBindingResult.Success(model);
return Task.CompletedTask;
}
}
As an expansion on Option 2 you could reasonably write some reflection that gets all the leaf property names of your nested model.
Okay, so i have my ViewModel and I understand what the controller does, I'm just having difficulty implementing it. I don't know how to code a controller for the ViewModel, i've tried researching it myself and can't find anything.
Here is my viewModel, how would I go about constructing the controller? Not asking you to do it for me, just how to do it
public class ViewOrderViewModel
{
//From ORDER Table
public int OrderId { get; set; }
public System.DateTime OrderDate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Email { get; set; }
public decimal Total { get; set; }
//from Products
public List<Product> Products { get; set; }
}
UPDATE
public class ViewOrderController : Controller
{
// GET: ViewOrder
public ActionResult ViewOrders()
{
var order = new Order();
var viewModel = GetViewModel(order);
return View(viewModel);
}
public ViewOrderViewModel GetViewModel(Order orderObject)
{
ViewOrderViewModel viewModel = new ViewOrderViewModel();
viewModel.OrderId = orderObject.OrderId;
viewModel.OrderDate = orderObject.OrderDate;
viewModel.FirstName = orderObject.FirstName;
viewModel.LastName = orderObject.LastName;
viewModel.City = orderObject.City;
viewModel.Address = orderObject.Address;
viewModel.Country = orderObject.Country;
viewModel.Email = orderObject.Email;
viewModel.PostalCode = orderObject.PostalCode;
viewModel.Total = orderObject.Total;
return viewModel;
}
}
Still unsure about how to map the List of products in the ViewModel class to the list of products in the db
Typically, your view is going to be "bound" to the ViewModel. It's like saying "Ok, I'm the view for an 'Order' and I only need to worry about the properties that you defined in ViewOrderViewModel".
The controller is not required for that binding to happen. The binding is declared at the top of your view:
Order.cshtml
#Model MyProject.Web.ViewModels.ViewOrderViewModel
<div>
<!-- Html for the view-->
</div>
This allows you to access properties on that model within the view. Razor has some functions that make life easy. For example, if you want to display the OrderId it might look like this:
<span>OrderId: #Model.OrderId </span>
The view doesn't care what values are set for each of those properties, it only cares that the properties exist. Where the controller comes into play is populating those properties with the values you want and then passing the ViewModel to the view:
public ActionResult Order()
{
var viewModel = new ViewOrderViewModel();
// Load data into each property
viewModel.OrderId = 123; // etc..
// Return it to the view. Asp.net knows to return
// it to the Order.cshtml view because the view
// and the controller action share the same name.
return View(viewModel);
}
Edit: In response to your questions in the comments: If you need to populate your ViewModel with values from a different model (such as a database model) you can create a mapper like so:
public ViewOrderViewModel GetViewModel(Order orderObject)
{
ViewOrderViewModel viewModel = new ViewOrderViewModel();
viewModel.OrderId = orderObject.OrderId;
viewModel.FirstName = orderObject.FirstName;
// etc...
return viewModel;
}
and then in your controller you would do something like this:
// var order = new Order()
var viewModel = GetViewModel(order);
I would like to fill 2 divs in View with data from 2 ViewModels, but I have a problem.
My 2 viewModels:
public class ChatLogsNameTimeViewModel
{
public UserProfile UserProfile { get; set; }
public string Message { get; set; }
public DateTime Time { get; set; }
}
public class UserProfile
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public bool IsOnline { get; set; }
public virtual ICollection<ChatLogs> ChatLogs { get; set; }
}
Which means I want to show data from ChatLogsNameTimeViewModel in one div in View and data from UserProfile in other div in View.
This is my ViewModel that uses both viewModels above:
public class ChatLogsUsersViewModel
{
public IEnumerable<ChatLogsNameTimeViewModel> ChatLogs { get; set; }
public IEnumerable<UserProfile> Users { get; set; }
}
And this is my Index() action in controller:
var chatLogs = db.getChatLog().ToList();
var users = dba.getOnlineUsers().ToList();
var view = new ChatLogsUsersViewModel(chatLogs, users);
return View(view);
My problem is that I can not access to ViewModel attributes at all.
When I create foreach loop in view all I can access is this:
Which means I cannot access attributes at all to print them in foreach.
I have this in View:
#model IEnumerable<Chat.Models.ChatLogsUsersViewModel>
I assume that I am not doing something right in my controller. I have methods getChatLog() and getOnlineUsers() implemented in Model, they work alone no problem. I just don't know how to make them work together in one view.
You need to update the type of you view.
You are not passing the view a list of Chat.Models.ChatLogsUsersViewModel, you only have one and this model has two lists.
So update it to:
#model Chat.Models.ChatLogsUsersViewModel
Your model is strongly typed towards the wrong model. As Queti put it, your model should be typed #model Chat.Models.ChatLogsUsersViewModel.
As is, your model is attempting to access a collection of these models. You should find that if you do:
#for each (var x in Model) {
x.
}
x is then a single ChatLogsUsersViewModel and should display its properties in your dev environment. But again, this is not how you want to strongly type your model here.
If I'm not mistaken, this is what you are trying to do, and you can still access your data using a for each loop:
#model Chat.Models.ChatLogsUsersViewModel
for each (var log in Model._chatLogs) {
#<div>#log.Message</div>
}
...
for each (var user in Model._users) {
#<div>#user.UserName</div>
}
It's difficult for me for developping an functionality without JavaScript..
I have a ViewModel :
public class AccountRegisterViewModel
{
#region Properties
public User User { get; set; }
public ExternalAccounts ExtAccounts { get; set; }
public LocalPassword Password { get; set; }
public Company CompanyARegister { get; set; }
public Company CompanyBRegister { get; set; }
public bool SameCompanies { get; set; }
public int NbCompanies { get; set; }
...
}
In view, i have a link with checkbox for copying the first company with the second
But I don't know how pass this viewModel (View to Controller) for keeping my data and return the same View with the copie of company..
i try this, in view :
#Html.ActionLink("Click", "CopyCompanies","Account", new { model = Model })
#Html.CheckBoxFor(model => model.SameCompanies)
In Controller :
[AllowAnonymous]
public ActionResult CopyCompanies(AccountRegisterViewModel model)
{
...
if (model.SameCompanies)
{
// copie
}else //clear
...
return View("Step2Register", model);
}
Any idea ?? Thank you for your help
If the CopyCompanies action method requires the AccountRegisterViewModel object, then you will need to provide it. Unfortunately, you will not be able to provide the value using the approach you are following when creating the link.
Your two options would be to have a hidden field for each property in AccountRegisterViewModel and then let the model binding create the object, but even this would not be ideal, since the viewModel is composed of complex objects, so you would have way too many hidden fields.
Your second option, which I think is a better approach, would be to pass in some kind of Id that corresponds to the AccountRegisterViewModel that CopyCompany can use to look up the values it would need.