The concrete classes (BankAccount
and CreditCard
) are not visible on controller.
I'm stuck with this issue.
I'm using the example from this site:
The view
The CreateUser
:
If the CreditCard
was selected it should be associated to the User
class.
The diagram
The code
UserController:
[HttpPost]
public ActionResult Create(User user)//The Watch above came from this user instance
{
if (ModelState.IsValid)
{
context.User.Add(user);
context.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PossibleBillingDetail = context.BillingDetail;
return View(user);
}
User\_CreateOrEdit.cshtml
:
User\Create.cshtml
:
@model TPTMVC.Models.User
@using TPTMVC.Models;
<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.5.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
$('.divbank').hide();
$('input[type=radio]').live('change', function () { updateweather(); });
});
function updateweather() {
//alert();
if ($('input[type=radio]:checked').val() == 'Bank') {
$('.divcard').fadeOut(1000);
$('.divcard').hide();
$('.divbank').fadeIn(1000);
}
else {
$('.divbank').fadeOut(1000);
$('.divbank').hide();
$('.divcard').fadeIn(1000);
}
}
</script>
<div id="json"></div>
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.ValidationSummary(true)
<fieldset>
<legend>User</legend>
@Html.Partial("_CreateOrEdit", Model)
<div ='none' class="divcard">
<div class="editor-label">
@Html.LabelFor(model => ((CreditCard)model.billingDetail).ExpiryMonth)
</div>
<div class="editor-field">
@Html.EditorFor(model => ((CreditCard)model.billingDetail).ExpiryMonth)
@Html.ValidationMessageFor(model => ((CreditCard)model.billingDetail).ExpiryMonth)
</div>
<div class="editor-label">
@Html.LabelFor(model => ((CreditCard)model.billingDetail).ExpiryYear)
</div>
<div class="editor-field">
@Html.EditorFor(model => ((CreditCard)model.billingDetail).ExpiryYear)
@Html.ValidationMessageFor(model => ((CreditCard)model.billingDetail).ExpiryYear)
</div>
</div>
<div='none' class="divbank">
<div class="editor-label">
@Html.LabelFor(model => ((BankAccount)model.billingDetail).BankName)
</div>
<div class="editor-field">
@Html.EditorFor(model => ((BankAccount)model.billingDetail).BankName)
@Html.ValidationMessageFor(model => ((BankAccount)model.billingDetail).BankName)
</div>
<div class="editor-label">
@Html.LabelFor(model => ((BankAccount)model.billingDetail).Swift)
</div>
<div class="editor-field">
@Html.EditorFor(model => ((BankAccount)model.billingDetail).Swift)
@Html.ValidationMessageFor(model => ((BankAccount)model.billingDetail).Swift)
</div>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Classes code:
namespace TPTMVC.Models{
public class BillingDetail
{
[Key]
[ForeignKey("user")]
public int UserID { get; set; }
public string Owner { get; set; }
public string Number { get; set; }
public virtual User user { get; set; }
}}
namespace TPTMVC.Models{
public class User
{
public int UserId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual BillingDetail billingDetail { get; set; }
}}
namespace TPTMVC.Models{
[Table("BankAccounts")]
public class BankAccount:BillingDetail
{
public string BankName { get; set; }
public string Swift { get; set; }
}}
namespace TPTMVC.Models{
[Table("CreditCards")]
public class CreditCard:BillingDetail
{
public int CardType { get; set; }
public string ExpiryMonth { get; set; }
public string ExpiryYear { get; set; }
}}
The problem
When I click the create button, I get this result:
I selected a CreditCard
but the result was BillingDetail
. I tried to make a casting but I got a error, as you can see.
:(
Why only BillingDetail
appear on UserController
?
My first solution
[HttpPost]
public ActionResult Create(User user, CreditCard card, BankAccount bank, String Radio)
{
//String teste=formCollection["Radio"];
if (ModelState.IsValid)
{
switch (Radio)
{
case "CredCard":
user.billingDetail = card;
break;
case "Bank":
user.billingDetail = bank;
break;
}
context.User.Add(user);
context.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PossibleBillingDetail = context.BillingDetail;
return View(user);
}
Although I agree with Matt, it's usually a good idea to work with view models, the direct cause of your issue is in the line
This also includes
BankAccount
s, so someBillingDetail
objects can't be cast toCreditCard
.Replace the line by
I think there are a few things wrong here:
BillingDetail
, a c# class, into a model for your view and then return it with form submission. As far as I know you can only return plain data and form fields in a form submit. You can have your view model contain other view models and concrete classes, but you can only return plain fields and view models with plain fields in them.Solution wise you should do this:
CreditCard
andBankAccount
, each with their respective properties. (You should do the same for yourUser
object so you adhere to SoC)BillingDetail
.You are passing a
User
object to your View. This has a navigation property toBillingDetail
which can be aCreditCard
or aBankAccount
. You cast it like this in the View(CreditCard)model
and(BankAccount)model
. It will work when your creating because you are casting an instance that is null, but it will cause a run-time error when you have a non-null instance because one of the casts will fail.To fix that you can use
model as CreditCard
andmodel as BankAccount
then check they are not null before you render the appropriate editors. But you'll need to work out what to do when your user wants to change the payment method.When the form is returned to the controller, because your
Create
method signature takes aUser
parameter, the defaultModelBinder
knows that it should instantiate aUser
. It is quite capable of that, but it is not able to work out what to do with the values that appear in theFormCollection
that relate to theBillingDetail
.With inheritance you can't rely on the default
ModelBinder
. You need to work out what suits you best. Here's some references I found useful:Get an understanding of ModelBinding
Custom model binders - one person's opinion
The solution I went with - but look at all the other solutions here too!
Here's some example code from my project that should give you an idea: