Developer

Upgrading Windows Applications That Use the SAP.Net.Http Assembly

Upgrade your Windows application to use the SAP.Net.Http networking component.

Context

SAP.Net.Http changes affect your Windows application if it is either a:
  • Windows Store app or library and uses SAP.Net.Http.HttpClient in code or uses the SAP.Data.OData.Online library
  • .NET app - while .NET applications are not affected by the code changes, we recommend that you read SAP.Net.Http.HttpClient Features and Usage to use new features provided by the 3.0 SP09 version of the networking library.
Until SAP Mobile Platform SDK version 3.0 SP08, Windows SAP.Net.Http.HttpClient used System.Net.Http.HttpClient internally. System.Net.Http.HttpClient is an incomplete port of the .NET version of the HttpClient in the Windows Runtime environment and is also deprecated. To overcome certain limitations and provide new networking features, it was necessary to migrate the SAP code to use the Windows.Web.Http.HttpClient internally. Due to this, the public API of the networking component has changed for Store Apps. The new networking library provides these features and improvements:
  • Cookie copying is no longer required if the app targets the Windows Runtime API set.
  • Double authentication no longer occurs with SAML authentication
  • The certificate can now be explicitly defined for HTTP clients on Windows Runtime
  • TLS 1.2 support
  • Localized and adaptive UIs (Basic authentication forms, SAML authentication forms)
  • UI on the phone now shows correctly in a ContentDialog (SAML)
  • Progress reporting (download status)
  • Traceable HTTP requests
  • You can change Basic authentication credentials without reinstantiating the HttpClient class
  • Redirect bugs of the native MS implementation of the HttpClient class are handled internally (switch from HTTP to HTTPS)
  • Custom handlers and filters can now be added to the handler/filter chain
  • All platforms now support cancelling requests
  • Improved error handling (for example, Windows Runtime normally only returns an HRESULT value, but the new SAP.Net.Http.HttpClient provides a more meaningful error)

Procedure

  1. Update code in a Windows Store App:
    1. If the existing code creates only an SAP.Net.Http.HttpClient that is passed to the OpenAsync method of the online/offline OData Store, you need modify only the manner in w HttpClient and its filter is created, as illustrated by the highlighted code:

      SP08 (current code)

      SP09 and later (new/upgraded code)

      var client = new SAP.Net.Http.HttpClient(
      	new System.Net.Http.HttpClientHandler() {
      		Credentials = new System.Net.NetworkCredential( 		
           registrationContext.BackendUserName,
      	registrationContext.BackendPassword)
      	}, true
      );
      
      
      client.DefaultRequestHeaders.TryAddWithoutValidation("X-SMP-APPCID", connectionId);
      client.DefaultRequestHeaders.TryAddWithoutValidation("X-SUP-APPCID", connectionId);
      
      client.ShouldHandleXcsrfToken = true;
      client.ShouldHandleSamlRequests = true;
      client.SamlFinishEndpoint = new 
      UriBuilder(registrationContext.IsHttps ? "https" : "http",
      	registrationContext.ServerHost,
      	registrationContext.ServerPort, 	
           "/SAMLAuthLauncher").Uri;
      client.SamlFinishEndpointParameter = "finishEndpointParam";
      
      
      await odataStore.OpenAsync(client);
      
      var client = new HttpClient() {
      	PasswordCredential = new 
      PasswordCredential(registrationContext.BackendUserName, 
      registrationContext.BackendPassword);
      };
      
      
      
      client.DefaultRequestHeaders.TryAddWithoutValidation("X-SMP-APPCID", connectionId);
      client.DefaultRequestHeaders.TryAddWithoutValidation("X-SUP-APPCID", connectionId);
      
      client.ShouldHandleXcsrfToken = true;
      client.ShouldHandleSamlRequests = true;
      client.SamlAuthenticationProperties.SamlFinishEndpoint = new 
      UriBuilder(registrationContext.IsHttps ? "https" : "http",
      	registrationContext.ServerHost,
      	registrationContext.ServerPort, 	
           "/SAMLAuthLauncher").Uri;
      client.SamlAuthenticationProperties.SamlFinishEndpoint = new 
      
      
      UriBuilder(registrationContext.IsHttps ? "https" : "http",
      	registrationContext.ServerHost,
      	registrationContext.ServerPort, 	"/SAMLAuthLauncher").Uri;
      
      client.SamlAuthenticationProperties.SamlFinishEndpointParameter = "finishEndpointParam";
      
      await SharedContext.Context.Store.OpenAsync(client);
      
      If you used properties of the HttpClientHandler other than "Credentials," then you must also create an instance of the HttpBaseProtocolFilter class, set the properties accordingly, then pass this filter to the SAP.Net.Http.HttpClient instance on Windows Runtime:

      HttpClientHandler properties

      HttpBaseProtocolFilter properties

      bool AllowAutoRedirect { get; set; }
      bool AllowAutoRedirect { get; set; } 
      Use the AllowAutoRedirect property of the RedirectBehavior property on the SAP.Net.Http.HttpClient instance instead. For example:
      client.RedirectBehavior.AllowAutoRedirect = false;
      DecompressionMethods AutomaticDecompression { get; set; }

      N/A

      There is no way to set methods for automatic decompression on Windows Runtime; however the filter supports automatic decompression.

      N/A

      bool AllowUI { get; set; }
      ClientCertificateOption ClientCertificateOptions { get; set; }

      N/A

      N/A

      HttpCacheControl CacheControl { get; }

      N/A

      Certificate ClientCertificate { get; set; }
      CookieContainer CookieContainer { get; set; }
      HttpCookieManager CookieManager { get; }
      ICredentials Credentials { get; set; }
      PasswordCredential ServerCredential { get; set; }

      N/A

      IList<ChainValidationResult> IgnorableServerCertificateErrors { get; }
      int MaxAutomaticRedirections { get; set; }

      N/A

      Use the RedirectBehavior property on the SAP.Net.Http.HttpClient instance. For example:
      client.RedirectBehavior.MaxAutomaticRedirections = 5;

      N/A

      uint MaxConnectionsPerServer { get; set; }
      long MaxRequestContentBufferSize { get; set; }

      N/A

      bool PreAuthenticate { get; set; }

      N/A

      IWebProxy Proxy { get; set; }

      N/A

      N/A

      PasswordCredential ProxyCredential { get; set; }

      N/A

      PasswordCredential ServerCredential { get; set; }
      bool SupportsAutomaticDecompression { get; }
      bool AutomaticDecompression { get; set; }
      bool SupportsProxy { get; }

      N/A

      bool SupportsRedirectConfiguration { get; }

      N/A

      bool UseCookies { get; set; }

      N/A

      bool UseDefaultCredentials { get; set; }

      N/A

      bool UseProxy { get; set; }
      bool UseProxy { get; set; }
    2. If your code also uses SAP.Net.Http.HttpClient to send individual HTTP requests:
      • If your code targets only the Windows Runtime runtime:
        • If it is used, remove the System.Net.Http namespace from the fully qualified type names.
        • Add "using Windows.Web.Http;" and "using Windows.Web.Http.Filters;" lines to the "using" section of your code.
        • Use the Windows Runtime HTTP related classes instead of the .NET classes.
        • Create an HttpBaseProtocolFilter instance instead of the HttpClientHandler class and parameterize it accordingly, if necessary.
      • If your code targets both the .NET runtime and the Windows Runtime runtime:
        • If used, remove the System.Net.Http namespace from the fully qualified type names.
        • Add these lines to the "using" section of your code:
          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          #else
          using System.Net;
          using System.Net.Http;
          #endif
          
        • Use type aliases, if necessary, to avoid using a lot of #if NETFX_CORE…#else…#endif code. For example:

          Instead of:

          Use:

          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          
          #else
          using System.Net;
          using System.Net.Http;
          
          #endif
          
          …
          Task<HttpResponseMessage> UploadContentAsync(Uri requestUri,
          	Func<
          #if NETFX_CORE
          		IHttpContent
          #else
          		HttpContent
          #endif
          	> contentGenerator) {
          	…
          	client.PostAsync(requestUri, () => contentGenerator());
          	…
          }
          …
          UploadContentAsync(uri, () => 
          	new
          #if NETFX_CORE
          	HttpStringContent
          #else
          	StringContent
          #endif
          	("abrakadabra")
          );
          
          
          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          using HttpContent = Windows.Web.Http.IHttpContent;
          using StringContent = Windows.Web.Http.HttpStringContent;
          #else
          using System.Net;
          using System.Net.Http;
          using HttpContent = System.Net.Http.HttpContent;
          using StringContent = System.Net.Http. StringContent;
          #endif
          
          …
          Task<HttpResponseMessage> UploadContentAsync(Uri requestUri, Func<HttpContent> contentGenerator) {
          	…
          	client.PostAsync(requestUri, () => contentGenerator());
          	…
          }
          …
          UploadContentAsync(uri, () => new StringContent("abrakadabra"));
          
          
        • For values for which type aliases cannot be created, use the conditional compiler directives to create constants. For example:

          Instead of:

          Use:

          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          
          #else
          using System.Net;
          using System.Net.Http;
          
          #endif
          
          
          …
          // app logic is different per platform
          
          #if NETFX_CORE
          request.Content = new HttpStringContent(
          	content,
          	Windows.Storage.Streams.UnicodeEncoding.Utf8, 	"application/atom+xml");
          #else
          request.Content = new StringContent(
          	content,
          	Encoding.UTF8,
          	"application/atom+xml");
          #endif
          …
          
          
          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          using StringContent = Windows.Web.Http.HttpStringContent;
          #else
          using System.Net;
          using System.Net.Http;
          using StringContent = System.Net.Http.StringContent;
          #endif
          
          
          …
          // the app logic can be the same now on all platforms
          
          request.Content = new StringContent(
          	content,
          	Helper.Utf8Encoding,
          	"application/atom+xml");
          …
          
          
          
          
          
          
          
          // put this into an internal "Helper" class for reuse
          
          internal class Helper {
          	public const 
          #if NETFX_CORE
          	Windows.Storage.Streams.UnicodeEncoding Utf8Encoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
          #else
          	Encoding Utf8Encoding = Encoding.UTF8;
          #endif
          }
          
          
        • Use the var keyword to declare variables instead of using type names.
        • In some cases it might be easier to cast to an integral type that to declare constants for different platforms. For example:

          Instead of:

          Or instead of:

          Use:

          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          #else
          using System.Net;
          using System.Net.Http;
          #endif
          …
          if (response.StatusCode != HttpStatusCode.
          #if NETFX_CORE
          	Ok
          #else
          	OK
          #endif
          ) {
          	…
          }
          
          
          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          #else
          using System.Net;
          using System.Net.Http;
          #endif
          …
          if (response.StatusCode != Helper.Ok) {
          …
          }
          
          
          
          …
          internal class Helper {
          	public const HttpStatusCode Ok = 
          HttpStatusCode.
          #if NETFX_CORE
          	Ok;
          #else
          	OK;
          #endif
          }
          
          
          #if NETFX_CORE
          using Windows.Web.Http;
          using Windows.Web.Http.Filters;
          #else
          using System.Net;
          using System.Net.Http;
          #endif
          …
          var statusCode = (int)response.StatusCode;
          if (statusCode == 200) {
          …
          }
          
          
  2. (Windows Store apps, that is, Windows Runtime only) You must disable caching after upgrading your Windows Store app code, if requests are not always sent to the server but out-of-date responses are still being received.
    You may experience the behavior that when you send HTTP requests in code, HTTP requests are not actually sent, but a response is received. By default, the HttpBaseProtocolFilter instance used by the HttpClient to send requests caches responses. If the HttpClient is instantiated using the default, parameterless constructor, then caching is enabled and cannot be switched off. To disable caching, create an HttpBaseProtocolFilter instance and pass it to the proper constructor of the HttpClient.
  3. Upload logs and traces using the networking library.

    In versions earlier than 3.0 SP09, developers who used the Supportability library had to implement the SAP.Supportability.IUploader and the SAP.Supportability.IUploadResult interfaces to upload logs and BTX documents. Beginning with 3.0 SP09, the SAP.Supportability.Uploader library includes the default implementation of these interfaces. The DefaultUploader class of this library can be used to upload logs and BTX documents, however, developers are not limited to the DefaultUploader class for uploading; the Supportability library also accepts custom log and BTX uploader implementations.

    1. Create the URL that needs to be passed to the DefaultUploader constructor.
      Sample code that demonstrates one possible way for creating the URL that needs to be passed to the DefaultUploader constructor. This URL can be used to upload logs and BTX documents:
      var registrationContext = logonCore.LogonContext.RegistrationContext;
      var path = "";
      if (!string.IsNullOrEmpty(registrationContext.ResourcePath)) {
           path = registrationContext.ResourcePath + (registrationContext.ResourcePath.EndsWith("/") ? "" : "/") +
                    (string.IsNullOrEmpty(registrationContext.FarmId) ? "" : registrationContext.FarmId + "/");
      }
      var url = new UriBuilder(registrationContext.IsHttps ? "https" : "http", registrationContext.ServerHost, registrationContext.ServerPort, path).Uri;
      
    2. Upload the logs.
      Sample code that demonstrates how the uploader can be used to upload logs:
      using SAP.Supportability.Uploader;
      …
      try {
           await SAP.Supportability.SupportabilityFacade.Instance.ClientLogManager.UploadClientLogsAsync(
                 new DefaultUploader(httpClient, url, UploadType.Log, cancellationTokenSource.Token)
           );
      } catch (Exception ex) {
           var supportabilityException = ex as SAP.Supportability.ISupportabilityException;
           if (supportabilityException != null) {
                 // handle the exception here
           }
      …
      }
      
    3. Upload the BTX document.
      Sample code that demonstrates how the uploader can be used to upload a BTX document:
      using SAP.Supportability.Uploader;
      
      …
      try {
           await SAP.Supportability.SupportabilityFacade.Instance.E2ETraceManager.UploadBtxAsync(
                 new DefaultUploader(httpClient, url, UploadType.Btx, cancellationTokenSource.Token)
           );
      } catch (Exception ex) {
           var supportabilityException = ex as SAP.Supportability.ISupportabilityException;
           if (supportabilityException != null) {
                 // handle the exception here
           }
      …
      }
      
  4. Retrieve cookies for a URL:
    • In .NET applications:
      To get cookies for a URL that is retrieved by SAP.Net.Http.HttpClient or System.Net.Http.HttpClient in a .NET application, create your own cookie container, and a handler that uses it. Pass the handler to the HttpClient instance. You can then access the cookies for a URL using the cookie container you created.
      You can use the following code to get cookies retrieved by WebBrowser controls:
    • In a Windows Store (Windows Runtime) application:
      Use this code to retrieve cookies by WebView controls, SAP.Net.Http.HttpClient and Windows.Web.Http.HttpClient instances:
  5. Handle errors.

    Normally, when networking errors occur you get an exception of type System.Exception in a Windows Runtime application with the error message Exception from HRESULT: 0x…. For many developers, this is not particularly helpful, and it is difficult to get additional information about the failure.

    The SAP.Net.Http.HttpClient library presents errors by throwing a more specific exception, providing a human-readable error message, the Win32 error code (if available), and the named error constant of the reason of failure.
    • If you only have an HRESULT error code, you can use this code to get the reason of the failure:
      HttpErrorStatus errorReason = SAP.Net.Http.HttpError.GetStatus()
    • The exceptions thrown by the system when sending HTTP requests are wrapped by the SAP.Net.Http.HttpRequestException which gives you a human readable error message, the Win32 error code (if available) and the named error constant of the reason of failure.

    See SAP.Net.Http.HttpRequestException class, SAP.Net.Http.HttpError.GetStatus() method, and SAP.Net.Http.HttpErrorStatus enumeration type API documentation.