|
In general that question is surprisingly popular on this site. Having been asked multiple times over the years. General answer to all of those is to find the specification for the device and then implement it.
Member 14930272 wrote: new to C# code. I want to communicate with my Cobas e411 machine
Presumably you are not new to programming and are at least mid-level programmer in some other programming language. So 3-5 years of professional experience. Otherwise this is going to be a very difficult task for you. It would be a non-trivial task for me and I have many years of experience and have interfaced with multiple devices including serial port devices in the past.
Steps
1. Learn about serial programming in general
2. Find the protocol/communication specification. You MUST have this. Be careful if googling since I came across at least one site that seemed sketchy. But since you presumably have the actual machine maybe you have a business account that will provide it.
3. Study the specification. Read it end to end. Don't skim.
4. Find some examples in C# for serial communication. NOT for this device. But rather just how to use serial ports. Maybe find several.
5. With all of the above you can probably start laying out a solution to your actual problem.
|
|
|
|
|
Thank you brother. I ll try and then will revert.
|
|
|
|
|
I'd attempt explicit reads and track "bytes read" before depending on the (flaky, IMO) received event.
"Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I
|
|
|
|
|
We have an an app that allows the user to import their Google contacts. We're modifying it because Google changed their auth process.
The idea is to call the Google Auth page and use an HTTPListener to listen on a port for the redirect URI. I went to the Google API Console [^]and set the Redirect URI to "https://127.0.0.1". We can't hardcode a port here.
There's a WPF sample from Google[^] here. Copy to a WPF window to see how it works.
So I set up the HTTPListener to handle the callback. The problem is that by using a random port number, how can you specify a callback URI in the control panel? Here's my code:
public async Task TryAuthorizeAsync()
{
string state = RandomDataBase64url(32);
string codeVerifier = RandomDataBase64url(32);
string codeChallenge = Base64urlencodeNoPadding(SHA256(codeVerifier));
string localHost = $"https://{IPAddress.Loopback}";
string listenerURI = $"{localHost}:{GetRandomUnusedPort()}/";
var listener = new HttpListener();
RaiseStatusChanged($"listening on {listenerURI}");
listener.Prefixes.Add(listenerURI);
listener.Start();
var authorizationRequest = $"{AuthorizationEndpoint}?response_type=code&" +
$"scope=openid%20profile&" +
$"redirect_uri={Uri.EscapeDataString(localHost)}&" +
$"client_id={ClientID}&" +
$"state={state}&" +
$"code_challenge={codeChallenge}&" +
$"code_challenge_method={CODE_CHALLENEGE_METHOD}";
Process.Start(authorizationRequest);
bool success = false;
await Task.Run(() =>
{
IAsyncResult context2 = listener.BeginGetContext(new AsyncCallback(result => ListenerCallback(result, state, codeVerifier, listenerURI)), listener);
if (HANDLE_TIMEOUT)
{
success = context2.AsyncWaitHandle.WaitOne(RESPONSE_TIMEOUT, true);
}
});
if (HANDLE_TIMEOUT && !success)
{
RaiseAuthorizationTimedOut();
}
}
When I run this, the user's Google auth page opens in the browser and when the user selects their account and authorizes, but the ListenerCallback never fires.
What I do see is the page directs to
This site can't be reached
127.0.0.1 refused to connect
The problem seems to be is that the auth needs the redirect port number. But we can't use a dedicated port number in the Google API Console, so how then can I redirect to the listener method?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
modified 23-Jun-23 12:30pm.
|
|
|
|
|
The listener listens on a single port.
The URL MUST specify the port. There is no other option.
That is fundamental to how HTTP and the underlying TCP/IP works.
After that I am not sure where your confusion lies.
You do realize that you can and should use a static port right? Same port for all customers/clients? The additional information in the URL differentiates each of them.
Also presumably the code above is just test code, because you would need a fixed service listener that responds to those requests. Can't create the listener in the same code that initiates the request.
|
|
|
|
|
jschell wrote: You do realize that you can and should use a static port right?
That's the problem. We can't specify a port in the Google Console. The port needs to be random at runtime
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
Kevin Marois wrote: We can't specify a port in the Google Console. Is the problem that there is no separate input field for the port number? In that case, have you tried putting the port number into the URL?
http://127.0.01:5687
5687 is the port number.
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
No, we are not allowed to use a static port
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
Kevin Marois wrote: The port needs to be random at runtime
Then you will need to put in in the URL.
As I noted the URL allows for a port in it. So you can indeed put it in there.
But that means you will need to fly the server every time. No idea what that will do to your security model. Certainly won't work without a range since some ports are required and some are considered high security risks (even when something else it attached.)
And for some reason that it will not accept a perfectly valid URL with a port then you are done. Your solution will not work.
Summarizing above.
1. You say the server port must be random.
2. You must include the port in the URL
3. Either 1 is false, or 2 must be true or your solution will not work
|
|
|
|
|
I agree, and I'm not sure how else I would do this.
What it comes down to is waiting for the OAuth to complete and getting the reponse url. Do you know of any other way of doing this other than using an HTTPListener?
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
Update on this. Here's some things I found...
First, on the Google People API Console[^], we had a Web Application set up, which I now think is wrong. We want a Desktop App since this is a WPF app.
One key difference is that the Web Application page shows fields for Authorized redirect URIs[^]. This one has '<https: 127.0.0.1="">' in it.
The Desktop App [^] does NOT have redirect URIs.
But when I downloaded the credentials JSON, it contains
"redirect_uris":["http://localhost"]}
Then, in my code I have
public async Task TryAuthorizeAsync()
{
...
string listenerURI = $"https://{IPAddress.Loopback}:{GetRandomUnusedPort()}/";
string callbackURI = "<a href="http:
RaiseStatusChanged($"listening on {listenerURI}");
try
{
_callbackListener.Prefixes.Add(listenerURI);
_callbackListener.Start();
}
catch (Exception e)
{
throw e;
}
var authorizationRequest = $"{AuthorizationEndpoint}?response_type=code&" +
$"scope=openid%20profile&" +
$"redirect_uri={Uri.EscapeDataString(callbackURI)}&" +
$"client_id={ClientID}&" +
$"state={state}&" +
$"code_challenge={codeChallenge}&" +
$"code_challenge_method={CODE_CHALLENEGE_METHOD}";
Process.Start(authorizationRequest);
bool success = false;
await Task.Run(() =>
{
var callback = new AsyncCallback(result => ListenerCallback(result, state, codeVerifier, listenerURI));
IAsyncResult context2 = _callbackListener.BeginGetContext(callback, _callbackListener);
if (HANDLE_TIMEOUT)
{
success = context2.AsyncWaitHandle.WaitOne(RESPONSE_TIMEOUT, true);
}
});
}
But it still doesn't work. The auth page comes up and when I select a Google account it attempts to redirect but fails with Site can't be reached. Localhost refused to connect[^]
Here's the thing, this is Google's sample code[^] and it appears that they're doing the same thing. I can't see their console, but their docs show a Loopback IP address (Redirect URI)[^]
So, it seems that for a web app it's required, and for a desktop app it's defaulting to localhost. Yet I can's seem to make this work.
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
|
|
|
|
|
127.0.0.1 and localhost are the same thing.
And neither is going to connect to anything but the box, the exact box, that originated the request.
For the internet an actual addressable IP must be public. Local networks can use private IP addresses.
HTTP uses IP addresses. Host names just are resolved to a IP address.
If the above is new to you then you can start by reading the following
IP address - Wikipedia[^]
|
|
|
|
|
It's not new.
The console won't accept wildcards
http: is not allowed. So, if the console requires a port #, and we are not allowed to use a static port #, then I don't see how this can work.
The question still is, how is there sample app doing it? If their app's redirectURI is 127.0.0.1 (according to the credentials JSON), and the client code is using a random port, then how does it work??
[UPDATE]
For some reason I can't explain, this now works
string listenerURI = $"http ://127.0.0.1:{GetRandomUnusedPort()}/";
In theory, theory and practice are the same. But in practice, they never are.”
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
modified 27-Jun-23 13:15pm.
|
|
|
|
|
|
Hey guys,
i was facing an error on my logging today which made me struggle a bit.
To clarify the issue i wanted to know if anyone knows such things happen on a regular basis or is just bad luck.
We are within a REST Endpoint where multiple users fire requests to, the Endpoint will then process all these requests async and return the results.
There problem happened here (i narrowed it down to the important parts and underlined them):
private ProjectInformationBase? GetProjectBaseByDevelopmentProject(Guid idProject)
{
const string getProject = "SELECT * FROM Projects WHERE idProject = @idProject";
var getProjectCommand = new SqlCommand(getProject, DbConnection);
getProjectCommand.Parameters.Add("@idProject", SqlDbType.UniqueIdentifier).Value = idProject;
var projectData = getProjectCommand.ReadDataByCommand();
if (projectData.Rows.Count == 1)
{
return new ProjectInformationBase
{
IdProjectInformationBase = (Guid)projectData.Rows[0]["idProject"],
};
}
return null;
}
private ProjectInformationBase? GetProjectBaseByEcrProject(Guid idProject)
{
const string getEcrProject = "SELECT * FROM EcrProject WHERE ecrIdProject = @idProject";
var getEcrProjectCommand = new SqlCommand(getEcrProject, DbConnection);
getEcrProjectCommand.Parameters.Add("@ecrIdProject", SqlDbType.UniqueIdentifier).Value = idProject;
var projectData = getEcrProjectCommand.ReadDataByCommand();
if (projectData.Rows.Count == 1)
{
try
{
return new ProjectInformationBase
{
IdProjectInformationBase = (Guid)projectData.Rows[0]["ecrIdProject"],
};
}
catch (Exception e)
{
throw;
}
}
return null;
}
On the log it said the column "idProject" wasn't found, which looks to me like it was using the wrong method / sql request or dataTable "projectData". Honestly i thought since they get created and populated each time they should be "threadsafe" and not interfere with each other.
Anyone care to explain what could have happened here?
*Edit**Also filled in the blank code lines*
Please note that the "DbConnection" is defined within the class and the class is registered as Singleton.
I could not come up with a better title, but to explain what i mean by that.
The error produced was, that within the Method for getting the Development Project, the result table did not contain a column with "idProject". This error should never happen, because we only read if we have exactly 1 row and the table on the SQL Server hasn't been changed for ages, nor are there null rows inside.
What I thought of now is that if both methods being executed, for some magical reason it could happen, because of the same names for variables within both of these methods, that the development project retrieval gets the result table of the ecr project which obviously does not contain that "idProject" column.
I have no other explanation for that issue, therefore I wanted to know if someone else could have an idea where it possibly could go wrong instead.
Rules for the FOSW ![ ^]
MessageBox.Show(!string.IsNullOrWhiteSpace(_signature)
? $"This is my signature:{Environment.NewLine}{_signature}": "404-Signature not found");
modified 21-Jun-23 5:15am.
|
|
|
|
|
Where is getProjectCommand defined, and what does the ReadDataByCommand method look like?
Is there a reason you're using SELECT * FROM ... instead of specifying the list of columns you want to load? That usually leads to performance problems, where you're loading more data that you actually need.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
The Command is defined in that "..." area, i just didn't want to bloat all the code inside.
ReadDataByCommand is a simple extension:
public static DataTable ReadDataByCommand(this SqlCommand command, bool mapResultsToDatabase = true)
{
try
{
using (var sqlDataAdapter = new SqlDataAdapter(command))
{
var resultTable = new DataTable();
if (mapResultsToDatabase)
{
sqlDataAdapter.FillSchema(resultTable, SchemaType.Mapped);
}
sqlDataAdapter.Fill(resultTable);
return resultTable;
}
}
catch (SqlException ex)
{
var exNew = new Exception(command.BuildErrorMessage(), ex);
throw exNew;
}
catch (InvalidOperationException ex)
{
var exNew = new Exception(command.BuildErrorMessage(), ex);
throw exNew;
}
catch (Exception ex)
{
var exNew = new Exception(command.BuildErrorMessage(), ex);
throw exNew;
}
}
I am loading all columns because i'll need all in the future, for testing purposes we just read out one yet.
Rules for the FOSW ![ ^]
MessageBox.Show(!string.IsNullOrWhiteSpace(_signature)
? $"This is my signature:{Environment.NewLine}{_signature}": "404-Signature not found");
|
|
|
|
|
Nothing obviously wrong there. However:
Quote: Please note that the "DbConnection" is defined within the class and the class is registered as Singleton.
That's a really bad idea. Every request will be fighting to use the same connection instance.
Instead, create the connection when you need it, and wrap it in a using block to ensure it's always disposed of properly when you've finished with it.
Connection pooling[^] will automatically take care of keeping a pool of database connections hanging around in case you're worried about the cost of creating the connection.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I do know and found that out earlier, i think this is actually the only place where those two methods will do this. So you think it could be related to that?
Because I had worse issues in the past and then decided that each time a request to the SQL Server gets fired we will create a new connection, with the exception of queries that need to be done one after another in a single method.
Rules for the FOSW ![ ^]
MessageBox.Show(!string.IsNullOrWhiteSpace(_signature)
? $"This is my signature:{Environment.NewLine}{_signature}": "404-Signature not found");
|
|
|
|
|
The SqlConnection class isn't thread-safe. If you're storing a single connection instance in a field of a singleton class, and then using that for queries across multiple threads, I wouldn't be surprised if the queries ended up with some cross-contamination.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
After reading that, i must admit, yes. I have no other words to describe my feelings right now
Rules for the FOSW ![ ^]
MessageBox.Show(!string.IsNullOrWhiteSpace(_signature)
? $"This is my signature:{Environment.NewLine}{_signature}": "404-Signature not found");
|
|
|
|
|
OK but can you expand on what you mean by "threading related naming and usage of const" though?
The var projectData in each method are completely unrelated by the way. They're different variables, having the same name doesn't have any meaning except to humans who read the source code.
|
|
|
|
|
I'll update that in the initial post.
I would assume so too, because everything is only declared and instantiated in each method separately. Nonetheless I had this strange behavior.
Rules for the FOSW ![ ^]
MessageBox.Show(!string.IsNullOrWhiteSpace(_signature)
? $"This is my signature:{Environment.NewLine}{_signature}": "404-Signature not found");
|
|
|
|
|
I think the usual convention is to "capitalize" column names in the database; to avoid the confusion you're experiencing with similar looking "variables".
I would think the "data base column definitions" should be looked at. Maybe somebody changed them when you weren't looking. There's nothing to indicate you're actually accessing the correct database or table (connection strings).
"Before entering on an understanding, I have meditated for a long time, and have foreseen what might happen. It is not genius which reveals to me suddenly, secretly, what I have to say or to do in a circumstance unexpected by other people; it is reflection, it is meditation." - Napoleon I
|
|
|
|
|
Expanding a bit more on why more than one DBConnection is needed.
This is not just a feature of the class. Rather it is the nature of the connection to the database itself.
For example most if not all databases have an explicit 'transaction' (in some definition of that word) when a statement runs across the connection. Regardless of whether the transaction type is changed by the code. And this is tied to the connection itself.
So for example a long running query (seconds hopefully and not minutes) has transaction type X. Then you attempt to use it in another process thread where you want to change the transaction type to Y. And then you send that second statement down the same connection. Which transaction is then in play.
Why do they do it that way? Because if the connection is lost the database must decide whether the currently running statement succeeded or failed. And different database DO decide that differently. Some decide it success which means they might commit a transaction. Others deem it a failure, so the fail the transaction.
|
|
|
|