MVC 5 dynamic model in partial view - model-view-controller

I have a few (around 6) lookup tables like degreetype, categorytype, etc. All these table has similar column id, name and isactive fields. I created CRUD stored procedure for each table.
In my MVC5 project, i created a model, repository and DAL to pass data to sp for each.
I also created viewmodel, controller and crud views for each.
I realized the crud (create/edit/detail/list/delete) view pages uses the same html except for calling the model at the top of the page.
Question: Is there any way i can create a partial view of the html and use a dynamic model in the pages?
eg:
#model Microsoft.myorg.viewmodels.degreetypesVM
<!-- need to use a dynamic model (degreetypesVM, categorytypeVM etc) above and create a partial view for the below html -->
#{
ViewBag.Title = "Degree Type";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
<table class="container">
<tbody>
<tr class="row">
<td class="col2" scope="row">
<div class="title-text">
#Html.LabelFor(model => model.Name)
<span title="This field is required." class="warning">*</span>
</div>
</td>
<td class="gray">
#Html.EditorFor(model => model.Name, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Name, "", new { #class = "text-danger" })
</td>
</tr>
..
..
</tbody>
..
..

you can simply write a partial view and pass model in it or just wrap up all common fileds
and write their own model for your partial view

Related

Using a partial view to represent a table row

I am trying to use a partial view to represent rows of a table in my project. I currently have
<table>
<thead>
<tr>
<th >
Column 1
</th>
<th >
Column 2
</th>
<th >
Column 3
</th>
</tr>
</thead>
<tbody>
#foreach(var item in Model.Items)
{
#Html.Action("ItemCalculatedView", new { Id = item.Id})
}
</tbody>
</table>
In my partial view I have this
#using (Ajax.BeginForm("SaveStuff", "Whatever",
new { id = #Model.Id }, new AjaxOptions()
{
HttpMethod = "Post",
OnSuccess = "Success"
}))
{
#Html.HiddenFor(m => m.Id)
<tr>
<td>
#Html.Label("Col1", Model.Col1)
</td>
<td>
#Html.TextBox("Number", Model.Number)
</td>
<td>
<input type="submit" id='submit-#Model.Id'/>
</td>
</tr>
}
How can I make this work?
You can put a form inside a table cell, but you can't have the form inside a tbody element, or spanning multiple columns. So there are three options:
Use a CSS layout instead of a table, or use divs with CSS display set to "table". (for example)
Put the entire form (TextBox and Submit) inside a td
Put another table inside the td element
I'd recommend #1 -- use a CSS layout to construct the table, since it's difficult to style table tags:
Main
<div class="table">
<div class="header-row">
<div class="header-cell">Column 1</th>
<div class="header-cell">Column 2</th>
<div class="header-cell">Column 3</th>
</div>
#foreach(var item in Model.Items)
{
#Html.Action("ItemCalculatedView", new { Id = item.Id})
}
</div>
Partial
#using (Ajax.BeginForm(
actionName: "SaveStuff",
controllerName: "Whatever",
routeValues: new { id = #Model.Id },
ajaxOptions: new AjaxOptions
{
HttpMethod = "Post",
OnSuccess = "Success"
},
htmlAttributes: new { #class = "row" }
))
{
<div class="cell">
#Html.HiddenFor(m => m.Id)
</div>
<div class="cell">
#Html.Label("Col1", Model.Col1)
</div>
<div class="cell">
#Html.TextBox("Number", Model.Number)
</div>
<div class="cell">
<input type="submit" id='submit-#Model.Id'/>
</div>
}
CSS
.table { display: table; }
.header-row, row { display: table-row; }
.header-cell, cell { display: table-cell; }
You have several issues here. First, as dbaseman mentions, you can't place forms within the structure of a table and have it be legal HTML. It may work, or it might not, and even if it does work, you can't guarantee it will continue to work.
I would instead wrap your table in the form, and then on the post figure out which button was pressed based on its value and/or index.
I would strongly advise against using css tables for tabular data. It's just not semantically correct.
Another possible solution is, instead of using the Ajax.BeginForm, instead use jQuery $.ajax and then you can select a row of data in javascript to post to the server.

Mvc3 IEnumerable<QuestionModel> have a List<QuestionOptionModel> property. When I post, I get null list

I'm making a survey application. Survey has Questions and Questions have QuestionOption. haveThere is a example here.
I am trying to use this technique in a large form with a list(List) but when I post back, the Viewmodel.Order that should’ve contained list of items and activities return with the lists empty.
My QuestionModel.cs like this.
public int Id { get; set; }
public string QuestionText { get; set; }
public System.Nullable<bool> OptionType1 { get; set; }
public System.Nullable<bool> OptionType2 { get; set; }
public List<QuestionOptionModel> OptionList = new List<QuestionOptionModel>();
When I post back "IEnumerable questions" List OptionList comes null. How can I do this?
public ActionResult CallSurvey()
{
IEnumerable<QuestionModel> questionModelList = (IEnumerable<QuestionModel>)SessionHelper.GetSessionObject(SessionKeys.SurveyKey);
questionModelList = questionSrv.GetQuestionModel();
return View(questionModelList);
}
questionModelList include all my survey question and question options. When I post it, post back is coming with only null optionList.
[HttpPost]
public ActionResult CallSurvey(IEnumerable<QuestionModel> questions)
{ ..... }
CallSurvey.cshtml
<body>
#using ((Html.BeginForm()))
{
#ViewBag.Test
<section class="slides layout-regular template-kendo">
#foreach (var item in Model)
{<article>
#Html.Partial("QuestionEditor", item)
</article>
}
<div class="slide-area" id="prev-slide-area"></div>
<div class="slide-area" id="next-slide-area"></div>
</section>
}
</body>
QuestionEditor.cshtml
#model LSMM.Business.Model.Survey.QuestionModel
#using LSMM.Web.Helpers
<div>
#using (Html.BeginCollectionItem("Questions"))
{
<table id="table1">
<tr>
<td>
<div id="#Model.Id" class="hint">
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.QuestionText)
#Html.HiddenFor(m => m.OptionType1)
#Html.HiddenFor(m => m.OptionType2)
#for (int i = 0; i < Model.OptionList.Count; ++i)
{
#Html.LabelFor(m => m.OptionList[i].Id)
#Html.LabelFor(m => m.OptionList[i].QuestionId)
#Html.LabelFor(m => m.OptionList[i].Checked)
#Html.LabelFor(m => m.OptionList[i].Description)
#Html.LabelFor(m => m.OptionList[i])
}
<span id="sorular">#Model.Id. #Model.QuestionText</span>
<br />
<br />
</div>
</td>
</tr>
<tr>
<td>
<div class="hint2">
#Html.Partial("QuestionOptionEditor", Model)
</div>
</td>
<td>
<div id="#Model.Id-Img">
<h2 style="top: 200px; right: 0;">
<img src="../../Content/css/img/#Model.Id-Img.png"></h2>
</div>
</td>
</tr>
</table>
and QuestionOptionEditor.cshtml
#model LSMM.Business.Model.Survey.QuestionModel
#using LSMM.Web.Helpers
#foreach (var option in #Model.OptionList)
{
<p>
#if (#Model.OptionType1 == false)
{
#Html.Partial("QuestionOptionModel", option)
}
else
{
#Html.Partial("../Shared/DisplayTemplates/QuestionOptionModel", option)
}
</p>
}
Here QuestionOptionModel views like this;
#model LSMM.Business.Model.Survey.QuestionOptionModel
#(Html.RadioButtonFor(m => m.Id, true, new { Id = #Model.Id, Name = #Model.QuestionId })) #Html.Label("Evet")
<br />
<br />
#(Html.RadioButtonFor(m => m.Id, false ,new { Id=#Model.Id, Name = #Model.QuestionId})) #Html.Label("Hayır")
The name attribute on your radio buttons is not "correct" according to the naming rules used by the default ModelBinder. That's why you aren't seeing the values you expect, the ModelBinder couldn't find what it was looking for.
This is easy to fix and you should end up with less code.
Take advantage of the framework and let it do work for you:
MVC can loop IEnumerables for you. Let it. There's little reason to write foreach loops in views these days.
Get rid of those partials. Use MVC's Templates feature instead.
Then your view can be as simple as:
<body>
#using ((Html.BeginForm()))
{
#ViewBag.Test
<section class="slides layout-regular template-kendo">
<table id="table1">
#* Table header goes here *#
#Html.EditorFor(model => model.OptionList)
</table>
<div class="slide-area" id="prev-slide-area"></div>
<div class="slide-area" id="next-slide-area"></div>
</section>
}
</body>
When you use an EditorTemplate or a DisplayTemplate, MVC will automatically fix the name attribute on your form fields. It won't do that with a partial.
Another suggestion, also if you don't mind. Get rid of those nulla-bools on your model and write different QuestionOptionModel types. DisplayTemplate/EditorTemplates are wise to the type system and MVC will pick the template that matches the type it is presented.
So you could have another class that derives from QuestionOptionModel:
public class ExtendedQuestionOptionModel : QuestionOptionModel
{
//Stuff goes here.
}
and you could have a List<QuestionOptionModel> hanging off your model that contains:
QuestionOptionModel
ExtendedQuestionOptionModel
QuestionOptionModel
and when you do this:
#Html.EditorFor(model => model.QuestionOptions)
MVC will go through the option list and render the Editor Template for QuestionOptionModel, then the Editor Template for ExtendedQuestionOptionModel, then the Editor Template for QuestionOptionModel again.
The game you want to play is to try to minimize the C# code in the views, because the views cannot be unit tested. Code that isn't unit tested will eventually come back and get you.
Your question is abit ambiguous. Frankly i hope this is what you are loooking for : IEnumerable can be transformed to list using
List<foo>listFoo =iEnumerableEntityFoo.toList()

how to create form dynamically with in form

I have created an extension using formbuilder. Now I have used it in my view.
My view looks like:
#using (Html.BeginForm("addDataInd", "CustInformations", FormMethod.Post))
{
<fieldset class="field">
<legend>Addresses</legend>
<table>
<tr>
#Html.EditorFor(model => model.addresses)
</tr>
</table>
</fieldset>
}
where
#Html.EditorFor(model=>model.addresses)
calls my EditorTemplate which looks like:
<td>
#Html.hSearch("txtSearch", "", "lblsrch", "Search Text: ", "Search", "Fetch", "LookUp", new { script = "Select Aid, FullAreaName from fGetAreaTB()" }, null)
</td>
When i run the program the page looks like
i used fire bug to know the error. All I found was that, the code generated for the first
upper image (i.e. for permanent address) it doesn't create a form, but for other two it creates a form. So when I click the first search button, it doesn't work but when i click the second and third button it works well.
I just want when i run the program all the button must be in form.
You cannot nest HTML forms. For this reason you will have to use multiple forms and put them inside the template. Like this:
<fieldset class="field">
<legend>Addresses</legend>
<table>
<tr>
#Html.EditorFor(model => model.addresses)
</tr>
</table>
</fieldset>
and inside the editor template:
#model Address
<td>
#using (Html.BeginForm("addDataInd", "CustInformations", FormMethod.Post))
{
#Html.hSearch("txtSearch", "", "lblsrch", "Search Text: ", "Search", "Fetch", "LookUp", new { script = "Select Aid, FullAreaName from fGetAreaTB()" }, null)
}
</td>

Updating Entity With A Child Using EditorTemplate

I have an Entity (QuoteSheet) that contains a child entity (QuoteTask), which is loaded using the EntityFramework. However, I am receiving an error when I submit this form.
I have created an edit page for the QuoteSheet entity, which then uses an EditorTemplate to edit the QuoteTask child entity.
The controller code is as follows:
public ActionResult TestEdit(int Id)
{
var quote = DataContext.QuoteSheets.Where(x => x.ID == Id).FirstOrDefault();
return View(quote);
}
[HttpPost]
public ActionResult TestEdit(Models.QuoteSheet quote)
{
return View(quote);
}
A stripped down version of the view is as follows:
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(x => x.JobID);
<div class="sectionHeader">Sheet Details</div>
<div class="sectionContent">
<table>
<tr>
<td width="150">Sheet Desc.</td><td>#Html.TextBoxFor(x => x.Description, new { size = "50" })</td>
</tr>
<tr>
<td>Quantity Required</td><td>#Html.EditorFor(x => x.Quantity)</td>
</tr>
</table>
</div>
<div class="sectionHeader">Tasks</div>
<div class="sectionContent">
<table id="Tasks">
<tr>
<th>Labour Group</th>
<th>Task Description</th>
<th>Total Hrs</th>
<th>Rate</th>
<th>Cost</th>
</tr>
#Html.EditorFor(x => x.QuoteTasks)
</table>
<input type="button" name="AddTasks" id="AddTasks" value="Add" />
</div>
<input type="submit" value="Submit" />
#Html.ValidationSummary()
}
And the EditorTemplate is:
#model Ornavi.Models.QuoteTask
<tr>
<td>#Html.EditorFor(x => Model.LabourGroup)</td>
<td>#Html.EditorFor(x => Model.Description)</td>
<td>#Html.EditorFor(x => Model.TotalHours)</td>
<td>#Html.EditorFor(x => Model.Rate)</td>
<td>#Html.HiddenFor(x => Model.ID)</td>
</tr>
When I submit the form, I am getting the following error:
The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph.
This only occurs when I use the EditorTemplate - if I remove the editor template and just submit the main entity, it works fine.
I have placed a breakpoint in the [httppost] TestEdit function, but the exception occurs before it reaches this point.
Any ideas on how to successfully use an EditorTemplate to edit a child entity?
The problem is, that the default modelbinder tries to instantiate your EF class and set the navigation properties when binding the form data to your parameter types.
See some similar questions like this one.
You have two options:
Don't use your EF classes as viewmodels but create own viewmodel classes to pass the data between controller and view.
Don't bind directly to the EF class in your Edit controller action but use a FormCollection parameter and bind yourself with UpdateModel as shown in the linked question.

MVC3 Razor Partial view render does not include data- validation attributes

I have a farily straight forward form that renders personal data as a partial view in the center of the form. I can not get client side validation to work on this form.
I started chasing down the generate html and came up with the same model field rendered on a standard form and a partial view.
I noticed that the input elements are correctly populated on the first call, #html.partial, the following only happens when the partialview is reloaded via an ajax request.
First the header of my partial view, this is within a Ajax.BeginForm on the main page.
#model MvcMPAPool.ViewModels.EventRegistration
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function ()
{
$(".phoneMask").mask("(999) 999-9999");
});
</script>
#{
var nPhn = 0;
var dTotal = 0.0D;
var ajaxOpts = new AjaxOptions{ HttpMethod="Post", UpdateTargetId="idRegistrationSummary", OnSuccess="PostOnSuccess" };
Html.EnableClientValidation( true );
Html.EnableUnobtrusiveJavaScript( true );
}
Here is the razor markup from the partial view:
#Html.ValidationMessageFor(model=>Model.Player.Person.Addresses[0].PostalCode)
<table>
<tr>
<td style="width:200px;">City*</td>
<td>State</td>
<td>Zip/Postal Code</td>
</tr>
<tr>
<td>#Html.TextBoxFor(p=>Model.Player.Person.Addresses[0].CityName, new { style="width:200px;", maxlength=50 })</td>
<td>
#Html.DropDownListFor(p=> Model.Player.Person.Addresses[0].StateCode
, MPAUtils.GetStateList(Model.Player.Person.Addresses[0].StateCode))</td>
<td>
<div class="editor-field">
#Html.TextBoxFor(p=>Model.Player.Person.Addresses[0].PostalCode, new { style="width:80px;", maxlength=10 })
</div>
</td>
</tr>
</table>
Here is the rendered field from the partial view:
<td>
<div class="editor-field">
<input id="Player_Person_Addresses_0__PostalCode" maxlength="10" name="Player.Person.Addresses[0].PostalCode" style="width:80px;" type="text" value="" />
</div>
</td>
Here is the same model field rendered in a standard view:
<div class="editor-field">
<input data-val="true" data-val-length="The field Postal/Zip Code must be a string with a maximum length of 10." data-val-length-max="10" data-val-required="Postal or Zip code must be provided!" id="Person_Addresses_0__PostalCode" maxlength="10" name="Person.Addresses[0].PostalCode" title="Postal/Zip Code is required" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Person.Addresses[0].PostalCode" data-valmsg-replace="true"></span>
</div>
Notice that the partial view rendering has no data-val-xxx attributes on the input element.
Is this correct? I do not see how the client side validation could work without these attributes, or am I missing something basic here?
In order to create the unobtrusive validation attributes, a FormContext must exist. Add the following at the top of your partial view:
if (this.ViewContext.FormContext == null)
{
this.ViewContext.FormContext = new FormContext();
}
If you want the data validation tags to be there, you need to be in a FormContext. Hence, if you're dynamically generating parts of your form, you need to include the following line in your partial view:
#{ if(ViewContext.FormContext == null) {ViewContext.FormContext = new FormContext(); }}
You then need to make sure you dynamically rebind your unobtrusive validation each time you add/remove items:
$("#form").removeData("validator");
$("#form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("#form");

Resources