Skip to content

Instantly share code, notes, and snippets.

@johnnyreilly
Created September 22, 2012 05:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnnyreilly/3765280 to your computer and use it in GitHub Desktop.
Save johnnyreilly/3765280 to your computer and use it in GitHub Desktop.
A class which when added to a web application exposes the provides an OData service on top of CRM / xRM
using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.Web;
using System.ServiceModel.Web;
using Microsoft.Xrm.Client;
using log4net;
namespace CrmOData
{
/// <summary>
/// Exposes an OData service providing access to CRM
///
/// Examples of how to use service.
///
/// URI : http://myWebServer/CrmOData/Crm.svc
/// Purpose : Demonstrates exposed endpoints
///
/// URI : http://myWebServer/CrmOData/Crm.svc/myCustomer
/// Purpose : Demonstrates how to retrieve all customers
///
/// URI : http://myWebServer/CrmOData/Crm.svc/myCustomer?$filter=lastName eq 'Reilly'
/// Purpose : Demonstrates how to retrieve all customers with the Surname "Reilly"
///
/// URI : http://myWebServer/CrmOData/Crm.svc/myCustomer?$select=firstName,lastName
/// Output : Does not work.
///
/// "$select statements are not supported. This problem is being discussed
/// here http://social.msdn.microsoft.com/Forums/en/adodotnetdataservices/thread/366086ee-dcef-496a-ad15-f461788ae678
/// and is caused by the fact that CrmDataContext implements the IExpandProvider interface which in turn causes
/// the DataService to lose support for $select projections"
///
/// See http://social.microsoft.com/Forums/en/crmdevelopment/thread/31daedb4-3d75-483a-8d7f-269af3375d74 for original post discussing this
///
/// URI : http://myWebServer/CrmOData/Crm.svc/myCustomer(guid'783323a1-b1f1-4910-b5be-a2f37e62d0ba')/currentBalance
/// Purpose : Retrieves the current balance of the customers account
///
/// URI : http://myWebServer/CrmOData/Crm.svc/myCustomer(guid'783323a1-b1f1-4910-b5be-a2f37e62d0ba')/currentBalance/$value
/// Output : 321186905.8600
/// Purpose : The raw value
///
/// URI : http://myWebServer/CrmOData/Crm.svc/myCustomer(guid'783323a1-b1f1-4910-b5be-a2f37e62d0ba')?$expand=transactions
/// Purpose : Retrieves a customer by their guid'783323a1-b1f1-4910-b5be-a2f37e62d0ba', with the transactions property expanded (the equivalent of Include in Entity Framework I guess)
/// </summary>
public class Crm : DataService< Xrm.DataContext >
{
private static ILog _log;
/// <summary>
/// Initialise the service (this method is called only once to initialize service-wide policies.)
/// </summary>
/// <param name="config"></param>
public static void InitializeService(DataServiceConfiguration config)
{
//Allows access to everything
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.SetEntitySetPageSize("*", 10); //Only allow access to 10 items at a time - don't want to bring down CRM
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
// set cache policy to this page
HttpContext context = HttpContext.Current;
HttpCachePolicy cachePolicy = HttpContext.Current.Response.Cache;
// server&private: server and client side cache only - not at proxy servers
cachePolicy.SetCacheability(HttpCacheability.ServerAndPrivate);
// default cache expire: 60 seconds
cachePolicy.SetExpires(HttpContext.Current.Timestamp.AddSeconds(60));
// cached output depends on: accept, charset, encoding, and all parameters (like $filter, etc)
cachePolicy.VaryByHeaders["Accept"] = true;
cachePolicy.VaryByHeaders["Accept-Charset"] = true;
cachePolicy.VaryByHeaders["Accept-Encoding"] = true;
cachePolicy.VaryByParams["*"] = true;
//allow client to send Cache-Control: nocache headers to invalidate cache
cachePolicy.SetValidUntilExpires(false);
//Log service startup initialisation
_log = log4net.LogManager.GetLogger("Crm.svc");
_log.Info("Crm.svc initialising...");
}
/// <summary>
/// Allows the user to get the id of a specific CrmEntity given a supplied entity name
/// and a supplied predicate which consists of a propertyName and a string propertyValue (eg "112001-S").
///
/// If there is a need for a predicate with different type of value (eg int / datetime / decimal)
/// then it could be introduced
///
/// Example URI : http://myWebServer/CrmOData/Crm.svc/GetId?entityName='myCustomer'&propertyName='customerNumber'&propertyValue='23456KL-P'
/// </summary>
/// <param name="entityName">eg "myCustomer"</param>
/// <param name="propertyName">eg "customerNumber"</param>
/// <param name="propertyValue">eg "23456KL-P"</param>
/// <returns></returns>
[WebGet]
public Guid? GetEntityId(string entityName, string propertyName, string propertyValue)
{
var entities = CurrentDataSource.GetEntities(entityName);
var entitiesWhere = entities.Where(x => (x.GetPropertyValue(propertyName) as string) == propertyValue);
var guid = entitiesWhere.Select(x => x.Id)
.SingleOrDefault();
return guid;
}
/// <summary>
/// Handle exceptions
/// </summary>
/// <param name="args"></param>
protected override void HandleException(HandleExceptionArgs args)
{
base.HandleException(args);
//Log all exceptions
_log.Error(string.Format("\r\nResponseContentType: {0}\r\nResponseStatusCode: {1}\r\nResponseWritten: {2}\r\nUser: {3}{4}",
args.ResponseContentType, args.ResponseStatusCode, args.ResponseWritten, HttpContext.Current.User.Identity.Name, args.Exception.GetExceptionDetails()),
args.Exception);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment