Why is Orchard auto-encoding strings to the view? - c#

In my driver, I am passing the dynamic object to the view, but it is automatically encoding all my strings which is mangling the links I am constructing in the view.
Here is what I am trying to do:
public class SomeWidgetDriver : ContentPartDriver<SomeWidgetPart>
{
// GET
protected override DriverResult Display(SomeWidgetPart part, string displayType, dynamic shapeHelper)
{
return ContentShape("Parts_SomeWidget",
() => shapeHelper.Parts_SomeWidget(
AppUrl: part.AppUrl,
AppVersion: part.AppVersion,
RenderTo: part.RenderTo,
Test: "xxxx&"));
}
}
When I add the below to the view:
#Model.Test
It renders like this:
xxxx&
Is there a way to stop this from happening? I am trying to get it to exactly render "xxxx&".

I don't think this has to do with Orchard at all.
In Razor everything is html encoded.
Did you try:
#Html.Raw(Model.Test)
?

Related

Html.ActionLink Fires on Load Unintentionally

I have a Partial View for showing the list of products. In this view I have ActionLinks above each column to handle sorting. There are total 4 of them and they are all identical to below one:
<div class="col-xs-2">
#Html.ActionLink(Html.DisplayNameFor(m => m.ProductName).ToHtmlString(), "Products", AppendQueryString.AppendQuery("sortOrder", ViewData["ProductNameSort"].ToString()))
</div>
In order to be able to append the current URL without losing any previously querystrings, I created a static class AppendQueryString and a method in it called AppendQuery. The class looks like below:
public static RouteValueDictionary AppendQuery(string key, string value)
{
RouteValueDictionary routeValues = new RouteValueDictionary();
var context = HttpContext.Current;
var req = context.Request.QueryString;
foreach (string qkey in req)
{
routeValues[qkey] = req[qkey];
}
if (!routeValues.ContainsKey(key))
{
routeValues.Add(key, value);
}
else
{
routeValues.Remove(key);
routeValues.Add(key, value);
}
return routeValues;
}
First I didn't realize what was the problem but something was trying to rewrite the same key value sent into this method so I had to use that ugly if-else there to add the key-value into the RouteValueDictionary properly and continue on with the project.
However, now the problem is obvious that for some reason, on page loading these four ActionLinks I have are being fired one after another so the AppendQuery method is being called 4 times on page load. The main View that renders this partial view is being called only once I am sure about it. Any of you have any idea why is this happening ? Thanks in advance.

ASP.Net MVC Refresh Page without destroying ViewModel

I want to create a multilingual webpage. To switch between languages I've got a dropdown on my page. If the change event of the dropdown gets fired the Method called "ChangeLanguage" in my Controller is called.
public ViewModels.HomeViewModel HVM { get; private set; }
// GET: Home
public ActionResult Index()
{
this.HVM = new ViewModels.HomeViewModel();
return View(this.HVM);
}
public JsonResult ChangeLanguage(int id) {
return Json(new {Success = true});
}
Now I'd like to to change my "SelectedLanguage" Property in my ViewModel (HVM) - but the Reference is null. May anyone explain why HVM is null in my ChangeLanguage Method?
After my SelectedLanguage Property is changed I'd like to reload my whole page to display it's texts in another language
e.g.
#model ViewModels.HomeViewModel
<html>
<div class="HeaderText">
Text = #{
#Model.TextToDisplay.Where(o =>
o.Language.Equals(Model.SelectedLanguage)).First()
}
</div>
Here's what I want to do in PseudoCode:
PseudoCode:
public JsonResult ChangeLanguage(int id) {
this.HVM.SelectedLanguage =
this.HVM.AvailableLanguages.Where(o =>
o.ID.Equals(id)).First();
Page.Reload();
return Json(new {Success = true});
}
May anyone explain why HVM is null in my ChangeLanguage Method?
Adhering to stateless nature of HTTP protocol, all (unless explicitly added into request header) requests (MVC method calls) loose state data associated with it. Web server treats every request a new request and creates new instances of classes right from controller itself.
In your case since it is a new request, controller has a HVM property defined but in ChangeLanguage it is not instantiated (it gets instantiated only into Index method which is not called when you invoke ChangeLanguage) hence it is null.
After my SelectedLanguage Property is changed I'd like to reload my
whole page to display it's texts in another language.
Option 1: Refresh page
Simple option to implement. Pass the language selection to server, server will return a new view with specific data. Drawback, whole page will refresh.
Option 2: Update view selectively
If option 1 is really not acceptable, then consider this option. There are multiple ways you can achieve it. Basically it involves either (a) breaking you view into partial view and update only the portion that is affect by selection or (b) bind data element with a JS object.
(a) - Not much need to be said for this.
(b) - Data binding can easily be done if you employ a JS library like KnockoutJS.
Change your methods to these methods , This trick will work for you =>pass your model to Change language from view. Also update JsonResult to ActionResult.
public ActionResult ChangeLanguage(ViewModels.HomeViewModel model,int id)
{
this.HVM.SelectedLanguage =
this.HVM.AvailableLanguages.Where(o =>
o.ID.Equals(id)).First();
return RedirectToAction("Index",model);
}
public ActionResult Index(ViewModels.HomeViewModel model)
{
if(model == null)
{
this.HVM = new ViewModels.HomeViewModel();
}
return View(this.HVM);
}

How to render derived types of a class differently?

I have an Item class. I have around 10-20 derivatives of it each containing different types of data. Now when it comes to rendering different types of Item, I'm forced to use likes of:
<div>
#if (Model is XItem)
{
... rendering logic 1 ...
}
#if (Model is YItem)
{
... rendering logic 2 ...
}
#if (Model is ZItem)
{
... rendering logic 3 ...
}
... goes on and on forever ...
</div>
Unfortunately #Html.DisplayFor() does not work in this case because the Model is type of Item, DisplayTemplates\Item.cshtml is displayed.
HTML helpers don't help either because of the same "if/is" chain.
I could incorporate rendering logic inside the classes themselves, and call #Model.Render() but they belong to business logic, not presentation. It would be a sin.
There is only one option of #Html.Partial(Model.GetType().Name) but it feels wrong. You expect a solution without meta-magic. Is there a better way?
Use Display Templates.
Inside your ~/Views/Shared/DisplayTemplates folder you can add a view with the same name as your type.
When you do #Html.DisplayFor(item) you'll get the view related to that specific type.
UPDATE
I just saw your comment RE DisplayFor so if this doesn't help i'll remove my answer.
I think your approach is fine. You can make it better with an extension method like this:
public static MvcHtmlString GetTypePartial<T>(this HtmlHelper htmlHelper, T model, string viewPrefix = "")
{
string typeName = typeof (T).Name;
string viewName = string.Concat(viewPrefix, typeName);
return htmlHelper.Partial(viewName, model);
}

Trying to use DropDownListFor without model getting "tree may not contain dynamic operation" error

I am trying to use a DropDownListFor<> in my LayoutTemple so I don't have access to a model. So what I did was in the #{} block at the top of the page I added FFInfo.DAL.SoloClassesContext db = new FFInfo.DAL.SoloClassesContext(); which calls an instance of the DBContext with the class I want to use. I then placed the List where I wanted using
#Html.DropDownListFor(
m => m.ID,
new SelectList(db.CultureCodes, "ID", "DisplayName"));
but when I run the code I get an error for the line m => m.ID. The error given is:
An expression tree may not contain a dynamic operation
I have never used this type of dropdown and am very new to MVC. Can anyone tell me what I am doing wrong and how to fix it?
I suggest some changes in your solution:
Instead of DropDownListFor() use just DropDownList()
#Html.DropDownList("CultureCode", new SelectList(db.CultureCodes, "ID", "DisplayName"))
Instead of accessing your database data in your view... which is very out of the standard and you are coupling the Views (usually HTML) with the database... you should put the query in your controller and put the data in the ViewBag collection.
So, in your Layout, instead of the code I suggested above, you should use:
#Html.DropDownList("CultureCode", (SelectList)ViewBag.Codes, "Select one...")
In your controller you load it as follow:
ViewBag.Codes = new SelectList(db.CultureCodes, "ID", "DisplayName");
EDIT:
You can do an action filter, to load or inject the CultureCodes in the ViewBag:
public class IncludeCultureCodesAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var controller = filterContext.Controller;
// IController is not necessarily a Controller
if (controller != null)
{
var db = new YourContext();
controller.ViewBag.Codes = new SelectList(db.CultureCodes, "ID", "DisplayName"));;
}
}
}
Then, in your Controller actions... you can decorate them with [IncludeCultureCodes]. So, the action with that attribute will load the collection of codes.
But I think is a better approach to load one time the layout (on Home/Index for example) and then use partial views. This way you only reload the layout going back to home... or other full view calls.

Using jquery templating with c#'s dynamics?

I didn't think this would be that hard, but I am trying to pass a class that has a dynamic for a property (which is currently being set as an expando object in the c#) into the View. In this view, a good bit is getting rendered w/ some jQuery Templating and I thought that if I had this in the c# code:
public dynamic SomeProperty {get;set;}
...
SomeProperty = new ExpandoObject();
SomeProperty.SomeValue = "5";
return View(TheClassThatContainsSomeProperty);
Such that in the jquery templating I could do:
${SomeProperty.SomeValue}
...and that it would hopefully work. It doesn't... If you inspect the response, you can see that it essentially gets sent over as a dictionary:
SomeProperty: [{SomeValue, Value:5}]
0: {Key:SomeValue, Value:5}
which leads to (I guess) my next question: Is there an easy way to access dictionaries in jquery templating? I did try this:
${SomeProperty["SomeValue"]}
to no avail either. At this point the only thing I know to do is to leverage the ability to put a function in the template (as copied here from the jquery website):
Template:
<tr><td>${getLanguages(Languages, " - ")}</td></tr>
Code:
function getLanguages( data, separator ) {
return data.join( separator );
}
So am I over complicating this? Can I easily either 1) access a dynamic value from jquery template or 2) Easily lookup a value from a dictionary in jquery template?
ExpandoObject derives from IEnumerable<KeyValuePair<string, Object>>, and most serializers will recognize a dynamic as this type when you assign an ExpandoObject. This is why you see an array type on the javascript side with named Key::Value pairs.
ExpandoObject Class (System.Dynamic) # MSDN
One alternative to using ExpandoObject is to use C# anonymous types. When serialized to json, these map field by field as you expect.
Anonymous Types (C# Programming Guide) # MSDN
It is possible to access values declared with dynamic from jQuery, but most likely you won't be returning a MVC View() with the model to be consumed with jQuery, as any server-side view template engine (razor, etc.) can already perform the same template activities with less overhead. Instead, jQuery templates are better used with Ajax calls.
Here are code examples demonstrating three cases where a variable declared dynamic on the server is consumed with jQuery templates in the browser.
The first example uses an anonymous type for the member field SomeValue, and has a jQuery template that treats it as a member object.
The second example uses an array of anonymous types for the member field SomeValue and has a template that uses {{each}} syntax to enumerate the items. Note that this is a scenario where things can go badly with dynamic, as you get no strongly-typed support and must either know the correct type or discover it at the time you access it.
The third example uses an ExpandoObject for the member field SomeValue, and has a jQuery template like the first example (single member object). Note that in this case, we need to use a helper function pivotDictionaryMap() to pivot Key::Value pairs into object members.
Starting with a blank C# MVC3 Web Project, we need to modify three files to demonstrate these examples.
Inside _Layout.cshtml, add script includes for jQuery templates and a proper version of jQuery in your <head> element.
<script src="#Url.Content("~/Scripts/jquery-1.8.2.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.tmpl.js")" type="text/javascript"></script>
Inside HomeController.cs, we'll add some methods that return json ActionResults. Also, for brevity we'll just declare a class SomeModelType here; and note that a proper application would probably have this class declared in its Models.
using System.Dynamic; // up top...
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult SomeDataSource(int id)
{
dynamic d = new { innerId = 99, innerLabel = "inside object" };
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = d;
return Json(obj, "text/plain");
}
public ActionResult SomeDataSourceWithArray(int id)
{
dynamic d1 = new { innerId = 99, innerLabel = "inside object (first array member)" };
dynamic d2 = new { innerId = 100, innerLabel = "inside object (second array member)" };
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = new object[] {d1, d2};
return Json(obj, "text/plain");
}
public ActionResult SomeDataSourceWithExpando(int id)
{
dynamic d = new ExpandoObject();
d.innerId = 99;
d.innerLabel = "inside object";
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = d;
return Json(obj, "text/plain");
}
}
public class SomeModelType
{
public SomeModelType(int initId, string initLabel)
{
Id = initId;
Label = initLabel;
}
public int Id { get; set; }
public string Label { get; set; }
public dynamic SomeValue { get; set; }
}
Finally, in the default view, we will add script tags for the templates and the javascript necessary to consume them. Note the use of $.post() and not $.get(), as a JsonResult in MVC disallows GET requests by default (you can turn these on with an attribute).
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<script id="someDataTemplate" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has an inner item with id <b>${SomeValue.innerId}</b> whose label is "<i>${SomeValue.innerLabel}</i>".
</script>
<h3>SomeDataSource Example #1 (Single Item)</h3>
<div id="someData">
</div>
<script id="someDataArrayTemplate" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has these inner items:
<ul>
{{each SomeValue}}
<li><b>${innerId}</b> has a label "<i>${innerLabel}</i>".</li>
{{/each}}
</ul>
</script>
<h3>SomeDataSource Example #2 (Array)</h3>
<div id="someArrayData">
</div>
<script id="someDataTemplateFromExpandoObject" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has an inner item with id <b>${SomeValue.innerId}</b> whose label is "<i>${SomeValue.innerLabel}</i>".
</script>
<h3>SomeDataSource Example #3 (Single Item, Expando Object)</h3>
<div id="someDataFromExpandoObject">
</div>
<script type="text/javascript">
function pivotDictionaryMap(src)
{
var retval = {};
$.each(src, function(index, item){
retval[item.Key] = item.Value;
});
return retval;
}
</script>
<script type="text/javascript">
$(document).ready(function() {
// Ajax Round-Trip to fill example #1
$.post("/Home/SomeDataSource/5", function(data) {
$("#someDataTemplate").tmpl(data).appendTo("#someData");
}, "json");
// Ajax Round-Trip to fill example #2
$.post("/Home/SomeDataSourceWithArray/67", function(data) {
$("#someDataArrayTemplate").tmpl(data).appendTo("#someArrayData");
}, "json");
// Ajax Round-Trip to fill example #3
$.post("/Home/SomeDataSourceWithExpando/33", function(data) {
data.SomeValue = pivotDictionaryMap(data.SomeValue);
$("#someDataTemplateFromExpandoObject").tmpl(data).appendTo("#someDataFromExpandoObject");
}, "json");
});
</script>
I won't mark my own answer as "correct" just on premise that I don't like this answer. If someone figures out what I was trying to do above, I'll gladly give you the points.
function getDisplayValue(data, toMatchOn) {
var _return = $.grep(data, function (n, i) { return (n.Key == toMatchOn); })[0];
if (_return != null)
return _return.Value;
return "";
}
and in the jquery template:
${getDisplayValue(DisplayFields, 'Something')}
So I was able to get this to work with the method described above so here it is as a possible solution but I don't know javascript well enough to know how bad of a performance hit this could be creating (and as I research it, I'll update this answer) but I thought javascript dictionaries were optimized against their key value, so the fact that MVC doesn't serialize expando as a true javascript dictionary seems to make this answer very inefficient. And the fact that I originally took this tack with the dynamic c# object was that I originally thought this would serialize down into a cleaner form. Anyway, this works but Occam's Razor is just making this feel way too complicated.
Not sure if this will help or not, but have a look at this gist. It is hard to tell from your code snippets but if you are turning that ExpandoObject into JSON, then try wrapping it on a DynamicJsonObject first, as done in the gist.
Code from the Gist copy/pasted for those who don't want to click the link:
// By default, Json.Encode will turn an ExpandoObject into an array of items,
// because internally an ExpandoObject extends IEnumerable<KeyValuePair<string, object>>.
// You can turn it into a non-list JSON string by first wrapping it in a DynamicJsonObject.
[TestMethod]
public void JsonEncodeAnExpandoObjectByWrappingItInADynamicJsonObject()
{
dynamic expando = new ExpandoObject();
expando.Value = 10;
expando.Product = "Apples";
var expandoResult = System.Web.Helpers.Json.Encode(expando);
var dictionaryResult = System.Web.Helpers.Json.Encode(new DynamicJsonObject(expando));
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", expandoResult); // FAILS (encodes as an array instead)
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", dictionaryResult); // PASSES
}
You're right that performance is going to be sketchy if you're iterating over an array to find a key. But you should be able to comfortably convert the key-value-pair array (that the server sends back) into a "true" Javascript dictionary/map. Eg:
var kvps = [ {key: "test", value: "expando"}, {key: "hello", value: "world" } ];
var map = {};
kvps.forEach(function(kvp) { map[kvp.key] = kvp.value; } );
console.log( JSON.stringify(map) );
{"test":"expando","hello":"world"}
Of course, if you're nested objects, then you'd have to apply recursion to the above approach to make it work.

Categories