Décider entre HttpClient et WebClient


219

Notre application Web s'exécute dans .Net Framework 4.0. L'interface utilisateur appelle les méthodes du contrôleur via des appels ajax.

Nous devons consommer le service REST de notre fournisseur. J'évalue la meilleure façon d'appeler le service REST dans .Net 4.0. Le service REST nécessite un schéma d'authentification de base et il peut renvoyer des données à la fois en XML et en JSON. Il n'y a aucune exigence pour le téléchargement / téléchargement de données énormes et je ne vois rien à l'avenir. J'ai examiné quelques projets de code open source pour la consommation REST et je n'ai trouvé aucune valeur dans ceux-ci pour justifier une dépendance supplémentaire dans le projet. Commencé à évaluer WebClientet HttpClient. J'ai téléchargé HttpClient pour .Net 4.0 à partir de NuGet.

J'ai cherché des différences entre WebClientet HttpClientet ce site a mentionné qu'un seul HttpClient peut gérer les appels simultanés et qu'il peut réutiliser le DNS résolu, la configuration des cookies et l'authentification. Je n'ai pas encore vu les valeurs pratiques que nous pourrions gagner en raison des différences.

J'ai fait un test de performance rapide pour trouver comment WebClient(appels de synchronisation), HttpClient(synchronisation et async) fonctionnent. et voici les résultats:

Utilisation de la même HttpClientinstance pour toutes les requêtes (min - max)

Synchronisation WebClient: 8 ms - 167 ms
Synchronisation HttpClient: 3 ms - 7228 ms
Async HttpClient: 985 - 10405 ms

Utiliser un nouveau HttpClientpour chaque demande (min - max)

Synchronisation WebClient: 4 ms - 297 ms
Synchronisation HttpClient: 3 ms - 7953 ms
Async HttpClient: 1027 - 10834 ms

Code

public class AHNData
{
    public int i;
    public string str;
}

public class Program
{
    public static HttpClient httpClient = new HttpClient();
    private static readonly string _url = "http://localhost:9000/api/values/";

    public static void Main(string[] args)
    {
       #region "Trace"
       Trace.Listeners.Clear();

       TextWriterTraceListener twtl = new TextWriterTraceListener(
           "C:\\Temp\\REST_Test.txt");
       twtl.Name = "TextLogger";
       twtl.TraceOutputOptions = TraceOptions.ThreadId | TraceOptions.DateTime;

       ConsoleTraceListener ctl = new ConsoleTraceListener(false);
       ctl.TraceOutputOptions = TraceOptions.DateTime;

       Trace.Listeners.Add(twtl);
       Trace.Listeners.Add(ctl);
       Trace.AutoFlush = true;
       #endregion

       int batchSize = 1000;

       ParallelOptions parallelOptions = new ParallelOptions();
       parallelOptions.MaxDegreeOfParallelism = batchSize;

       ServicePointManager.DefaultConnectionLimit = 1000000;

       Parallel.For(0, batchSize, parallelOptions,
           j =>
           {
               Stopwatch sw1 = Stopwatch.StartNew();
               GetDataFromHttpClientAsync<List<AHNData>>(sw1);
           });
       Parallel.For(0, batchSize, parallelOptions,
            j =>
            {
                Stopwatch sw1 = Stopwatch.StartNew();
                GetDataFromHttpClientSync<List<AHNData>>(sw1);
            });
       Parallel.For(0, batchSize, parallelOptions,
            j =>
            {
                using (WebClient client = new WebClient())
                {
                   Stopwatch sw = Stopwatch.StartNew();
                   byte[] arr = client.DownloadData(_url);
                   sw.Stop();

                   Trace.WriteLine("WebClient Sync " + sw.ElapsedMilliseconds);
                }
           });

           Console.Read();
        }

        public static T GetDataFromWebClient<T>()
        {
            using (var webClient = new WebClient())
            {
                webClient.BaseAddress = _url;
                return JsonConvert.DeserializeObject<T>(
                    webClient.DownloadString(_url));
            }
        }

        public static void GetDataFromHttpClientSync<T>(Stopwatch sw)
        {
            HttpClient httpClient = new HttpClient();
            var response = httpClient.GetAsync(_url).Result;
            var obj = JsonConvert.DeserializeObject<T>(
                response.Content.ReadAsStringAsync().Result);
            sw.Stop();

            Trace.WriteLine("HttpClient Sync " + sw.ElapsedMilliseconds);
        }

        public static void GetDataFromHttpClientAsync<T>(Stopwatch sw)
        {
           HttpClient httpClient = new HttpClient();
           var response = httpClient.GetAsync(_url).ContinueWith(
              (a) => {
                 JsonConvert.DeserializeObject<T>(
                    a.Result.Content.ReadAsStringAsync().Result);
                 sw.Stop();
                 Trace.WriteLine("HttpClient Async " + sw.ElapsedMilliseconds);
              }, TaskContinuationOptions.None);
        }
    }
}

Mes questions

  1. Les appels REST reviennent en 3-4 secondes, ce qui est acceptable. Les appels au service REST sont lancés dans des méthodes de contrôleur qui sont appelées à partir d'appels ajax. Pour commencer, les appels s'exécutent dans un thread différent et ne bloquent pas l'interface utilisateur. Alors, puis-je rester avec les appels de synchronisation?
  2. Le code ci-dessus a été exécuté dans ma boîte locale. Dans la configuration de la prod, la recherche DNS et proxy sera impliquée. Y a-t-il un avantage à utiliser HttpClientplus WebClient?
  3. La HttpClientconcurrence est-elle meilleure que WebClient? D'après les résultats du test, je constate que les WebClientappels de synchronisation fonctionnent mieux.
  4. Sera HttpClientun meilleur choix de conception si nous passons à .Net 4.5? La performance est le facteur clé de conception.

5
Votre test est injuste GetDataFromHttpClientAsynccar il s'exécute en premier, les autres invocations bénéficient potentiellement d'avoir des données cahed (que ce soit sur la machine locale ou tout proxy transparent entre vous et la destination) et seront plus rapides. De plus, dans les bonnes conditions, var response = httpClient.GetAsync("http://localhost:9000/api/values/").Result;un blocage peut se produire car vous épuisez les threads de pool de threads. Vous ne devez jamais bloquer sur une activité qui dépend du pool de threads dans les threads ThreadPool, vous devez le faire à la awaitplace pour qu'il renvoie le thread dans le pool.
Scott Chamberlain

1
HttpClient avec Web API Client est fantastique pour un client JSON / XML REST.
Cory Nelson

@Scott Chamberlain - Merci pour votre réponse. Comme tous les appels de test s'exécutent dans Parallel.Foreach, rien ne garantit lequel aurait été exécuté en premier. De plus, si le premier appel au service était de GetDataFromHttpClientAsync, tous les appels suivants de GetDataFromHttpClientAsync auraient dû bénéficier du cache et s'exécuter plus rapidement. Je n'ai pas vu cela dans le résultat. Rgd attend, nous utilisons toujours 4.0. Je suis d'accord avec vous que HttpClient en mode synchrone conduirait à un blocage et je rejette cette option hors de ma considération de conception.
user3092913

@CoryNelson Pouvez-vous expliquer pourquoi HttpClient avec Web API Client est fantastique pour un client JSON / XML REST?
user3092913

2
Voici quelques mots sur la différence entre HttpClient et WebClient: blogs.msdn.com/b/henrikn/archive/2012/02/11/…
JustAndrei

Réponses:


243

Je vis dans les mondes F # et Web API.

Il y a beaucoup de bonnes choses qui se passent avec l'API Web, en particulier sous la forme de gestionnaires de messages pour la sécurité, etc.

Je sais que la mienne n'est qu'une opinion, mais je ne recommanderais que son utilisation HttpClientpour tout travail futur . Peut-être existe-t-il un moyen de tirer parti de certaines des autres pièces qui sortent System.Net.Httpsans utiliser directement cet assemblage, mais je ne peux pas imaginer comment cela fonctionnerait pour le moment.

En parlant de comparer ces deux

  • HttpClient est plus proche de HTTP que WebClient.
  • HttpClient n'était pas censé être un remplacement complet de Web Client, car il existe des choses comme la progression des rapports, le schéma d'URI personnalisé et les appels FTP fournis par WebClient - mais HttpClient ne le fait pas.
+--------------------------------------------+--------------------------------------------+
|               WebClient                    |               HttpClient                   |
+--------------------------------------------+--------------------------------------------+
| Available in older versions of .NET        | .NET 4.5 only.  Created to support the     |
|                                            | growing need of the Web API REST calls     |
+--------------------------------------------+--------------------------------------------+
| WinRT applications cannot use WebClient    | HTTPClient can be used with WinRT          |
+--------------------------------------------+--------------------------------------------+
| Provides progress reporting for downloads  | No progress reporting for downloads        |
+--------------------------------------------+--------------------------------------------+
| Does not reuse resolved DNS,               | Can reuse resolved DNS, cookie             |
| configured cookies                         | configuration and other authentication     |
+--------------------------------------------+--------------------------------------------+
| You need to new up a WebClient to          | Single HttpClient can make concurrent      |
| make concurrent requests.                  | requests                                   |
+--------------------------------------------+--------------------------------------------+
| Thin layer over WebRequest and             | Thin layer of HttpWebRequest and           |
| WebResponse                                | HttpWebResponse                            |
+--------------------------------------------+--------------------------------------------+
| Mocking and testing WebClient is difficult | Mocking and testing HttpClient is easy     |
+--------------------------------------------+--------------------------------------------+
| Supports FTP                               | No support for FTP                         |
+--------------------------------------------+--------------------------------------------+
| Both Synchronous and Asynchronous methods  | All IO bound methods in                    |
| are available for IO bound requests        | HTTPClient are asynchronous                |
+--------------------------------------------+--------------------------------------------+

Si vous utilisez .NET 4.5, veuillez utiliser la qualité asynchrone avec HttpClient que Microsoft fournit aux développeurs. HttpClient est très symétrique par rapport aux frères côté serveur du HTTP, ce sont HttpRequest et HttpResponse.

Mise à jour: 5 raisons d'utiliser la nouvelle API HttpClient:

  • En-têtes fortement typés.
  • Caches partagés, cookies et informations d'identification
  • Accès aux cookies et cookies partagés
  • Contrôle de la mise en cache et du cache partagé.
  • Injectez votre module de code dans le pipeline ASP.NET. Code plus propre et modulaire.

Référence

C # 5.0 Joseph Albahari

(Channel9 - Video Build 2013)

Cinq bonnes raisons d'utiliser la nouvelle API HttpClient pour se connecter aux services Web

WebClient vs HttpClient vs HttpWebRequest


4
Il convient de mentionner que HttpClient est également disponible pour .NET 4.0 .
Todd Menier du

2
Cela n'explique pas pourquoi WebClient semble être des amplitudes plus rapides que HttpClient. WebClientSemble également avoir des méthodes asynchrones maintenant.
écraser

8
@crush c'est parce que l'OP crée une nouvelle instance de HttpClient pour chaque requête. Au lieu de cela, vous devez utiliser une seule instance de HttpClient pour la durée de vie de votre application. Voir stackoverflow.com/a/22561368/57369
Gabriel

6
Il convient de noter que ce WebClientn'est pas disponible en .Net Coremais l' HttpClientest.
Pranav Singh

3
Depuis .Net Core 2.0 WebClient (parmi des milliers d'autres API) sont de retour et disponibles.
CoderBang

56

HttpClient est la plus récente des API et présente les avantages de

  • a un bon modèle de programmation asynchrone
  • Henrik F Nielson, qui est fondamentalement l'un des inventeurs de HTTP, et il a conçu l'API afin qu'il vous soit facile de suivre la norme HTTP, par exemple en générant des en-têtes conformes aux normes
  • est dans le framework .Net 4.5, donc il a un certain niveau de support garanti pour l'avenir prévisible
  • a également la version xcopyable / portable-framework de la bibliothèque si vous souhaitez l'utiliser sur d'autres plateformes - .Net 4.0, Windows Phone etc.

Si vous écrivez un service Web qui effectue des appels REST vers d'autres services Web, vous devriez vouloir utiliser un modèle de programmation asynchrone pour tous vos appels REST, afin de ne pas frapper de faim. Vous souhaiterez probablement également utiliser le plus récent compilateur C # qui prend en charge async / attente.

Remarque: ce n'est pas un AFAIK plus performant. C'est probablement un peu plus performant si vous créez un test équitable.


S'il avait un moyen de changer de proxy, ce serait fou
ed22


3

Premièrement, je ne suis pas une autorité sur WebClient vs HttpClient, en particulier. Deuxièmement, d'après vos commentaires ci-dessus, il semble suggérer que WebClient est sync UNIQUEMENT alors que HttpClient est les deux.

J'ai fait un test de performance rapide pour découvrir comment WebClient (appels de synchronisation), HttpClient (synchronisation et async) fonctionnent. et voici les résultats.

Je vois cela comme une énorme différence lorsque l'on pense à l'avenir, c'est-à-dire des processus longs, une interface graphique réactive, etc. (ajouter à l'avantage que vous suggérez par le cadre 4.5 - qui, selon mon expérience réelle, est extrêmement plus rapide sur IIS)


4
WebClientsemble avoir des capacités asynchrones dans les dernières versions de .NET. Je voudrais savoir pourquoi il semble surpasser HttpClient à une telle échelle.
écraser

1
Selon stackoverflow.com/a/4988325/1662973 , cela semble être le même, à part le fait que l'un est une abstraction de l'autre. Cela dépend peut-être de la façon dont les objets sont utilisés / chargés. Le temps minimum prend en charge la déclaration selon laquelle webclient est en fait une abstraction de HttpClient, il y a donc une valeur de millisecondes de surcharge. Le cadre pourrait être «sournois» dans la façon dont il regroupe ou élimine vraiment le client Web.
Anthony Horne

2

J'ai une référence entre HttpClient, WebClient, HttpWebResponse puis j'appelle Rest Web Api

et résultat Call Rest Web Api Benchmark

--------------------- Étape 1 ---- 10 Demande

{00: 00: 17.2232544} ====> HttpClinet

{00: 00: 04.3108986} ====> WebRequest

{00: 00: 04.5436889} ====> WebClient

--------------------- Étape 1 ---- Demande 10 - Petite taille

{00: 00: 17.2232544} ====> HttpClinet

{00: 00: 04.3108986} ====> WebRequest

{00: 00: 04.5436889} ====> WebClient

--------------------- Étape 3 ---- Demande de synchronisation 10 - Petite taille

{00: 00: 15.3047502} ====> HttpClinet

{00: 00: 03.5505249} ====> WebRequest

{00: 00: 04.0761359} ====> WebClient

--------------------- Étape 4 ---- Demande de synchronisation 100 - Petite taille

{00: 03: 23.6268086} ====> HttpClinet

{00: 00: 47.1406632} ====> WebRequest

{00: 01: 01.2319499} ====> WebClient

--------------------- Étape 5 ---- Demande de synchronisation 10 - Taille maximale

{00: 00: 58.1804677} ====> HttpClinet

{00: 00: 58.0710444} ====> WebRequest

{00: 00: 38.4170938} ====> WebClient

--------------------- Étape 6 ---- Demande de synchronisation 10 - Taille maximale

{00: 01: 04.9964278} ====> HttpClinet

{00: 00: 59.1429764} ====> WebRequest

{00: 00: 32.0584836} ====> WebClient

_____ WebClient est plus rapide ()

var stopWatch = new Stopwatch();
        stopWatch.Start();
        for (var i = 0; i < 10; ++i)
        {
            CallGetHttpClient();
            CallPostHttpClient();
        }

        stopWatch.Stop();

        var httpClientValue = stopWatch.Elapsed;

        stopWatch = new Stopwatch();

        stopWatch.Start();
        for (var i = 0; i < 10; ++i)
        {
            CallGetWebRequest();
            CallPostWebRequest();
        }

        stopWatch.Stop();

        var webRequesttValue = stopWatch.Elapsed;


        stopWatch = new Stopwatch();

        stopWatch.Start();
        for (var i = 0; i < 10; ++i)
        {

            CallGetWebClient();
            CallPostWebClient();

        }

        stopWatch.Stop();

        var webClientValue = stopWatch.Elapsed;

//-------------------------Les fonctions

private void CallPostHttpClient()
    {
        var httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
        var responseTask = httpClient.PostAsync("PostJson", null);
        responseTask.Wait();

        var result = responseTask.Result;
        var readTask = result.Content.ReadAsStringAsync().Result;

    }
    private void CallGetHttpClient()
    {
        var httpClient = new HttpClient();
        httpClient.BaseAddress = new Uri("https://localhost:44354/api/test/");
        var responseTask = httpClient.GetAsync("getjson");
        responseTask.Wait();

        var result = responseTask.Result;
        var readTask = result.Content.ReadAsStringAsync().Result;

    }
    private string CallGetWebRequest()
    {
        var request = (HttpWebRequest)WebRequest.Create("https://localhost:44354/api/test/getjson");

        request.Method = "GET";
        request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;

        var content = string.Empty;

        using (var response = (HttpWebResponse)request.GetResponse())
        {
            using (var stream = response.GetResponseStream())
            {
                using (var sr = new StreamReader(stream))
                {
                    content = sr.ReadToEnd();
                }
            }
        }

        return content;
    }
    private string CallPostWebRequest()
    {

        var apiUrl = "https://localhost:44354/api/test/PostJson";


        HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(new Uri(apiUrl));
        httpRequest.ContentType = "application/json";
        httpRequest.Method = "POST";
        httpRequest.ContentLength = 0;

        using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
        {
            using (Stream stream = httpResponse.GetResponseStream())
            {
                var json = new StreamReader(stream).ReadToEnd();
                return json;
            }
        }

        return "";
    }

    private string CallGetWebClient()
    {
        string apiUrl = "https://localhost:44354/api/test/getjson";


        var client = new WebClient();

        client.Headers["Content-type"] = "application/json";

        client.Encoding = Encoding.UTF8;

        var json = client.DownloadString(apiUrl);


        return json;
    }

    private string CallPostWebClient()
    {
        string apiUrl = "https://localhost:44354/api/test/PostJson";


        var client = new WebClient();

        client.Headers["Content-type"] = "application/json";

        client.Encoding = Encoding.UTF8;

        var json = client.UploadString(apiUrl, "");


        return json;
    }

1
Voir le commentaire de Gabriel ci-dessus. En bref, HttpClient est beaucoup plus rapide si vous créez une instance de HttpClient et la réutilisez.
LT Dan

1

Vous pourriez peut-être penser le problème différemment. WebClientet HttpClientsont essentiellement des implémentations différentes de la même chose. Ce que je recommande, c'est d'implémenter le modèle d'injection de dépendances avec un conteneur IoC dans toute votre application. Vous devez construire une interface client avec un niveau d'abstraction plus élevé que le transfert HTTP de bas niveau. Vous pouvez écrire des classes concrètes qui utilisent à la fois WebClientet HttpClient, puis utiliser le conteneur IoC pour injecter l'implémentation via config.

Ce que cela vous permettrait de faire serait de basculer entre HttpClientet WebClientfacilement afin de pouvoir objectivement tester dans l'environnement de production.

Donc des questions comme:

HttpClient sera-t-il un meilleur choix de conception si nous passons à .Net 4.5?

Peut effectivement être répondu objectivement en basculant entre les deux implémentations client à l'aide du conteneur IoC. Voici un exemple d'interface sur lequel vous pourriez compter et qui n'inclut aucun détail sur HttpClientou WebClient.

/// <summary>
/// Dependency Injection abstraction for rest clients. 
/// </summary>
public interface IClient
{
    /// <summary>
    /// Adapter for serialization/deserialization of http body data
    /// </summary>
    ISerializationAdapter SerializationAdapter { get; }

    /// <summary>
    /// Sends a strongly typed request to the server and waits for a strongly typed response
    /// </summary>
    /// <typeparam name="TResponseBody">The expected type of the response body</typeparam>
    /// <typeparam name="TRequestBody">The type of the request body if specified</typeparam>
    /// <param name="request">The request that will be translated to a http request</param>
    /// <returns></returns>
    Task<Response<TResponseBody>> SendAsync<TResponseBody, TRequestBody>(Request<TRequestBody> request);

    /// <summary>
    /// Default headers to be sent with http requests
    /// </summary>
    IHeadersCollection DefaultRequestHeaders { get; }

    /// <summary>
    /// Default timeout for http requests
    /// </summary>
    TimeSpan Timeout { get; set; }

    /// <summary>
    /// Base Uri for the client. Any resources specified on requests will be relative to this.
    /// </summary>
    Uri BaseUri { get; set; }

    /// <summary>
    /// Name of the client
    /// </summary>
    string Name { get; }
}

public class Request<TRequestBody>
{
    #region Public Properties
    public IHeadersCollection Headers { get; }
    public Uri Resource { get; set; }
    public HttpRequestMethod HttpRequestMethod { get; set; }
    public TRequestBody Body { get; set; }
    public CancellationToken CancellationToken { get; set; }
    public string CustomHttpRequestMethod { get; set; }
    #endregion

    public Request(Uri resource,
        TRequestBody body,
        IHeadersCollection headers,
        HttpRequestMethod httpRequestMethod,
        IClient client,
        CancellationToken cancellationToken)
    {
        Body = body;
        Headers = headers;
        Resource = resource;
        HttpRequestMethod = httpRequestMethod;
        CancellationToken = cancellationToken;

        if (Headers == null) Headers = new RequestHeadersCollection();

        var defaultRequestHeaders = client?.DefaultRequestHeaders;
        if (defaultRequestHeaders == null) return;

        foreach (var kvp in defaultRequestHeaders)
        {
            Headers.Add(kvp);
        }
    }
}

public abstract class Response<TResponseBody> : Response
{
    #region Public Properties
    public virtual TResponseBody Body { get; }

    #endregion

    #region Constructors
    /// <summary>
    /// Only used for mocking or other inheritance
    /// </summary>
    protected Response() : base()
    {
    }

    protected Response(
    IHeadersCollection headersCollection,
    int statusCode,
    HttpRequestMethod httpRequestMethod,
    byte[] responseData,
    TResponseBody body,
    Uri requestUri
    ) : base(
        headersCollection,
        statusCode,
        httpRequestMethod,
        responseData,
        requestUri)
    {
        Body = body;
    }

    public static implicit operator TResponseBody(Response<TResponseBody> readResult)
    {
        return readResult.Body;
    }
    #endregion
}

public abstract class Response
{
    #region Fields
    private readonly byte[] _responseData;
    #endregion

    #region Public Properties
    public virtual int StatusCode { get; }
    public virtual IHeadersCollection Headers { get; }
    public virtual HttpRequestMethod HttpRequestMethod { get; }
    public abstract bool IsSuccess { get; }
    public virtual Uri RequestUri { get; }
    #endregion

    #region Constructor
    /// <summary>
    /// Only used for mocking or other inheritance
    /// </summary>
    protected Response()
    {
    }

    protected Response
    (
    IHeadersCollection headersCollection,
    int statusCode,
    HttpRequestMethod httpRequestMethod,
    byte[] responseData,
    Uri requestUri
    )
    {
        StatusCode = statusCode;
        Headers = headersCollection;
        HttpRequestMethod = httpRequestMethod;
        RequestUri = requestUri;
        _responseData = responseData;
    }
    #endregion

    #region Public Methods
    public virtual byte[] GetResponseData()
    {
        return _responseData;
    }
    #endregion
}

Code complet

Implémentation de HttpClient

Vous pouvez utiliser Task.Runpour faire WebClientfonctionner de manière asynchrone dans son implémentation.

L'injection de dépendance, lorsqu'elle est bien faite, aide à atténuer le problème d'avoir à prendre des décisions de bas niveau dès le départ. En fin de compte, la seule façon de connaître la vraie réponse est d'essayer à la fois dans un environnement réel et de voir celle qui fonctionne le mieux. Il est tout à fait possible que WebClientcela fonctionne mieux pour certains clients et HttpClientfonctionne mieux pour d'autres. C'est pourquoi l'abstraction est importante. Cela signifie que le code peut être rapidement échangé ou modifié avec la configuration sans changer la conception fondamentale de l'application.


1

Opinion impopulaire de 2020:

Quand il vient aux applications ASP.NET je préfère encore WebClientplus HttpClientparce que:

  1. L'implémentation moderne est fournie avec des méthodes basées sur les tâches asynchrones / attendables
  2. A une empreinte mémoire plus petite et 2x-5x plus rapide (d'autres réponses le mentionnent déjà)
  3. Il est suggéré de " réutiliser une seule instance de HttpClient pour la durée de vie de votre application ". Mais ASP.NET n'a pas de "durée de vie de l'application", seulement la durée de vie d'une demande.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.