ttajic
10/11/2016 - 7:51 AM

CrmService

CrmService

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.ServiceModel.Description;
using System.Threading;
using System.Web;
using Adacta.Porsche.CallCenter.Web.Helpers;
using Adacta.Porsche.CallCenter.Web.Services.Interfaces;
using Adacta.Porsche.CallCenter.Web.Config;
using AutoMapper;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;

namespace Adacta.Porsche.CallCenter.Web.Services
{
    public interface ICrmService : IDisposable
    {
        Guid CallerId { get; set; }
        Guid? CurrentUserId { get; set; }

        // Gets (retrieve one by Id)
        Entity Get(string entityLogicalName, Guid entityId, ColumnSet columns);
        T Get<T>(Guid entityId) where T : Entity;

        // Retrieves (one or many with filters)
        Entity Single(string entityLogicalName, string attribute, object value, ColumnSet columns);
        T Single<T>(string attribute, object value) where T : Entity;

        IList<Entity> Retrieve(string entityLogicalName, string attribute, object value, ColumnSet columns);
        IList<T> Retrieve<T>(string attribute, object value) where T : Entity;
        IList<Entity> Retrieve(string entityLogicalName, FilterExpression filter, ColumnSet columns);
        IList<T> Retrieve<T>(FilterExpression filter) where T : Entity;

        IList<Entity> Retrieve(QueryBase query);
        IList<T> Retrieve<T>(QueryBase query) where T : Entity;

        // Retrieve all
        IList<Entity> Retrieve(string entityLogicalName, ColumnSet columns);
        IList<T> Retrieve<T>() where T : Entity;

        // FetchXML
        IList<Entity> Fetch(string fetchXml);
        IList<T> Fetch<T>(string fetchXml) where T : Entity;
        IList<Entity> Fetch(string fetchXml, out int totalRecordCount);
        IList<T> Fetch<T>(string fetchXml, out int totalRecordCount);

        // Creates
        Guid Create(Entity entity);
        Guid Create<T>(T entity) where T : Entity;

        // Updates
        void Update(Entity entity);
        void Update<T>(T entity) where T : Entity;

        // Deletes
        void Delete(string entityLogicalName, Guid entityId);
        void Delete<T>(T entity) where T : Entity;

        // Execute
        OrganizationResponse Execute(OrganizationRequest request);

        // Misc
        string RetrieveOptionSetLabel(string entityName, string attribute, int value);
        void ChangeState(string entityName, Guid entityId, int stateCode, int statusCode);
        void ChangeState(Entity entity, int stateCode, int statusCode);
    }

    public sealed class CrmServiceConfiguration
    {
        public Uri ServerUrl { get; set; }
        public string OrganizationName { get; set; }
        public string Domain { get; set; }
        public string Username { get; set; }
        public string Password { get; set; }
        public bool ImpersonateUsingWif { get; set; }
    }
    
    public class CrmService : ICrmService
    {
        #region Private Attributes and Properties

        private readonly OrganizationServiceProxy _orgService;

        public Guid CallerId
        {
            get { return _orgService.CallerId; }
            set { _orgService.CallerId = value; }
        }

        public Guid? CurrentUserId { get; set; }

        #endregion

        #region Constructor

        public CrmService(CrmServiceConfiguration config)
        {
            var organizationUri = new Uri(config.ServerUrl, string.Format("{0}/XRMServices/2011/Organization.svc", config.OrganizationName));
            var credentials = CreateCredentials(config);
            
            _orgService = new ManagedTokenOrganizationServiceProxy(organizationUri, credentials);
            _orgService.EnableProxyTypes();

            // Check if we need to impersonate
            if (config.ImpersonateUsingWif)
            {
                CallerId = GetCallerId();
                _orgService.CallerId = CallerId;
            }

            CurrentUserId = GetCurrentUserId();
        }

        private Guid? GetCurrentUserId()
        {
            var response = _orgService.Execute(new WhoAmIRequest());
            if (response == null || response.Results.Count == 0)
                return null;

            return (Guid) response.Results["UserId"];
        }

        #region Credentials

        private ClientCredentials CreateCredentials(CrmServiceConfiguration config)
        {
            var credentials = new ClientCredentials();

            if (string.IsNullOrWhiteSpace(config.Username)) {
                credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
                return credentials;
            }

            if (!string.IsNullOrWhiteSpace(config.Domain)) {
                credentials.Windows.ClientCredential = new NetworkCredential(config.Username, config.Password, config.Domain);
            }

            else {
                credentials.UserName.UserName = config.Username;
                credentials.UserName.Password = config.Password;
            }

            return credentials;
        }

        #endregion

        #endregion

        #region WIF Impersonation

        private Guid GetCallerId()
        {
            var columnSet = new ColumnSet("domainname", "fullname");

            var filter = new FilterExpression();

            // WIF passes through our domain name
            filter.AddCondition("domainname", ConditionOperator.Equal, Thread.CurrentPrincipal.Identity.Name);

            var response = Retrieve("systemuser", filter, columnSet);

            // Take the first user that fits - usualy there will be only one
            if (response.Count > 0) {
                return response[0].Id;
            }

            HttpContext.Current.Response.StatusCode = 403;
            HttpContext.Current.Response.End();

            return Guid.Empty;
        }

        #endregion

        #region Helpers

        private ColumnSet GenerateColumnSet<T>() where T : Entity
        {
            var columns = new ColumnSet(false);

            // Iterate through all properties and check for attributes
            var entityProperties = typeof(T).GetProperties();
            foreach (var property in entityProperties)
            {
                var attribute = property.GetFirstOrDefaultCustomAttribute<AttributeLogicalNameAttribute>();

                if (attribute != null) {
                    columns.AddColumn(attribute.LogicalName);
                }
            }

            return columns;
        }

        private FilterExpression CreateSimpleFilter(string attribute, object value)
        {
            var filter = new FilterExpression(LogicalOperator.And);
            filter.AddCondition(attribute, ConditionOperator.Equal, value);

            return filter;
        }

        #endregion

        #region Gets

        public Entity Get(string entityLogicalName, Guid entityId, ColumnSet columns)
        {
            return _orgService.Retrieve(entityLogicalName, entityId, columns);
        }

        public T Get<T>(Guid entityId) where T : Entity
        {
            //var entityInstance = Activator.CreateInstance<T>();
            var entityName = typeof(T).GetLogicalName();
            var columnSet = GenerateColumnSet<T>();

            var baseEntity = _orgService.Retrieve(entityName, entityId, columnSet);

            Mapper.CreateMap<Entity, T>();
            return Mapper.Map<T>(baseEntity);
        }

        #endregion

        #region Retrieves

        #region Singles

        public Entity Single(string entityLogicalName, string attribute, object value, ColumnSet columns)
        {
            var filter = CreateSimpleFilter(attribute, value);
            var results = Retrieve(entityLogicalName, filter, columns);

            return SingleInternal(results);
        }

        public T Single<T>(string attribute, object value) where T : Entity
        {
            var filter = CreateSimpleFilter(attribute, value);
            var results = Retrieve<T>(filter);

            return SingleInternal(results);
        }

        private T SingleInternal<T>(IList<T> results) where T : Entity
        {
            var resultCount = results.Count;

            // No result
            if (resultCount == 0) {
                return null;
            }

            // More than one
            if (resultCount > 1) {
                throw new InvalidOperationException("Current filter returned more than one record, consider using Retrieve instead.");
            }

            return results[0];
        }

        #endregion

        public IList<Entity> Retrieve(string entityLogicalName, string attribute, object value, ColumnSet columns)
        {
            var filter = CreateSimpleFilter(attribute, value);
            return Retrieve(entityLogicalName, filter, columns);
        }

        public IList<T> Retrieve<T>(string attribute, object value) where T : Entity
        {
            var filter = CreateSimpleFilter(attribute, value);
            return Retrieve<T>(filter);
        }

        public IList<Entity> Retrieve(string entityLogicalName, FilterExpression filter, ColumnSet columns)
        {
            var query = new QueryExpression(entityLogicalName) {
                Criteria = filter, 
                ColumnSet = columns
            };

            return Retrieve(query);
        }

        public IList<T> Retrieve<T>(FilterExpression filter) where T : Entity
        {
            var query = new QueryExpression(typeof(T).GetLogicalName()) 
            {
                ColumnSet = GenerateColumnSet<T>(),
                Criteria = filter
            };

            return Retrieve<T>(query);
        }

        public IList<Entity> Retrieve(QueryBase query)
        {
            var results = _orgService.RetrieveMultiple(query);
            return results.Entities.ToList();
        }

        public IList<T> Retrieve<T>(QueryBase query) where T : Entity
        {
            var results = _orgService.RetrieveMultiple(query);

            Mapper.CreateMap<Entity, T>();
            return Mapper.Map<IList<T>>(results.Entities);
        }

        public IList<Entity> Retrieve(string entityLogicalName, ColumnSet columns)
        {
            return Retrieve(entityLogicalName, null, columns);
        }

        public IList<T> Retrieve<T>() where T : Entity
        {
            return Retrieve<T>((FilterExpression) null);
        }

        #endregion

        #region Fetch

        public IList<Entity> Fetch(string fetchXml)
        {
            int totalCount;
            return Fetch(fetchXml, out totalCount);
        }

        public IList<T> Fetch<T>(string fetchXml) where T : Entity
        {
            int totalCount;
            return Fetch<T>(fetchXml, out totalCount);
        }

        public IList<Entity> Fetch(string fetchXml, out int totalRecordCount)
        {
            var query = new FetchExpression(fetchXml);
            var results = _orgService.RetrieveMultiple(query);

            totalRecordCount = results.TotalRecordCount;

            return results.Entities.ToList();
        }

        public IList<T> Fetch<T>(string fetchXml, out int totalRecordCount)
        {
            var query = new FetchExpression(fetchXml);
            var results = _orgService.RetrieveMultiple(query);

            totalRecordCount = results.TotalRecordCount;

            Mapper.CreateMap<Entity, T>();
            return Mapper.Map<IList<T>>(results.Entities);
        }

        #endregion

        #region Creates

        public Guid Create(Entity entity)
        {
            return Create<Entity>(entity);
        }

        public Guid Create<T>(T entity) where T : Entity
        {
            if (string.IsNullOrEmpty(entity.LogicalName))
                entity.LogicalName = typeof(T).GetLogicalName();

            return _orgService.Create(entity);
        }

        #endregion

        #region Updates

        public void Update(Entity entity)
        {
            Update<Entity>(entity);
        }

        public void Update<T>(T entity) where T : Entity
        {
            if (string.IsNullOrEmpty(entity.LogicalName))
                entity.LogicalName = typeof(T).GetLogicalName();

            _orgService.Update(entity);
        }

        #endregion

        #region Deletes

        public void Delete(string entityLogicalName, Guid entityId)
        {
            _orgService.Delete(entityLogicalName, entityId);
        }

        public void Delete<T>(T entity) where T : Entity
        {
            Delete(entity.LogicalName, entity.Id);
        }

        #endregion

        #region Misc

        public OrganizationResponse Execute(OrganizationRequest request)
        {
            return _orgService.Execute(request);
        }

        public string RetrieveOptionSetLabel(string entityName, string attribute, int value)
        {
            var attributeRequest = new RetrieveAttributeRequest {
                EntityLogicalName = entityName,
                LogicalName = attribute,
                RetrieveAsIfPublished = true
            };

            try
            {
                var response = _orgService.Execute(attributeRequest) as RetrieveAttributeResponse;
                if (response == null) {
                    return null;
                }

                // PicklistAttributeMetadata in StateAttributeMetadata
                var metadata = response.AttributeMetadata as EnumAttributeMetadata;
                if (metadata == null) {
                    return null;
                }

                var option = metadata.OptionSet.Options.SingleOrDefault(o => o.Value == value);
                return option != null ? option.Label.UserLocalizedLabel.Label : null;
            }

            catch (Exception) {
                return null;
            }
        }

        public void ChangeState(string entityName, Guid entityId, int stateCode, int statusCode)
        {
            var changeRequest = new SetStateRequest {
                EntityMoniker = new EntityReference(entityName, entityId),
                State = new OptionSetValue(stateCode),
                Status = new OptionSetValue(statusCode)
            };

            _orgService.Execute(changeRequest);
        }

        public void ChangeState(Entity entity, int stateCode, int statusCode)
        {
            ChangeState(entity.LogicalName, entity.Id, stateCode, statusCode);
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            _orgService.Dispose();
        }

        #endregion
    }
}