I'm building a questionnaire mvc webapp, and i cant figure out how to pass an unknown number of arguments to the controller from the form.
My form is something like:
<% using (Html.BeginForm())
{ %>
<div id="Content">
<% foreach (var group in ViewData.Model.QuestionGroups)
{ %>
<div class="Group">
<%=group.Description %>
<% foreach (var question in group.Questions)
{%>
<div class="Question">
<div class="QuestionTitle">
<%=question.Title %>
</div>
<%=Html.Hidden("Id", question.ID) %>
<div class="QuestionText">
<%switch (question.TypeAsEnum)
{
case QuestionTypeEnum.Text:%>
<%=Html.TextBox("somename") %>
<% break;
case QuestionTypeEnum.Number:%>
<%=Html.TextBox("somename") %>
<% break;
case QuestionTypeEnum.PhoneNumber:%>
<%=Html.TextBox("somename")%>
<% break;
case QuestionTypeEnum.Email:%>
<%=Html.TextBox("somename")%>
<% break;
case QuestionTypeEnum.Date:%>
<%=Html.TextBox("somename")%>
<% break;
case QuestionTypeEnum.YesNo:%>
<%=Html.RadioButton("somename", true)%>
<%=Html.RadioButton("somename", false)%>
<% break;
case QuestionTypeEnum.Alternative:%>
<%=Html.DropDownList("somename", question.Answers)%>
<% break;
}%>
</div>
</div>
<% } %>
</div>
<% } %>
</div>
<div id="submittButton">
<%=Html.SubmitButton()%></div>
<% } %>
Now what i need in my controller is List< ResponseAnswer >,
where ResponseAnswer has the properties:
string questionID,
string AnswerText,
bool AnswerBool,
number AnswerNumber,
...
So how can i pass an unknown number of items containing questionID, AnswerType and Answer to the controller.
In webforms i solved this by rendering the form with repeaters instead of foreach, and then iterating through the question repeater checking the control id, each repeater item containing a hidden questionid element and a input with id=AnswerType.
But this will seriously break Separation of concern in mvc?
So is there any way of getting my controller to accept List< ResultAnswer > and somehow build this list without breaking soc, and if not, how do i pass the entire formresult back to the controller so i can do the iteration of the form data there instead of in the view.
You can add an argument to your action like
public ActionResult MyAction(FormCollection form)
Then the form parameter will contain all the data from the posted form. From that you can do what you want.
You could probably implement a binder that could map to ResponseAnswers but I have no experience of doing that so I'll leave that to someone else.
Garry's answer will work (and hence, up voted). However, you can model bind directly to a list, and I think it's a bit more elegant. There are instructions in this blog post.
To clarify gary's answer, you can then easily walk through all fields of the form like this
foreach (string s in Request.Form.Keys)
{
if (s.Contains("number"))
number = int.Parse ((Request.Form[s]));
//do something
}
Related
I am working on SharePoint to create a Feedback questionnaire form using an application page that is basically a aspx page.
I wish to do this by emulating MVC as far as possible. I've set up my model in the code-behind:
public List<QuestionViewModel> modelQuestions = new List<QuestionViewModel>();
Next, I need to display each question and an appropriate input depending on the question type (e.g. single line, multi line, single selection, multiple selection).
I've got it displaying the questions correctly:
<fieldset class="what-went-wrong">
<% for (int i = 0; i < modelQuestions.Count; i++) { %>
<p>
<label for="QuestionText">
<% if (modelQuestions[i].Required) { %>
<span class="req-field indicator">*</span>
<% } %>
<%= modelQuestions[i].QuestionText %>
<% if (modelQuestions[i].Required) { %>
<span class="req-field right">* Required field</span>
<% } %>
</label>
</p>
<% } %>
</fieldset>
This give's me the question text. I'm now trying to construct the appropriate input, but this <% %> tags is not working for this:
<% if(modelQuestions[i].QuestionTypeId == QuestionType.SingleLine) { %>
<input id="modelQuestions_<% i %>" name="modelQuestions[<% i %>]" type="text" placeholder="<% modelQuestions[i].Placeholder %>" />
<% } %>
I can't seem to get it to construct the html element using details from the model (in the value for id, name, placeholder etc.)
Also, I've no idea how to go about posting this back to the server when I get to that point.
Is there any merit in continuing? Are there other controls/methods more appropriate to use in this case with aspx?
You cannot generate HTML markup like this. Even data-binding expressions will not help, because they bind ASP.NET controls' attributes values, not the plain output HTML in the page.
You should generate the markup in the "code behind", like this:
Page markup:
<div id='AnswersPanel'>
<div>
Page code behind:
protected void PageLoad(...)
{
AnswersPanel.InnerHtml = "";
AnswersPanel.InnerHtml += string.Format("<input id='modelQuestions_{0}' name='modelQuestions[{0}]' type='text' placeholder='{1}' />",
i.ToString(),
modelQuestions[i].Placeholder);
}
I have created a new 'Jobs' Content type. Basically I want this to work in the same fashion as Modules, they will simply allow job listings to be posted on a Careers page. I have it mostly working, the content type shows up in the admin and I can enter data as I want. However I cannot get it to render on the page now - I keep receiving errors when I try to loop through and print out the Jobs.
In my JobsTemplate.aspx file I have the following:
<% if (!Content.Jobs.IsNullOrEmpty()) {
foreach (var item in Content.Jobs.AsSmartEnumerable()) {
var job = item.Value.Value as Jobs; %>
<% if (item.Index % 3 == 0) { %>
<div class="row-fluid modules">
<% } %>
<div class="span4">
<h2><%: job.Title %></h2>
<%= job.Body %>
<% if (job.Link.HasValue()) { %><p><a class="btn" href="<%= job.Link.Url() %>"><%: Resources.Strings.LearnMore %> ยป</a></p>
<% } %>
</div>
<% if (item.IsLast || !item.IsFirst && item.Index % 2 == 0) { %>
</div>
<% } %>
<% } %>
<% }%>
And this yields the error:
Compiler Error Message: CS1061: 'Mindroute.Lemoon.Modules.CoreModule.Page' does not contain a definition for 'Jobs' and no extension method 'Jobs' accepting a first argument of type 'Mindroute.Lemoon.Modules.CoreModule.Page' could be found (are you missing a using directive or an assembly reference?)
What am I missing? On the JobsTemplate.aspx file I am using the JobsTemplate as the codebehind and inherits values - is this where I am screwing up?
Thanks.
It looks like your JobTemplates.aspx page is used to render a page of content type Mindroute.Lemoon.Modules.CoreModule.Page and the compiler tells you that the Page content type does not have a Jobs property.
What you should do is create a content type called Careers (or modify an existing) and add a property definition like:
public List<ContentRef> Jobs { get; set; }
You must then tell your Template that it should render a content item of type Careers like:
public partial class CareersTemplate : PageBase<Careers>
and finally you need to edit the Careers content type in admin to use the CareersTemplate.
I have an ICollection<String> being passed to my view and I do a foreach to load a partial view. It loops through the correct number of times, however, the value it passes is the same and I know in the model that this is not the case.
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View("Index", new List<String>());
}
[HttpPost]
public ActionResult Index(List<String> txtValue)
{
return View("Index", txtValue);
}
}
View:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<List<String>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Home
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script type="text/javascript">
//Deletes the div the Control is in.
$(".delete").live("click", function () {
$(this).parent().remove();
});
//Adds the TextBoxes to divControls
function AddTextBox(Value) {
var elements = "<div><input name=\"txtValue\" type=\"text\" /><input type=\"button\" class=\"delete\" value=\"-\" /><br/></div>";
$("#divControls").append(elements);
}
</script>
<h2>Controls!!!</h2>
<input id="btnAdd" type="button" name="Refresh" value="+" onclick="AddTextBox()" />
<% using (Html.BeginForm())
{ %>
<input id="btnsubmit" type="submit" name="Submit" onclick="Submit" />
<div id="divControls">
<%
foreach (var text in this.Model)
{ %>
<%=Html.TextBox("txtValue", text, new { id = "Value", name = "txtValue" })%>
<% Html.RenderPartial("TextControl", text);
}
%>
</div>
<%
}
%>
</asp:Content>
TextControl.ascx
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<String>" %>
<div>
<%=Html.TextBox("txtValue", this.Model, new { id = "Value", name = "txtValue" }) %>
<input id="btn" type="button" class="delete" value="-" /><br/>
</div>
The values of the model passed from the controller to the view are correct, even when passed to the user control "TextControl" the value is correct, but when the Textbox displays they are all just the first value of the original model passed in.
Ex.
Model as List<String> { "1", "2", "3", "4" }
passed to the view, will iterate through each one correctly, passing the correct string to "TextControl" to create a Html.TextBox("name", this.Model). Everything on the debugging side appears correct, however, when it finishes all the textboxes are "1" (or the first value in the list).
Here's a link to my exact code: http://www.sendspace.com/file/sypl1u
Note:
I came up with a solution of just using <input type="text" name="txtValue" value="<%= this.Model %>" /> instead.
Potential issue: you are using ElementAt which is LINQ method that fave special behavior for IList argument, but you are passing in txtValue as result of some query. In this case ElementAt may case sequence to be enumerated multiple times and may even fail if sequence can't be re-enumerated.
Consider simple foreach on the collection instead:
foreach (var text in Model)
{
Html.RenderPartial("TextControl", text);
}
I'm new in MVC2, so sorry for stupid question. I looked for nice answer, but can't find it. So my question is:
I have view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MyProject.MyDB.MyProducts>>" %>
<%# Import Namespace="MyProject.MyDB" %>
<asp:Content ID="Content1" ContentPlaceHolderID="Content" runat="server">
<% using (Html.BeginForm())
{%>
<table>
<%
foreach (var item in Model)
{%>
<tr>
<td>
<%:item.name%>
</td>
<td>
<%:String.Format("{0:g}", item.date)%>
</td>
</tr>
<% } %>
</table>
<div>
<%:Html.TextArea("MyTextArea")%>
</div>
<p>
<input type="submit" value="Send" />
</p>
<% } %>
</asp:Content>
My controller is:
[HttpGet]
public ActionResult ViewMyProducts(int id)
{
List<MyProducts> myModel = GetMyProducts(id);
return View(myModel);
}
[HttpPost]
public ActionResult ViewMyProducts(/*???What should I put here???*/)
{
if(/*I want "MyTextArea" value here*/ == something && myModel/*from view*/.count==5}
{
//do something
}
return View(myModel);
}
So, in HttpPost I need myModel from view and value of "MyTextArea" from view. How can I get them?? I'll appreciate any help.
I would think that the following should work:
[HttpPost]
public ActionResult ViewMyProducts(string MyTextArea)
A helpful thing to do would be to explicitly call your Action in your Form - by changing this line:
<% using (Html.BeginForm())
to
<% using (Html.BeginForm("ViewMyProducts","ControllerName",HttpMethod.Post))
to ensure that the Submit action redirects it to the right Action.
As far as the model is concerned:
If you are just checking the Count - you could make a hidden field that returns the number of items in the "Model" like such:
<%: Html.Hidden("modelCount", Model.Count());
but if you want the entire Model - it would be need to be something like this:
<%: Html.Hidden("myModel", Model);
then you could further modify your Action to look something like this:
ViewMyProducts(string MyTextArea, int modelCount)
{
//...
}
or
ViewMyProducts(string MyTextArea, IEnumerable<MyProject.MyDB.MyProducts> myModel)
{
//...
}
Although you have access inside of the Controller to refresh the Model - so if you didn't need to pass back the entire thing you could still repopulate your view with a fresh call.
string myTextArea - or you could just check the FormCollection (I would recommend the named variable).
If you want to get the model back from the view, you will need to serialize it out as well in order to get back the values. If this is the case, I would convert the whole thing to a view model that either derives from your Model or has a public property that is your model, add a property for MyTextArea and then emit hidden input's for you model, named for the appropriate properties. Assuming that your model is persisted somewhere (database), I would just pass the key (id) and rehydrate the object from within the action result.
[HttpPost]
public ActionResult ViewMyProducts(ViewMyProductsViewModel viewModel)
{
if(viewModel.MyTextArea == "something" && (IEnumerable<foo>)myModel).Count()==5)) {
var model = repo.Get(myModel.First().Id);
// do something with the model
}
return View(viewModel);
}
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<MyProject.MyDB.MyProducts>>" %>
<%# Import Namespace="MyProject.MyDB" %>
<asp:Content ID="Content1" ContentPlaceHolderID="Content" runat="server">
<% using (Html.BeginForm())
{%>
<table>
<%
foreach (var item in Model)
{%>
<tr>
<td>
<input type="hidden" name="viewModel.Id" value="<%:item.id%>" />
<%:item.name%>
</td>
<td>
<%:String.Format("{0:g}", item.date)%>
</td>
</tr>
<% } %>
</table>
<div>
<%:Html.TextArea("MyTextArea")%>
</div>
<p>
<input type="submit" value="Send" />
</p>
<% } %>
</asp:Content>
First of all You are rendering item.name and item.date as a text not html control. So You won't be able to receive it in controller method parameters.
I am writing an MVC application with C#. In this one particular section, I have a conditional switch for the navigation to highlight the appropriate tab. Here is the code for that:
<script type="text/C#" runat="server">
string oController;
string oAction;
const string current = "class=\"current_page\"";
protected override void OnLoad(EventArgs e)
{
oController = ViewContext.RouteData.Values["controller"].ToString();
oAction = ViewContext.RouteData.Values["action"].ToString();
base.OnLoad(e);
}
</script>
<div id="menu">
<ul>
<li <%= (oController.Equals("Home") ? current : "") %>>Home</li>
<li>Customer Manager</li>
<!-- <%: oController.Equals("Home") %> -->
<!-- <%: oAction %> -->
</ul>
</div>
On this line (<li <%= (oController.Equals("Home") ? current : "") %>>Home</li>) if I use the <%: %> ASP nugget instead of <%= %> (as is recommended, since the latter is being phased out soon), the generated text comes out as
<li class="current_page">Home</li>
Instead of
<li class="current_page">Home</li>
Any suggestions and/or reasons why this is happening? Thanks!
Because <%: is intended to encode data such as user input. In your case, that is self-generated (trusted) html, so you don't want to encode it; <%= is the correct usage.
<%: is for things like <%:user.Name%>, which conveniently prevents issues for malicious text with xss etc in it.
You could just have the class be empty when the value is not Home. Clearly you are trying to omit it all together, but it would be a solution none the less.
const string current = "current_page";
<li class="<%: (oController.Equals("Home") ? current : "") %>">Home</li>