ASP.NET MVC DefaultModelBinder with nested lists - asp.net

I have a View with a table representing an employee's timesheet. Days across the top, projects down the side, with each day/project intersection containing two values for regular hours and overtime.
The (simplified) class definitions for the page model are:
public class TimesheetFormModel {
public List<Project> Projects;
// other things...
}
public class Project {
public string Name;
public List<WorkUnit> WorkUnits;
}
public class WorkUnit {
public DateTime Date;
public decimal RegularHours;
public decimal OvertimeHours;
}
The form elements on the page are named as follows in an attempt to get the DefaultModelBinder to pick up on them.
model.Projects[0].Name // "New Project"
model.Projects[0].WorkUnits[0].Date // "5/23/2009 12:00:00 AM"
model.Projects[0].WorkUnits[0].RegularHours // 0
model.Projects[0].WorkUnits[0].OvertimeHours // 0
model.Projects[0].WorkUnits[1].Date // "5/24/2009 12:00:00 AM"
model.Projects[0].WorkUnits[1].RegularHours // 0
model.Projects[0].WorkUnits[1].OvertimeHours // 0
model.Projects[0].WorkUnits[2].Date // "5/25/2009 12:00:00 AM"
model.Projects[0].WorkUnits[2].RegularHours // 0
model.Projects[0].WorkUnits[2].OvertimeHours // 0
// etc.
When the view is submitted however, the model parameter isn't being completely populated. model.Projects contains projects, but the Project's WorkUnits field is empty. Does the DefaultModelBinder support nested collections like I'm trying to do? If not, what should I do?

I eventually figured out why DefaultModelBinder wasn't picking up on the properties of WorkUnit: Because they weren't properties, they were fields. DefaultModelBinder only works with properties. Changing the class definition of WorkUnit and Project to use fields made everything click:
public class Project {
public IList<WorkUnit> WorkUnits { get; set; }
public string Name { get; set; }
}
public class WorkUnit {
public DateTime Date { get; set; }
public decimal RegularHours { get; set; }
public decimal OvertimeHours { get; set; }
}
(Note: The source code in the original question had Project.Name defined as a field, in my actual code it was a property. This is why the Projects list was getting populated but WorkUnits wasn't.)

Related

How to specify default property values for owned entity types in Entity Framework Core 2.0?

I have a simple POCO type, say something like
public class OwnedEntity {
public string stringProperty { get; set; }
public decimal decimalProperty { get; set; }
public bool boolProperty { get; set; }
public int intProperty { get; set; }
}
and an actual entity with an OwnedEntity reference
public class SomeEntity {
public string Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I set up the relationship like described in the documentation using EF Core's Fluent API:
protected override void OnModelCreating (ModelBuilder builder) {
base.OnModelCreating (builder);
builder.Entity<SomeEntity> ().OwnsOne (e => e.OwnedEntity);
}
I can't find anything on how to define default-values for all the properties of OwnedEntity. I tried to initialize the properties like this:
public class OwnedEntity {
public string stringProperty { get; set; } = "initial"
public decimal decimalProperty { get; set; } = -1M;
public bool boolProperty { get; set; } = false;
public int intProperty { get; set; } = -1;
}
but with no effect. Same goes with the [DefaultValueAttribute] (but that was to expect since it's explicitly mentioned).
There's a bit of information on how to handle initial values for regular entities:
modelBuilder.Entity<SomeOtherEntity>()
.Property(e => e.SomeIntProperty)
.HasDefaultValue(3);
But since I'm facing an Owned Entity Type, I can't access the type via Entity<T>.
Is there a way of doing what I'm looking for?
Some things worth mentioning:
I have a solid amount of specific entities where most of them are using the OwnsOne relation
Declaring all OwnedEntity-properties in a base class is not an option since not all the entities have those properties
I`m using EF Core 2.0.3 and ASP.NET Core MVC 2.0.4
Edit:
Originally, I wanted to have newly created SomeEntity instances to come with preset properties for all of the 'embedded' SomeEntity.OwnedEntity properties.
But looking at how my associated controller works, it all makes sense... I have the following methods for the 'Create' operation:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create (SomeEntity model) {
context.Add (model);
await context.SaveChangesAsync ();
// redirect etc.
}
Which means that no object is created for the [HttGet] overload of Create and all the HTML inputs linked to properties (via asp-for) are initially empty. Okay. So I guess the proper way of doing this is to manually create a new instance of SomeEntity and pass it to the Create view like this:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create), new SomeEntity());
}
Is this the right approach then or are there some more things to keep in mind?
Assuming you understand what EF Core Default Values are for, and just looking for equivalent of Entity<T>().Property(...) equivalent.
The owned entities are always configured for each owner type by using the ReferenceOwnershipBuilder<TEntity,TRelatedEntity> class methods. To access this class you either use the result of OwnsOne method, or use the OwnsOne overload taking second argument of type Action<ReferenceOwnershipBuilder<TEntity,TRelatedEntity>>.
For instance, using the second approach:
builder.Entity<SomeEntity>().OwnsOne(e => e.OwnedEntity, ob =>
{
ob.Property(e => e.stringProperty)
.HasDefaultValue("initial");
ob.Property(e => e.decimalProperty)
.HasDefaultValue(-1M);
// etc.
});

Using OData how can I sort a property that holds a list?

Here is the problem I need to solve:
I need to display a grid that contains a group of columns that are dynamic, meaning that the number can change depending on the user parameters.
I have attached a sample below as an image to illustrate:
GRID SAME IMAGE
I have these c# POCOs to keep my question simple
public class OrderItem
{
public string ProductName { get; set; }
public string Status { get; set; }
public List<CityOrderInfo> CityOrders { get; set; }
}
public class CityOrderInfo
{
public int OrderCount { get; set; }
}
I have a web api controller that is able to accept the OData request, plus other arguments that the repository accepts. However the problem is that while the parameter $orderby for ProductName and Status works, when I do "$orderby='CityOrders[1]\OrderCount asc' it fails.
public class OrdersControllers : ApiController
{
private readonly IOrdersRepository _repository;
public OrdersControllers(IOrdersRepository repository)
{
this._repository = repository;
}
public IEnumerable<OrderItem> GetOrderItems([FromUri] ODataQueryOptions<OrderItem> oDataQuery)
{
var result = this._repository.GetOrders().ToList();
var queryableData = oDataQuery.ApplyTo(result.AsQueryable());
var transformedData = queryableData as IEnumerable<OrderItem>;
return transformedData;
}
}
The reason I opted to hold the city orders in list is because I thought it would too painful to make a POCO with every city in the USA as a property so instead made it more generic.
The question is how can a sort on a property that holds a list using OData? Is this possible? I keep getting syntax error at position n. As of now I have not found an answer.

Using (showing) data from another model from my current view

No code to show. I just want to understand something. I already do some MVC code (I have a model, I ask Visual Studio to create Controller and View).
Each view has only "ONE MODEL" associated. So, with Razor, I can show data from this model. I play with my code and I understand it up to now.
BUT ...
On the same view, HOW we can work with another MODEL ?
For me, a model is simply a class with properties, etc. My database has an equivalent "data table" for each model. I can manipulate it with Entity Framework ... no problem. But, I need to use DATA from different model (different table) in the SAME VIEW and Visual Studio does not give me permission to use another MODEL in the view.
What is the strategy ? (or maybe I don't understand something ...)
Thank you.
The strategy is to build a view model, a model built to be displayed, and represents the data that you need to use.
Example :
You have these classes, classes who are a representation of your database :
public class FootballTeam{
public string Name{get;set;}
public string Logo{get;set;}
}
public class FootballGame{
public Datetime Date {get;set;}
public string Competition {get;set;}
}
public class Referee{
public string Name{get;set;}
public int Experience {get;set;}
}
To display information about a match game, you can create a view model for this, class who can references some classes of your business model if necessary :
public class GameViewModel{
[DisplayName("Home team")]
public FootballTeam HomeTeam{get;set;}
[DisplayName("Referee")]
public Referee Referee{get;set;}
[DisplayName("Visitor team")]
public FootballTeam VisitorTeam {get;set;}
[DisplayName("Comments")]
public List<string> RedactionComments{get;set;}
}
And create a view who will consume this GameViewModel.
In general, when you create a new MVC project, your have a folder named "ViewModels" in your presentation layer who contains some classes like this one.
This method allows to separate your business model to your presentation model, which are 2 completely different things.
There are very good answers here : What is ViewModel in MVC?
You can update your model type of your razor view to any type you want. It will work as long as you are passing that type from your action method.
Simply open up the razor view and change the line where it says what type the model is.
#model Customer
Now you need to make sure that you are passing a Customer object from your action
public ActionResult Create()
{
return View( new Customer());
}
Also when you create a view, You do not need to necessarily select the Model type in the Dialog box. You can keep that empty and add it to the razor view as needed ( as shown above)
If you want to bring data from 2 different table, Create a new view model which has properties needed for the view and use that as your view's model type.
You should use ViewModal to Create a ViewModal that will be combination of two modals properties as per our need
ViewModel contains the fields which are represented in the strongly-typed view. It is used to pass data from controller to strongly-typed view with Own Defined Modals
Understand how to use View Modal in MVC From Link Below -
Understand View Modal In MVC
CODE THAT DEMONSTRATE HOW TO USE VIEWMODALS IN MVC
Product.cs
public class Product
{
public Product() { Id = Guid.NewGuid(); }
public Guid Id { get; set; }
public string ProductName { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
}
ProductCategory.cs
public class ProductCategory
{
public int Id { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
ProductViewModel.cs
public class ProductViewModel
{
public Guid Id { get; set; }
[Required(ErrorMessage = "required")]
public string ProductName { get; set; }
public int SelectedValue { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
[DisplayName("Product Category")]
public virtual ICollection<ProductCategory> ProductCategories { get; set; }
}

Validating that a Reason field is filled if Date field is today

I use ASP.NET MVC4 in my solution. I have the ViewModel below where I would like to validate that the field EmergencyReason is filled only if the field Date is today. I try this:
public class LoadingViewModel
{
public DateTime Date { get; set; }
[RequiredIf("Date", Comparison.IsEqualTo, DateTime.Today)]
public string EmergencyReason { get; set; }
...
}
It doesn't work. The third argument of RequiredIf must be a constant expression, ...
Any idea how can I force the user to enter an EmergencyReason only if Date field is today?
Thanks.
You seem to be using some non-standard RequiredIf attribute which is not part of the standard ASP.NET MVC 4 package.
As you know C# allows you to only pass constant values to attributes. So one possibility is to write a custom attribute:
public class RequiredIfEqualToTodayAttribute: RequiredIfAttribute
{
public RequiredIfEqualToTodayAttribute(string field)
: base(field, Comparison.IsEqualTo, DateTime.Today)
{
}
}
and then:
public class LoadingViewModel
{
public DateTime Date { get; set; }
[RequiredIfEqualToToday("Date")]
public string EmergencyReason { get; set; }
...
}
C# doesn't support DateTime literals, a workaround for this is to use a String like this, but it won't resolve your problem. I suggest you move the validation code inside the Controller and return a ModelState.AddModelError("EmergencyReason", "Emergency Reason is required")

Populate dropdownlist from another dropdownlist with objects in MVC 2

I am trying to develop a simple MVC 2 timesheet application for my small business.
I have a sort of mock model for now until I have a database in place, just to make things simpler while I develop the functionality. It consists of the following:
public class CustomersRepository
{
public CustomersRepository()
{
Customers = new List<Customer>();
}
public List<Customer> Customers { get; set; }
}
public class Task
{
public Task()
{
Customer = new Customer();
TimeSegments = new List<TimeSegment>();
}
public override string ToString()
{
return Name;
}
public string Name { get; set; }
public Customer Customer { get; set; }
public List<TimeSegment> TimeSegments { get; set; }
}
public class TimeSegment
{
public string Id { get; set; }
public string Date { get; set; }
public int Hours { get; set; }
}
public class Customer
{
//To show the name in the combobox instead of the object name.
public override string ToString()
{
return Name;
}
public Customer()
{
Tasks = new List<Task>();
}
public List<Task> Tasks { get; set; }
public string Name { get; set; }
}
I initialize the repository in the controller, and pass the "model" to the view:
CustomersRepository model = new CustomersRepository();
public ActionResult Index()
{
InitializeRepository();
return View(model);
}
Now, in the view I populate a dropdownlist with the customers:
<div>
<%:Html.DropDownListFor(m => m.Customers, new SelectList(Model.Customers), new {#id="customerDropDownList"}) %>
</div>
But then I need to populate a second dropdownlist (taskDropDownList for the tasks associated with a particular customer) based on the selection the user chooses in the customer dropdownlist.
But how do I do this exactly? I have seen examples with jQuery, but I'm not sure how to apply them to this situation. Also, the examples seem to just populate the lists with string values. I need to be able to access the objects with all their properties. Because the next thing I need to do is to be able to populate the TimeSegments list of the selected task with values from input fields (i.e. the hours worked for particular dates). And for that to be saved to the "model" (eventually to the database) in the controller, how do I get it there, unless the objects are all part of the same model bound to the View?
I'm on rather thin ice with this since I still find the connection between the View and the Controller hard to handle, compared with e.g. Windows development, where these things are rather easy to do. So I would really appreciate a good step by step example if anyone would be so kind as to provide that!
I found the answer here:
http://www.pieterg.com/post/2010/04/12/Cascading-DropDownList-with-ASPNET-MVC-and-JQuery.aspx
It needed some tweaks, and I got help here from CGK. See this post:
Cascading dropdownlist with mvc and jQuery not working

Resources