|
 Here is the function:
public static IList<TestDataModel> GetAllTestData(string keyName)
{
DataSet ds = new DataSet();
DataNamesMapper<TestDataModel> mapper = new DataNamesMapper<TestDataModel>();
DataTable dataTableALL = new DataTable();
List<TestDataModel> testData = new List<TestDataModel>();
using (var connection = new
OdbcConnection(TestDataFileConnection()))
{
connection.Open();
OdbcCommand cmd = new OdbcCommand();
cmd.Connection = connection;
System.Data.DataTable dtSheet = null;
dtSheet = connection.GetSchema(OdbcMetaDataCollectionNames.Tables, null);
foreach (DataRow row in dtSheet.Rows)
{
string sheetName = row["TABLE_NAME"].ToString();
if (!sheetName.EndsWith("$"))
continue;
var query = string.Format("select * from [{0}] where TestName = '{1}'", sheetName, keyName);
cmd.CommandText = query;
DataTable dt = new DataTable();
dt.TableName = sheetName;
OdbcDataAdapter da = new OdbcDataAdapter(cmd);
da.Fill(dt);
ds.Tables.Add(dt);
}
cmd = null;
connection.Close();
}
DataTable data= JoinExcelDatatoOneRow(ds);
testData = mapper.Map(data).ToList();
return testData.ToList();
}
|
|
|
|
|
So your data is coming in via ODBC which leaves you with a datatable. Make sure the source datatable has the same names as the destination object. Presumably excel does not have the payment options in the source so you need to load the default into the excel result set, simply return the default instead of the get/set property.
If Excel has the paymentoptions then it may be a complex type and will need specific processing to assign multiple values into your target object
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
So, if I browse to my site and login, I get a page with my personal images.
For example, I'm on-site with a client, take pictures, login and upload them. I want to retrieve them automatically from my laptop. To do so automatically, I need to login, get the HTML, grab the links then use WebClient.DownloadFile. Problem is, I can't see the HTML without a browser.
SO, more specifically - When I login with a browser and right click and "view HTML" I don't see the images in the markup, just the header and the footer HTML. The ONLY way to see the source is to use the inspector tool by right clicking on one of the images. So this code is generated by the browser, but not part of the original request.
I wrote a C# app that will log into my page and it works. I can login via SSL and my credentials, navigate pages BUT ... I can't see all the HTML.
I added a WebView2 form to my project (new Edge browser plugin for VS) and I can see the page, but I STILL can't get the source to get the path to the image.
So is there a way to get at this generated HTML programatically in C#? I can launch the dev tools (inspector) window with
myWebView2Browser.CoreWebView2.OpenDevToolsWindow(); ... but I need a way to view the ELEMENTS tab in that tool where the true HTML is.
I tried
myHTML = await myWebView2Browser.ExecuteScriptAsync("document.documentElement.outerHTML;"); ... and it gets the HTML, but NOT the FINAL rendered HTML that I can see in the inspector.
Hope this makes sense, it's making me crazy being so close! Thanks in advance!
|
|
|
|
|
Ask yourself - where are the images stored when I upload them?
They will either be in a database or the file system on the server. Instead of trying to use the HTML to get the file go directly to the source and download them from there, you should already have the credentials to access the server.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
What you're looking for is called a headless browser, so google "c# headless browser". However I agree that if you can simply download the images directly that would be better.
|
|
|
|
|
While I was reading code produced by coworker of mine, I got confused.
What does
public void DoSomething(double someParameter)
{
_SomeComponent.Set(someParameter);
Task.Run(() => Check(someParameter)).Wait();
} actually do, i.e. how does it differ from
public void DoSomething(double someParameter)
{
_SomeComponent.Set(someParameter);
Check(someParameter);
} The Check function is synchronous. So, the two versions could be equivalent?
That Task.Run starts the Check function in a new Task . But because of the Wait , DoSomething won't be left immediately after starting that Task , but only when that Task ran to completion (or failure).
But I am too confused by that snippet now to be sure of my assumptions.
Oh sanctissimi Wilhelmus, Theodorus, et Fredericus!
|
|
|
|
|
Bernhard Hiller wrote: how does it differ from
Well, it uses more memory, and adds a thread to the system ...
But no, you are right. In essence starting a Task to call Check and then calling Wait is the same as calling the method directly: the calling method will not continue until Check is complete.
It's possible that the cow-orker doesn't know what he is doing, it's possible that the Check method was used elsewhere and called via a Task without the Wait, but that code caused problems when re-used. We don't know, and will probably never find out.
But it's pretty poor code in it's current form!
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
Thanks.
When you say that the cow-orker did not know what he did, you may be right. Next to that code sits a TimeSpan.FromSeconds(SET_XY_MAX_WAIT_TIME_MS) (seconds vs. implied milliseconds ms)...
Or he just tries to obfuscate his code so that nobody else can deal with it - job security by obscurity!
Oh sanctissimi Wilhelmus, Theodorus, et Fredericus!
|
|
|
|
|
GO to QA - you'll meet a lot of that!
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
Is it possible that there are other tasks / threads and the author is trying to allow them to have a turn?
|
|
|
|
|
Hi all, just revisiting some code that scans a local LAN for servers broadcasting on UDP port 3483 and I'm seeing strange characters in the response string when I run the code on Win 10
I expect
"ENAME0\LMSPi\JSON0\9000"
but I'm getting
"ENAME\u0005LMSPi0JSON\u00049000"
I'm sure the weren't there when I ran the code under Win 7
What I'm after is the port number the server is running on which is the 9000 after JSON ( without the u0004 )
Any idea why I'm seeing these characters ?
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace LMS
{
class ScanLAN
{
[STAThread]
static int Main()
{
ScanForServer();
return 0;
}
static void ScanForServer()
{
IPEndPoint ReceiveIP = null;
UdpClient listener = null;
Byte[] RequestData = null;
byte[] BytesReceived = null;
string RetVal = "No response from LAN";
int UDPPort = 3483;
try
{
Console.WriteLine($"Starting LAN broadcast on port {UDPPort}...");
ReceiveIP = new IPEndPoint(IPAddress.Any, UDPPort);
listener = new UdpClient(UDPPort);
RequestData = listener.Receive(ref ReceiveIP);
if (RequestData != null && RequestData.Length > 0)
RetVal = Encoding.UTF8.GetString(RequestData);
Console.WriteLine($"Initial response data {RetVal}");
listener.Send(RequestData, RequestData.Length, ReceiveIP);
BytesReceived = listener.Receive(ref ReceiveIP);
if (BytesReceived != null && BytesReceived.Length > 0)
RetVal = Encoding.UTF8.GetString(BytesReceived);
Console.WriteLine($"Final response data {RetVal}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if (listener != null && listener.Client.Connected)
listener.Close();
}
}
}
}
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
There seems to be an encoding problem.
The MSDN examples on UdpClient.Receive all use Encoding.ASCII.GetString, whereas your code uses Encoding.UTF8
Also when I run
string s1= "ENAME\u0005LMSPi0JSON\u00049000";
byte[] bytes=Encoding.UTF8.GetBytes(s1);
string s2 = Encoding.ASCII.GetString(bytes);
then s2 looks almost identical to what you expected.
If this would be the solution, then I can't explain why it worked well on Win7.
Beware, code working well does not imply code being correct...
|
|
|
|
|
Thanks Luc I'll give it a shot
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
Hi Luc, I tried it with ASCII and no different I'll show you the entire string this time ( I abridged it in the original post )
"ENAME\u0005LMSPiJSON\u00049000UUID$0b5a576d-a276-4920-a9e8-134c41133102VERS\u00057.9.2"
Breaking it down
ENAME = LMSPi -- my server hostname
JSON = 9000 -- my server port
UUID = self explanatory
VERS = software version
If I print the byte array one byte at a time and convert it to a char I get
for (int i = 0; i < BytesReceived.Length; ++i)
{
char x = (char)BytesReceived[i];
Console.Write(x);
}
ENAMELMSPiJSON9000UUID$0b5a576d-a276-4920-a9e8-134c41133102VERS7.9.2
which is more like what I expect
Thanks very much for your help
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
modified 1-Jul-20 13:28pm.
|
|
|
|
|
So it is a series of key-value pairs, where each value (except the GUID that has a fixed length) would need either a length or a separator; in this case a length is used, i.e. the 6 characters "\u000X" stand for a the length of X for the value that follows; normally a small length could be stored in a single byte.
As you receive the lengths in this escape format no matter what encoding is used to receive them (so you told me), it suggests something might be wrong at the transmitter, not the receiver.
ALERT
Wait a minute, where are you seeing those unexpected strings exactly?
You are aware Visual Studio sometimes uses escapes, quotes, etc in its immediate window and maybe elsewhere too. Use Console.WriteLine to make sure, and/or apply string.Length and count the characters manually to compare!!!
modified 5-Jul-20 9:29am.
|
|
|
|
|
Hi Luc it was VS misleading me here is what I ended up doing to parse the values
Response from server - there are non printable characters in the response which I can't get to display here which contain the data lengths
ENAMELMSPiJSON9000UUID$0b5a576d-a276-4920-a9e8-134c41133102VERS7.9.2;
ResponseColumn ParseResponseString(string FieldName,string ResponseString)
{
ResponseColumn rc = new ResponseColumn();
int pos = ResponseString.IndexOf(FieldName);
string ColumnName = ResponseString.Substring(pos,5);
int DataLen = Convert.ToInt32(ColumnName[4]);
pos+= 5;
string ColumnValue = ResponseString.Substring(pos,DataLen);
rc.ColumnName = FieldName;
rc.ColumnValue = ColumnValue;
return rc;
}
public class ResponseColumn
{
public string ColumnName {get;set;}
public string ColumnValue{get;set;}
}
and use it thus
ResponseColumn rcPortNumber = ParseResponseString("JSON",ResponseString);
ResponseColumn rcServerName = ParseResponseString("NAME",ResponseString);
ResponseColumn rcServerVer = ParseResponseString("VERS",ResponseString);
Thanks for all your help
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
modified 2-Jul-20 7:34am.
|
|
|
|
|
You're welcome.
Two comments though:
1. you completely replaced the content of this message; I got an e-mail when you first created it. And obviously not when you replaced all of it, so it took a while before I accidentally noticed the new content. You should not replace/delete messages on CodeProject, you can create new ones, or add to or slightly edit existing ones.
2. your code is fine with the current data. It has a few weaknesses in general: it assumes field names are always 4 chars (see constants 4 and 5), and it locates field names by a string search, which could go wrong when one field name happens to appear as (part of) another field's value.
The universal way to handle this would be to scan the entire line and build a Dictionary<string,string> holding all the key-value pairs in the line; that too would rely on some restrictions on field names and/or values (e.g. field names are uppercase letters only, and field value lengths must be less than 'A').
Cheers
modified 5-Jul-20 9:45am.
|
|
|
|
|
Hi Luc, just some answers to your observations
it assumes field names are always 4 chars
they have been 4 chars for the last 25 years
and it locates field names by a string search,
How else could it be done ?
which could go wrong when one field name happens to appear as (part of) another field's value.
The fields always end with a data length
Thanks again
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
modified 6-Jul-20 4:27am.
|
|
|
|
|
Let's assume your server was renamed ZVERSA (or MYJSONMACHINE) and you start getting the string
ENAMEZVERSAJSON9000UUID$0b5a576d-a276-VERS-a9e8-134c41133102VERS7.9.2
^^^^
And now you try to get the version (or the json) field...
|
|
|
|
|
I agree with you totally Luc but I can't see a better way of parsing the string - if you have any suggestions please post them
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
Sure, here is some C#-like pseudocode that scans the line from left to right:
private Dictionary<string,string> parseLine(line) {
dict=New Dictionary<string,string>;
while not at end of line
key=readString(4)
length=readInt(1)
value=readString(length)
dict[key]=value
repeat
return dict
}
Afterwards you can easily access the values like so: dict["VERS"] ; beware they are all stored as strings, when a number is required, you'd have to apply int.TryParse() or similar while pulling them from the dict.
|
|
|
|
|
 Hi Luc, I was just going to post what I've done and I saw your reply - anyway here it is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LMS
{
public class ParseLMSResponse
{
private const string NAME = "ENAME";
private const string PORT = "JSON";
private const string UUID = "UUID";
private const string VERS = "VERS";
private byte[] ResponseBytes { get; set;}
private Dictionary<string, string> KeyValuePairs { get; set; }
public ParseLMSResponse(byte[] ResponseBytes)
{
this.ResponseBytes = ResponseBytes;
this.GetKeyValuePairs();
}
private void GetKeyValuePairs()
{
string keyname = "";
string keyvalue = "";
int i = 0;
byte b;
int DataLen = 0;
KeyValuePairs = new Dictionary<string, string>();
while (i < ResponseBytes.Length)
{
keyname = "";
keyvalue = "";
b = ResponseBytes[i];
while (b >= 65 && b <= 90)
{
keyname += Convert.ToChar(b);
++i;
b = ResponseBytes[i];
}
DataLen = Convert.ToInt32(b);
++i;
keyvalue = Encoding.UTF8.GetString(ResponseBytes, i, DataLen);
KeyValuePairs.Add(keyname, keyvalue);
i += DataLen;
}
}
public string GetHostName
{
get
{
return GetValue(NAME);
}
}
public string GetUUID
{
get
{
return GetValue(UUID);
}
}
public string GetVersion
{
get
{
return GetValue(VERS);
}
}
public int GetPortNumber
{
get
{
return Convert.ToInt32(GetValue(PORT));
}
}
private string GetValue(string Name)
{
string RetVal = "";
KeyValuePairs.TryGetValue(Name, out RetVal);
return RetVal;
}
}
}
A much better way methinks - thanks for your help
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|
|
OK, you've got it!
|
|
|
|
|
 Hi Pete,
while your last message keeps showing up and disappearing again, I changed your code a bit to be more concise and better match C# conventions:
using System;
using System.Collections.Generic;
using System.Text;
namespace LMS {
public class LMSResponseParser {
private byte[] responseBytes;
private Dictionary<string, string> dict;
public LMSResponseParser(byte[] responseBytes) {
this.responseBytes=responseBytes;
}
public string HostName {
get {
return getValue("ENAME");
}
}
public string UUID {
get {
return getValue("UUID");
}
}
public string Version {
get {
return getValue("VERS");
}
}
public int PortNumber {
get {
return Convert.ToInt32(getValue("PORT"));
}
}
private string getValue(string key) {
try {
if (dict==null) parse();
return dict[key];
} catch (Exception exc) {
throw new ApplicationException(
"Error parsing LMS response '"+responseBytes+"'", exc);
}
}
private void parse() {
dict=new Dictionary<string, string>();
for (int i = 0; i<responseBytes.Length;) {
string key = "";
byte b = responseBytes[i++];
while (b>='A'&&b<='Z') {
key+=(char)b;
b=responseBytes[i++];
}
int len = b;
string value = Encoding.ASCII.GetString(responseBytes, i, len);
i+=len;
dict[key]=value;
}
}
}
}
What I changed:
- usage slightly different: create an LMSResponseParser then get its properties; no reuse, parsing the next LMSresponse will require a new parser object!
- public variables, properties, method names start with uppercase, locals/privates don't.
- class names don't start with a verb, think "object" not "action".
- property names don't start with a verb (except sometimes "Is"); think "characteristic".
(OTOH Java does not have properties, there one uses getXxx() and setXxx() methods.)
- no internal properties (such as your ResponseBytes), there was no need to make them properties.
- no advance declaration of local variables, declare on first use instead (C# isn't C !)
- lazy parsing: only parse (once) when some result is actually asked for.
- error handling: what if an invalid set of bytes is given? or one of the keywords isn't present while trying to get its value? The above code catches it all and throws an exception.
PS: I did not test, there might be minor mistakes...
PS2: my way of placing { at the right rather than on the next line isn't what most people do, however I prefer it as it is more vertically compact.
|
|
|
|
|
Hi Luc, why did you remove the const strings ?
"We can't stop here - this is bat country" - Hunter S Thompson - RIP
|
|
|
|