Changes to VM after POST not reflected in the Page [duplicate] - asp.net-mvc

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.

Related

Route to a POST method with model and id

I want to pass in two Ids to my method. The method is called DeleteAttendee and is on my SessionController in the Training area.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult DeleteAttendee(int sessId, int attId)
I have created a link to get there that takes you to https://localhost:<port>/Training/Session/DeleteAttendee?sessId=1&attId=3.
<a asp-action="DeleteAttendee" asp-route-attId="#item.Attendee.Id" asp-route-sessId="#Model.Id">Delete</a>
Using the default routing, this page can't be found. What do I need to change or set up to route to this method?
Edit: Apparently the problem is that the link is performing a GET, but I need it to POST. How do I change it?
I think you can accomplish what I want to do with a button control. It will actually work better for me now if I can pass in the model and a specific id. I tried the button below. It looks correct in the markup, but the id keeps getting replaced with the sessionId when the button is clicked.
<button formaction="/Training/Session/DeleteAttendee/#item.Id" formmethod="post">Edit</button>
Can you use a ActionLink instead?
#Html.ActionLink("Delete", "DeleteAttendee", "Session", new { sessId = Model.Id, attId = item.Attendee.Id })
I ended up using a submit button that calls javascript and added the value to the viewmodel to get this done.
On the page:
<input type="hidden" asp-for="SelectedAttendeeId" />
<input type="button" onclick="DeleteAttendee(#item.Id)" value="E" />
In javascript:
function DeleteAttendee(attendeeId) {
var selectedAtt = $('#SelectedAttendeeId');
selectedAtt.val(attendeeId);
var model = $('#frmSession').serialize();
$.post('/Training/Session/DeleteAttendee', model, function (data) {
// success logic here
});
}

Render string as MVC View

I have a simple Email Composer class where I get all Application Emails content from.
In this example, it sends the products to the email.
Now, I want to print them as well, and I'm trying to re-use the same method to grab the full html from the Email Composer and output it to a View.
the controller action
public ActionResult PrintRules()
{
var products = rep.ListAllProductsByCompanyId(currentCompany.company_id);
string body = mail.GetProductRules(products);
ViewBag.email = HttpUtility.HtmlEncode(body);
return View();
}
the view is:
#{
Layout = null;
string email = HttpUtility.HtmlDecode(ViewBag.email);
}
#Html.Raw(email)
<script>
window.print();
</script>
If I pass the body as a Model I do get errors on the parser, so I'm using the ViewBag instead.
as outputs:
#Html.Raw(email) will output nothing at all
#Html.Raw(email.Length) will output 17463
#email will output the code but the browser outputs it, does not parse it (image below)
What am I missing? I know it must be a really simple thing, but I'm totally blank...
browser output from using #email
Try this
#(new HtmlString(mystring))
or
#MvcHtmlString.Create(ViewBag.Stuff)

How to remove list from validation summary

I added ValidationSummary Html helper for my View Model class which has 5 required fields. And it works got nice red words missing 1, missing 2 etc.
But I need to display just one message not five of them (something like: "Your input is not valid."). Can this be done with ValidationSummary?
You have two options (at least):
Either use the validation summary and exclude property errors:
#Html.ValidationSummary(true, "The input is not valid")
or associate a error message with a custom key in your action:
if (!ModelState.IsValid)
{
ModelState.AddModelError("myerrorsummary", "The input is not valid");
}
and display it on your page:
#Html.ValidationMessage("myerrorsummary")
You can try skipping the helpers if all you want to do is simply display a message if the ModelState is not valid. Simply check the ModelState within ViewData and that should work.
#if (!ViewData.ModelState.IsValid)
{
<p>Your input is not valid.</p>
}
If you look at MVC3 source code you'll see that, currently, if you use ValidationSummary with excludePropertyErrors=true while having UnobtrusiveJavaScriptEnabled, there won't be any validation summary rendered.
I was able to display just a single message with MVC3 with UnobtrusiveJavascript enabled, for client side validation. Don't use #Html.ValidationSummary at all, and render:
#{
//Show the message when there are server side errors
ViewBag.ValidationSummaryClass = ViewData.ModelState.IsValid ? "validation-summary-valid" : "validation-summary-errors";
}
<div class="#ViewBag.ValidationSummaryClass" data-valmsg-summary="true">
<span>Before you can continue, please make sure you have completed the mandatory fields highlighted above.</span>
<ul style="display:none"/>
</div>
Notice the display:none, unobtrusive javascript still fills the list with error messages, but they are kept hidden.
One brute force approach I've used in MVC3:
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Some contextual error message");
}
and display it on your page:
<% if(!ViewData.ModelState.IsValid) { %>
<span class="error"><%=ViewData.ModelState[String.Empty].Errors[0].ErrorMessage %> </span>
<% } %>

What are the security reasons for not allowing get requests with MVC/ajax? [duplicate]

As part of the ASP.NET MVC 2 Beta 2 update, JSON GET requests are disallowed by default. It appears that you need to set the JsonRequestBehavior field to JsonRequestBehavior.AllowGet before returning a JsonResult object from your controller.
public JsonResult IsEmailValid(...)
{
JsonResult result = new JsonResult();
result.Data = ..... ;
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
return result;
}
What is the reasoning behind this? If I am using JSON GET to try and do some remote validation, should I be using a different technique instead?
The reason for the DenyGet default is on MSDN with a link to Phil Haack's blog for further details. Looks like a Cross-Site scripting vulnerability.
HTTP GET is disabled by default as part of ASP.NET's Cross-Site Request Forgery (CSRF/XSRF) protections. If your web services accept GET requests, then they can be vulnerable to 3rd party sites making requests via <script /> tags and potentially harvesting the response by modifying JavaScript setters.
It is worth noting however that disabling GET requests is not enough to prevent CSRF attacks, nor is it the only way to protect your service against the type of attack outlined above. See Robust Defenses for Cross-Site Request Forgery for a good analysis of the different attack vectors and how to protect against them.
I also had your problem when I migrated my MVC website from Visual Studio 2008 to Visual Studio 2010.
The main aspx is below, it has an ViewData which calls a Category Controller in order to fill up ViewData["Categories"] with SelectList collection. There's also a script to call a Subcategory Controller to fill up the second combo with javascript. Now I was able to fix it adding up AlloGet attribute on this second controller.
Here's the aspx and javascript
<head>
<script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#CategoryId").change(function () {
var categoryId = $(this)[0].value;
$("#ctl00_MainContent_SubcategoryId").empty();
$("#ctl00_MainContent_SubcategoryId").append("<option value=''>-- select a category --</option>");
var url = "/Subcategory/Subcategories/" + categoryId;
$.getJSON(url, { "selectedItem": "" }, function (data) {
$.each(data, function (index, optionData) {
$("#ctl00_MainContent_SubcategoryId").append("<option value='" + optionData.SubcategoryId + "'>" + optionData.SubcategoryName + "</option>");
});
//feed our hidden html field
var selected = $("#chosenSubcategory") ? $("#chosenSubcategory").val() : '';
$("#ctl00_MainContent_SubcategoryId").val(selected);
});
}).change();
});
</script>
<body>
<% using (Html.BeginForm()) {%>
<label for="CategoryId">Category:</label></td>
<%= Html.DropDownList("CategoryId", (SelectList)ViewData["Categories"], "--categories--") %>
<%= Html.ValidationMessage("category","*") %>
<br/>
<label class="formlabel" for="SubcategoryId">Subcategory:</label><div id="subcategoryDiv"></div>
<%=Html.Hidden("chosenSubcategory", TempData["subcategory"])%>
<select id="SubcategoryId" runat="server">
</select><%= Html.ValidationMessage("subcategory", "*")%>
<input type="submit" value="Save" />
<%}%>
here's my controller for subcategories
public class SubcategoryController : Controller
{
private MyEntities db = new MyEntities();
public int SubcategoryId { get; set; }
public int SubcategoryName { get; set; }
public JsonResult Subcategories(int? categoryId)
{
try
{
if (!categoryId.HasValue)
categoryId = Convert.ToInt32(RouteData.Values["id"]);
var subcategories = (from c in db.Subcategories.Include("Categories")
where c.Categories.CategoryId == categoryId && c.Active && !c.Deleted
&& c.Categories.Active && !c.Categories.Deleted
orderby c.SubcategoryName
select new { SubcategoryId = c.SubcategoryId, SubcategoryName = c.SubcategoryName }
);
//just added the allow get attribute
return this.Json(subcategories, JsonRequestBehavior.AllowGet);
}
catch { return this.Json(null); }
}
I don't know if this is the reason they chose to change that default, but here's my experience:
When some browsers see a GET, they think they can cache the result. Since AJAX is usually used for small requests to get the most up-to-date information from the server, caching these results usually ends up causing unexpected behavior. If you know that a given input will return the same result every time (e.g. "password" cannot be used as a password, no matter when you ask me), then a GET is just fine, and browser caching can actually improve performance in case someone tries validating the same input multiple times. If, on the other hand, you expect a different answer depending on the current state of the server-side data ("myfavoriteusername" may have been available 2 minutes ago, but it's been taken since then), you should use POST to avoid having the browser thinking that the first response is still the correct one.

Validation detected dangerous client input - post from TinyMCE in ASP.NET

I get this error when I post from TinyMCE in an ASP.NET MVC view.
Error:
Request Validation has detected a potentially dangerous client input value, and processing of the request has been aborted
From googling, it says to just add a validateRequest in the Page directive at the top which I did, but I STILL get this error. As you can see, below is my code in the view:
<%# Page validateRequest="false" Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>
Try this solution. simply add to TinyMce control
tinyMCE.init({
...
encoding : "xml"
});
http://wiki.moxiecode.com/index.php/TinyMCE:Configuration/encoding
http://blog.tentaclesoftware.com/archive/2010/07/22/96.aspx
Try using the [AllowHtml] attribute in your model.
class MyModel{
[AllowHtml]
public string Content{get;set;}
}
Use the decorator [ValidateInput(false)].
You will then want to write a HTMLEncode method to make it safe.
Let me know if you want me to post the one I use.
Added the Encode I use
public static class StringHelpers
{
public static string HtmlEncode(this string value)
{
if (!string.IsNullOrEmpty(value))
{
value = value.Replace("<", "<");
value = value.Replace(">", ">");
value = value.Replace("'", "&apos;");
value = value.Replace(#"""", """);
}
return value;
}
public static string HtmlDecode(this string value)
{
if (!string.IsNullOrEmpty(value))
{
value = value.Replace("<", "<");
value = value.Replace(">", ">");
value = value.Replace("&apos;", "'");
value = value.Replace(""", #"""");
}
return value;
}
}
Annoyingly in version 4 of tinymce they seem to have removed the encoding: xml option.
I ended up using a javascript HTML encoding function from this answer, and on my submit button I encode the contents of the textarea before the form submits, by using tinymce's getContent and setContent methods
I had the same problem. I didn't want to disable ASP.NET MVC validation feature, so I kept looking until I reached this solution:
At the tinyMCE plugin code encode your content (I'm using the older version)
tinyMCE.init({
...
encoding: "xml"
});
And after this I didn't get any more the application validation error. Then I came up with another problem when I edited my form the code would come up with the html tags
<strong>My input value</strong>
instead of this
My input value
So, I had to decode the html for that field when getting my values at the Controller, like this:
...
entity.field = HttpUtility.HtmlDecode(entity.field);

Resources