Showing posts with label odata. Show all posts
Showing posts with label odata. Show all posts

Tuesday, August 14, 2012

Part 2 - Consuming custom WCF Data Services (oData) Endpoint (Javascript/jQuery and c#) - Create and Delete

In the first post we have created a service which worked fine for retrieving data. most time we also need delete and create functionality. Here is how it works.

This Article is based on the Part 1 - Consuming custom WCF Data Services. If you just want to download the current version or check out the code, check the codeplex project page.

Preparation:
  • Add JSON2 (via NuGet)
  • Add Styles-Folder
Implementation:
  • Configure DataService to V2
At the moment, the V3 Implementation of Microsoft does not support Create Functionality. You will run into a 415 Unsupported Media Type Exception. More information you can find on Stackoverflow.
The solution for this is quite simple. Just change the value for MaxProtocolVersion in your Service from "V3" to "V2" . 



The difficult part of the Create/Delete Functionality was to create a custom IUpdatable Implementation. Well, this is NOT required for your implementation. You can just replace my implementation with your Entity-Framework Context. 
I want my example independent from database/files. So i have created a static collection of items, controlled by the Context.

  • Replace CarContext.cs code with the following.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
using System.Data.Services;
 
namespace MK.oDataConsuming.Web.Model {
    public class CarContext : IUpdatable {
 
        #region Members
        private static List<Car> _cars;
        #endregion
 
        #region Constructor
        public CarContext() {
            if (_cars == null) {
                _cars = new List<Car>();
            }
        }
        #endregion
 
        #region Properties
        public IQueryable<Car> Cars {
            get {
                return _cars.AsQueryable<Car>();
            }
        }
        #endregion
 
        #region IUpdatable Methods
        public void AddReferenceToCollection(object targetResource, string propertyName, object resourceToBeAdded) {
            throw new NotImplementedException();
        }
 
        public void ClearChanges() {
            throw new NotImplementedException();
        }
 
        public object CreateResource(string containerName, string fullTypeName) {
 
            Car car = new Car();
 
            int tmpId = 0;
            if (_cars.Count > 0) {
                tmpId = _cars.Max(c => c.Id);
            }
 
            car.Id = ++tmpId;
            return car;
        }
 
        public void DeleteResource(object targetResource) {
            List<Car> carsToDelete = (List<Car>)targetResource;
            Car carToDelete = carsToDelete[0];
            _cars.Remove(carToDelete);
        }
 
        public object GetResource(IQueryable query, string fullTypeName) {
            List<Car> carList = query.Cast<Car>().ToList();
            return carList;
        }
 
        public object GetValue(object targetResource, string propertyName) {
            throw new NotImplementedException();
        }
 
        public void RemoveReferenceFromCollection(object targetResource, string propertyName, object resourceToBeRemoved) {
            throw new NotImplementedException();
        }
 
        public object ResetResource(object resource) {
            throw new NotImplementedException();
        }
 
        public object ResolveResource(object resource) {
            if (resource is Car) {
                _cars.Add((Car)resource);
            }
            return resource;
        }
 
        public void SaveChanges() {
            //We don't have any database/file to store the data in this test.            
        }
 
        public void SetReference(object targetResource, string propertyName, object propertyValue) {
            throw new NotImplementedException();
        }
 
        public void SetValue(object targetResource, string propertyName, object propertyValue) {
            if (propertyName == "Manufacturer") {
                ((Car)targetResource).Manufacturer = (string)propertyValue;
            } else if (propertyName == "ProductionYear") {
                ((Car)targetResource).ProductionYear = (string)propertyValue;
            } else {
                throw new Exception("Property not implemented.");
            }
        }
        #endregion
    }
}



This is not a reference implementation of IUpdatable. At the moment i have not found a good documentation/example. So i have tested a little bit. It works for testing purposes.

  • Add new item (to Styles-Folder) --> Web --> Style Sheet


  • Add css-Code
body 
{
    backgroundnone repeat scroll left top #EEEEEE;
    color#444444;
    font13px Arial,Tahoma,Helvetica,FreeSans,sans-serif;
}
 
div.entry 
{
    background-colorWhite;
    width300px;    
    border1px solid #444444;    
    padding2px;
    margin1px;
}
 
div.entry_delete 
{
    text-alignright;
}
 
div.buttonarea 
{
    text-alignright;
    width300px;    
}
 
div.inputfields 
{
    text-alignright;
    width300px;
}


Only some basic formatting...

  • Update odataaccess.js
The javascript-part is extended with examples for create and delete. I also have added a UI Clear-Function. You can just comment out the first line of the "RetrieveData" Method if you want to see the history of your actions. 
/*
* Retrieve data from service
**/
function RetrieveData() {
    ClearView();
    $.ajax({
        type: "GET",
        async: false,
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: "/CarService.svc/Cars",
        success: RetrieveMultipleCallback,
        error: function () {
            alert("Error");
        },
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept", "application/json;odata=verbose");
        }
    });
}

/*
* Handles the RetrieveMultiple Response
**/
function RetrieveMultipleCallback(data) {
    var $newDiv;
    $.each(data.d, function (i, item) {
        $newDiv = $("
" + "
" + item.Id + " - " + item.Manufacturer + " - " + item.ProductionYear + "
" + "
" + "
"); $("#listofcars").append($newDiv); }); $newDiv = $("
----
"); $("#listofcars").append($newDiv); } /* * Create data by service **/ function CreateData(id) { var car = {}; var man = $("#man_id").val(); var year = $("#year_id").val(); car.Manufacturer = man; car.ProductionYear = year; var carStringified = JSON.stringify(car); $.ajax({ type: "POST", async: false, data: carStringified, contentType: "application/json", datatype: "json", url: "/CarService.svc/Cars", success: CreateCallback, error: function () { alert("Error"); }, beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); } }); } /* * Handles the Create Response **/ function CreateCallback(data) { $("#man_id").val(""); $("#year_id").val(""); RetrieveData(); } /* * Delete data by service **/ function DeleteData(data) { $.ajax({ type: "DELETE", async: false, //data: carStringified, contentType: "application/json", datatype: "json", url: "/CarService.svc/Cars(" + data + ")", success: DeleteCallback, error: function () { alert("Error"); }, beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); } }); } /* * Handles the Delete Response **/ function DeleteCallback(data) { RetrieveData(); } /* * Clears the entry-view **/ function ClearView() { $("#listofcars").empty(); }
  • Run and test the Web Project
The List is empty if you start first. So just add some entries by yourself.


Cheers,
Markus

Wednesday, August 8, 2012

Part 1 - Consuming custom WCF Data Services (oData) Endpoint (Javascript/jQuery and c#)

The current version of the WCF Data Services 5.0 is working fine if you are in the .NET domain. Accessing with jQuery can cause some issues.

So here is one example using .NET (c#) and a second one using Javascript (jQuery).

If you want to view the final sourcecode, check it out from codeplex.
http://odataconsuming.codeplex.com/

You also can download the final solution as zip-File.
http://odataconsuming.codeplex.com/downloads/get/467041

You can find Part 2 of this documentation here.

Preparation

Creating Custom WCF Data Service
  • Create new Empty Web Application
  • Add Entity Framework (via NuGet)
  • Add jQuery (via NuGet)
  • Add new Item --> Web --> WCF Data Service (for oData V3)

  • Create entity-class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services.Common;
 
namespace MK.oDataConsuming.Web {
 
    [DataServiceKey("Id")]
    public class Car {        
        public int Id { getset; }
        public string Manufacturer { getset; }
        public string ProductionYear { getset; }
    }
}
  • Create Context
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Entity;
 
namespace MK.oDataConsuming.Web.Model {
    public class CarContext  {
 
        private List<Car> _cars;
 
        public CarContext() {
            _cars = new List<Car>();
            _cars.Add(new Car() { Id = 1, Manufacturer = "BMW", ProductionYear = "1997" });
            _cars.Add(new Car() { Id = 2, Manufacturer = "Mercedes", ProductionYear = "2000" });
            _cars.Add(new Car() { Id = 3, Manufacturer = "Opel", ProductionYear = "2011" });
            _cars.Add(new Car() { Id = 4, Manufacturer = "Ford", ProductionYear = "1999" });
        }
 
        public IQueryable<Car> Cars {
            get {
                return _cars.AsQueryable<Car>();
            }
        }
    }
}
  • Configure access rules
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
 
using MK.oDataConsuming.Web.Model;
 
namespace MK.oDataConsuming.Web
{
    public class CarService : DataService<CarContext>
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("Cars"EntitySetRights.All);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
        }
    }
}
  • Set .svc as Start Page
  • Test Service



Create HTML Test-Page
  • Add new Item --> Web --> HTML Page
  • Add HTML-Code to file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>oData Access</title>
    <script src="Scripts/jquery-1.7.2.min.js"></script>
    <script src="Scripts/json2.js"></script>
    <script src="Scripts/odataaccess.js"></script>
</head>
<body>
    <div>
        <input id="RetrieveButton" type="button" value="Retrieve" onclick="RetrieveData()" />
    </div>
    <div id="listofcars">    
    </div>
</body>
</html>
  • Add new Item (to Scripts-Folder) --> Web --> JScript File
  • Add Javascript Code to file
//Retrieve data from service
function RetrieveData() {
    $.ajax({
        type: "GET",
        async: false,
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: "/CarService.svc/Cars",
        success: RetrieveMultipleCallback,
        error: function () {
            alert("Error");
        },
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept""application/json;odata=verbose");
        }
    });
}
 
//Handles the RetrieveMultiple Response
function RetrieveMultipleCallback(data) {
    $.each(data.d, function (i, item) {
        var $newDiv = $("<div>" + item.Manufacturer + " - "  + item.ProductionYear + "</div>");
        $("#listofcars").append($newDiv);
    });
}

The Most important part ist the "beforeSend" Handler. Here we set the RequestHeader "Accept: application/json;odata=verbose". In older versions of WCF Data Services, this was "Accept: application/json" This does never work!
  • Test the Page


Create Console Application
  • Add new Console Project
  • Add Service Reference

  • Add Code to Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
using MK.oDataConsuming.ConsoleApp.CarServiceReference;
 
namespace MK.oDataConsuming.ConsoleApp {
    class Program {
        static void Main(string[] args) {
 
            //Create Service Contexst
            CarContext context = new CarServiceReference.CarContext(new Uri("http://localhost:1184/CarService.svc"));
            
            //Create query, retrieve and convert to list
            var query = context.Cars.Where(c => c.Manufacturer == "Opel");
            List<Car> resultSet = query.ToList();
 
            //Check results
            if (resultSet != null && resultSet.Count > 0) {
                Car opel = resultSet[0];
                Console.WriteLine("{0}, {1}, {2}", opel.Id, opel.Manufacturer, opel.ProductionYear);
            } else {
                Console.WriteLine("Car not found.");
            }
 
            Console.Read();
        }
    }
}



  • Test the application

Cheers

Wednesday, July 11, 2012

CRM 2011 - Accessing and filtering custom/crm WCF Data Service (oData) from sandboxed Plugin

As described in some MSDN-Forums and by Eric Pool, Quering data against the context directly via Linq in sandboxed plugins is not possible. You can retrieve the whole DataSet, but filtering is not possible.

If you need to access and filtering your results from an oData endpoint (also custom ones), you can use the following syntax.

Example OrganizationDataService (with FirstOrDefault):
//Define the Request URI
Guid accountId = new Guid("66BD3ABE-D2CA-E111-9E1F-000C29BDAB09");
string serviceAddress = @"http://crm:5555/DEV/XRMServices/2011/OrganizationData.svc/";
string accountQueryByPrimaryKey = string.Format("AccountSet(guid'{0}')", accountId.ToString());
Uri svcUri = new Uri(serviceAddress);
Uri accountUri = new Uri(string.Format("{0}{1}", serviceAddress, accountQueryByPrimaryKey));
 
//Create context and credentials ('My' is the name of the CRM organization, these classes are created when you add a service reference to CRM DataService endpoint)
CrmService.MyContext context = new CrmService.MyContext(svcUri);
context.Credentials = new System.Net.NetworkCredential("username""password""domain");
 
//Retrieve data
Account retrievedAccount = context.Execute<Account>(accountUri).FirstOrDefault();


Example Custom Service (with Enumeration):
// Define the Request URI
Uri svcUri = new Uri(@"http://crm:8888/MyDataService/CustomDataService.svc/");
Uri uriAccounts = new Uri(string.Format("{0}{1}", svcUri, "ACCOUNT(3)"));
 
// Create the context
DataServiceContext context = new DataServiceContext(svcUri)

// Enumerate over the query result.
StringBuilder sb = new StringBuilder();
foreach (ACCOUNT account in context.Execute<ACCOUNT>(uriAccounts)) { sb.AppendLine("Name: " + account.name); }

The trick is to use the oData (URI) for Filtering and to interprete just the results.

You can find the MSDN description here.

Tuesday, December 28, 2010

CRM 2011 - Webresources and SOAP Endpoint

Like mentioned in my previouse post about the Limitations of the oData Endpoint, there is "currently" no option for assign records or use the Execute-Functionality of the CRM-Service using the oData Endpoint. I found an interesting hint in the new RC-SDK.

You can use JScript and Silverlight Web resources to access Microsoft Dynamics CRM 2011 and Microsoft Dynamics CRM Online data from within the application. There are two web services available, each provides specific strengths. The following table describes the appropriate web service to use depending on the task you need to perform.
Task Web Service
Create, Retrieve, Update and Delete records. REST Endpoint
Associate and Disassociate records REST Endpoint
Assign Records SOAP Endpoint
Retrieve Metadata SOAP Endpoint
Execute Messages SOAP Endpoint
Both of these Web services rely on the authentication provided by the Microsoft Dynamics CRM application. They cannot be used by code that executes outside the context of the application. They are effectively limited to use within Silverlight, JScript libraries, or JScript included in Web Page (HTML) Web resources.
The REST endpoint provides a ‘RESTful’ web service using OData to provide a programming environment that is familiar to many developers. It is the recommended web service to use for tasks that involve creating, retrieving, updating and deleting records. However, in this release of Microsoft Dynamics CRM the capabilities of this Web service are limited to these actions. Future versions of Microsoft Dynamics CRM will enhance the capabilities of the REST endpoint.
The SOAP endpoint provides access to all the messages defined in the Organization service. However, only the types defined within the WSDL will be returned. There is no strong type support. While the SOAP endpoint is also capable of performing create, retrieve, update and delete operations, the REST endpoint provides a better developer experience. In this release of Microsoft Dynamics CRM the SOAP endpoint provides an alternative way to perform operations that the REST endpoint is not yet capable of.
That means, you have to use the SOAP-Endpoint for Assign, SetState... in your Webresource. How this can be handled is not shown in the SDK at the moment. I hope for an update.

UPDATE: Walkthrough: Use the SOAP Endpoint for Web Resources with Silverlight.

Sunday, November 21, 2010

Limitations oData Endpoint CRM 2011

I was very excited in the upcoming REST-Endpoint, especially for further silverlight applications. But the following Limitations, found in the SDK, are a kind of a showstopper.

Only Create, Retrieve, Update and Delete actions can be performed on entity records.
  • Messages that require the Execute method cannot be performed.
  • Associate and Disassociate actions are performed as updates.
Authentication is only possible within the application
Use of the REST endpoint is effectively limited to JScript libraries or Silverlight Web Resources.
The OData protocol is not fully implemented. Some system query options are not available.
See System Query Options for more information. (SDK)

Especially lack of "Excecute" is hard to handle. I hope for an update of the "Update" Functionality.

cheers