I've created a simple mvc webage with textarea. I submit data to server and then I need to refresh view. But textarea always contains browser data, but my model has changed.
Debug shows me:
Html.TextArea("EmailBody", Model != null ? Model.EmailBody : "Message")
{<textarea cols="20" id="EmailBody" name="EmailBody" rows="2"> Message</textarea>} System.Web.Mvc.MvcHtmlString {System.Web.Mvc.{Dynamic}.DynamicMvcHtmlString}
But at the same time
Model != null ? Model.EmailBody : "Message" "123" string
i.e. rendering on page differs from real data (real data is 123, but I get old data typed in textarea). Please, advice.
Problem resolved with
<textarea id="EmailBody" name="EmailBody"><%= Model.EmailBody %></textarea>
Thanks a lot.
I would use
#Html.TextAreaFor(model => model.EmailBody, .....
and in my controller:
public ActionResult Index()
{
var viewModel = new MyViewModel();
viewmodel.EmailBody = "Default";
return this.View(viewModel);
}
[HttpPost]
public ActionResult Index(MyViewModel inputViewModel)
{
string body = inputViewModel.EmailBody // Whatever....
}
The key point is don't bother passing a NULL viewmodel to your view, give it some default data to work with. That way, you can use the HTML helpers like TextBoxFor() or TextAreaFor() to render for you.
Related
Hi I have a drop down list that is filled in from comma delimited values in the config. This works fine.
What I am trying to do is to send the selected value on button click to a ActionResult in the HomeController.
I created a Model, which is taking a string. When I hit the button I get error:
The view 'TestAction' or its master was not found or no view engine supports the searched locations.
This is what my Controller looks like:
[HttpPost]
[ActionName("TestAction")]
public ActionResult TestAction(SQL_Blocks_App.Models.DropdownList SelectedValue)
{
//System.Diagnostics.Debug.WriteLine(SelectedValue);
return View();
}
This is what my model looks like:
public class DropdownList
{
//
// GET: /DropdownList/
[Display(Name = "Servers")]
public string SelectedValue{ get; set; }
}
and this is what my Index View looks like:
<form id="SelectedValue" action="/Home/TestAction" method="post" style="margin: 0">
<div class="col-lg-5">
#{
ViewBag.Title = "Index";
}
#Html.DropDownList("YourElementName", (IEnumerable<SelectListItem>)ViewBag.DropdownVals, "--Choose Your Value--", new
{
//size = "5",
style = "width: 600px"
})
</div>
<div class="col-lg-5">
<input type="submit" value="Run Query" />
<input id="Button2" type="button" value="Clear" onclick="window.location.reload()" />
</div>
</form>
I want to clarify. My end goal is to use the selected value in a SQL query in the ActionResult and return the results back to the index so I can fill them in a table. ( You don't have to show me how to do the SQL part for now I just would like to see the selected value at least printed in the output.)
Redirect to index action, and pass the parameters along
[HttpPost]
[ActionName("TestAction")]
public ActionResult TestAction(SQL_Blocks_App.Models.DropdownList _selectedValue)
{
//System.Diagnostics.Debug.WriteLine(SelectedValue);
return RedirectToAction("Index", "[Controller]", new {#_selectedValue = _selectedValue });
}
and then your Index method should accept the parameter.
[HttpGet]
public ActionResult Index(SQL_Blocks_App.Models.DropdownList _selectedValue)
{
//use _selectedValue
}
I would recommend using another method other than your index, or make Dropdownlist nullable/set a default for it.
The default framework behavior of return View() is to return a view with the same name as the currently-executing action. Which is TestAction. The error is telling you that no such view was found.
You have a couple of options. You can either create the view, or you can return something else. For example, if you want to redirect back to the Index then you can return a redirect result:
return RedirectToAction("Index");
You could also specify the Index view in the response:
return View("Index");
However, keep in mind that the URL will still be for TestAction and not for Index, which could result in unexpected changes to behavior if you're not aware of this.
Edit: Based on comments on this answer, it sounds like what you actually want is to build a pair of actions which generally operate on the same view. This isn't particularly common for an index view, but is very common for edit views. The only difference is semantics, structurally the concept works anywhere.
Consider two actions:
public ActionResult Index()
{
// just show the page
return View();
}
[HttpPost]
public ActionResult Index(SQL_Blocks_App.Models.DropdownList SelectedValue)
{
// receive data from the page
// perform some operation
// and show the page again
return View();
}
Requests between these two actions would differ only by the HTTP verb (GET or POST), not by the action name on the URL. That name would always be "Index". But when the form on the index view is submitted via POST and has a "SelectedValue", the second action is invoked instead of the first.
In that second action you would perform your database interaction, gather whatever data you needed, and if necessary include a model or some additional data in the response.
You TestAction method is returning to a View. Make sure View TestAction.cshtml exists and is in the Home folder.
I am a beginner and I am going through some tutorials in my MVC. So, I came across two scenarios.
Scenario 1.
I had to pass some data to my view on post and then send that data as hidden field. Here is the code.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ForgotPassword(ForgotPasswordMV viewModel)
{
if (ModelState.IsValid)
{
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
}
^^ USING ANONYMOUS OBJECTS
return View();
}
public ActionResult VerifyToken(string emailId = null)
{
VerifyTokenMV viewModel = new VerifyTokenMV
{
EmailId = emailId
};
return View(viewModel);
}
VerifyToken View
#using (#Html.BeginForm("VerifyToken", "Security"))
{
#Html.HiddenFor(m => m.EmailId)
<button class="btn btn-primary">Continue</button>
}
Works Perfectly fine. I am able to receive values of EmailId. So far so good.
Scenario 2.
Needed to open a partial view from Main view, here is the snippet.
Main cshtml file
<div class="abc">
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
</div>
partial cshtml file
#{
string custWidgetElementName = ViewBag.ElementName;
}
// some html code below
Observation:
In scenario 2 why have I used ViewDataDictionary. Although both example works perfectly fine. But is there any reason that I had to use ViewDataDictionary. In scenraio 1 can we use ViewDataDictionary? If Yes, then which one is optimum solution.
Question: When I need to pass values shall I use new {key : value} or use ViewDataDictionary or there is no corelation? Instead of ViewDataDictionary can I use anonymous object in Senario 2
Your two scenarios are totally different. They are not doing the same thing.
In scenario 1 when using this line:
return RedirectToAction("VerifyToken", new { emailId = viewModel.EmailId });
A new URL is genrated and sent back to the client (the browser) with HTTP Code 301 or 302. When received the browser will re-contact your application wiht the received URL. With that URL, your application will execute the associated action. In your case, the client's browser will call VerifyToken action with the emailId parameter setted when you call RedirectionToAction into ForgotPassword action. So using RedirectionToAction method is just telling that method to generate a new URL with parameter defined in the anonymous type.
In scenario 2 is completely different to scenario 1. With this line:
#Html.Partial("../Widget/Customize", Model.Unit, new ViewDataDictionary() { { "ElementName", "UnitWidget" } })
You're telling your view to inject the partial view which path is ../Widget/Customize. Because that partial view the strongly typed, you passed Model.Unit as an second parameter. You use also a third parameter new ViewDataDictionary() { { "ElementName", "UnitWidget" } } because your partial seems to internally need to access to the dynamic property ViewBag or dictionary property ViewData of your view.
Conclusion:
In scenario 1 you are just telling the client's browser to go to the new URL you have generated after requesting ForgetPassword URL. We just call that a rediretion.
In scenario 2, you're just rendering a partial view into a view. The client's broswer doesn't know anything what's going on with partial views they don't know if they exist.
I want to send a message to userID=3 by going to /MyController/Message/3
This executes Message() [get] action, I enter some text in the text area and click on Save to post the form
Message() [post] action saves the changes, resets the value of SomeText to empty string and returns to the view.
At this point I expect the text area to be empty because I have set ViewData["SomeText"] to string.Empty.
Why is text area value not updated to empty string after post action?
Here are the actions:
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Message(int ID)
{
ViewData["ID"] = ID;
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Message(int ID, string SomeText)
{
// save Text to database
SaveToDB(ID, SomeText);
// set the value of SomeText to empty and return to view
ViewData["SomeText"] = string.Empty;
return View();
}
And the corresponding view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm())
{ %>
<%= Html.Hidden("ID", ViewData["ID"])%>
<label for="SomeText">SomeText:</label>
<%= Html.TextArea("SomeText", ViewData["SomeText"]) %>
<input type="submit" value="Save" />
<% } %>
</asp:Content>
The problem is that your ModelState is re-filled with the posted values.
What you can do is clear it on the Action that has the Post attribute :
ModelState.Clear();
The problem is the HtmlHelper is retrieving the ModelState value, which is filled with the posted data. Rather than hacking round this by resetting the ModelState, why not redirect back to the [get] action. The [post] action could also set a temporary status message like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Message(int ID, string SomeText)
{
// save Text to database
SaveToDB(ID, SomeText);
TempData["message"] = "Message sent";
return RedirectToAction("Message");
}
This seems to me like more correct behaviour.
The html helpers read the value from the ModelState. And there's no elegant way to override this behaviour.
But if you add this line after SaveToDB(ID, SomeText), it should work :
ModelState["SomeText"].Value =
new ValueProviderResult("", "", CultureInfo.CurrentCulture);
I tried everything, but only worked when I did something like this:
ModelState.Clear();
//This will clear the address that was submited
viewModel.Address = new Address();
viewModel.Message = "Dados salvos com sucesso!";
return View("Addresses", ReturnViewModel(viewModel));
Hope this helps.
Instead of using ModelState.Clear() which clears the whole modelstate, you can do ModelState.Remove("SomeText"), if you want to. Or render the Input without the htmlhelper-extensions.
They are designed to take the Value from ModelState instead of the Model (or viewdata).
That is a clientside behavior. I would recommend using javascript. If you use JQuery, you can do it like this:
<script type="text/javascript">
$(function(){ $("#SomeText").val("");});
</script>
I don't use Javascript anymore, but I believe in regular JS that it is like:
document.getElementById("SomeText").value = "";
(You would do this on one of the load events.
<body onload="...">
Hope this helps.
I am fairly certain the textarea is grabbing the value from the Request.Form under the hood since ViewData["SomeText"] is empty.
Is it possible that the model state has been updated with an error? I believe that it will pull the attempted value from the model state rather than from view data or the model if the model state isn't valid.
EDIT:
I'm including the relevant section of the source code from the TextArea HtmlHelper extension below. It appears to me that it does exactly what I expected -- if there has been a model error, it pulls the value from the model state, otherwise it uses it from ViewData. Note that in your Post method the "SomeText" key shouldn't even exist until you set it, i.e., it won't be carried forward from the version of the code that responds to the GET.
Since you explicitly supply a value to the ViewData, useViewData should be false, attemptedValue should be false unless an error has been set in the model state.
// If there are any errors for a named field, we add the css attribute.
ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(name, out modelState)) {
if (modelState.Errors.Count > 0) {
tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
}
}
// The first newline is always trimmed when a TextArea is rendered, so we add an extra one
// in case the value being rendered is something like "\r\nHello".
// The attempted value receives precedence over the explicitly supplied value parameter.
string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.SetInnerText(Environment.NewLine + (attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : value)));
return tagBuilder.ToString(TagRenderMode.Normal);
Do s.th. like this:
add:
ModelState.Clear();
before the return statement of the submit buttons action method. Works for me. It could work for you.
So I am working on a MVC which is basically three steps.
Create a view for each step i.e.
StepOne
StepTwo
StepThree
On step one and two I ask the users to enter some details.
All the values for the multiple step I store in one Model.
And getting from StepOne to StepTwo is fine. Certain values in my model are being set and maintained.
But on StepTwo when I do my second httppost and pass the model, it seems to just create a new instance of the model and values from stepone are not maintained.
<% using (Html.BeginForm("StepTwo", "Home", FormMethod.Post, new { id = "restrictionForm" })) { %>
<%: Html.AntiForgeryToken() %>
<div id="wrapping" class="clearfix">
<h3>Postcode Restriction Type : </h3>
<%= Html.DropDownListFor(x => x.SelectedRestriction, Model.RestrictionTypes,"Select Restriction...", new { #class = "selmenu required" }) %>
<h3>Restriction Description : </h3>
<%= Html.TextBoxFor(m => m.RestrictionDescription, new { #class = "txtblock required" }) %>
</div>
<section id="buttons">
<input type="submit" value="Submit" id="submitBtn" />
</section>
And in my controller
On Page Load my Model is still intact and still maintains values from previous step.
[Authorize]
public ActionResult StepTwo(PostcodesModel model)
{
var summaryMessage = "";
model.SummaryMessage = summaryMessage;
model.RestrictionTypes = _Provider.GetRestrictionTypes();
return View(model);
}
But at the Httppost, the model has lost values and seems to have created new instance of model.
[Authorize]
[HttpPost]
[ActionName("StepTwo")]
[ValidateAntiForgeryToken]
public ActionResult StepTwoPost(PostcodesModel model)
{
return View(model);
}
Any idea how I can maintain model between Http Posts ?
It seems from your question that you believe models persist across requests. This is not true.
You either pass information to the view via your model from the controller, or submit values from your view to your controller and MVC handles this by binding html form inputs to your View Model.
If you want to persist your View Model across each step you need to take the values accepted and copy them into a new model (or directly inject it) when calling your new view.
Something like this (I just typed this up off my head so its not clean but should give you an idea):
[HttpGet]
public ActionResult StepOne()
{
var model = new MyNewModel();
return View(model);
}
/* NOTE THE MODEL PASSED BACK HERE IS NOT THE EXACT SAME OBJECT
AS THE ONE CREATED IN THE GET ACTION ABOVE, MODEL BINDING HAS OCCURRED
TO READ YOUR FORM INPUTS AND MATCH THEM TO A NEW MODEL WHICH IS EXPECTED */
[HttpPost]
public ActionResult StepOne(MyNewModel model)
{
if (ModelState.IsValid)
{
// Do something here
// pass model to new view
TempData["model"] = model;
return RedirectToAction("StepTwo");
}
return View(model);
}
[HttpGet]
public ActionResult StepTwo()
{
MyNewModel model;
if (TempData["model"] != null)
{
model = (MyNewModel) TempData["model"];
// make some changes if necessary
model.MyProperty = 2;
return View(model);
}
return RedirectToAction("StepOne");
}
I think you can also keep your model in Session ( per application ) or in a ViewState ( per page ).
Every time you make a post you upgrade the session. It's also optimal because on the client side you receive only a session identifier.
Some differences between Session and Viewstate:
Session is per application, while ViewState is per page
Session sends to the client side only a session identifier, while ViewState sends an ecrypted text
I have a model that I am using in my view that is full of data. This data is then edited in the view. I need to figure out a way to resubmit this data back over to the controller.
Here is what I have so far.
VIEW:
#using (Html.BeginForm("DownloadCSV", "Respondents", FormMethod.Post))
{
#Html.HiddenFor(m => m.FilterSet)
<div class="btn btn-default pull-right" id="dispoCSV" onclick="$('#csvFormSubmit').click()">
<i class="icon-file-alt"></i> Disposition Report
</div>
<input id="csvFormSubmit" type="submit" style="display:none;" />
}
CONTROLLER:
[HttpPost]
public ActionResult DownloadCSV(RespondentsFilterSet model)
{
string csv = "Charlie, Test";
return File(new System.Text.UTF8Encoding().GetBytes(csv), "text/csv", "DispositionReport.csv");
}
MODEL:
public class RespondentsFilterSet : ColdListFilterSet
{
public List<int> OwningRecruiters { get; set; }
public List<int> RecruitingGroups { get; set; }
public override bool HasAtLeastOneFilter()
{
return base.HasAtLeastOneFilter() || OwningRecruiters.IsNotNullOrEmpty() || RecruitingGroups.IsNotNullOrEmpty();
}
public override ExpressionBase ToExpression()
{
var expr = base.ToExpression();
var expressions = expr == null ? new List<ExpressionBase>() : new List<ExpressionBase> { expr };
if (OwningRecruiters.IsNotNullOrEmpty())
{
expressions.Add(new InExpression<int> { Field = Create.Name<Respondent>(r => r.RecruitedBy), Values = OwningRecruiters });
}
if (RecruitingGroups.IsNotNullOrEmpty())
{
expressions.Add(new InExpression<int> { Field = Create.Name<Respondent>(r => r.RecruitingGroupId), Values = RecruitingGroups });
}
return expressions.Count == 0 ? null : BuildAndExpressionFromList(expressions);
}
}
I realize that my controller is not not finalized. I just have displaying some static csv. But I can't figure out why my model from my view is always null when returned to the controller.
Just look at your form. There's not a single input element (except the submit button). You cannot expect to get anything back on the server in this case.
Please read about HTML and how forms work in HTML. In HTML forms you have input fields. Things like text fields, hidden fields, checkboxes, radio buttons, ... - fields that the user interacts with get submitted to the server.
The fact that you have made your HttpPost controller action take some model as parameter doesn't mean at all that this parameter will be initialized. In ASP.NET MVC you have a default model binder. This model binder looks at what gets sent to the server as values when the form is submitted and uses the names of the fields to bind to the corresponding properties. Without input fields in the form, nothing gets sent to the server. Just use the debugging tools built into your web browser to inspect what exactly gets sent to the server.
Contrary to classic ASP.NET WebForms, ASP.NET MVC is stateless. There's no ViewState to remember your model.
So all this rambling is to say that you should read more about HTML forms first and understand the stateless nature of the web before getting into ASP.NET MVC. As far as your particular problem is concerned, well, assuming the user is not supposed to modify any values of the view model in your view throughout some input fields, you could simply include a hidden field containing the id of your model in the form. This id will then be sent to your POST controller action as parameter and you could use it to retrieve your original model from wherever it is stored (I guess a database or something).