I have a JavaScript function that looks as follows:
function exportToExcel() {
$.ajax({
url: "/eBird/ExportToExcel",
data: jsonSightingData,
type: 'POST',
contentType: 'application/json'
});
}
My MVC controller looks like this:
public ActionResult ExportToExcel(List<Entities.MyClass> data)
{
try
{
...
}
catch (System.Exception exception)
{
...
}
MyClass defintion is:
public class MyClass
{
public string comName { get; set; }
public int howMany { get; set; }
public double lat { get; set; }
public double lng { get; set; }
public string locID { get; set; }
public string locName { get; set; }
public bool locationPrivate { get; set; }
public string obsDt { get; set; }
public bool obsReviewed { get; set; }
public bool obsValid { get; set; }
public string sciName { get; set; }
}
The class matches the JSON data coming in exactly. The problem is that when my controller method is called, 'data' is always NULL. My understanding was that the MVC model binder would automatically bind the JSON data to my MyClass list. But it doesn't appear to be working.
Sample JSON is as follows:
[{"comName":"Great Black-backed Gull","lat":42.4613266,"lng":-76.5059255,"locID":"L99381","locName":"Stewart Park","locationPrivate":false,"obsDt":"2014-09-19 12:40","obsReviewed":false,"obsValid":true,"sciName":"Larus marinus"}]
Use a General Object (Like JContainer) to "capture" the incoming data. or even Object if you are not sure what you get.
Then see what you have inside.
I use an external deserializer to convert json back to class. (using .ToString() to make it readable to the serializer)
(try Json.Net)
Also - make sure that the JSON is not converted into Unicode. I've had an issue with that as well.
another remark - I think content type should be application/json.
Read this: SO about json content type
Run Fiddler and capture Json sent to controller. The controller expects a List but looks like you are sending single object. Stuff coming in as null happens to many of us. I'll try and run what you have. Can you also mess with controller and give it just MyClass coming in?
Related
Today I've wanted to write a webscraper that searches through a calendar website and finds the tags of events, so I can search through them and get info about who worked for the event.
The Problem is: The div I want to search for gets added by js, so how do I get it with htmlagilitypack?
The Calendar Website: https://esel.at/termine
My Code:
using System;
using HtmlAgilityPack;
using System.Linq;
using System.Diagnostics;
using System.Threading;
namespace ESEL_Scraper
{
class Program
{
static void Main(string[] args)
{
string Url = $"https://esel.at/termine";
HtmlWeb web = new HtmlWeb();
HtmlDocument doc = web.Load(Url);
HtmlNode[] nodes = doc.DocumentNode.SelectNodes("//div[#class='content']").ToArray();
for(int i = 0; i < nodes.Length; i++) {
Console.WriteLine(nodes[i].InnerText);
}
}
}
}
SelectNodes returns null when it doesn't find what you're looking for. So that's why you get the null exception. There are no "div" elements with class = "content". If you change to a class that is used by a div element on that page you'll get results.
With HtmlAgility pack "SelectNodes" you need to do a null check in some way before using the result.
Short anwser: You can't. Parsing the web page for data, that gets added when the page loads is not possible by using HtmlAgilityPack - the initial source code of the page doesn't have the data.
Long anwser: There is probably some API call that gets the data for the events, and is then pushed via javascript to the page. Try to figure out what URL is used, and try to parse that. That would be this one: https://esel.at/api/termine/data?date=05.09.2020&selection=false
As it's stated Javascript append the content. Using basic network inspecting you will see that there is another network request.
What you get here is data in JSON format which gets appended in HTML using Javascript.
Instead of using HtmlAgility pack you will need to parse JSON. In the example below I have used Newtonsoft.Json package to do that.
Here is the code:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
namespace ESEL_Scraper
{
internal class Program
{
private static void Main(string[] args)
{
//Simply create request to the API and deserialize JSON using the Root class
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
CookieContainer cookies = new CookieContainer();
// Set the date you want in the link, in this example it's 06.09.2020
var request = (HttpWebRequest)WebRequest.Create("https://esel.at/api/termine/data?date=06.09.2020&selection=false");
request.CookieContainer = cookies;
request.Method = "GET";
request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36";
request.ContentType = "application/json";
request.Headers.Add("accept-language", "en,hr;q=0.9");
request.Headers.Add("accept-encoding", "");
request.Headers.Add("Upgrade-Insecure-Requests", "1");
WebResponse response = request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
string responseFromServer = reader.ReadToEnd();
reader.Close();
response.Close();
//Deserialize Json
Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(responseFromServer);
foreach (var el in myDeserializedClass.termine)
{
//Get any field you need
Console.WriteLine(el.title);
Console.WriteLine(el.location);
}
Console.ReadLine();
}
}
// Based on the JSON response https://pastebin.com/Xa5gSp50 I have generated classes using this website: https://json2csharp.com/
public class Termine
{
public int id { get; set; }
public string title { get; set; }
public string category { get; set; }
public string startdate { get; set; }
public string startdatetime { get; set; }
public string starttime { get; set; }
public string enddate { get; set; }
public List<object> runtime { get; set; }
public string thumbnail { get; set; }
public string thumbnail_credits { get; set; }
public string type { get; set; }
public string recommended { get; set; }
public bool online_event { get; set; }
public object feed_urls { get; set; }
public string status { get; set; }
public string tags { get; set; }
public string url { get; set; }
public string sort_date { get; set; }
public string sort_category { get; set; }
public string location_url { get; set; }
public string location { get; set; }
}
public class Meta
{
public List<string> next { get; set; }
public DateTime now { get; set; }
public List<string> date { get; set; }
public DateTime end { get; set; }
public DateTime runtime { get; set; }
public int upcoming { get; set; }
public int running { get; set; }
public int termine { get; set; }
}
public class Root
{
public List<Termine> termine { get; set; }
public Meta meta { get; set; }
}
}
This is related to my previous question.
I'm wondering, can I pass a JS array the same way (as the accepted answer in previous question) even if now I'm trying to send a bit more complex array:
If yes, then how? Getting nulls while receiving in controller.
this is my model:
public class QuestionModel
{
public bool Choice { get; set; }
public List<object> ChoiceQuestions { get; set; } //can i use List<object>?
public int Id { get; set; }
public string Question { get; set; }
public bool Short { get; set; }
public object ShortQuestion { get; set; } //here aswell - can I use object?
public string Type { get; set; }
}
Not even sure if I can reuse the js code for sending the data to controller that was given in the answer. If no, then how should I solve this? Still I have to be able to send the data via post to controller, and then after processing data return it back to the view.
You may make Json of any object as a single hidden form field and then post the form to action with string parameter. Then on server side desterilize json to the object inside the action method - even in another controller. You may return different view from the controller method.
Javascript (Say on Stock Page/View)
var form = document.createElement("form");
var input = document.createElement("input");
input.setAttribute("type", "hidden") ;
input.setAttribute("name", "outwardStocks") ;
input.setAttribute("value", JSON.stringify(selectedrecords));
form.appendChild(input);
document.body.appendChild(form);
form.action = "/CRM/Invoice/Index";
form.method = "post"
form.submit();
Controller code (Say Invoice Controller)
[HttpPost]
public IActionResult Index(string outwardStocks)
{
InvoiceItems invItems = JsonSerializer.Deserialize<InvoiceItems>(outwardStocks);
......
return View("Invoice" InvoiceVM);
}
I have three dropdownlist BodyPart, ExamDetail and ExamView and I have complete data set for these lists. I don't need to call controller again and again whenever the dropdown change event call but I want to fetch list from my model's property list. I am using JS Library to apply LINQ Queryto avoid any loops in code.
My Class Architecture is given below:
public class BodyPart
{
public string ID { get; set; }
public string Text { get; set; }
public List<ExamDetail> examdetail { get; set; }
}
public class ExamDetail
{
public string ID { get; set; }
public string Text { get; set; }
public List<ExamView> examview { get; set; }
}
public class ExamView
{
public string ID { get; set; }
public string Text { get; set; }
}
I have proper data in all my list, here is my js code to fetch ExamDetail Record
var selectedBodyPart = $("#BodyPartDDL").val();
var examdetailList = JSLINQ(#Model.bodypart).Where(function (item) {
return item.ID == selectedBodyPart; });
But I am getting "Uncaught SyntaxError: Unterminated template literal(…)" error where I pass my model list. I need to pass this examdetailList to my ExamDetail partial view. Thank you.
I have a list of (for example) countries in my database, which might have a class representation that looks like;
public class Country {
public int CountryId { get; set; }
public string CountryName { get; set; }
}
Now i have a ViewModel class that models a Student for example, and it looks like;
public class StudentViewModel {
public int StudentId { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
}
When i want to provide the Country the Student comes from, i usually include the following properties in the StudentViewModel class;
public int CountryId { get; set; }
public string CountryName { get; set; }
Now, i not quite sure if i am doing the right thing because on the client (using javascript), i have to keep the CountryId and CountryName synchronized. The reason i have done it this way for a while is because i used the CountryId property to set the initial value of the dropdownlist and CountryName to show the textual value on a different section of the same form.
I would like to know how other people are handling situations like this?
I am building JSON output from my array that I am intended to pass back to server where I have model class to bind JSON data variable to class variables. In this class I am also taking multiple records of say for argument 'Component' and to bind this part I have IList in my model class.
Now I have managed to pass data back to controller except the Components that is in IList... I am struggling to find answer.. your help will be really appreciated..
Model class
public class QualificationElementComponents_ViewModel
{
public int ElementIndex { get; set; }
public string ElementMarkingSchemeTitle { get; set; }
public int ElementAvailableMark { get; set; }
public int ElementPassMark { get; set; }
public int ElementMeritMark { get; set; }
public int ElementDistinctionMark { get; set; }
public IList<ECom1> ElementComponent { get; set; }
}
IList 'Component' Model class
public class ECom1
{
public int componentIndex { get; set; }
public int componentMark { get; set; }
}
Controller Method
public ActionResult CreateNewQualification(QualificationViewModel newQualificationData, IList<QualificationElementComponents_ViewModel> ElementComponentList)
{
in view
//build component list... possible will have multiple records in array
selectedComponentList.push({ componentIndex: recordId, componentMark: ComponentSchemeMark });
// build element list
selectElementList.push({ ElementIndex: E_RecordId, ElementMarkingSchemeTitle: E_MarkingSchemeTitle, ElementAvailableMark: E_AvailableMark, ElementPassMark: E_PassMark, ElementMeritMark: E_MeritMark, ElementDistinctionMark: E_DistinctionMark });
//bind arrays
selectElementList.push({ ElementComponent: selectedComponentList });
QualificationElemenetsAndComponentsList.push.apply(QualificationElemenetsAndComponentsList, selectElementList);
JSON Output
{"QualificationElemenetsAndComponentsList":[{"ElementIndex":1,"ElementMarkingSchemeTitle":"fg","ElementAvailableMark":"56","ElementPassMark":"6","ElementMeritMark":"5","ElementDistinctionMark":"6"},{"ElementComponent":[{"componentIndex":1,"componentMark":"23"},{"componentIndex":2,"componentMark":"44"}]}]}
require JSON Output
in comparison to above JSON I require following JSON format
{"QualificationElemenetsAndComponentsList":[{"ElementIndex":1,"ElementMarkingSchemeTitle":"d2","ElementAvailableMark":"223","ElementPassMark":"32","ElementMeritMark":"12","ElementDistinctionMark":"2","ElementComponent":[{"componentIndex":2,"componentMark":551}]}]}
Instead of adding the ElementComponent property to a new object and then into the array, you need to include it with the other properties like so:
//build component list... possible will have multiple records in array
selectedComponentList.push({ componentIndex: recordId, componentMark: ComponentSchemeMark });
// build element list
selectElementList.push({ ElementIndex: E_RecordId, ElementMarkingSchemeTitle: E_MarkingSchemeTitle, ElementAvailableMark: E_AvailableMark, ElementPassMark: E_PassMark, ElementMeritMark: E_MeritMark, ElementDistinctionMark: E_DistinctionMark, ElementComponent: selectedComponentList });
//Add ElementComponent with all the other properties