I am struggling a bit with passing list of object to C# code from view. If I pass a simple string it works fine. but its not working for List. So I am sure I am missing something here.
View
<div class="row control-actions">
#{
List<MyObject> test = ViewBag.ObjectsList;
<button type="button" class="btn btn-primary btn-wide" onclick="addAllObjectsToExp('#test')">Add All</button>
}
</div>
<script type="text/javascript">
function addAllObjectsToExp(objList) {
$.post('#Url.Action("AddAllObjectsToExp", "ExportObjects")', { objectsList: objList},
function (result) {
$('#numbers').html(result);
});
}
</script>
Code
[HttpPost]
[OutputCache(Location = System.Web.UI.OutputCacheLocation.None, NoStore = false, Duration = 0)]
public int AddAllObjectsToExp(List<MyObject> objectsList)
{
foreach(MyObject obj in objectList)
{
//Do something here
}
//and return an integer
}
While debugging I can see that the #test variable is getting populated with the list of MyObject. But when I reach the code side its always null.
Please let me know if i am missing something here. Also tell me if more information is needed.
You're passing a C# object into a Javascript function. It doesn't know how to read that. You should serialize it into JSON before passing it in.
If you use Json.NET you can do it by
ViewBag.ObjectsList = JsonConvert.SerializeObject(yourlist);
Then you can continue as you were.
Some notes:
You should try to start using ViewModels instead of putting things in the ViewBag. On the Javascript side you should bind event handlers for things like clicking instead of using onclick as it would make your code much more manageable and reusable.
Related
I have an Ajax button that whenever I click it, it shows a single record from the database (in my Controller I used .Take(1) )
public PartialViewResult BtnNext()
{
List<Queue> model = db.Queues.OrderBy(x => x.QueueNumber).Take(1).ToList();
return PartialView("_queuenumber", model);
}
What I would like to do here is - whenever I click the next button it will display the first record from the database, then when I click it again it will show the second record and so on..
I wonder if that is even possible and what kind of stuff should I use to do that?
Yes. Its possible.
Just set Application["counter"] = 0 in Application_Start function then make value increments by 1 in result view and use it to get next record.
public PartialViewResult BtnNext()
{
List<Queue> model = db.Queues.OrderBy(x => x.QueueNumber).Skip(Application["counter"]).Take(1).ToList();
Application["counter"] = Application["counter"] + 1;
return PartialView("_queuenumber", model);
}
Reference
Use FormCollection try following code.
public PartialViewResult BtnNext(FormCollection Form)
{
Int32? Count = Convert.ToInt32(Form["Count"]??0);
List<Queue> model = db.Queues.OrderBy(x => x.QueueNumber).ToList();
model.ElementAt(count); // [NotMapped] public Int32? count { get; set; } add in model class
model.count=count+1;
return PartialView("_queuenumber", model);
}
on view
<input type="submit" class="btn btn-primary" value="Save" id="BtnNext">
<input type="hidden" id="Count" name="Count" value="#Model.Count" />
A good practice when you realize your Views need to handle and manipulate your data, is to create a ViewModel class that wraps all the objects that you need to send to that view. In your case, you can start with a simple
public class QueueViewModel
{
public Queue Queue { get; set ; }
public int CurrentRecord { get; set ; }
}
Now, all you have to do is changing the action method the controller so that you initialize and pass the ViewModel to the View. It will also be better to have an optional argument acting as the default record, and then using the linq instruction Skip to go to and take a specific record:
Public PartialViewResult NextRecord(int current = 0)
{
QueueViewModel model = new QueueViewModel();
model.CurrentRecord = current;
model.Queue = db.OrderBy(x => yourClause).Skip(current).Take(1);
return PartialView(“yourview”, model);
}
I changed the List<Queue> within your model as I think you don’t need a list if you’re only showing one record at a time, but you can easily go back to the generics if you feel you really need to.
As for the view part where you handle the index on the model, there are many ways to achieve the same result. What I personally like to do is using the model to fill a data attribute of a DOM element and use that in the Ajax call. Since you now have
#model yourModelNamespace.QueueViewModel
it is possible for you to set an element (let’s say a button) to host the current value:
<button data-current-record=“#Model.CurrentRecord”>...</button>
You can now very easily retrieve that value within your Ajax call to the action method:
var currentRecord = parseInt($(‘button’).data()[currentRecord]);
$.ajax({
url: yourPathToTheAction,
type: ‘GET’,
data: {
current: currentRecord + 1
}
});
This way you can go further and add other functions calling the same controller to move to previous record or jump to the last or the first and so on...
I am trying to call IEnumerable method in my _Layout.cshtml file. At the final I was adviced to "use html.action - to call server method that populates collection and returns partial view".
Currently I have created partial file _Dodatki.cshtml, that contains call of IEnumerable method (Aktualnosci.cs is model file):
#model IEnumerable<DluzynaSzkola.Models.Aktualnosci>
In my _Layout.cshtml I called method from my constructor with:
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
And at the final I want to create method in my AktualnosciConstructor.cs file. Currenly I have method:
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
Unfortunately, when using syntax as above, it gives me message in compiler:
"cannot create an instance of the abstract class or interface
'IList'".
When replacing 'IList' with 'List', it gives me exception:
"System.Web.HttpException: The controller for path '/' was not found
or does not implement IController."
I have no idea how in other way I can populate collection in the method.
edit: As per request, below AktualnosciController.cs definition, with no other methods:
namespace DluzynaSzkola.Controllers
{
public class AktualnosciController : Controller
{
//here are other methods
[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
IList<Aktualnosci> lista = new IList<Aktualnosci>();
return PartialView("_Dodatki", lista);
}
}
}
as noticed by GTown-Coder your controller name seems wrong. Updated my answer accordingly.
I think that your problem might be the same as answered by this SO post.
try specifying the Area name and, if this controller is not in an area simply add an empty area name.
#Html.Action("_Dodatki", "AktualnosciController ", new {area="" })
Even if this does not solve your problem it is good practice because if this view is latter used within an area it will try to find the controller in the area and not in the root space.
Allright, I have implemented changes to my project, that works fine.
My in _Layout.cshtml call is changed a bit. AktualnosciController supposed to be called just Aktualnosci !!!
<div class="kontenerDodatkiLayout hidden-xs hidden-sm">
<div class="archiwum">Archiwum</div>
#Html.Action("_Dodatki", "Aktualnosci", new { area = "" })
</div>
My partial view _Dodatki.cshtml model call is changed a bit:
#model IEnumerable<DateTime>
<div class="wpisDodatki">
#foreach (var item in Model)
{
<div> #Html.DisplayFor(modelItem => item)</div>
}
<p>test<br />test<br />test</p>
</div>
And method in my controller AktualnosciController.cs looks like that:
//[ChildActionOnly]
[ActionName("_Dodatki")]
public ActionResult Dodatki()
{
using (var context = new DluzynaContext())
{
var lista = context.Indeks.Select(it => it.Dzien).ToList();
return PartialView("_Dodatki", lista);
}
}
in here lista is passed to my partial view _Dodatki, and it is populated with context property Indeks and model property Dzien.
Thanks guys for your help #Wndrr , #GTown-Coder.
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);
}
I've been reading this site and finally have a question that wasn't already answered specifically enough for my needs, so here goes.
I have an observable array created from a data array passed to my KO view model in a C# application.
self.Stuff = ko.observableArray(data.Stuff);
This array doesn't have observable elements and there lies my problem. I need to edit an element (status) via an onclick. I know I need to either make the elements observable (not sure how with the data.Stuff portion) or do a "valueHasMutated" but I'm not entirely sure how that syntax would work.
Of course, my push and remove work fine since they trigger the arrayobservable and refresh the view.
if ($form.valid() && isValidStuff) {
self.Stuff.push({ ABC: self.ABCInput(), XYZ: self.XYZInput(), Status: self.StatusInput()});
self.resetValues();
}
self.removeStuff = function () {
self.Stuff.remove(this);
};
self.StuffStatusChng= function (){
//What to do?
self.Stuff.vauleHasMutated();
};
Any help or push in the right direction would be a great help, thanks! If I don't have enough info, please let me know what I can provide.
Thanks,
How about using the knockout mapping plugin:
http://knockoutjs.com/documentation/plugins-mapping.html
It has a fromJSON method that takes an array of json strings and makes an observable array with observable property for each property:
A fiddle can be found here: http://jsfiddle.net/jiggle/uzn7Z/, notice once bound, you can update the first name and it will update the property accordingly
HTML:
<div data-bind="foreach: people">
<div>
<input type="text" data-bind="value:firstName"/>
<span data-bind="text: firstName"></span>
<span data-bind="text: lastName"></span>
</div>
</div>
Code:
var stuff = '[{"firstName":"fred","lastName":"bloggs"},{"firstName":"david","lastName":"frost"}]';
console.log(stuff);
var people = ko.mapping.fromJSON(stuff);
console.log(people());
var viewModel ={};
viewModel.people=people;
ko.applyBindings(viewModel);
Just need to download and include knockout.mapping.js.
Does this help you?
var OneStuff = function (data) {
this.ABC = ko.observable(data.ABC);
this.XYZ = ko.observable(data.XYZ);
this.Status = ko.observable(data.Status);
};
self.Stuff = ko.observableArray(ko.utils.arrayMap(data.Stuff, function (oneStuffData) {
return new OneStuff(oneStuffData);
}));
This will make the individual properties observable.
To push:
self.Stuff.push(new OneStuff({ ABC: self.ABCInput(), XYZ: self.XYZInput(), Status: self.StatusInput()}));
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.