Partager via


Create a Universal Windows app to access SAP data by using SAP Gateway for Microsoft

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

Learn how to build a Universal Windows app that can access SAP data by using SAP Gateway for Microsoft.

This article describes how you can develop a Windows Store app to get authorized access to SAP. Authorized access is a prerequisite for a Windows Store App or a Windows Phone Store app that reads and writes SAP data by using the SAP Gateway for Microsoft (GWM).

For a complete code sample that demonstrates the solution described in this article, see the SAP Gateway for Microsoft sample.

Before you begin

The following are prerequisites for the procedures in this article.

  • Visual Studio 2013 with Update 2 or later, which you can download from here.

  • SAP Gateway for Microsoft (GWM) deployed and configured in Microsoft Azure. See the documentation for SAP Gateway for Microsoft.

  • An organizational account in Microsoft Azure with access permissions to GWM. See Create an organizational user account.

    Note  

    Login to your organizational account and change the temporary password after the account is created by navigating to login.microsoftonline.com.

    Contact your GWM administrator to add permissions for your organizational account to access GWM.

  • An SAP OData endpoint with sample data in it. See the documentation for SAP Gateway for Microsoft.

  • A basic familiarity with Azure AD. See Getting started with Azure AD.

  • A basic familiarity with how the authentication flow works in the OAuth2.0 APIs for common consent. See Authorization Code Grant Flow.

Understand authentication and authorization to GWM with OAuth 2.0 in Azure AD

Azure Active Directory (Azure AD) uses OAuth 2.0 to grant you authorized access to web applications and web APIs in your Azure AD tenant, such as SAP Gateway for Microsoft (GWM). Access to GWM from a Windows app is implemented by using the OAuth 2.0 Authorization Code Grant flow. In this flow, the organizational user delegates access to the Windows app. This transaction is protected and mediated by the exchange of an authorization code from Azure AD for an access token that provides access to the resource. In this exchange, the client application never "sees" the user’s credentials, and the user agent or browser environment never "sees" the access token.

The process is as follows.

  1. The app starts the flow by redirecting the user agent to the Azure AD authorization endpoint (login.windows.net). The user signs in there.

  2. The Azure AD authorization endpoint redirects the user agent to the redirect Uri of the app with an authorization code.

  3. The app requests access from the Azure AD token issuance endpoint with the authorization code.

  4. The Azure AD token issuance endpoint returns an access token and a refresh token. The refresh token can be used to request a new access token when the current access token expires.

  5. The app uses the access token to authenticate to GWM. After the access token is validated by GWM, SAP data is returned to the app.

For a detailed description and diagram of the OAuth flow used by Azure AD, see Authorization Code Grant Flow.

Build a sample Universal Windows app to access GWM data

Create the project

  1. In Visual Studio 2013, create a blank Universal Windows app.
    1. Select File > New > Project.
    2. In the New Project dialog box, in the left menu, select Templates > Visual C# > Store Apps > Universal Apps, and then select the Blank App (Universal Apps) project template.
    3. Name the project and click OK. The example described in this article uses the name SAP2UniveralApp.
  2. Add the Json.Net package to the SAP2UniversalApp project to handle JSON data received from GWM.
    1. In Solution Explorer, right-click the SAP2UniversalApp.Windows project, and then click Manage NuGet Packages.
    2. In the Manage NuGet Packages dialog box, select Online in the left menu, and then enter Json.Net in the search box.
    3. In the list of search results, select Json.NET, and then click the Install button beside it. Click Close after it’s installed.
    4. Repeat these same steps for the SAP2UniversalApp.WindowsPhone project.

Register the app in Microsoft Azure AD and add GWM permissions for it

  1. Login into the Windows Azure management portalwith your Azure service administrator account.

  2. Select Active Directory.

  3. Select the directory associated with GWM. (The example in this article assumes that the directory is named GWMDemo.)

  4. In the top navigation bar, choose APPLICATIONS.

  5. Choose Add on the command bar at the bottom of the screen.

  6. In the dialog box that opens, choose Add an application my organization is developing.

  7. In the ADD APPLICATION dialog box, give the application a name. The example described in this article uses the name SAP2WindowsUniveralApp.

  8. Choose Native Client Application as the application type, and then click the right arrow button.

  9. On the second page of the dialog box, set a Redirect URI for your app. This will be used to redirect your authorization code from Azure in the authorization code grant flow. The example described in this article uses http://my_gwm.cloudapp.net/.

    Note  You can also choose to use your app’s callback URI obtained by calling the Windows.Security.Authentication.Web.WebAuthenticationBroker.GetCurrentApplicationCallbackUri method.

  10. Check the checkmark button. The Azure dashboard for the application opens with a success message.

  11. Choose CONFIGURE at the top of the page.

  12. Scroll to the CLIENT ID and make a copy of it. You will need this value for a later procedure.

  13. In the permissions to other applications section, select your GWM service application.

  14. Open the Delegated Permissions drop-down list and select the permissions to the GWM service that your app requires.

  15. Click Save at the bottom of the screen.

Add a data model class for GWM data

  1. In Visual Studio, right-click the SAP2UniversalApp.Shared project and select Add > Class. Add a new class file to the project named DataModel.cs. This is the data model for the data that your app gets from the GWM.

  2. In DataModel.cs, add following code.

    The fields defined below are only an example. Change the names and types of the fields (and the number of fields) to match the data from the SAP OData endpoint.

    public class DataModel
    {
        public string Title
        {
            get
            {
                return String.Format("{0}-{1}", Brand, Model);
            }
        }
        public string Brand { get; set; }
        public string Model { get; set; }
        public string Year { get; set; }
        public string Engine { get; set; }
        public string ExtColor { get; set; }
        public string Price { get; set; }
        public string Status { get; set; }
        public string ContactPhone { get; set; }
    }
    

Add a helper class to authenticate to Azure AD

  1. In Visual Studio, right-click the SAP2UniversalApp.Shared project and select Add > Class. Add a new class file to the project named AADAuthHelper.cs. This class implements the Azure OAuth authorization code grant flow.

  2. In AADAuthHelper.cs, add the following using statements.

    using System.Net;
    using System.Net.Http;
    using System.Threading.Tasks;
    using Newtonsoft.Json.Linq;
    
  3. Add following code.

    internal class AADAuthHelper
    {
        internal static AADAuthHelper Instance = new AADAuthHelper();
    
        private AADAuthHelper() { }
    
        private const string Authority = "<value>";
        private const string ClientID = "<value>";
        private const string ResourceId = "<value>";
        internal const string AppRedirectUrl = "<value>";
    
        private string refreshToken;
        private Tuple<string, DateTimeOffset> accessToken;
    
        internal bool IsAuthenticated
        {
            get { return !string.IsNullOrEmpty(this.refreshToken); }
        }
    
        internal async Task<string> GetAccessToken()
        {
            if (this.accessToken == null ||
                string.IsNullOrEmpty(this.accessToken.Item1) ||
                this.accessToken.Item2 <= DateTimeOffset.UtcNow)
            {
                await RenewAccessToken();
            }
    
            return this.accessToken.Item1;
        }
    
        internal string AuthorizeUrl
        {
            get
            {
                return string.Format("https://login.windows.net/{0}/oauth2/authorize?response_type=code&redirect_uri={1}&client_id={2}&resource={3}&state={4}",
                    Authority,
                    AppRedirectUrl,
                    ClientID,
                    ResourceId,
                    Guid.NewGuid().ToString());
            }
        }
    
        internal async Task AcquireTokenWithAuthorizeCode(string authCode)
        {
            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
                string.Format("https://login.windows.net/{0}/oauth2/token", Authority));
            string tokenreq = string.Format(
                    "grant_type=authorization_code&code={0}&client_id={1}&redirect_uri={2}",
                    authCode,
                    ClientID,
                    WebUtility.UrlEncode(AppRedirectUrl));
    
            request.Content = new StringContent(tokenreq, Encoding.UTF8, "application/x-www-form-urlencoded");
            HttpResponseMessage response = await client.SendAsync(request);
            string responseString = await response.Content.ReadAsStringAsync();
    
            JObject tokens = JObject.Parse(responseString);
            this.refreshToken = (string)tokens["refresh_token"];
            var accessTokenString = (string)tokens["access_token"];
            var accessTokenExpireOnSecond = (double)tokens["expires_on"];
            var accessTokenExpireOn = DateTimeOffset.Parse("1970/01/01 00:00:00Z");
            accessTokenExpireOn = accessTokenExpireOn.AddSeconds(accessTokenExpireOnSecond);
            this.accessToken = new Tuple<string, DateTimeOffset>(accessTokenString, accessTokenExpireOn);
        }
    
        private async Task RenewAccessToken()
        {
            HttpClient client = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
                string.Format("https://login.windows.net/{0}/oauth2/token", Authority));
            string tokenreq = string.Format(
                    "grant_type=refresh_token&refresh_token={0}&resource={1}&client_id={2}",
                    this.refreshToken,
                    WebUtility.UrlEncode(ResourceId),
                    ClientID);
            request.Content = new StringContent(tokenreq, Encoding.UTF8, "application/x-www-form-urlencoded");
            HttpResponseMessage response = await client.SendAsync(request);
            string responseString = await response.Content.ReadAsStringAsync();
    
            JObject tokens = JObject.Parse(responseString);
            var accessTokenString = (string)tokens["access_token"];
            var accessTokenExpireOnSecond = (double)tokens["expires_on"];
            var accessTokenExpireOn = DateTimeOffset.Parse("1970/01/01 00:00:00Z");
            accessTokenExpireOn = accessTokenExpireOn.AddSeconds(accessTokenExpireOnSecond);
            this.accessToken = new Tuple<string, DateTimeOffset>(accessTokenString, accessTokenExpireOn);
        }
    }
    
  4. Set the constant field Authority to your Azure tenant name. In the example described in this article, the organization account is <user>@some_domain.onmicrosoft.com, so the authority is some_domain.onmicrosoft.com.

    private const string Authority = "some_domain.onmicrosoft.com";
    
  5. Insert the client ID that you saved from Azure AD in an earlier procedure as another constant field. The following is an example.

    private const string ClientID = "682a0d7e-80cd-4430-9029-a3273cd79931";
    
  6. Set the constant field ResourceId to the APP ID URI of GWM. Obtain this value from the administrator who deployed and configured GWM in Azure. The following is an example.

    private const string ResourceId = "http://my_gwm.cloudapp.net/";
    
  7. Insert the Redirect Uri that you configured in Azure AD in an earlier procedure as another constant field.

    internal const string AppRedirectUrl = "http://my_gwm.cloudapp.net/";
    

Add UI for the Windows Store app

  1. Edit MainPage.xaml in the project SAP2UniversalApp.Windows. The example described in this article uses the following XAML for the main Grid.

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="360"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    
        <StackPanel Grid.Row="0" Grid.Column="0" Margin="30,30,30,0">
            <Border Background="Transparent"  BorderBrush="Gray" BorderThickness="5">
                <Button x:Name="SAPGWM" Click="SAPGWM_Click" HorizontalAlignment="Center" BorderBrush="Gray">
                    <StackPanel Width="auto">
                        <TextBlock Text="Get Data" FontSize="40" />
                        <TextBlock Text="Click to access GWM data" FontSize="16"/>
                    </StackPanel>
                </Button>
            </Border>
        </StackPanel>
    
        <StackPanel Grid.Row="0" Grid.Column="1" Margin="30,30,30,0">
            <Border Background="Transparent"  BorderBrush="Gray" BorderThickness="5">
                <StackPanel>
                    <TextBlock x:Name="HelpText"  Text="This is the sample Windows Store app for retrieving GWM data." TextWrapping="Wrap" FontSize="40" />
                    <WebView x:Name="SignInPage" Height="800" NavigationStarting="SignInPage_NavigationStarting"/>
                    <ListView x:Name="DataList" ItemsSource="{Binding}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <StackPanel>
                                    <TextBlock Grid.Row="0" Grid.ColumnSpan="3" Text="{Binding Title}" FontSize="35"/>
                                    <TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Engine}" FontSize="20"/>
                                    <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding ExtColor}" FontSize="20"/>
                                    <TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding Price}" FontSize="20"/>
                                    <TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding Status}" FontSize="20"/>
                                    <TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding ContactPhone}" FontSize="20"/>
                                </StackPanel>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </StackPanel>
            </Border>
        </StackPanel>
    </Grid>
    
  2. Edit MainPage.xaml.cs. Add the following using statements.

    using System.Collections.ObjectModel;
    using System.Threading.Tasks;
    
  3. Add the following method to handle the click event on the Button in the UI.

    private async void SAPGWM_Click(object sender, RoutedEventArgs e)
    {
        if (!AADAuthHelper.Instance.IsAuthenticated)
        {
            HelpText.Visibility = Visibility.Collapsed;
            SignInPage.Navigate(new Uri(AADAuthHelper.Instance.AuthorizeUrl));
        }
        else
        {
            await GetData();
        }
    }
    
  4. Add the following method to handle the Azure authorization code after the user signs in and is redirected back to the app.

    private async void SignInPage_NavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args)
    {
        if (args.Uri.AbsoluteUri.StartsWith(AADAuthHelper.AppRedirectUrl))
        {
            sender.Stop();
    
            var querys = args.Uri.Query.Substring(1).Split('&');
            string code = null;
            foreach (string query in querys)
            {
                if (query.StartsWith("code="))
                {
                    code = query.Substring(5);
                    break;
                }
            }
    
            if (string.IsNullOrEmpty(code)) throw new UnauthorizedAccessException();
    
            await AADAuthHelper.Instance.AcquireTokenWithAuthorizeCode(code);
            await GetData();
        }
    }
    
  5. Add the following method to retrieve data from GWM.

    private async Task<IAsyncAction> GetData()
    {
        DataModel[] gwmData = await GWMDataHelper.Instance.GetGWMData();
        return this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            SignInPage.Visibility = Visibility.Collapsed;
            DataList.DataContext = new ObservableCollection<DataModel>(gwmData);
        });
    }
    

Add UI for the Windows Phone Store app

  1. Edit MainPage.xaml in the project SAP2UniversalApp.WindowsPhone. The example described in this article uses the following XAML for the main Grid. This is the XAML provided above for the Windows app, adjusted for the screen size of a Windows Phone app.

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="60"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
    
        <StackPanel Grid.Row="0" Grid.Column="0" Margin="0,0,0,0">
            <Button x:Name="SAPGWM" Click="SAPGWM_Click" HorizontalAlignment="Center" BorderBrush="Gray">
                <StackPanel Width="auto">
                    <TextBlock Text="Get Data" FontSize="20" />
                </StackPanel>
            </Button>
        </StackPanel>
    
        <StackPanel Grid.Row="1" Grid.Column="0" Margin="0,0,0,0">
            <TextBlock x:Name="HelpText"  Text="This is the sample application of Windows 8 to retrieve GWM data." TextWrapping="Wrap" FontSize="20" />
            <WebView x:Name="SignInPage" Height="600" NavigationStarting="SignInPage_NavigationStarting"/>
            <ListView x:Name="DataList" ItemsSource="{Binding}">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel>
                            <TextBlock Text="{Binding Title}" FontSize="28"/>
                            <TextBlock Text="{Binding Engine}" FontSize="20"/>
                            <TextBlock Text="{Binding ExtColor}" FontSize="20"/>
                            <TextBlock Text="{Binding Price}" FontSize="20"/>
                            <TextBlock Text="{Binding Status}" FontSize="20"/>
                            <TextBlock Text="{Binding ContactPhone}" FontSize="20"/>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackPanel>
    </Grid>
    
  2. In MainPage.xaml.cs of the Windows Phone project, use the same code as in MainPage.xaml.cs of the Windows App.

Get the GWM data

  1. In Visual Studio, right-click the SAP2UniversalApp.Shared project and select Add > Class. Add a new class file to the project named GWMDataHelper.cs. This class retrieves the GWM data.

  2. Edit GWMDataHelper.cs to retrieve data from GWM. Add the following using statements.

    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Threading.Tasks;
    using Newtonsoft.Json.Linq;
    
  3. Add the following constant fields. Get the SAP end point from your GWM administrator. Replace the SAP OData query with the query string you want.

    // Replace the value with your SAP OData endpoint stem. End it with a single forward slash.
    const string SAPEndPoint = "http://my_gwm.cloudapp.net:8080/perf/sap/opu/odata/sap/ZCAR_POC_SRV";
    // Replace parameter with an OData object and optional query parameters.
    const string SAPODataQuery = "/DataCollection?$top=10";
    
  4. Add the following method.

        internal class GWMDataHelper
            internal async Task<DataModel[]> GetGWMData()
            {
                var accessToken = AADAuthHelper.Instance.GetAccessToken();
                string requestUrl = SAPEndPoint + SAPODataQuery;
                using (HttpClient client = new HttpClient())
                {
                    using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl))
                    {
                        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await accessToken);
                        request.Headers.Add("Accept", "application/json");
                        HttpResponseMessage response = await client.SendAsync(request);
                        string dataString = await response.Content.ReadAsStringAsync();
    
                        return JObject.Parse(dataString)["d"]["results"].ToObject<DataModel[]>();
                    }
                }
    
            }
    

Now run and test the Windows app or the Windows Phone app. After you click the Get Data button, wait a few seconds for the data to appear in the user interface.