Show TOC

Upgrading Windows Applications That Use the SAP.Net.Http Assembly Locate this document in the navigation structure

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.
Note You need not change your code unless you use SAP.Net.Http.HttpClient directly. That is, if you use only the libraries that are built on top of it (for example, LogonCore or FlowEngine), you do not have to change your code.
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;
      Note You can change the client.RedirectBehavior.AllowAutoRedirect property even if requests have already been sent.
      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;
      Note You can change the client.RedirectBehavior.MaxAutomaticRedirections property even if requests have already been sent.

      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; }
      Note To specify Basic authentication credentials, use the PasswordCredential property of the HttpClient instance. You can modify this property of the client instance at any time, even after sending HTTP requests.
      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; }
      Note If you have shared code between a .NET and Windows Store application or library, you can use the NETFX_CORE symbol to address platform differences using the #if NETFX_CORE … #else … #endif directives.
    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) {
          …
          }
          
          
          Note Case-sensitivity: Windows Runtime uses "Ok" while the .NET version uses "OK".
  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.
    Sample Code
    using Windows.Web.Http;
    using Windows.Web.Http.Filters;
    …
    var filter = new HttpBaseProtocolFilter() {
    	… // initialization of the properties you need for your scenario here
    };
    // switch of caching of responses
    filter.CacheControl.ReadBehavior = HttpCacheReadBehavior.MostRecent;
    filter.CacheControl.WriteBehavior = HttpCacheWriteBehavior.NoCache;
    this.httpClient = new SAP.Net.Http.HttpClient(filter);
    Note When creating the HttpClient instance, you must use fully qualified type names to avoid ambiguity and compilation errors between the various HttpClient types.
  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.

    Remember
    • When uploading traces, switch off tracing on any HttpClient instances. Reenable tracing when the upload is done.
    • Create a new "transaction" after uploading a BTX document.
    • If a BTX document being uploaded contains no steps and no requests, the server returns 403 forbidden.
    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.
      Sample Code
      var cookieContainer = new System.Net.CookieContainer();
      var client = new SAP.Net.Http.HttpClient(
      			new System.Net.Http.HttpClientHandler() {
      				CookieContainer = cookieContainer
      				// set up the rest of the properties you need here…
      			}, true);
      
      // send requests…
      …
      var collection = cookieContainer.GetCookies(new Uri("http://YOUR_URI_GOES_HERE"));
      foreach (System.Net.Cookie cookie in collection) {
      	// do something with the retrieved cookies here…
      }
      
      You can use the following code to get cookies retrieved by WebBrowser controls:
      Sample Code
      static class UnsafeNativeMethods {
      	internal const int INTERNET_COOKIE_HTTPONLY = 0x00002000; // copied from WinInet.h >> #define INTERNET_COOKIE_HTTPONLY        0x00002000
      
      	// http://msdn.microsoft.com/en-us/library/windows/desktop/aa384714(v=vs.85).aspx
      	[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
      	internal static extern bool InternetGetCookieEx(
      						string lpszURL,
      						string lpszCookieName, StringBuilder lpszCookieData,
      						ref int lpdwSize, Int32 dwFlags,
      						IntPtr lpReserved);
      }
      
      static string GetCookieStringForUri(Uri uri) {
      	int size = 0;
      	// get the buffer size first
      	if (!UnsafeNativeMethods.InternetGetCookieEx(uri.ToString(), null, null, ref size, UnsafeNativeMethods.INTERNET_COOKIE_HTTPONLY, IntPtr.Zero)) return null;
      	if (size <= 0) return null;
      
      	// now get the data
      	StringBuilder cookiesRaw = new StringBuilder(size + 1);
      	if (!UnsafeNativeMethods.InternetGetCookieEx(uri.ToString(), null, cookiesRaw, ref size, UnsafeNativeMethods.INTERNET_COOKIE_HTTPONLY, IntPtr.Zero)) return null;
      
      	return cookiesRaw.ToString();
      }
      …
      // let's get the cookie string>>
      var cookiesRaw = GetCookieStringForUri(new Uri("http://YOUR_URI_GOES_HERE"));
      
      Note The returned string might contain several cookies. From this raw string, the developer must split the cookie string and retrieve the name/value pairs of cookies.
    • 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:
      Sample Code
      using Windows.Web.Http;
      using Windows.Web.Http.Filters;
      …
      using (var filter = new Windows.Web.Http.Filters.HttpBaseProtocolFilter()) {
      	var cookieCollection = filter.CookieManager.GetCookies(new Uri("http://YOUR_URI_GOES_HERE"));
      	foreach (var cookie in cookieCollection) {
      		// do something with the retrieved cookies here…
      	}
      }
      
  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()
      Note This method can return a reason code even in cases where the Windows.Web.WebError.GetStatus method only returns Unknown.
    • 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.