I have a View that loads a few partial views at load time depending on a database. It's working great so far with one exception: because this is a database first situation, I'm using a meta class to add the appropriate markup to the model fields. It worked perfectly fine when I had all the stuff on a single page but since I broke it out into partials, it refuses to recognize the meta class anymore.
There's a lot of unrelated code everywhere so I've broken out the important stuff (some names have been changed to protect the innocent)...
In the view to load said partials:
foreach (var p in MyProject.Controllers.UtilityController.GetPViews())
{
#Html.Partial("../Partial/" + p.ViewName, new ViewDataDictionary { { "column", "column" + p.ColumnSize } })
}
The main part of the meta class:
namespace MyProject.Models
{
public class MyProjectData_Meta
{
[MetadataType(typeof(MyProjectData_Meta))]
public partial class MyProjectData
{
public class MyProjectData_Meta
{
//Gets and sets and stuff. For example:
[Required(ErrorMessage = "Please enter a first name.")]
[Display(Name = "First Name")]
[UIHint("FloatLabel")]
public string MemberFirstName { get; set; }
}
}
}
}
And finally the code that pulls the list from said database...
public static List<ModuleList> GetPViews()
{
MyProjectEntities pv = new MyProjectEntities();
List<ModuleList> lvl = pv.ModuleLists.OrderBy(s => s.PageOrder).ToList();
return lvl;
}
So is there some glaring detail I'm missing or more information you need from me in order to tell me where I'm being stupid?
EDIT: Doh! Forgot to put in my actual view code:
#model MyProject.Models.MyProjectData
<div class="#ViewBag.column">
<fieldset id="fsBasic">
<legend>Basic Information</legend>
<div class="editor-label">
#Html.LabelFor(model => model.MemberFirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MemberFirstName)
#Html.ValidationMessageFor(model => model.MemberFirstName)
</div>
</div>
To answer the other question: I know it's not using the meta class because the LabelFor is showing "MemberFirstName" instead of "First Name" which is what it does actually do when I'm not using a partial class.
Related
For some reason I'm having trouble getting option values I add to an ASP.NET MVC dropdown at runtime to post.
In this case, I'm adding options to the dropdown at runtime using jquery.
This is what I have so far:
The Razor HTML:
#Html.DropDownListFor(sc => sc.SelectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name= "SelectedComponents", size = "5", multiple = "multiple" })
The relevant portion of the model:
public IEnumerable<string> SelectedComponents { get; set; }
Also, I can't get the posted values added at runtime to appear in the Forms Collection when I look in the controller action.
Can anyone suggest the best way to handle this particular situation?
Thanks much,
Pete
More information:
I'm trying to implement a simple solution using "BeginCollectionItem" to post added items at runtime when I submit the Razor Form.
In the main view I have this to display the partial view:
#foreach (var components in Model.selectedComponents)
{
#Html.Partial("SelectedComponents", Model)
}
The Partial View looks like this:
#model blah.blah.RequestViewModel
#using(Html.BeginCollectionItem("selectedComponents"))
{
<div class="form-group">
<label for="Component" class="control-label col-xs-12 col-sm-3">Selected Components:</label>
<div class="col-xs-12 col-sm-6">
#Html.DropDownListFor(model => model.selectedComponents, Enumerable.Empty<SelectListItem>(), new { #class = "form-control", #id = "SelectedComponents", #name = "SelectedComponents", size = "5", multiple = "multiple" })
</div>
<div class="col-xs-0 col-sm-3">
</div>
</div>
}
The relevant portion of the viewmodel looks like this:
public class RequestViewModel
{
public IEnumerable<string> selectedComponents { get; set; }
}
I simply want to post a list of strings that are added at runtime using jquery. This part is working and is not shown.
The controller that I'm posting to looks like this:
public ActionResult Create(HttpPostedFileBase[] files, RequestViewModel data)
{
}
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:
http://weblogs.asp.net/manavi/archive/2010/12/28/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt.aspx
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);
}
You are passing a User object to your View. This has a navigation property to BillingDetail which can be a CreditCard or a BankAccount. 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 and model 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 a User parameter, the default ModelBinder knows that it should instantiate a User. It is quite capable of that, but it is not able to work out what to do with the values that appear in the FormCollection that relate to the BillingDetail.
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:
public ActionResult CreateOrEdit(FormCollection values)
{
//The FormCollection is either a Property or a Block
BaseProperty model;
if (values["PropertyTypeID"] != null)
{
//it must be a Property!
Property property = new Property();
TryUpdateModel(property);
_Uow.PropertyRepository.InsertOrUpdate(property);
model = property;
}
else
{
Block block = new Block();
TryUpdateModel(block);
_Uow.BlockRepository.InsertOrUpdate(block);
model = block;
}
//etc....
I think there are a few things wrong here:
You are not adhering to separation of concerns. If the model diagram you provided is for your entities you shouldn't be using them as front-end models. Your data layer and view layer should have separate models -- this lets you decouple the way your data is designed versus what the user is interacting with.
SO users please correct me if I'm wrong, but you can't return concrete server-side objects in webpage data. In this case you are attempting to cast 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.
You are attempting to cast a base class into a derived class. This is possible when you have passed a derived class as a base class and then re-cast it somewhere else, but you can't take a pure base class and transform it into a more specific object. It's like trying to force a rectangle to be a square.
Solution wise you should do this:
Create 2 separate view models for CreditCard and BankAccount, each with their respective properties. (You should do the same for your User object so you adhere to SoC)
Populate your view using the model with the two new view models in lieu of BillingDetail.
When your form is submitted use your radio buttons as a conditional in your controller to determine which type of payment the user chose and then create the respective object, map the view model properties to the concrete object, add it to your user, and then save.
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
ViewBag.PossibleBillingDetail = context.BillingDetail;
This also includes BankAccounts, so some BillingDetail objects can't be cast to CreditCard.
Replace the line by
ViewBag.PossibleBillingDetail = context.BillingDetail.OfType<CreditCard>();
maybe this question is seen repeatedly around here but i was not able to find a answers.
my project is about reservations for hotels. I have a class Reservation witch has a Icollection of ChoosenRooms and a class that represents de User making the reservation, and other stuff like dates and other stuff.
The process is this:
In my first view I get the chosen rooms, dates, etc, then i pass that to my second view where i´m going to get the user info, and then i have a third view where i want to show all the gathered information so the user can finally click a button to save the data.
My problem is that i need to pass the reservation object class across all these views. In my testing i see that primitive types pass just fine BUT The iColletion of ChoosenRooms is lost when i post back from the view to the next controller action.
can someone post some example how to, Posting back from a view to a controller, complex types like ChoosenRooms inside another class Reservations, are not lost in the process?
Or maybe explain why this info is lost?
the code:
public class Reserva
{
public virtual int ID { get; set; }
[NotMapped]
public string[] q { get; set; }
[Display(Name = "Cliente")]
public virtual Utilizador utilizador { get; set; }
[Display(Name = "Quarto")]
public virtual ICollection<Quartos> ChoosenRooms{ get; set; }
[Display(Name = "Serviços Adicionais")]
public virtual ICollection<ReservasItens> itens { get; set; }
The view
#model SolarDeOura.Models.Reserva
#{
ViewBag.Title = "AddReservaUser";
var _reserva = TempData["reserva"] as Reserva;
}
<h2>AddReservaUser</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Reserva</legend>
<div class="editor-label">
#Html.LabelFor(model => model.dtEntrada)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.dtEntrada)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.dtSaida)
</div>
<div class="editor-field">
#Html.DisplayFor(model => model.dtSaida)
</div>
<div class="editor-label">
#Model.q.Count() Choosen Rooms
</div>
#foreach (var q in Model.ChoosenRooms)
{
<ul>
<li>
#Html.DisplayFor(modelitem => q.descricao)
</li>
</ul>
}
posting back from here is the Problem. In this view " #foreach (var q in Model.ChoosenRooms)" has data but posting back the data is lost.
The concept of model binder at this point was not very clear to me and some knowledge about this topic is all it takes to solve the question.
In resume:
The view gets a model which is a complex type: class [reserva] has a collection of [ChoosenRooms] which is also a complex type.
The line #Html.DisplayFor(modelitem => q.descricao) renders the necessary html elements to display the data, but not the necessary html to be posted back to the controller (input element or hidden field ) so the model binder fails.
Also the controller (post) action argument didn't had the property name that matches the field, in this case it needed to be a String[] type since its a collection of values.
I would also recommend reading about Display Templates and Editor Templates.
I have a MVC Project.
I have questions in a database that I want my users to answer. I have them in the database because they need to be able to be dynamic. The user needs to submit an answer for each question. So when the view displays, it shows the question and a textbox for each question.
What would the best way to go about doing this as I do not want to hard code in the question like #Html.textBox("Question1") etc.
Question 1 [__textbox1___]
Question 2 [__textbox2___]
... etc.
I'd probably have a ViewModel that contains a collection of a custom QuestionResponder type.
IEnumerable<IQuestionResponder> Questions{get;set;}
public interface IQuestionResponder{
Guid QuestionId{get;set;}
string Question{get;set;}
string Answer{get;set;}
}
Then you can create the display items you require for your new view model.
#for(var i = 0; i <= questionList .Count; i++)
{
#Model.Questions.ToList()[i].Question
#Html.TextBoxFor(m => m.Questions.ToList()[i].Answer)
}
Alternartively you can create an EditorTemplate to avoid looping in your view:
Insdie ~/Views/Shared/EditorTemplates/ add a new view called QuestionResponder (the name of your custom class).
Inside that template you can then add:
#model MyApp.Models.QuestionResponder
<div>
#Html.DisplayFor(m => m.Question)
#Html.TextBoxFor(m => m.Answer)
</div>
While you'll then call from your original view:
#Html.EditorFor(m => m.Questions)
There's builtin way of doing that in MVC. Very simple way, by the way. There are many alternatives, take a look at this article by Phil Haack to inspect them all. One of those is with dictionaries. Quesion.Id will be key, Answer will be value
Sample:
First, create appropriate ViewModels
public class AnswerQuestionViewModel
{
public Quesion Question { get; set; }
public string Answer { get; set; }
}
public class Quesion //
{
public int Id { get; set; }
// Maybe some other properties.
}
Inside ~/Views/Shared/EditorTemplates/, create editor that will render Editor.
#model Models.AnswerQuestionViewModel
#Html.HiddenFor(model => model.Question.Id)
#Html.EditorFor(model => model.Answer)
And ~/Views/ControllerName/ActionName.cshtml
#model IEnumerable<ControllerInspectorTest.Models.AnswerQuestionViewModel>
#using (Html.BeginForm())
{
#Html.EditorForModel();
<p>
<input type="submit" value="Create" />
</p>
}
And when you create post action, parameter will be filled in
[HttpPost]
public ActionResult AnswerQuestions(IEnumerable<AnswerQuestionViewModel> quesions)
{
// questions parameter is filled in correctly
//do save job;
}
Note that question parameter can by type of IList or List too
Okay, maybe I'm missing something, but I can't figure this out. Using ASP.NET MVC 3, Razor views.
I have a model object like this:
public class MyModel
{
public HttpPostedFileBase File { get; set; }
public string Title { get;set; }
public string Description { get; set; }
}
When, in a strongly typed view, I call #Html.EditorForModel(), it only generates the Title and Description form fields.
I created the file: Views\Shared\EditorTemplates\HttpPostedFileBase.cshtml, with dummy content, but it still doesn't get rendered.
Is it possible to get EditorForModel to generate file input fields?
I managed to get it working by creating a custom Object.cshtml editor template:
#foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else
{
<div class="editor-container">
<div class="editor-label">
#Html.Label(prop.PropertyName, prop.DisplayName)
</div>
<div class="editor-field">
#Html.Editor(prop.PropertyName, prop.TemplateHint)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
</div>
}
}
Basically it calls Html.Editor() for each property of the model. I don't know if it's a good solution, but it works for now.
I have investigated a similar problem - the editor for complex properties is not output. Your case may be different but the workaround that I found (creating an editor for your model - Model.ascx) should work for you too.