Skip to content
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

Closed
wants to merge 3 commits into from
Closed

use httpclient instead of webrequest #2124

wants to merge 3 commits into from

Conversation

flerka
Copy link

@flerka flerka commented Oct 18, 2019

Fixes #1756
Refactor PictureBox to use HttpClient instead of using WebRequest
Before implementing read comments from this pr #1952
Introduced HttpClient field as suggested by @RussKie in #1952 (comment)

Proposed changes

  • introduce optional HttpClient parameter in PictureBox constructor
  • use HttpClient instead of using WebRequest
  • use CancellationToken to cancel request
  • update error handling due to HttpClient behavior

Customer Impact

  • may be a breaking change: introduce optional HttpClient parameter in PictureBox constructor

Regression?

  • Yes

Risk

  • Low/Medium

Test methodology

  • Run all tests in the solution
  • Manual testing and debugging with test project according to debugging guide for various cases (success, unsuccessful such as remote server fail, non-existed image etc.)

Test environment(s)


.NET Core SDK (reflecting any global.json):
 Version:   3.0.100
 Commit:    04339c3a26

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.18362
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.0.100\

Host (useful for support):
  Version: 3.0.0
  Commit:  7d57652f33

.NET Core SDKs installed:
  2.1.801 [C:\Program Files\dotnet\sdk]
  2.2.401 [C:\Program Files\dotnet\sdk]
  3.0.100 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

Microsoft Reviewers: Open in CodeFlow

@flerka flerka requested a review from a team as a code owner October 18, 2019 08:55
@dnfclas
Copy link

dnfclas commented Oct 18, 2019

CLA assistant check
All CLA requirements met.

@@ -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)
Copy link
Contributor

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?

Copy link
Author

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?

Copy link
Contributor

@weltkante weltkante Oct 22, 2019

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

Copy link
Author

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?

Copy link
Member

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();
Copy link
Contributor

@weltkante weltkante Oct 18, 2019

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

HttpResponseMessage responseMessage = await _httpClient.GetAsync(CalculateUri(_imageLocation), _cancellationTokenSource.Token);
if (!responseMessage.IsSuccessStatusCode)
{
PostCompleted(new Exception($"HttpClient received non-successful status code: {responseMessage.StatusCode}"), false);
Copy link
Contributor

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

Copy link
Author

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?

Copy link
Contributor

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.

@ghost ghost added the waiting-author-feedback The team requires more information from the author label Oct 18, 2019
@ghost ghost removed the waiting-author-feedback The team requires more information from the author label Oct 22, 2019
@codecov
Copy link

codecov bot commented Oct 22, 2019

Codecov Report

Merging #2124 into master will increase coverage by 0.27317%.
The diff coverage is 7.69231%.

@@                 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
Flag Coverage Δ
#Debug 28.42538% <7.69231%> (+0.27317%) ⬆️
#production 28.42538% <7.69231%> (+0.27317%) ⬆️
#test 100% <ø> (ø) ⬆️

return;
}

_contentLength = (int)responseMessage.Content.Headers.ContentLength.GetValueOrDefault();
Copy link
Contributor

@weltkante weltkante Oct 22, 2019

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)

Suggested change
_contentLength = (int)responseMessage.Content.Headers.ContentLength.GetValueOrDefault();
_contentLength = (int)(responseMessage.Content.Headers.ContentLength ?? -1);

Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Member

@RussKie RussKie left a 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)
Copy link
Member

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;
Copy link
Member

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();
Copy link
Member

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.

Comment on lines +593 to +598
responseStream.BeginRead(
_readBuffer,
0,
ReadBlockSize,
new AsyncCallback(ReadCallBack),
responseStream);
Copy link
Member

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.

@ghost ghost added the waiting-author-feedback The team requires more information from the author label Jan 31, 2020
@ghost ghost added the no-recent-activity label Feb 7, 2020
@ghost
Copy link

ghost commented Feb 7, 2020

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.

@ghost ghost closed this Feb 14, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Feb 3, 2022
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
no-recent-activity waiting-author-feedback The team requires more information from the author
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PictureBox to use HttpClient instead of using WebRequest
5 participants