views:

188

answers:

3

Hi

I recently started evaluating ASP.NET MVC. While it is really easy and quick to create Controllers and Views for Models with only primitive properties (like shown in the starter videos from the official page) I didn't find any good way to work with references to complex types. Let's say, I have these Models:

public class Customer {  
    public int Id { get; set; }  
    public string Name { get; set; }  
    public Address Address { get; set; }  
    public IList<Order> Orders { get; set; }  
}

public class Address {  
    public int Id { get; set; }  
    public string .....  
    .....  
}

public class Order {  
    public int Id { get; set; }  
    public Customer Customer { get; set; }  
    public string OrderName { get; set; }  
    .....  
}

Note that I don't have foreign keys in the models (like it's typical for LINQ to SQL, which is also used in the sample video) but an object reference.

How can I handle such references in asp.net mvc? Does someone has some good tips or links to tutorials about this problem? maybe including autobinding with complex types.

+3  A: 

You can use Data Transfer Objects for this issue or you can use expand methods on entity - include method in EF -

Nothing is different with primitive properties. If thats not what you mean, correct me then I'll help you again

Thanks

DTO:

public class OrderDTO
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public string CustomerName { get; set; }
}

NHibernate repository:

public IList<OrderDTO> GetOrders()
{
    return Session.Linq<Order>()
                    .Select(o => new OrderDTO {
                                        Id = o.Id
                                        CustomerId = o.Customer.Id
                                        CustomerName = o.Customer.Name
                                        ...
                    }).ToList();
}

View:

With Expand - Include - Model type is 'Order':

<%= Model.Customer.Name %>

With DTO - Model type is 'OrderDTO':

<%= Model.CustomerName %>

edit:

Okay, first you may want to use FormViewModel for create/edit actions. like this;

Controller:

public ActionResult Edit(int id)
{
    Order order = null; // OrderService.Get(id);
    IList<Customer> customers = null; // CustomerService.GetAll();

    OrderFormViewModel model = OrderFormViewModel.Create(order);
    model.Customers = customers.Select(c => new SelectListItem {
        Value = c.Id,
        Text = c.Name
    });

    return View(model);
}

[HttpPost]
public ActionResult Edit(int customerId, Order order)
{
    //customerId - selected from dropdown.
}

public class OrderFormViewModel
{
    public static OrderFormViewModel Create(Order order)
    {
        return new OrderFormViewModel {
            Order = order
        };
    }

    public Order Order { get; internal set; }
    public IEnumerable<SelectListItem> Customers { get; internal set; }
    public int CustomerId { get; internal set; }
}

View:

<%= Html.DropDownListFor(o => o.CustomerId, Model.Customers) %>
cem
thanks for your answer. But working without DTOs how can I set this reference (e.g. in a create form) <%= Model.Customer = ...... %>? There is a SelectList but this only supports string values
Fabiano
@Fabiano: Not at all and i just edited now. Use `IEnumerable<SelectListItem>` for dropdown etc. - or if this is not what you want, tell me i'll edit.
cem
+1  A: 

I would combine everything into my view model:

CustomerViewModel.Customer
CustomerViewModel.Address
CustomerViewModel.Orders // maybe an IEnumerable<Order>
CustomerViewMode.CurrentOrder

You should be able to bind your complex objects without too much trouble using some of the input helpers:

//.. begin form
<%=Html.HiddenFor(m=>m.Customer.Id)%>
<%=Html.TextboxFor(m=>m.Customer.FirstName)%>
<%=Html.TextBoxFor(m=>m.Address.City)%>
<%=Html.TextBoxFor(m=>m.ActiveOrder.OrderName%>
//.. submit ..
//.. end form ..

The should bind back ok if your action method looks like:

[HttpPost]
public ActionResult UpdateComplexObject(string id, CustomerViewModel customerViewModel) {
// if (!id.Equals(customerViewModel.Customer.Id) throw something
// just one of my own conventions to ensure that I am working on the correct active
// entity - string id is bound from the route rules.
ValidateModel(customerViewModel);
service.UpdateCustomer(customerViewModel.Customer);
serviceOrder.UpdateOrder(customerViewModel.ActiveOrder);
serviceAddress.UpdateAddress(customerViewModel.Address);
return RedirectToAction("DisplayComplexObject"); // or whatever
}

Hal

Hal
In addition, if you need to do some heavy customization on your objects, you should be able to implement your own model binder and handle it there.
Hal
+1  A: 

I prefer to keep my views as 'dumb' as possible, so I like to create custom view models for binding.

In your controller you can map your rich domain objects to the view model's flatter model. For example:

public class ChooseCustomerModel {

    public Order Order { get; set; }

    // Project Customer objects into this container, which is easy to bind to
    public IDictionary<int, string> PotentialCustomers { get; set; }
}

If you're looking for good material on this subject, I recommend ASP.NET MVC View Model Patterns.

Jeff Sternal
Thanks for the link. It's a helpful article.
Fabiano