Omotač oko HttpClient. HTTPS ne traži ništa posebno: u BaseAddress upišeš https://..., a šifrovanje i provera sertifikata idu automatski.
HttpClient se pravi tek pri prvom zahtevu, kad su properties iz dizajnera već postavljeni.AllowUntrustedCertificate: samo za testiranje uređaja ili lokalnog servera sa self-signed sertifikatom (npr. https://192.168.1.50). U produkciji False.ResponseReceived i RequestFailed stižu na UI niti.HttpClientComponent.cs
using System.ComponentModel;
namespace WinFormsApp_test
{
/// <summary>
/// HttpClient kao non-visual komponenta. https radi samo od sebe — dovoljno je https:// u BaseAddress.
/// </summary>
[ToolboxItem(true)]
[DesignerCategory("Code")]
[DefaultEvent(nameof(ResponseReceived))]
[DefaultProperty(nameof(BaseAddress))]
public class HttpClientComponent : Component
{
private HttpClient? _http;
private readonly SynchronizationContext? _ui = SynchronizationContext.Current;
public HttpClientComponent() { }
public HttpClientComponent(IContainer container)
{
container.Add(this);
}
[Category("Connection"), Description("Npr. https://api.example.com/")]
[DefaultValue("https://localhost/")]
public string BaseAddress { get; set; } = "https://localhost/";
[Category("Connection"), Description("Timeout zahteva u sekundama.")]
[DefaultValue(30)]
public int TimeoutSeconds { get; set; } = 30;
[Category("Security"), Description("Prihvati i nevažeći/self-signed sertifikat. Samo za testiranje!")]
[DefaultValue(false)]
public bool AllowUntrustedCertificate { get; set; }
[Category("Data")]
public event EventHandler<string>? ResponseReceived;
[Category("Data")]
public event EventHandler<Exception>? RequestFailed;
// HttpClient se pravi tek pri prvom zahtevu, kad su properties već postavljeni
private HttpClient Http => _http ??= CreateClient();
private HttpClient CreateClient()
{
var handler = new HttpClientHandler();
if (AllowUntrustedCertificate)
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
return new HttpClient(handler)
{
BaseAddress = new Uri(BaseAddress),
Timeout = TimeSpan.FromSeconds(TimeoutSeconds),
};
}
public async Task<string?> GetAsync(string path)
{
try
{
string body = await Http.GetStringAsync(path);
Raise(() => ResponseReceived?.Invoke(this, body));
return body;
}
catch (Exception ex)
{
Raise(() => RequestFailed?.Invoke(this, ex));
return null;
}
}
public async Task<string?> PostJsonAsync(string path, string json)
{
try
{
using var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
using var response = await Http.PostAsync(path, content);
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
Raise(() => ResponseReceived?.Invoke(this, body));
return body;
}
catch (Exception ex)
{
Raise(() => RequestFailed?.Invoke(this, ex));
return null;
}
}
private void Raise(Action a)
{
if (_ui != null) _ui.Post(_ => a(), null); else a();
}
protected override void Dispose(bool disposing)
{
if (disposing) _http?.Dispose();
base.Dispose(disposing);
}
}
}private async void button1_Click(object sender, EventArgs e)
{
await httpClientComponent1.GetAsync("api/status");
}
private void httpClientComponent1_ResponseReceived(object sender, string body)
{
textBox1.Text = body; // već na UI niti
}