-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use httpclient instead of webrequest #2124
use httpclient instead of webrequest #2124
Conversation
@@ -101,8 +109,11 @@ public class PictureBox : Control, ISupportInitialize | |||
/// Creates a new picture with all default properties and no Image. The default PictureBox.SizeMode | |||
/// will be PictureBoxSizeMode.NORMAL. | |||
/// </summary> | |||
public PictureBox() | |||
/// <param name="httpClient">Http client instance that will be used to download the image.</param> | |||
public PictureBox(HttpClient httpClient = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure designers will understand that constructor when generating/parsing Designer.cs files, did you test that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello! How do you think maybe it's better to remove HttpClient
from constructor to avoid possible problems with designer and add it as optional parameter to LoadAsync
method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll probably have to wait for repo members to comment on what they prefer, its new public API. My current preference would be adding an overload to LoadAsync that takes the HttpClient to use as a parameter.
Keeping it a constructor is possible but you'll probably have to split it instead of using a default-initialized parameter (I havent tried but I suspect it is handled badly in the designer, having multiple constructors is definitely ok though). But having it in the constructor also means almost all of the PictureBox users (everyone who uses a designer) won't be able to set it in the first place, so not my favorite.
The alternative of exposing it as a property has various problems (which I can expand on if someone should think its a better idea, but for now I'll wait for more opinions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But having it in the constructor also means almost all of the PictureBox users (everyone who uses a designer) won't be able to set it in the first place, so not my favorite.
Good point.
@RussKie could you please comment on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying the constructor, whilst is an option in theory, won't play nicely with the Designer. Nor it would work for all the existing consumers. So we can't accept this.
I don't think it is a good idea to expose the HttpClient
outside the control as we would be leaking implementation details, and won't be able to replace it, if necessary.
For the purpose of tests we can access the instance of HttpClient
via reflection.
{ | ||
_httpClient = httpClient ?? new HttpClient(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The common case of placing the PictureBox in the designer will create multiple HttpClient instances, which (in the other PR) has been identified as something we do not want to do
src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs
Outdated
Show resolved
Hide resolved
HttpResponseMessage responseMessage = await _httpClient.GetAsync(CalculateUri(_imageLocation), _cancellationTokenSource.Token); | ||
if (!responseMessage.IsSuccessStatusCode) | ||
{ | ||
PostCompleted(new Exception($"HttpClient received non-successful status code: {responseMessage.StatusCode}"), false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other exceptions are localized, you probably want to stick to the same pattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't understand how to localize it properly so I changed implementation a bit to this:
new Exception($"{responseMessage.ReasonPhrase}: {responseMessage.StatusCode}")
. Is it Ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, localization is done through SR
members, refer to the other exceptions which already existed in the file to see how it looks like. I don't know either how to add new entries so maybe wait for a repo member to point you into the right direction.
src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms/src/System/Windows/Forms/PictureBox.cs
Outdated
Show resolved
Hide resolved
Codecov Report
@@ Coverage Diff @@
## master #2124 +/- ##
===================================================
+ Coverage 28.15221% 28.42538% +0.27317%
===================================================
Files 910 915 +5
Lines 266718 266744 +26
Branches 37942 37949 +7
===================================================
+ Hits 75087 75823 +736
+ Misses 186400 185693 -707
+ Partials 5231 5228 -3
|
return; | ||
} | ||
|
||
_contentLength = (int)responseMessage.Content.Headers.ContentLength.GetValueOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having read through the rest of the file I think the old code used -1
for unknown content length, it contains explicit checks against that value - leaving it at zero may cause a division by zero during the progress calculation (didn't test it but it looks like it)
_contentLength = (int)responseMessage.Content.Headers.ContentLength.GetValueOrDefault(); | |
_contentLength = (int)(responseMessage.Content.Headers.ContentLength ?? -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should effort to reference a const for -1 perhaps const int UnknownContentLengthSentinal = -1
or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with Zach, let's have a const instead of a magic number.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies, your PR has totally fallen off my radar.
I think you're moving in the right direction but I feel more work is required.
We have manual tests, but we will also need automated tests to confirm the correctness.
@@ -101,8 +109,11 @@ public class PictureBox : Control, ISupportInitialize | |||
/// Creates a new picture with all default properties and no Image. The default PictureBox.SizeMode | |||
/// will be PictureBoxSizeMode.NORMAL. | |||
/// </summary> | |||
public PictureBox() | |||
/// <param name="httpClient">Http client instance that will be used to download the image.</param> | |||
public PictureBox(HttpClient httpClient = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying the constructor, whilst is an option in theory, won't play nicely with the Designer. Nor it would work for all the existing consumers. So we can't accept this.
I don't think it is a good idea to expose the HttpClient
outside the control as we would be leaking implementation details, and won't be able to replace it, if necessary.
For the purpose of tests we can access the instance of HttpClient
via reflection.
/// <summary> | ||
/// Http client instance. | ||
/// </summary> | ||
private readonly HttpClient _httpClient; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This must be made static
to it is shared amongst all instances of PictureBox
an app may have.
The HttpClient
's API are confusing at best, and that causes a serious confusion amongst developers. There are many articles explaining how to use it correctly, e.g I may find this article useful.
return; | ||
} | ||
|
||
_contentLength = (int)responseMessage.Content.Headers.ContentLength.GetValueOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with Zach, let's have a const instead of a magic number.
responseStream.BeginRead( | ||
_readBuffer, | ||
0, | ||
ReadBlockSize, | ||
new AsyncCallback(ReadCallBack), | ||
responseStream); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be replaced with the new async API as well. Something like (pseudo code):
do
{
var bytesRead = await responseStream.ReadAsync(_readBuffer, 0, ReadBlockSize);
if (bytesRead == 0)
{
isMoreToRead = false;
_currentAsyncLoadOperation?.Post(_loadProgressDelegate, new ProgressChangedEventArgs(100, null));
continue;
}
await _tempDownloadStream.WriteAsync(_readBuffer, 0, bytesRead);
_totalBytesRead += bytesRead;
if (_contentLength != -1)
{
int progress = (int)(100 * (((float)_totalBytesRead) / ((float)_contentLength)));
_currentAsyncLoadOperation?.Post(_loadProgressDelegate, new ProgressChangedEventArgs(progress, null));
}
}
while (isMoreToRead);
I looked up the idea here.
This pull request has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 7 days. It will be closed if no further activity occurs within 7 days of this comment. |
Fixes #1756
Refactor
PictureBox
to useHttpClient
instead of usingWebRequest
Before implementing read comments from this pr #1952
Introduced HttpClient field as suggested by @RussKie in #1952 (comment)
Proposed changes
HttpClient
parameter inPictureBox
constructorHttpClient
instead of usingWebRequest
CancellationToken
to cancel requestHttpClient
behaviorCustomer Impact
HttpClient
parameter inPictureBox
constructorRegression?
Risk
Test methodology
Test environment(s)
Microsoft Reviewers: Open in CodeFlow