MVC , JS change value textboxfor not posting - javascript

I have this modelView
public class Ue
{ public class uEkranModel
{
public List<Grup> Grup = new List<Grup>();
private List<Secim> _secim;
public List<Secim> secim
{
get
{
if (_secim == null)
_secim = new List<Secim>();
return _secim;
}
set { _secim = value; }
}
}
public class Secim
{
public Guid uGuid { get; set; }
public Guid fGuid { get; set; }
}
}
I need to fell the List secims items with JS and post it back to controller.
I have tried to :
1)initialize the list in the controller :
Controller :
gidecek.Data = new Models.Ucak.UcakDeneme.uEkranModel();
gidecek.Data.secim.Add(new Models.Ue.Secim { uGuid = new Guid() });
gidecek.Data.secim.Add(new Models.Ue.Secim { uGuid = new Guid() });
View :
#using (Html.BeginForm("deneme", "U", FormMethod.Post, new { id = "secimTamam", style = "display:none" }))
{
#Html.EditorFor(m => m.Data.secim[0])
#Html.TextBoxFor(m => m.Data.secim[0].uGuid, new {id="gidis" })
}
JS :
$("#Data_secim_0__ucusGuid").attr("value", index);
This way , when the code is executed the value field of the textboxfor is changed(when the JS fired) but when I check the post data in controller , it is NULL.
also tried :
$("#Data_secim_0__ucusGuid").val(index);
which doesnt cahnge the value of teh textbox.
What I need is to fill the model values with js and post the form with js as well.(The data user is selecting is different, I am just posting back the GUID of the items within a form.)

2 possible issues. Your getter is initializing a new List<Secim>. Try initializing it in the constructor
public class uEkranModel
{
public uEkranModel()
{
secim = new List<Secim>();
}
public List<Secim> secim { get; set;}
....
}
Also I have seen other posts on SO indicating problems posting back GUID's (and one solution that was accepted was to use a view model with the GUID's converted to strings)

Related

Why is axios response in camel case when sending a request in asp.net core [duplicate]

I'm running through a WintellectNOW course on ASP.NET Core/Web API/Angular 2. I have the API portion implemented, but for whatever reason, the JSON that is being returned has the variable names being lowercased.
The returned JSON is formatted like...
[
{"id":1,"name":"Bowler","color":"black","count":1},
{"id":2,"name":"Fedora","color":"red","count":1},
{"id":3,"name":"Baseball Cap","color":"blue","count":3}
]
I'm expecting...
[
{"Id":1,"Name":"Bowler","Color":"black","Count":1},
{"Id":2,"Name":"Fedora","Color":"red","Count":1},
{"Id":3,"Name":"Baseball Cap","Color":"blue","Count":3}
]
Based on the C# model of...
namespace HatCollection.Models
{
public class Hat
{
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public int Count { get; set; }
}
}
I even went as far as decorating the properties with [DataMember(Name = "Id")] just to make sure and it still didn't matter.
On the off chance, it's relevant the Action and instance variable in the controller...
private static readonly List<Hat> MyHats = new List<Hat>
{
new Hat {Id = 1, Name = "Bowler", Color = "black", Count = 1 },
new Hat {Id = 2, Name = "Fedora", Color = "red", Count = 1 },
new Hat {Id = 3, Name = "Baseball Cap", Color = "blue", Count = 3 }
};
[HttpGet]
public IEnumerable<Hat> Get()
{
return MyHats;
}
How do I turn off the camelCase functionality, so that ASP.NET Core returns the property names without changing them?
In Asp.Net Core 3.0 some things have changed. For camelCase do nothing that is out of the box. For PascalCase or another set style use.
services.AddMvc(setupAction=> {
setupAction.EnableEndpointRouting = false;
}).AddJsonOptions(jsonOptions =>
{
jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null;
})
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
In Startup.cs ConfigureServices section
For those who needs a solution about a PascalCase within Api Project that has not the Mvc services you should add this after AddControllers services
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddJsonOptions(jsonOptions =>
{
jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null;
} ;
}
For Asp.Net Core 3.1 using the NewtonSoft.Json
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.UseMemberCasing();
});
In ASP.NET Core <3.0, JSON properties are camelCased by default (per this announcement).
You can disable this by replacing
services.AddMvc();
with
services
.AddMvc()
.AddJsonOptions(opt => opt.SerializerSettings.ContractResolver
= new DefaultContractResolver());
in your Startup.cs file. You'll have to add using Newtonsoft.Json.Serialization; to the top of the file.
With the DefaultContractResolver in place, the property names will be represented verbatim in the JSON output. No need for DataMember attributes.
Here is the answer for .net 5 :
https://learn.microsoft.com/en-us/aspnet/core/web-api/advanced/formatting?view=aspnetcore-5.0
Configure System.Text.Json based formatters Features for the
System.Text.Json based formatters can be configured using
Microsoft.AspNetCore.Mvc.JsonOptions.JsonSerializerOptions.
The
default formatting is camelCase. The following highlighted code sets
PascalCase formatting:
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.PropertyNamingPolicy = null);
}
Another solution in Asp.Net.Core 2.2 as following:
services.AddMvc()
.AddJsonOptions(jsonOptions => jsonOptions.UseMemberCasing())
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
In ASP.Net Core you can use two way:
First way: UseMemberCasing()
In StartUp.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddNewtonsoftJson(opt =>
{
opt.UseMemberCasing(); // <-- add this
});
}
Second way: ContractResolver
In StartUp.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews().AddNewtonsoftJson(opt =>
{
opt.SerializerSettings.ContractResolver = new DefaultContractResolver(); // <-- add this
});
}
depends on your project maybe you used AddMvc() or AddControllers() insted of AddControllersWithViews().
If AddNewtonsoftJson not found, you should install Nuget pacage : Microsoft.AspNetCore.Mvc.NewtonsoftJson (link).
You have to change the DefaultContractResolver which uses camelCase by default. Just set the NamingStatergy as null.
This should be done in the StartUp.ConfirgureService as follows.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddMvcOptions(o => o.OutputFormatters.Add(
new XmlDataContractSerializerOutputFormatter()));
.AddJsonOptions(o => {
if (o.SerializerSettings.ContractResolver != null)
{
var castedResolver = o.SerializerSettings.ContractResolver
as DefaultContractResolver;
castedResolver.NamingStrategy = null;
}
});
}
Option 2
Use JSonProperty as follows.
public class Hat
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("color")]
public string Color { get; set; }
[JsonProperty("count")]
public int Count { get; set; }
}
I am using the following solution because
a) I prefer using the .Net Core built in System.Text.Json serializer and
b) I do not want to rely on the not documented internal behaviour of
jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null;.
.
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = new MyTransparentJsonNamingPolicy();
});
where:
public class MyTransparentJsonNamingPolicy : JsonNamingPolicy
{
// You can came up any custom transformation here, so instead just transparently
// pass through the original C# class property name, it is possible to explicit
// convert to PascalCase, etc:
public override string ConvertName(string name)
{
return name;
}
}
In .NET 6 I used:
builder.Services.AddControllersWithViews().AddJsonOptions(opt => opt.JsonSerializerOptions.PropertyNamingPolicy = null);

How to reuse form on the same MVC page with the same model?

I have a built a view with a simple contact form with Name, Email and Phone. I re-use this form two or three times on the same parent page. When the user submits one of the forms, I detect the form submitted and post contents via javascript/jQuery to a database using Dapper.
The problem is when I re-use the contact form on any page, the input fields will not be generated with unique IDs. This causes w3 validation to fail saying Duplicate ID. In this particular case, I need the page to pass w3 validation.
How would I go about solving this? I tried with ViewData.TemplateInfo.HtmlFieldPrefix to prefix the input fields but does not really solve the problem as the prefix value is static. If I do a random prefix value, then how can I capture that in HttpPost controller?
Here is my code.
Index.cshtml
- with several references to ContactForm.cshtml:
<html>
<head>...</head>
<body>
....
#{Html.RenderAction("ContactForm")}
....
#{Html.RenderAction("ContactForm")}
....
</body>
</html>
ContactForm.cshtml
#model ProjectX.Models.CaseModel
#using (Html.BeginForm("SendMessage", "Home", FormMethod.Post))
{
#Html.TextInputFor(model => model.Name, Res.Name, new { placeholder = Res.Name } )
#Html.TextInputFor(model => model.Phone, Res.Phone, new { placeholder = Res.Phone, type = "tel" })
#Html.TextInputFor(model => model.Email, Res.Email, new { placeholder = Res.Email, type = "email" })
<input type="submit" value="Send" onclick="jsSaveForm(event);" />
}
// #Html.TextInputFor MvcHtmlString helper method
public static MvcHtmlString TextInputFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string title, Object htmlAttributes = null)
{
var req = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).IsRequired;
var name = helper.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
if (req)
title += " *";
string html = String.Format("<div class=\"inp\"><label for=\"{2}\">{0}</label>{1}</div>", helper.ValidationMessageFor(expression), helper.TextBoxFor(expression, htmlAttributes), name);
return new MvcHtmlString(html);
}
CaseModel.cs
public class CaseModel
{
[Required(ErrorMessageResourceType = typeof(Res), ErrorMessageResourceName = "ValidationRequired")]
public string Name { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
}
HomeController
// GET
[ChildActionOnly]
[Route("~/Home/ContactForm")]
public ActionResult ContactForm()
{
// tried this but this is a static value.
// ViewData.TemplateInfo.HtmlFieldPrefix = "SomePrefix";
return PartialView(new CaseModel());
}
// POST
[HttpPost]
[Route("~/Home/SendMessage")]
public async Task<PartialViewResult> SendMessage(CaseModel model)
{
... brevity...
await SaveData(model);
}
You can just remove the id attribute by setting it to an empty string in the htmlAttributes argument, for example
#Html.TextInputFor(model => model.Name, Res.Name, new { id = "", placeholder = Res.Name } )
although you might want to consider doing this in your TextInputFor() extension method.
As a side note, you extension method does not take into account the display name for your properties (when using the [Display(Name = "...")] attribute, and the title parameter is unnecessary. I suggest your code should be
public static MvcHtmlString TextInputFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
string displayName = metaData.DisplayName;
if (metaData.IsRequired)
{
displayName += " *";
}
StringBuilder html = new StringBuilder();
html.Append(helper.LabelFor(expression, displayName).ToString());
html.Append(helper.TextBoxFor(expression, htmlAttributes).ToString());
html.Append(helper.ValidationMessageFor(expression).ToString());
TagBuilder container = new TagBuilder("div");
container.AddCssClass("inp");
container.InnerHtml = html.ToString();
return new MvcHtmlString(container.ToString());
}

How to properly clear GridViewExtension data on page revisit or reload?

Background
I have an incomplete project built on MVC 5, EF 6 DB First & DevExpress extensions with following code spec (all original non-English variable names changed & entire code simplified to comply MCVE):
Model (Namespace: ProjectName.Models)
public class DBContext : DbContext
{
public DBContext : base("ConnectionStringName")
{
public DbSet<WinnerTable> Winners { get; set; }
public DbSet<UnitTable> Units { get; set; }
public DbSet<CustomerTable> Customers { get; set; }
}
}
[Table("WinnerTable")]
public class WinnerModel : IWinnerRepository
{
public String WinnerID { get; set; }
public String CustomerID { get; set; }
public String CustomerName { get; set; }
public String TranDate { get; set; }
public List<UnitModel> UnitList { get; set; }
public List<UnitModel> GetUnitList(String sessionID, DateTime tranDate)
{
// query to unit list
using (var DB = new DBContext())
{
var query = (from unit in DB.Units
where unit.SessionID == sessionID && unit.TranDate = tranDate
select new UnitModel()
{
// unit table to unit model definition
}).ToList();
return query;
}
}
}
[Table("UnitTable")]
public class UnitModel
{
public String UnitID { get; set; }
public String UnitName { get; set; }
// other definitions
}
Controller
using ProjectName.Models;
[RoutePrefix("Input")]
public class InputController : Controller
{
[HttpGet]
public ActionResult Winner()
{
WinnerModel model = new WinnerModel()
{
// default values on first visit/reload page
TranDate = DateTime.Now.Date,
UnitList = new List<UnitModel>(); // list declaration
}
return View(model);
}
public PartialViewResult CustomerData(String customerId, String sessionId, DateTime tranDate, WinnerModel model)
{
if (DevExpressHelper.IsCallback && !String.IsNullOrEmpty(customerId))
{
Session["CustomerID"] = customerId;
Session["SessionID"] = sessionId;
Session["TranDate"] = Convert.ToDateTime(tranDate);
using (var DB = new DBContext())
{
var query = DB.Customers.Where(c => c.CustomerID == customerId).FirstOrDefault();
// model property assignments
}
}
return PartialView("_CustomerData", model);
}
public PartialViewResult ShowItemsGrid(WinnerModel model)
{
String customerId = (Session["CustomerId"] ?? String.Empty).ToString();
String sessionId = (Session["SessionId"] ?? String.Empty).ToString();
String lastCustomer = (Session["LastCustomer"] ?? String.Empty).ToString();
DateTime tranDate = Convert.ToDateTime(Session["TranDate"] ?? DateTime.Now.Date);
using (var DB = new DBContext())
{
model.CustomerId = customerId;
model.SessionId = sessionId;
model.TranDate = tranDate;
model.UnitList = model.GetUnitList(sessionId, tranDate);
if (model.UnitList == null || model.UnitList.Count == 0)
{
model.UnitList = new List<UnitModel>();
}
Session["LastCustomer"] = lastCustomer;
return PartialView("_GridView", model);
}
}
}
View (Winner.cshtml)
#using ProjectName.Models
#model WinnerModel
#Html.EnableUnobtrusiveJavascript()
<script type="text/javascript">
var customer = null;
function initializeGrid()
{
ItemsGrid.PerformCallback(); // routine check if customer name exists
}
function comboChanged(s, e) {
customer = s.GetValue();
CustomerDataPanel.PerformCallback(); // callback to fill customer data for partial view & load units into gridview
}
// callback to insert values into session variable
function customerBeginCallback(s, e) {
e.customArgs["customerId"] = customer;
e.customArgs["sessionId"] = SessionId.GetValue();
e.customArgs["tranDate"] = TranDate.GetValue();
}
function customerEndCallback(s, e) {
ItemsGrid.PerformCallback();
}
// count checked data inside gridview
// this may be asked on other context and doesn't matter for this one
function countUnits(buttonName, url)
{
// other code
}
</script>
#using (Html.BeginForm("Winner", "Input", FormMethod.Post))
{
Html.DevExpress().TextBoxFor(m => m.SessionId, TextBoxSettings).GetHtml();
Html.DevExpress().DateEditFor(m => m.TranDate, DateEditSettings).GetHtml();
// this combobox has client-side event SelectedIndexChanged = "comboChanged"
// GetCustomers method just populate customers data into combobox and unrelated to this problem
Html.DevExpress().ComboBoxFor(m => m.CustomerId, ComboBoxSettings).BindList(ProjectName.Providers.GetCustomers()).GetHtml();
Html.RenderPartial("_CustomerData", Model); // DX callback panel
Html.RenderPartial("_GridView", Model);
// button to count all checked values inside gridview
Html.DevExpress().Button(CountButtonSettings).GetHtml();
Html.DevExpress().LabelFor(m => m.TotalPrice, PriceLabelSettings).GetHtml();
// button for submit & reset form here
Html.DevExpress().Button(SubmitButtonSettings).GetHtml();
Html.DevExpress().Button(ResetButtonSettings).GetHtml();
}
Partial View (_CustomerData.cshtml)
#using ProjectName.Models
#model WinnerModel
#{
// MVC DX callback panel for customer details
// Name = CustomerDataPanel
// CallbackRouteValues: Controller = Input, Action = CustomerData
// ClientSideEvents.BeginCallback = customerBeginCallback
// ClientSideEvents.EndCallback = customerEndCallback
Html.DevExpress().CallbackPanel(CallbackPanelSettings).GetHtml();
}
Partial View (_GridView.cshtml)
#using ProjectName.Models
#model WinnerModel
#{
// MVC DX GridView with row selection checkboxes
// The gridview column structure is exactly same as UnitModel has
// Name = ItemsGrid
// CallbackRouteValues: Controller = Input, Action = ShowItemsGrid
// ClientSideEvents.Init = initializeGrid
GridViewExtension grid = Html.DevExpress().GridView(GridViewSettings);
grid.Bind(Model.UnitList).GetHtml(); // grid bound to List<UnitModel>
}
All gridview changes require sufficent privileges (i.e. admin/supervisor).
Problem Statement
I want anyone help finding out where and how proper routine codes to empty gridview data must be attached on controller method(s) to give expected results. As I tried so far, the gridview still maintaining its previous state given from session variable when Winner page revisited or reloaded (immediate first visit after login worked because all session variables are empty, thus no data was populated to gridview).
Additionally, I want to show JS confirm message when user trying to close/reload Winner page while some/all gridview data are being checked.
Expected Results
For every first visit, revisit & reload on Winner page, the gridview content must empty.
After a user provides certain customer ID, the gridview will populated with some unit data from unit table, where changes inside it immediately lost when user accepts reloading/closing page confirm message.
Any kind of answers or suggestions will appreciated.

$http undefined error when fetching data from asp.net controller

I am using angularjs in asp.net
I made a controller with CRUD and am trying to get data from angularjs controller using $http service
Route params is getting correct querys from url, i tested that, but i get undefined error when requesting data
What am i doing wrong? :(
SongsController.cs method:
public ActionResult Index(string id)
{
/*var result = db.Songs.ToList();
return Json(result, JsonRequestBehavior.AllowGet);*/
string searchString = id;
var songs = from m in db.Songs
select m;
if (!String.IsNullOrEmpty(searchString))
{
songs = songs.Where(s => s.Title.Contains(searchString));
}
return Json(songs, JsonRequestBehavior.AllowGet);
}
songsService.js:
myApp.factory('songsService', ['$http', function ($http) {
var songsService = {};
songsService.getSongs = function (param) {
return $http.get('/Songs/Index/' + param);
}
return songsService;}])
songsController.js:
myApp.controller('songsController', ['$scope', '$routeParams', 'songsService', function ($scope, $routeParams, songsService) {
var search = $routeParams.query;
if (search == 'undefined' || search == null)
search = '';
getSongs(search);
function getSongs(searchText) {
songsService.getSongs(searchText)
.success(function (data) {
$scope.songs = data;
})
.error(function (error) {
$scope.status = 'Unable to load data: ' + error.message;
console.log($scope.status);
});
}}]);
EDIT:
Song class:
using System;
using System.Collections.Generic;
public class Song
{
public int ID { get; set; }
public string Title { get; set; }
public string Artist { get; set; }
public virtual ICollection<Navigation> Navigations { get; set; }
}
EDIT2: Navigation class:
using System;
public class Navigation
{
public int ID { get; set; }
public int SongID { get; set; }
public int PlaylistID { get; set; }
public virtual Song Song { get; set; }
public virtual Playlist Playlist { get; set; }
}
EDIT3:
If I name my .cs controller SongsController and navigate to url songs/index/something i get popup if i want to open or save something.json and just get redirected back to my default url defined by ngroute (#/songs/)
But, if i name .cs controller something else, like RandomController, if i navigate to same url i get this error:
A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.Navigation_7A1A3B789B740F23BAB0A6DAABE519BE3A‌​F91C300893047C23FF2FD8C44E6705'.
EDIT4: I've come to point at which everything if my SongsController.cs looks like this:
public ActionResult Index(string id)
{
var song = new List<Song>
{
new Song{Title="Paint It Black",Artist="Rolling Stones"},
new Song{Title="People Are Strange",Artist="The Doors"},
new Song{Title="With Or Without You",Artist="U2"},
new Song{Title="Wish You Were Here",Artist="Pink Floyd"},
new Song{Title="Fluorescent Adolescent",Artist="Arctic Monkeys"},
new Song{Title="La Guitarra",Artist="Orjan Nilsen"},
new Song{Title="Ping Pong",Artist="Armin Van Buuren"},
new Song{Title="Fade Out Lines",Artist="The Avenger"},
new Song{Title="Redemption Song",Artist="Bob Marley"},
new Song{Title="Wherever I May Roam",Artist="Metallica"},
};
return Json(songs, JsonRequestBehavior.AllowGet);*/
}
If it' like that everything works, but if it looks like i've wrote in original post i get undefined error when i run $http.get :/
EDIT5: Okay, I believe the problem is i'm trying to send objects containing array of Navigation class objects, how can i solve this? :(
You have a circular reference on your Song class.
When the Json serializer tries to process it, it finds the Navigations property and tries to serialize that as well, the problem is that each Navigation object on that collection have a instance of the same Song, so it enters a infinite loop trying to serialize all of it over and over again.
That happens because EntityFramework has its lazyloading and automatically populate the classes as the serializer tries to access them.
To fix it, you can do two things, simply disable the lazyloading for that call in particular:
public ActionResult Index(string id)
{
db.Configuration.LazyLoadingEnabled = false;
string searchString = id;
var songs = from m in db.Songs
select m;
if (!String.IsNullOrEmpty(searchString))
{
songs = songs.Where(s => s.Title.Contains(searchString));
}
return Json(songs, JsonRequestBehavior.AllowGet);
}
The other option is to create a model with only the data you need to return and populate it manually (or using a mapper tool).
public ActionResult Index(string id)
{
db.Configuration.LazyLoadingEnabled = false;
string searchString = id;
var songs = from m in db.Songs
select m;
if (!String.IsNullOrEmpty(searchString))
{
songs = songs.Where(s => s.Title.Contains(searchString));
}
var mappedSongs = songs.Select(it => new { Title = it.Title, Artist = it.Artist }).ToList();
return Json(mappedSongs , JsonRequestBehavior.AllowGet);
}

Passing local variable in Razor view by JavaScript to java script function and then to C# method

I would like to pass #survey which is object of class Survey to the JavaScript function SubmitClick and then to the SubmitSurvey in PersonController.cs
My button: when clicked it passes arguments to javascript:SubmitClick
<button class='mybutton' type='button' onclick="javascript:SubmitClick(#Model.Item1.Id, #survey);">Save Survey</button>
and JavaScript function:
function SubmitClick(pid, sid) {
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personId: pid, survey: sid }, function (data) {
alert('updated' + pid);
});
}
and method to which I want to pass #survey:
public void SubmitSurvey(int personId, Survey survey) {
}
The result is:
I want to point out that passing #survey.Id(int) worked so the only problem is passing the #survey.
Error pop ups at passing arguments to java script function.
EDIT
The button is inside foreach loop and the model is little bit complex. Can I just serialize it inside a loop?
I pass List of Survey to the view from here:
public ActionResult _Survey1(int id) {
System.Diagnostics.Debug.WriteLine("PASSED ID: " + id);
Person person = db.Persons.Find(id);
//Passing a Tuple to Partial View, I want to pass copies further I use copying constructor
List<Survey> localSurveysCopy = new List<Survey>();
foreach (Survey survey in db.Surveys) {
localSurveysCopy.Add(new Survey(survey));
}
var tuple = new Tuple<Person, List<Survey>>(person, localSurveysCopy) { };
return PartialView(tuple);
}
The view:
#using WebApplication2.Models
#model System.Tuple<Person, List<Survey>>
<hr />
<h1>Surveys</h1>
<input type="button" id="Coll" value="Collapse" onclick="javascript:CollapseDiv()" />
#{int i = 1;}
#foreach (var survey in Model.Item2) {
using (Html.BeginForm()) {
<h2>Survey #(i)</h2>
<p />
#Html.EditorFor(x => survey.Questions)
<button class='mybutton' type='button' onclick="javascript:SubmitClick(#Model.Item1.Id, #Newtonsoft.Json.JsonConvert.SerializeObject(survey));">Save Survey</button>
}
i++;
<hr style="background-color:rgb(126, 126, 126);height: 5px" />
}
<hr />
The script. I think I had to pass variable directly as I have many surveys and many buttons:
function SubmitClick(pid, sid) {
var url = '#Url.Action("SubmitSurvey", "Person")';
var objSurvey = $.parseJSON(sid);
$.post(url, { personId: pid, survey: objSurvey }, function (data) {
alert('updated person' + pid + ' survey ' + sid);
});
}
I get:
A first chance exception of type 'System.Web.HttpException' occurred in System.Web.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in System.Web.Mvc.dll
A first chance exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in System.Web.Mvc.dll
The class Survey looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebApplication2.Models {
public class Survey {
public int Id { set; get; }
public virtual ICollection<Question> Questions { set; get; }
public Survey() { }
public Survey(Survey survey) {
Id = survey.Id;
Questions = new List<Question>();
System.Diagnostics.Debug.WriteLine("SURVEY " + survey.Questions == null);
foreach (Question question in survey.Questions) {
Questions.Add(new Question(question));
}
}
}
public class Question {
public int Id { set; get; }
public string QuestionText { set; get; }
public virtual ICollection<Answer> Answers { set; get; }
public virtual Survey Survey { get; set; }
public string SelectedAnswer { set; get; } //this field is SET after clicking SAVE button
public Question() { }
public Question(Question question) {
Id = question.Id;
QuestionText = question.QuestionText;
Answers = question.Answers;
Survey = question.Survey;
SelectedAnswer = "";
}
}
public class Answer {
public int Id { set; get; }
public string AnswerText { set; get; }
public virtual Question Question { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
}
You cannot just pass C# object to JS and post it back.
You should:
Convert your object to JSON and pass it to JS. I advise to use Newtonsoft.JSON for it.
controller:
string json = JsonConvert.SerializeObject(survey);
markup:
<button class='mybutton' type='button' onclick="javascript:SubmitClick(#Model.Item1.Id, #json);">Save Survey</button>
Then from JS you should post JSON to your MVC controller and MVC will deserialize it your C# object.
markup:
function SubmitClick(pid, sid) {
var objSurvey = $.parseJSON(sid);
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personId: pid, survey: objSurvey }, function (data) {
alert('updated' + pid);
});
}
controller:
public void SubmitSurvey(int personId, Survey survey) { }
UPDATED:
Your Survey entity cannot be serialized correctly because of cycle references between classes. So you have few options here:
use [JsonIgnore] attribute to ignore backreferences. (So you should mark Survey property inside Question class with this attribute, Question property inside Answer class and so on) Please keep in mind that these fields will be not serialized to JS and deserialized back to C# objects.
create separate model without cycle references for serialization and use it when convert to JSON.
You are doing it wrong.
Basically when you do #survey, you are looking at a server side code. At the client side, this #survey, which is an instance of a strongly typed clr class, is converted to string, and you know what happens when you convert and object to string, you get its Type in string i.e
#survey.ToString() == "WebApplications2.Models.Survey"
obviously it is wrong cause, your button tag's markup, at the end, effectively becomes :
<button class='mybutton' type='button'
onclick="javascript:SubmitClick(#Model.Item1.Id,
WebApplications2.Models.Survey);">
Save Survey
</button>
You should basically first serialize your #survey object at the server side and store it in a hidden variable i.e.
#Html.Hidden("hdnSurvey", Newtonsoft.Json.JsonConvert.SerializeObject(Model))
and use this hidden variable inside your javascript
i.e.
function SubmitClick(pid) {
var objSurvey = $.parseJSON( $('#hdnSurvey').val());
var url = '#Url.Action("SubmitSurvey", "Person")';
$.post(url, { personId: pid, survey: objSurvey }, function (data) {
alert('updated' + pid);
});
}
Sending back the #survey variable is pointless, the changes from the UI will not be reflected from the variable, but from HTML input, what you really need is serializing the form.
Here is a complete solution for what you really need.
Model
public class Person
{
public int Id { set; get; }
}
public class Survey
{
public int Id { set; get; }
public virtual ICollection<Question> Questions { set; get; }
}
public class Question
{
public int Id { set; get; }
public string QuestionText { set; get; }
public virtual ICollection<Answer> Answers { set; get; }
public int SelectedAnswerId { set; get; } // Notice that I change it into int not string
}
public class Answer
{
public int Id { set; get; }
public string AnswerText { set; get; }
}
Controller
public ActionResult Index()
{
var person = new Person { Id = 1 };
var survey = new Survey
{
Id = 12,
Questions = new List<Question>
{
new Question
{
Id = 34,
QuestionText = "What is your favorite language?",
Answers = new List<Answer>
{
new Answer { Id = 56, AnswerText = "A#" },
new Answer { Id = 57, AnswerText = "B#" },
new Answer { Id = 58, AnswerText = "C#" }
}
}
}
};
var model = new Tuple<Person, List<Survey>>(person, new List<Survey> { survey });
return View(model);
}
[HttpPost]
public ActionResult SubmitSurvey(int personId, Survey survey)
{
return Json(new { success = true });
}
Index.cshtml
#model Tuple<Person, List<Survey>>
#{
ViewBag.Title = "Index";
}
<h2>Surveys</h2>
#{int i = 1;}
#foreach (var survey in Model.Item2)
{
using (Html.BeginForm())
{
<h3>Survey #(i++)</h3>
#Html.HiddenFor(x => survey.Id)
#Html.EditorFor(x => survey.Questions)
<button class="mybutton" type="button">Save Survey</button>
}
}
#section Scripts
{
<script>
$(".mybutton").click(function () {
var personId = "#Model.Item1.Id";
var survey = $(this).closest("form").serialize();
var data = survey + "&personId=" + personId;
$.ajax({
type: "POST",
url: "#Url.Action("SubmitSurvey", "Survey")",
data: data,
traditional: true,
success: function (data) {
alert("submitted :" + data.success);
}
});
});
</script>
}
EditorTemplates/Question.cshtml
#model Question
<h3>#Model.QuestionText </h3>
#Html.HiddenFor(x => x.Id)
#foreach (var a in Model.Answers)
{
<label>#Html.RadioButtonFor(b => b.SelectedAnswerId, a.Id) #a.AnswerText</label>
}
Result
If the user selected B# on the first survey on the first question, the submitted survey will return SelectedAnswerId as 57. The other properties like Answers and QuestionText are null and they're not important for saving so let it be.

Categories

Resources