Getting the value of JavaScript/HTML variables in C# - javascript

There is a webpage I am trying to extract data from. By looking at the HTML in the page Source, I can find the data I am interested inside script tags.
It looks like the following:
<html>
<script type="text/javascript">
window.gon = {};
gon.default_profile_mode = false;
gon.user = null;
gon.product = "shoes";
gon.books_jsonarray = [
{
"title": "Little Sun",
"authors": [
"John Smith"
],
edition: 2,
year: 2009
},
{
"title": "Little Prairie",
"authors": [
"John Smith"
],
edition: 3,
year: 2009
},
{
"title": "Little World",
"authors": [
"John Smith",
"Mary Neil",
"Carla Brummer"
],
edition: 3,
year: 2014
}
];
</script>
</html>
What I would like to achieve is, call the webpage by using its url, then retrieving the 'gon' variable from JavaScript and store it in a C# variable. In other words, in C#, I would like to have a data structure (a dictionary for instance) that would hold the value of 'gon'.
I have tried researching how to get a variable defined in JavaScript via C# WebBrowser, and this is what I found:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Net;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using mshtml;
namespace Mynamespace
{
public partial class Form1 : Form
{
public WebBrowser WebBrowser1 = new WebBrowser();
private void Form1_Load(object sender, EventArgs e)
{
string myurl = "http://somewebsite.com"; //Using WebBrowser control to load web page
this.WebBrowser1.Navigate(myurl);
}
private void btnGetValueFromJs_Click(object sender, EventArgs e)
{
var mydoc = this.WebBrowser1.Document;
IHTMLDocument2 vDocument = mydoc.DomDocument as IHTMLDocument2;
IHTMLWindow2 vWindow = (IHTMLWindow2)vDocument.parentWindow;
Type vWindowType = vWindow.GetType();
object strfromJS = vWindowType.InvokeMember("mystr",
BindingFlags.GetProperty, null, vWindow, new object[] { });
//Here, I am able to see the string "Hello Sir"
object gonfromJS = vWindowType.InvokeMember("gon",
BindingFlags.GetProperty, null, vWindow, new object[] { });
//Here, I am able to see the object gonfromJS as a '{System.__ComObject}'
object gonbooksfromJS = vWindowType.InvokeMember("gon.books_jsonarray",
BindingFlags.GetProperty, null, vWindow, new object[] { });
//This error is thrown: 'An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll; (Exception from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))'
}
}
}
I am able to retrieve values of string or number variables such as:
var mystr = "Hello Sir";
var mynbr = 8;
However, even though I am able to see that the 'gon' variable is being passed as a '{System.__ComObject}', I don't know how to parse it in order to see the values of its sub components. It would be nice if I could parse it, but if not, what I would like to have instead, is a C# Data Structure with keys/values that contains all the sub infos for the gon variable, and especially, be able to view the variable 'gon.books_jsonarray'.
Any help on how to achieve this would be very much appreciated. Note that I cannot change the source html/javascript in anyway, and so, what I need is a C# code that would allow to reach my goal.

You can cast the result of InvokeMember() to dynamic and use the property names directly in your C# code. Array indexing is tricky but can be done with another use of InvokeScript(), see my example:
private void btnGetValueFromJs_Click(object sender, EventArgs e)
{
var mydoc = this.WebBrowser1.Document;
IHTMLDocument2 vDocument = mydoc.DomDocument as IHTMLDocument2;
IHTMLWindow2 vWindow = (IHTMLWindow2)vDocument.parentWindow;
Type vWindowType = vWindow.GetType();
var gonfromJS = (dynamic)vWindowType.InvokeMember("gon",
BindingFlags.GetProperty, null, vWindow, new object[] { });
var length = gonfromJS.books_jsonarray.length;
for (var i = 0; i < length; ++i)
{
var book = (dynamic) mydoc.InvokeScript("eval", new object[] { "gon.books_jsonarray[" + i + "]" });
Console.WriteLine(book.title);
/* prints:
* Little Sun
* Little Prairie
* Little World
*/
}
}

You need to use JSON.stringify to convert your gon.books_jsonarray variable to JSON string
After you can retrive JSON using next C# code:
var gonFromJS = mydoc.InvokeScript("eval", new object[] { "JSON.stringify(gon.books_jsonarray)" }).ToString();
After you can deserialize JSON to object using Newtonsoft.Json
My full code is here:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var webBrowser = new WebBrowser();
webBrowser.DocumentCompleted += (s, ea) =>
{
var mydoc = webBrowser.Document;
var gonFromJS = mydoc.InvokeScript("eval", new object[] { "JSON.stringify(gon.books_jsonarray)" }).ToString();
var gonObject = JsonConvert.DeserializeObject<List<Books>>(gonFromJS);
};
var myurl = "http://localhost/test.html";
webBrowser.Navigate(myurl);
}
private class Books
{
public string Title { get; set; }
public List<string> Authors { get; set; }
public int Edition { get; set; }
public int Year { get; set; }
}
}
}
Also you can see output on screenshot:
EDIT:
Also you can have a trouble with JSON.stringify method.
It can returns null.
In this case you can review SO topics: here and here.
If JSON.stringify method returns null then try to add next code to your HTML page:
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' >
</head>

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);

Passing Javascript object throught JavascriptInterface to Android application [duplicate]

Is it possible to pass a JavaScript object from JavaScript to Java using addJavascriptInterface()? Something along these lines:
var javaScriptObject = {"field1":"string1", "field2":"string2"};
JavaScriptInterface.passObject(javaScriptObject);
How would such a call be captured on the Java side? I have no problem setting up the interface to send a string, but when I send an object, I receive null on the Java end.
AFAIK, addJavascriptInterface() only works with primitive types and Strings, and so you cannot pass arbitrary Javascript objects.
This is how I am doing...
In Android...
#JavascriptInterface
public void getJSONTData(String jsonData) {
try {
JSONObject data = new JSONObject(jsonData); //Convert from string to object, can also use JSONArray
} catch (Exception ex) {}
}
In JavaScript...
var obj = { Name : 'Tejasvi', Age: 100};
var str = JSON.stringify(obj);
Android.getJSONTData(str);
As of now, I could not find any other proper way to pass the native JavaScript object directly to JavascriptInterface.
Calling Android.getJSONTData({ Name : 'Tejasvi', Age: 100}) results in null (if parameter type is Object) or undefined (if parameter type is defined as String) in getJSONTData.
I found a solution, using JSON. My Java method returns a JSONArray, on my javascript code I receive this and convert to a javascript vector using JSON.parse(). See the example:
Java:
public class JavaScriptInterface {
Context mContext;
private static int ind=-1;
private static int [] val = { 25, 25, 50, 30, 40, 30, 30, 5, 9 };
public JavaScriptInterface(Context c) {
mContext = c;
}
#JavascriptInterface
public JSONArray getChartData() {
String texto = " [ {name: 'valor1', 2007: "+val[(++ind)%9]+"}, "+
" {name: 'valor2', 2007: "+val[(++ind)%9]+"}, "+
" {name: 'valor3', 2007: "+val[(++ind)%9]+"} ]";
JSONArray jsonar=null;
try {
jsonar = new JSONArray(texto);
} catch (JSONException e) {
e.printStackTrace();
}
return jsonar;
}
}
Now the javascript code:
window.generateData = function() {
/*var data = [ {name: 'valor1', 2007: 50},
{name: 'valor2', 2007: 20},
{name: 'valor3', 2007: 30} ]; */
var data = JSON.parse( Android.getChartData() );
return data;
};
The commented code above show how it was when static, and now the data came from the Java code.
It was testes on Android 2.1 and 3.2.
I can run this feature
In Javascript :
var data = {
'username' : $('#username').val().trim(),
'password' : $('#password').val().trim(),
'dns' : $('#dns').val().trim()
}
var str = JSON.stringify(data);
Native.getLoginService(str);
In Android :
#JavascriptInterface
public void getLoginService(String jsonData){
try{
JSONObject data = new JSONObject(jsonData);
String username = data.getString("username");
String password = data.getString("password");
String dns = data.getString("dns");
Log.i("TAG",username + " - " + password + " - " + dns);
}catch (Exception ex){
Log.i("TAG","error : " + ex);
}
}
Good luck with...
I think you can also pass JSONObject and JSONArray. So not only primitive types, but also primitive types stored in a javascript array [0,1,2] or dictionary {one:1, two:2}.
I have NOT verified this in code, just read the docs. Might be using it soon.
You can't pass JSONObject or JSONArray, but you can send strings with that form and parse them to those types.
Your option is to expose the method using strings and then you can use the JSONObject or JSONArray to parse the string and use it accordingly.
Here is what I did.
#JavascriptInterface
public void passJSON(String array, String jsonObj) throws JSONException
{
JSONArray myArray = new JSONArray(array);
JSONObject myObj = new JSONObject(jsonObj);
...
}
where array is '["string1","string2"]' and jsonObj is '{attr:1, attr2:"myName"}'

How to pass dictionary with empty array as value

I have this class:
public class State{
public Dictionary<SecurityType, List<long>> assets { get; set; }
}
And action:
[HttpPost]
public virtual ActionResult GetHoldings(State state)
{
return Json(new HoldingsBL().GetHoldings(state));
}
public enum SecuritySerachType
{
Company = 1,
Security,
}
when i try to pass something like this:
{state:{assets :[{"Key":1,"Value":[]}]}}
i got empty dictionary in asset property.
i already read this solution , but i don`t realize how to solve my problem.
Some easy solution?
edit:
I try to add ValueProviderFactory as Oleksii Aza said, but there is a problem to compare it to backingStore(there is already exist a dictionary check, probably for nested objects):
var d = value as IDictionary<string, object>;
if (d != null)
{
foreach (var entry in d)
{
AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
}
return;
}
So i still stuck with this issue.
Try using object-like JSON structure for C# Dictionary, for example:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
public class Program
{
public enum SecuritySerachType
{
Company = 1,
Security,
}
public class State{
public Dictionary<SecuritySerachType, List<long>> assets { get; set; }
}
public static void Main()
{
var state = JsonConvert.DeserializeObject<State>(#"{assets :{""1"":[]}}");
Console.WriteLine(state.assets[SecuritySerachType.Company].Count);
}
}
Link to dotnet fiddle

JavaScript equivalent of .NET XML Deserialization

I'm looking for a JavaScript library that can deserialize/unmarshal XML (strings or DOM) to JavaScript classes in a way similar to the .NET XmlSerializer's Deserialize method.
The functionality I'm looking for:
Classes are defined as JavaScript constructor functions.
Mapping between nodes and classes/properties is configurable.
Deserialization result consists of instances of these classes.
For example, the following XML:
<root textAttribute='text1' numberAttribute='1' attributeToIgnore1='ignored1' attributeToIgnore2='ignored2'>
<children>
<child>text2</child>
<child>text3</child>
</children>
<childToIgnore>ignored3</childToIgnore>
</root>
used with JavaScript definitions similar to these:
function RootClass() {
this.stringProperty = "";
this.integerProperty = 0;
this.collectionProperty = [];
}
function ChildClass() {
this.stringProperty = "";
}
should produce JavaScript objects similar to the following:
var result = new RootClass();
result.textProperty = "text1";
result.integerProperty = 1;
result.collectionProperty = [];
result.collectionProperty[0] = new ChildClass();
result.collectionProperty[0].textProperty = "text2";
result.collectionProperty[1] = new ChildClass();
result.collectionProperty[1].textProperty = "text3;
An example of .NET (C#) code that does the same would be something like (see this .NET Fiddle for a working example):
public class Program
{
public static void Main()
{
var result = Serializer.Deserialize();
Console.WriteLine("text: {0}", result.StringProperty);
Console.WriteLine("number: {0}", result.IntegerProperty);
Console.WriteLine("enum: {0}", result.EnumProperty);
Console.WriteLine("child[0].Value: {0}", result.CollectionProperty[0].Value);
Console.WriteLine("other#[0]: {0}", result.OtherAttributes[0].InnerText);
Console.WriteLine("other*[0]: {0}", result.OtherElements[0].InnerText);
}
}
public static class Serializer
{
public static RootClass Deserialize()
{
var type = typeof (RootClass);
var serializer = new XmlSerializer(type);
var xmlString = #"
<root textAttribute='text1' numberAttribute='1' enumAttribute='item1' attributeToIgnore1='ignored1' attributeToIgnore2='ignored2'>
<children>
<child>text2</child>
<child>text3</child>
</children>
<childToIgnore>ignored3</childToIgnore>
</root>";
using (var stringReader = new StringReader(xmlString))
{
return serializer.Deserialize(stringReader) as RootClass;
}
}
}
[XmlRoot("root")]
public class RootClass
{
[XmlAttribute("textAttribute")]
public string StringProperty;
[XmlAttribute("numberAttribute")]
public int IntegerProperty;
[XmlAttribute("enumAttribute")]
public Enumeration EnumProperty;
[XmlAnyAttribute]
public XmlAttribute[] OtherAttributes;
[XmlArray("children")]
[XmlArrayItem("child")]
public Collection<ChildClass> CollectionProperty;
[XmlAnyElement]
public XmlElement[] OtherElements;
}
public enum Enumeration
{
[XmlEnum("item1")]
Item1,
[XmlEnum("item2")]
Item2
}
public class ChildClass
{
[XmlText]
public string Value;
}
Jsonix by Alexey Valikov (source, guide) can deserialize XML to JavaScript based on configurable mapping.
I've contributed code to support deserializing custom classes using instance factories. This will hopefully make it into the next release of Jsonix (2.0.11).
var input = "<element1 attribute1='value1' />";
var Class1 = function () {};
Class1.prototype.toString = function () {
return this.Property1;
};
var mapping = {
elementInfos: [{
elementName: "element1",
typeInfo: new Jsonix.Model.ClassInfo({
name: "Element1",
instanceFactory: Class1,
propertyInfos: [
new Jsonix.Model.AttributePropertyInfo({
name: "Property1",
attributeName: "attribute1"
})
]
})
}]
};
var context = new Jsonix.Context([mapping]);
var unmarshaller = context.createUnmarshaller();
var result = unmarshaller.unmarshalString(input).value;
console.log(result.toString()); // logs "value1"
A longer working example on JSFiddle uses XML from question.

Serialize enum to const JsonNet

I am using Asp MVC 3 application.
I have an Enum:
public enum EmployeesOptions
{
John = 1,
Peter = 2,
Andrew = 3
}
And a MyViewModel class
public class MyViewModel
{
public MyViewModel()
{
Employees = new List<EmployeesOptions>()
{
EmployeesOptions.John,
EmployeesOptions.Peter,
EmployeesOptions.Andrew
};
}
public IEnumerable<EmployeesOptions> Employees { get; set; }
}
My Controller:
public ActionResult Index()
{
var vm = new MyViewModel();
return View(vm);
}
In My Index View:
#model MyViewModel
<script type="text/javascript">
var jsonString = '#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model))';
var data = ko.mapping.fromJSON(jsonString);
omega.franchiseInfo = ko.mapping.fromJS(data);
</script>
My serialized data coming from the server looks like this:
Emplyees:[1,2,3]
I want to be like this:
Emplyees:["John","Peter","Andrew"]
What am I missing ?
Update:
var jsonString = '#Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model, Newtonsoft.Json.Formatting.None, new Newtonsoft.Json.Converters.StringEnumConverter()))';
This do the job!
If you want this enum type always to be serialized with its string values, you can decorate it with a JsonConverter attribute and the StringEnumConverter class like this:
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
[JsonConverter(typeof(StringEnumConverter))]
public enum EmployeesOptions
{
John = 1,
Peter = 2,
Andrew = 3
}
Then you don't need to specify any converter classes in the serializer options anymore.
I tried decorating a List property that holds Enum values with [JsonConverter(typeof(StringEnumConverter))] but understandably that didn't work since it should decorate the Enum type directly.
// Did not work!
[JsonConverter(typeof(StringEnumConverter))]
public List<Assessment.AssessmentFunction> SelectedFunctions { get; set; }
then I did as you suggested and it worked as expected.
var selectedFunctions = #Html.Raw(JsonConvert.SerializeObject(Model.SelectedFunctions,
new StringEnumConverter()))
Instead of Enum int values now I get Enum strings in the JavaScript code inside a Razor .cshtml view. Just what I needed in a specific situation.
Before
var selectedFunctions = #Html.Raw(Json.Encode(Model.SelectedFunctions))
// Output
var selectedFunctions = [3, 3, 2, 2, 2, 3, 2, 2]
After
var selectedFunctions = #Html.Raw(JsonConvert.SerializeObject(Model.SelectedFunctions,
new StringEnumConverter()))
// Output
var selectedFunctions = ["Nurse","Nurse","Doctor","Doctor","Doctor","Nurse","Doctor","Doctor"]
You are returning enums, and by default it will display the enum values. You can modify your model like this.
public class MyViewModel
{
public MyViewModel()
{
Employees = new List<EmployeesOptions>()
{
EmployeesOptions.John.ToString(),
EmployeesOptions.Peter.ToString(),
EmployeesOptions.Andrew.ToString()
};
}
public IEnumerable<EmployeesOptions> Employees { get; set; }
}
By using the .ToString() extension method we can convert enums to the exact thing they are representing to string format.

Categories

Resources