ViewBag value is completely ignored while running ASP.NET MVC4 web page.
Here is the source for above image.
Even though I am checking if the ViewBag.SearchResultsJson is null or empty, ViewBag still isn't written in the output.
<script>
#{
HtmlString jsonText = new HtmlString("");
if (!string.IsNullOrWhiteSpace(ViewBag.SearchResultsJson))
{
jsonText = Html.Raw(ViewBag.SearchResultsJson);
}
}
$(document).ready(function() {
var json = #jsonText;
app.value('searchResultsJson', json);
})
</script>
What am I missing here?
According your code, an empty jsonText is a valid scenario so I won't focus on why this variable is empty.
The reason for your error is you're not wrapping #jsonText with quotes to render a valid string on Javascript side.
You should change
var json = #jsonText;
by
var json = '#jsonText';
Related
Quite simple question. I have the following code
#Html.Raw(following.Description).ToString()
when this comes from database it has some markup in it (its a forum post but i want to show a snippet in the list without the markup
is there any way to remove this and replace this line or shall I just regex it from the controller?
Here is a utility class extension method that is able to strip tags from fragments without using Regex:
public static string StripTags(this string markup)
{
try
{
StringReader sr = new StringReader(markup);
XPathDocument doc;
using (XmlReader xr = XmlReader.Create(sr,
new XmlReaderSettings()
{
ConformanceLevel = ConformanceLevel.Fragment
// for multiple roots
}))
{
doc = new XPathDocument(xr);
}
return doc.CreateNavigator().Value; // .Value is similar to .InnerText of
// XmlDocument or JavaScript's innerText
}
catch
{
return string.Empty;
}
}
Long story short, I'm trying to get the output from JsonConvert.SerializeObject to be sanitized without having to modify the contents of the saved data.
I'm working on an app that has the following markup in the view:
<textarea data-bind="value: aboutMe"></textarea>
If I save the following text, I run into problems:
<script type="text/javascript">alert("hey")</script>
The error I get in FF:
The relevant part of the offending rendered text:
$(document).ready(ko.applyBindings(new
MyProfileVm({"profileUsername":"admin","username":"Admin","aboutMe":"alert(\"hey\")","title":"Here's a
short self-bio!
:)","thumbnail":"https://i.imgur.com/H1HYxU9.jpg","locationZip":"22182","locationName":"Vienna,
VA"
And finally - at the bottom of my view:
<script type="text/javascript">
$(document).ready(ko.applyBindings(new MyProfileVm(#Html.Raw(JsonConvert.SerializeObject(Model, new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() })))));
</script>
Here, I'm passing the model that I get from the MVC controller into the js ViewModel for knockout to map into observable data. The Raw encoding seems to be the problem, but I'm not sure how to go about handling it.
To be clear, I'm getting data from the server, and outputting it to the client, which is mucking up the JSON/KO combo.
The problems is that you cannot have a closing </script> tag inside a JavaScript string literal because the browser interprets it as then end of the script block. See also: Script tag in JavaScript string
There is no builtin function in Asp.Net what could handle it on the server side you before outputting your generated script you need to replace the </script> to something else:
<script type="text/javascript">
$(document).ready(ko.applyBindings(new MyProfileVm(#Html.Raw(
JsonConvert.SerializeObject(Model,
new JsonSerializerSettings() {
ContractResolver = new CamelCasePropertyNamesContractResolver()
}).Replace("</script>", "</scripttag>")
))));
</script>
Of course if you will need this in multiple place you can move this logic into a helper/extension method, like:
public static class JavaScriptExtensions
{
public static string SerializeAndEscapeScriptTags(this object model)
{
return JsonConvert.SerializeObject(model,
new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}).Replace("</script>", "</scripttag>");
}
}
And use it with:
#using YourExtensionMethodsNamespace
<script type="text/javascript">
$(document).ready(ko.applyBindings(new MyProfileVm(#Html.Raw(
Model.SerializeAndEscapeScriptTags()))));
</script>
And on the JavaScript side in your Knockout viewmodel you need to replace back the </script> tag before the usage:
var MyProfileVm = function(data) {
//...
this.aboutMe = ko.observable(
// you need `"</scr"+ "ipt>"` because of the above mentioned problem.
data.aboutMe.replace(/<\/scripttag>/g, "</scr"+ "ipt>"));
}
Of course you can also create a helper function for this, like:
function fixScriptTags(data) {
for(var prop in data) {
if (typeof(data[prop]) == "string") {
data[prop] = data[prop].replace(/<\/scripttag>/g, "</scr"+ "ipt>");
}
//todo check for complex property values and call fixScriptTags recursively
}
return data;
}
And use it with:
ko.applyBindings(new ViewModel(fixScriptTags(data)));
Demo JSFiddle.
I've had a similar problem, it came from using knockout.js to get input from a <textarea> just like you did. Everything was fine on the "create" part, but once I put the data back into an action via #Html.Raw(...), it turned out to contain linefeed and carriage-return characters that broke the json string.
So I added something like this:
// Regex to replace all unescaped (single) backslashes in a string
private static Regex _regex = new Regex(#"(?<!\\)\\(?!\\)", RegexOptions.Compiled);
(I know it doesn't handle "\\\", but that doesn't appear from knockout)
Then I build my anonymous classes and do this:
var coJson = JsonHelper.Serialize(co);
var coJsonEsc = _regex.Replace(coJson, #"\\")
Maybe this can help you. I found it by breaking in the razor view and looking at the strings.
This problem also appears with unesacped tabs (\t) and possibly other escape sequences.
Say I have a action method.......
public ActionResult DisplayXml(int viewId, Dictionary<string,string> parameter, string dataFormat )
{
string xml = "";
return Content(xml, "text/xml");
}
and in view I did.......
<iframe title="Xml" class="ResultDisplay"
src = "#Url.Action("DisplayXml", "OutputData", new {viewId = Model.ViewId, parameter = Model.Parameter, dataFormat = Model.DataFormat })">
</iframe>
Here parameter is a dictionary and I am getting null.
How I can send it?????
You're trying to pass and arbitrary dictionary as a parameter in a querystring?
Its a pretty unusual requirement to need to serialize the contents of a dictionary to a query string parameter and back again. When generating querystring parameters, MVC will just call .ToString() on the values, and the Dictionary<,> object just uses the default implementation (which returns it's type)
Since this requirement is so uncommon, there's nothing built in to do this.. You can quite easily serialize the dictionary yourself to a string (perhaps json?) and then, change the parameter variable in your action to be a string. You'll have to deserialize the value back to a dictionary after that.
Before I provide much more of an example, I want to check you're absolutely sure this is what you want to do
Update:
Here is way of doing that (requires json.net):
public ActionResult DisplayXml(int viewId, string parameterJson, string dataFormat )
{
var parameter = JsonConvert.DeserializeObject<Dictionary<string,string>>(parameterJson);
string xml = "";
return Content(xml, "text/xml");
}
And use:
<iframe title="Xml" class="ResultDisplay"
src = "#Url.Action("DisplayXml", "OutputData", new {viewId = Model.ViewId, parameterJson = Newtonsoft.Json.JsonConvert.SerializeObject(Model.Parameter), dataFormat = Model.DataFormat })">
</iframe>
I have multiselect jquery plagin (Choosen) and when I use it in 'Multiple Select' mode I expect in controller next values:
posted string = 'value1,value2...'
really have
posted string = 'value2'
only if I reffer directly to FormCollection I'll get expected values as below:
[HttpPost]
public ActionResult TagSearech(/*string tagSelect*/FormCollection c)
{
// only one value here
// string[] names = tagSelect.Split(',');
// as expected: value1,....
string expectedValue = c['tagSelect'];
return View();
}
I cant understand what might cause this behavior.
EDIT
Here is View:
#using (Html.BeginForm("TagSearech", "Tag"))
{
#Html.DropDownList("tagSelect", Model, new { #class = "chzn-select", data_placeholder = "tag names", multiple = "" })
<input type="submit"/>
}
MVC will attempt to bind the input data on the URL into the model. I haven't seen how Chosen.js posts the data back to the server, but essentially its coming in in the wrong format, so MVC binds the first element it sees to the string Model.
The FormsCollection retrieves all of the data that was posted in the URL, which is why all of your selected values can be seen there.
Did you try changing the incoming model from string to string[], and see if all of the items are bound to the array?
I have a view from which I call another view to render some json inside a script tag in my html:
public ActionResult App()
{
return View();
}
public JsonResult SomeJsonData()
{
// ... here goes the code that generates the model
return Json(model, JsonRequestBehavior.AllowGet);
}
inside my App.cshtml file I have something like this:
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController");
</script>
The problem is that sometimes when I reload the page in the browser (using Chrome 20 right now) it shows all the markup, and if I go to the Network tab in the developer tools I can see that the Content-Type of the page request was of type "application/json". If I just reload the page then it loads correctly (the content type is "text/html" as it should be).
Any idea on why does this happen? or what am I doing wrong?
When you return a JsonResult you are modifying the response Content-Type to application/json. So you first invoke the App controller action which returns a View and obviously sets the Content-Type to text/html and inside the returned view you call the SomeJsonData action which craps on the previous content type and modifies it to application/json. Of course the last one wins and that's what the user agent sees at the end of the day: application/json.
So, here's how to proceed:
public ActionResult App()
{
// ... here goes the code that generates the model
var model = ...
return View(model);
}
and in your strongly typed view:
#model MyViewModel
<script type="text/javascript">
var myJsonData = #Html.Raw(Json.Encode(Model));
</script>
Actually I just found another related question
calling #Html.Action for JsonResult changes my response type in parent template
I couldn't find anything before I posted.
The approach I'm gonna take is just changing the content type when returning the json data:
public JsonResult SomeJsonData(bool returnAsHtml = false)
{
// ... here goes the code that generates the model
return returnAsHtml ?Json(model, "text/html", JsonRequestBehavior.AllowGet) : Json(model, JsonRequestBehavior.AllowGet);
}
and on App.cshtml
<script type='text/javascript'>
var myJsonData = #Html.Action("SomeJsonData", "MyController", new {returnAsHtml = true});
</script>
And I'm adding also a flag to allow calling the actionmethod from other places that are expecting an application/json response.
Like other have said, the action that is returning JSON is changing the content-type of the response.
I was able to work around this by using a HtmlHelper to place the JSON into the page. I am avoiding using the controller and action; my HtmlHelper is calling a static method. This solution probably won't work in all cases and is kind of a hack, but you can avoid having to put the data into a bunch of view models this way.
namespace System.Web.Mvc.Html
{
public static class JsonDataProviderHelper
{
public static MvcHtmlString JsonDataProvider(this HtmlHelper helper, JsonDataType jsonDataType)
{
switch (jsonDataType)
{
case JsonDataType.YARDSALE_MINI_CALENDAR:
var yardsales = SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId);
return new MvcHtmlString(JsonConvert.SerializeObject(SalesEventCrud.GetByMonthForJavascript(null, MvcApplication.CurrentPortalId)));
default:
return new MvcHtmlString("");
}
}
public enum JsonDataType
{
YARDSALE_MINI_CALENDAR
}
}
}
I am using json.net for serialization. I have the switch statement because I plan to use this helper to return JSON for other situations also.
And in the page:
<script type="text/javascript">
jQuery(document).ready(function () {
setupSmallSidebarCalendar(#Html.JsonDataProvider(JsonDataProviderHelper.JsonDataType.YARDSALE_MINI_CALENDAR));
});
</script>
In the page, the data is written in straight JSON, so no other parsing / massaging of the data is needed.
#Daran's found the cause - the ContentType gets overwritten with whatever was done last. His solution should work, however if you still like the Action method pattern, you should be able to do something like the following and simply change the ContentType before you return. It's still JSON data, but the content type plays nice with HTML views.
[HttpPost]
public JsonResult GetJson(int someId, int foobar)
{
JsonResult result = CreateResult(someId, foobar);
result.ContentType = "text/html";
return result;
}
The downside is that this is, well, a bit weird and/or unexpected behavior. I doubt this will work if you called the endpoint via AJAX, for example. But it looks like it would almost work and that would probably cause some confusion if it's in a shared codebase. For that reason, if you do this, it may be worth naming the endpoint in such a way that it isn't misused. You could even go so far as to create a new type of Result that is designed to work this way rather than use JsonResult.