Sample: Windows 8 desktop modern SOAP app
Applies To: Dynamics CRM 2013
This sample code is for Microsoft Dynamics CRM 2013 and Microsoft Dynamics CRM Online. It can be found in the following location in the download package:
SampleCode\CS\ModernAndMobileApps\ModernSoapApp
Download the Microsoft Dynamics CRM SDK package.
Requirements
This sample requires the Microsoft.Preview.WindowsAzure.ActiveDirectory.Authentication NuGet package. If you have a working internet connection, this package is automatically downloaded and installed when you load the project’s solution.
For more information about the requirements for running the sample code provided in this SDK, see Use the sample and helper code.
Demonstrates
This sample shows how to write a Windows 8 desktop modern application that can send requests to the organization web service without linking to the SDK assemblies. This sample uses the Microsoft Azure Active Directory Authentication Library (ADAL) and the SOAP protocol. The sample also demonstrates obtaining the OAuth endpoint URL at run time. While there are seven tiles displayed on the main app page, only the Accounts and Tasks tiles are connected to event handler code. The other tiles are just placeholders. The example code is configured for the Microsoft Dynamics CRM Online server and a fictitious organization. See the OData version of this sample for an Internet-facing deployment (IFD) configuration. Code snippets showing just the key sections of the full sample are shown later in this topic. |
Tiled user interface of the sample app |
Example
The following code snippet shows how to authenticate the user with the organization web service.
using Microsoft.Preview.WindowsAzure.ActiveDirectory.Authentication;
using System;
using System.Threading.Tasks;
using Windows.UI.Popups;
using Windows.Security.Authentication.Web;
using System.Net;
using System.Threading;
using System.IO;
using System.Text;
namespace ModernSoapApp
{
/// <summary>
/// Manages authentication with the organization web service.
/// </summary>
public static class CurrentEnvironment
{
# region Class Level Members
private static AuthenticationContext _authenticationContext;
// TODO Set these string values as approppriate for your app registration and organization.
// For more information, see the SDK topic "Walkthrough: Register an app with Active Directory".
private const string _clientID = "893262be-fbdc-4556-9325-9f863b69495b";
public const string CrmServiceUrl = "https://my-domain.crm.dynamics.com/";
# endregion
// <summary>
/// Perform any required app initialization.
/// This is where authentication with Active Directory is performed.
public static async Task<string> Initialize()
{
Uri serviceUrl = new System.Uri(CrmServiceUrl + "/XRMServices/2011/Organization.svc/web?SdkClientVersion=6.1.0000.0000");
// Dyamics CRM Online OAuth URL.
string _oauthUrl = DiscoveryAuthority(serviceUrl);
// Obtain the redirect URL for the app. This is only needed for app registration.
string redirectUrl = WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();
// Obtain an authentication token to access the web service.
_authenticationContext = new AuthenticationContext(_oauthUrl, false);
AuthenticationResult result = await _authenticationContext.AcquireTokenAsync(CrmServiceUrl, _clientID);
// Verify that an access token was successfully acquired.
if (AuthenticationStatus.Succeeded != result.Status)
{
if (result.Error == "authentication_failed")
{
// Clear the token cache and try again.
(AuthenticationContext.TokenCache as DefaultTokenCache).Clear();
_authenticationContext = new AuthenticationContext(_oauthUrl, false);
result = await _authenticationContext.AcquireTokenAsync(CrmServiceUrl, _clientID);
}
else
{
DisplayErrorWhenAcquireTokenFails(result);
}
}
return result.AccessToken;
}
/// <summary>
/// Method to get authority URL from organization’s SOAP endpoint URL using Microsoft Azure Active Directory Authentication Library (ADAL),
/// </summary>
/// <param name="result">The Authority Url returned from HttpWebResponse.</param>
public static string DiscoveryAuthority(Uri serviceUrl)
{
// Use AuthenticationParameters to send a request to the organization's endpoint and
// receive tenant information in the 401 challenge.
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationParameters parameters = null;
HttpWebResponse response = null;
try
{
// Create a web request where the authorization header contains the word "Bearer".
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(serviceUrl);
httpWebRequest.Headers[HttpRequestHeader.Authorization.ToString()] = "Bearer";
// The response is to be encoded.
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
response = (HttpWebResponse)httpWebRequest.GetResponse();
}
catch (WebException ex)
{
response = (HttpWebResponse)ex.Response;
// A good response was returned. Extract any parameters from the response.
// The response should contain an authorization_uri parameter.
parameters = Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationParameters.
CreateFromResponseAuthenticateHeader((response.Headers)["WWW-Authenticate"]);
}
finally
{
if (response != null)
response.Dispose();
}
// Return the authority URL.
return parameters.Authority;
}
/// <summary>
/// Returns a response from an Internet resource.
/// </summary>
public static WebResponse GetResponse(this WebRequest request)
{
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
IAsyncResult asyncResult = request.BeginGetResponse(r => autoResetEvent.Set(), null);
// Wait until the call is finished
autoResetEvent.WaitOne(DefaultRequestTimeout);
return request.EndGetResponse(asyncResult);
}
/// <summary>
/// Get the DefaultRequestTimeout from the server.
/// </summary>
public static TimeSpan DefaultRequestTimeout { get; set; }
/// <summary>
/// Display an error message to the user.
/// </summary>
/// <param name="result">The authentication result returned from AcquireTokenAsync().</param>
private static async void DisplayErrorWhenAcquireTokenFails(AuthenticationResult result)
{
MessageDialog dialog;
switch (result.Error)
{
case "authentication_canceled":
// User cancelled, so no need to display a message.
break;
case "temporarily_unavailable":
case "server_error":
dialog = new MessageDialog("Please retry the operation. If the error continues, please contact your administrator.",
"Sorry, an error has occurred.");
await dialog.ShowAsync();
break;
default:
// An error occurred when acquiring a token so show the error description in a MessageDialog.
dialog = new MessageDialog(string.Format(
"If the error continues, please contact your administrator.\n\nError: {0}\n\nError Description:\n\n{1}",
result.Error, result.ErrorDescription), "Sorry, an error has occurred.");
await dialog.ShowAsync();
break;
}
}
}
}
For this code to work, you must first register your app with a supported identity provider (AD FS or Microsoft Azure Active Directory). Next, you must set the variable values for _clientID, and CrmServiceUrl in the code. The value for client ID was defined during app registration. For more information about app registration, see Walkthrough: Register a CRM app with Active Directory.
Example
The following code snippet shows how to retrieve entity records from the organization web service using SOAP code in an HTTP request. The authentication access token is placed in the authorization header.
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace ModernSoapApp
{
public static class HttpRequestBuilder
{
/// <summary>
/// Retrieve entity record data from the organization web service.
/// </summary>
/// <param name="accessToken">The web service authentication access token.</param>
/// <param name="Columns">The entity attributes to retrieve.</param>
/// <param name="entity">The target entity for which the data should be retreived.</param>
/// <returns>Response from the web service.</returns>
/// <remarks>Builds a SOAP HTTP request using passed parameters and sends the request to the server.</remarks>
public static async Task<string> RetrieveMultiple(string accessToken, string[] Columns, string entity)
{
// Build a list of entity attributes to retrieve as a string.
string columnsSet = string.Empty;
foreach (string Column in Columns)
{
columnsSet += "<b:string>" + Column + "</b:string>";
}
// Default SOAP envelope string. This XML code was obtained using the SOAPLogger tool.
string xmlSOAP =
@"<s:Envelope xmlns:s='https://schemas.xmlsoap.org/soap/envelope/'>
<s:Body>
<RetrieveMultiple xmlns='https://schemas.microsoft.com/xrm/2011/Contracts/Services' xmlns:i='http://www.w3.org/2001/XMLSchema-instance'>
<query i:type='a:QueryExpression' xmlns:a='https://schemas.microsoft.com/xrm/2011/Contracts'><a:ColumnSet>
<a:AllColumns>false</a:AllColumns><a:Columns xmlns:b='https://schemas.microsoft.com/2003/10/Serialization/Arrays'>" + columnsSet +
@"</a:Columns></a:ColumnSet><a:Criteria><a:Conditions /><a:FilterOperator>And</a:FilterOperator><a:Filters /></a:Criteria>
<a:Distinct>false</a:Distinct><a:EntityName>" + entity + @"</a:EntityName><a:LinkEntities /><a:Orders />
<a:PageInfo><a:Count>0</a:Count><a:PageNumber>0</a:PageNumber><a:PagingCookie i:nil='true' />
<a:ReturnTotalRecordCount>false</a:ReturnTotalRecordCount>
</a:PageInfo><a:NoLock>false</a:NoLock></query>
</RetrieveMultiple>
</s:Body>
</s:Envelope>";
// The URL for the SOAP endpoint of the organization web service.
string url = CurrentEnvironment.CrmServiceUrl + "/XRMServices/2011/Organization.svc/web";
// Use the RetrieveMultiple CRM message as the SOAP action.
string SOAPAction = "https://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/RetrieveMultiple";
// Create a new HTTP request.
HttpClient httpClient = new HttpClient();
// Set the HTTP authorization header using the access token.
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
// Finish setting up the HTTP request.
HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, url);
req.Headers.Add("SOAPAction", SOAPAction);
req.Method = HttpMethod.Post;
req.Content = new StringContent(xmlSOAP);
req.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("text/xml; charset=utf-8");
// Send the request asychronously and wait for the response.
HttpResponseMessage response;
response = await httpClient.SendAsync(req);
var responseBodyAsText = await response.Content.ReadAsStringAsync();
return responseBodyAsText;
}
}
}
You can use the SOAPLogger tool provider in the SDK download to obtain the SOAP code for an organization request. For information about using the SOAPLogger tool, see Walkthrough: Use the Modern app SOAP endpoint with JavaScript.
See Also
Write mobile and modern apps
Authenticate the user with the web services
Sample: Windows 8 desktop modern OData app
Azure Authentication Library (AAL) for Windows Store: a Deep Dive
Securing a Windows Store Application and REST Web Service Using Azure AD (Preview)
Understanding SOAP