Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Consume Data from a OData WCF Data Service in a .NET Client

5.00/5 (3 votes)
15 May 2018CPOL5 min read 26K  
Consume data from a WCF Data service in a .NET client. MSAL OAuth 2.0

Table of Contents

Introduction

My previous article demonstrates how to build a WCF OData web service. This article demonstrates how to consume that service in a .NET client application.

Firstly, you should add the data services client nuget package.

PM> Install-Package Microsoft.Data.Services.Client

With Visual Studio 2017, Microsoft has removed support for OData service references, i.e., it will not be able to resolve the service metadata through the add service reference screen.

Image 1

Visual Studio 2017 will also not be able to update any existing service references that you added in previous versions of Visual Studio, i.e., when you right click on your existing service reference for your WCF OData web service and click on update, you will get the following message.

Image 2

So In order to add a service reference in visual 2017 you will need to go one of the following routes.

Connected Service

Firstly, you need to install the OData Connected Service extension from the Tools->Extensions and Updates menu.

Image 3

Right click your project and choose to add a “Connected Service”.

Image 4

Choose “OData Connected Service” from the list of choices. Note that this choice is added by the OData Connected Service extension.

Image 5

Enter your service endpoint. This URL should be obvious from checking out my previous article on creating the web service. Also, you want to give your service a friendly name. Click Next.

Image 6

If at this point you get a cannot access error then it's simply that the authentication in the web-service is blocking the extension from downloading the meta-data. You will unfortunately need to temporarily remove any authentication from the web-service.

Enter the file name for the class to be generated and click finish.

Image 7

If you get an error message that says "Value cannot be null.\r\nParameter name: path1" then you need to download the Microsoft WCF ToolKit and install it. Then go to the registry and find the following key: [HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Microsoft WCF Data Services]. Create a duplicate of "VS 2010 Tooling" or “VS 2012 Tooling” (if this doesn't exist, use "5.6" instead) named "VS 2014 Tooling". Then try again.

More information on the extension and about these errors can be found here.

When the extension completes, it will create a package under the connected services node. Right clicking the package reveals the option “Update OData Connected Service” which you will need to do if you make changes to the entities that the data service is serving. 

DataSvcUtil

The other option for generating the service reference is to use the data service utility command line program.

From an elevated command line, navigate to “C:\Windows\Microsoft.NET\{OSArchitecture}\{Framework Version}” where {OSArchitecture} is dependent on your OS/CPU architecture and {Framework Version} is the version of .NET.

e.g. C:\Windows\Microsoft.NET\Framework64\v4.0.30319

Then run the following to create the MyDataService.cs with all of the OData bindings. You will need to add this resulting code file to your client application. The {URI} should be replaced with the service endpoint.

datasvcutil /language:Csharp /out:C:\temp\MyDataService.cs /uri:{URI}

You could potentially build the command line into a post build command in your Visual Studio solution. However, unfortunately, similar to the connected service extension, the datasvsutil.exe cannot download from an authenticated endpoint.

Data Context

Add the WCF Data Services Client via nugget (it might be the case that this is added automatically by the connected service extension.)

PM> Install-Package Microsoft.Data.Services.Client. </font>

You can initialize a context object for queries and crud operations via the following. The ModelName and EntityCollectionName will be different depending on what you called things in the data service. If you’ve followed my article on creating the web service, it should be easy to discover what these need to be. The service endpoint is the same URL that you used with the connected service extension or the datsvcutil.exe.

C#
var context = new {ModelName}.{EntityCollectionName}(new Uri("{serviceendpoint}");

Basic Authentication

C#
context.Credentials = new NetworkCredential("username", "password");

OAuth Authentication MSAL

In your Azure portal search for app registrations:

Image 8

Click the add button and enter details for your client application. The application type should be native. The redirect URI can be anything for the moment but you will change it shortly.

Image 9

Once the app is registered, you will be able to see the application id that Azure has assigned. Make a note of this GUID as you will need to input it in various places. The RedirectURI can be anything for the moment but you will change it in the next step.

To change the redirectURI, click on Settings and then Redirect URIs. Update the URI as per the example below, replacing the {Application ID} part with your application ID GUID (which is obviously blacked out in my screenshot) e.g., msal14bc971b-0afd-4de5-9006-b2db00d2c0a2://auth.

Image 10

Next below RedirectURIs, click required permissions and finally click on the Add button.

Image 11

For API selection, search for the name of your data service application which should also be registered in the Azure portal on the same tenant. See my article on creating a WCF data service for more details..

Image 12

With the API selected, select the permission to allow access / user_impersonation.

Image 13

 Add the Microsoft.Identity.Client nuget package.

PM> Install-Package Microsoft.Identity.Client

Here is the code for using the msal client to authenticate and to include the access token in the dataservice header to be used with your data-service.

  • Replace {Application ID of native client} with your native client application ID GUID, e.g., 14bc971b-0afd-4de5-9006-b2db00d2c0a2.
  • Replace {tenant name} with the name of the tenant where you registered your application, e.g., mytenant.onmicrosoft.com.
  • Replace {Dataservice App ID URI} with the app ID uri/Sign On URL of the dataservice application, this can be found in the properties of the dataservice app registration (see my article on creating a WCF data service) e.g., https://mydataservice.azurewebsites.net.
C#
string clientId = "{Application ID of native Client}";
string[] applicationScopes = new string[] { "{DataService App ID URI}/user_impersonation" };
string authority = "https://login.microsoftonline.com/{tenant name}/";
string redirectUri = "msal{Application ID of native Client}://auth"</span>

Var msaClient = new PublicClientApplication(clientId,Authority)
{
 RedirectUri = redirectUri
};

//Try to get the cached token first, if cached token is null then use interactive login.
Var authResponse= await msaClient.AcquireTokenSilentAsync
                  (applicationScopes, msaClient.Users.FirstOrDefault()) ??
await msaClient.AcquireTokenAsync(applicationScopes,null);

//Attach handling to add auth header.
                    context.SendingRequest2 += (s, e) =>

                    {
                        //Add the OAuth 2.0 header.
                        e.RequestMessage.SetHeader
                            ("Authorization", authResponse.CreateAuthorizationHeader());
                    };

Xamarin.Forms RedirectURI

To make things work correctly in iOS and Android and to return to your application once the token is acquired, you must modify the native projects to understand the redirectUri scheme.

For Android, you need to edit Android_Project/Properties/AndroidManifest.xml.

Image 14

Add the following BrowserTabActivity to the application tag, replacing {Application ID of native client} with the correct GUID, e.g., msal14bc971b-0afd-4de5-9006-b2db00d2c0a.

XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"....>
 <application ....>
    <activity android:name="microsoft.identity.client.BrowserTabActivity">
      <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="msal{Application ID of native client}" android:host="auth" />
      </intent-filter>
    </activity>
    ...
 </application>
</manifest>

For iOS, you need to edit the info.plist file:

Image 15

Right click on the info.plist, choose "open with" and then select the "iOS Manifest Editor".

Image 16

In the advanced tab, click on "Add URL Type" and add an entry similar to below, replacing the {Application ID of Native Client} with the correct GUID, e.g., msal14bc971b-0afd-4de5-9006-b2db00d2c0a.

Image 17

CRUD

The following examples are taken from here.

Add

C#
// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri);
// Create the new product.
Product newProduct =
    Product.CreateProduct(0, "White Tea - loose", false);
// Set property values.
newProduct.QuantityPerUnit = "120gm bags";
newProduct.ReorderLevel = 5;
newProduct.UnitPrice = 5.2M;
try
{
    // Add the new product to the Products entity set.
    context.AddToProducts(newProduct);

    // Send the insert to the data service.
    DataServiceResponse response = context.SaveChanges();

    // Enumerate the returned responses.
    foreach (ChangeOperationResponse change in response)
    {
        // Get the descriptor for the entity.
        EntityDescriptor descriptor = change.Descriptor as EntityDescriptor;

        if (descriptor != null)
        {
            Product addedProduct = descriptor.Entity as Product;

            if (addedProduct != null)
            {
                Console.WriteLine("New product added with ID {0}.",
                    addedProduct.ProductID);
            }
        }
    }
}catch (DataServiceRequestException ex)
{
    throw new ApplicationException(
        "An error occurred when saving changes.", ex);
}

Update

C#
string customerId = "ALFKI";
// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri);
// Get a customer to modify using the supplied 
// ID.var customerToChange = (from customer in context.Customers
                        where customer.CustomerID == customerId
                        select customer).Single();
 // Change some property values.
customerToChange.CompanyName = "Alfreds Futterkiste";
customerToChange.ContactName = "Maria Anders";
customerToChange.ContactTitle = "Sales Representative";
try
{
    // Mark the customer as updated.
    context.UpdateObject(customerToChange);

    // Send the update to the data service.
    context.SaveChanges();
}catch (DataServiceRequestException  ex)
{
    throw new ApplicationException(
        "An error occurred when saving changes.", ex);
}

Delete

C#
// Create the DataServiceContext using the service URI.
NorthwindEntities context = new NorthwindEntities(svcUri);
try
{
    // Get the product to delete, by product ID.
    var deletedProduct = (from product in context.Products
                          where product.ProductID == productID
                          select product).Single();

    // Mark the product for deletion.   
    context.DeleteObject(deletedProduct);

    // Send the delete to the data service.
    context.SaveChanges();
}// Handle the error that occurs when the delete operation fails,
 // which can happen when there are entities with existing 
 // relationships to the product being deleted.catch (DataServiceRequestException ex)
{
    throw new ApplicationException(
        "An error occurred when saving changes.", ex);
}

History

  • Version 1

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)