11

I am trying to consume Core Services through the Event System. The Event System is a Class library as expected.

I added the content nodes from Tridion.ContentManager.CoreService.Client.dll.config to app.config. No matter which endpoint I am try to use I always get an error such as the following (which happens when trying to initialize SessionAwareCoreServiceClient):

Could not find endpoint element with name "" and contract

I would like to note I am using Core Services through a console application without any issues. Are there any other ways to consume Core Services through the Event System (class library type) or is something else causing this error?
Well, as you advised I was started to use TOM.NET API for this purpose but can't figure out why code below doesn't work. Trying to change component metadata date fields...

    public class Events:TcmExtension
{
    public Events()
    {
        ImplementedEvents();
    }
    private void ImplementedEvents()
    {
        //Here should be subscription for used events
        EventSystem.Subscribe<Component, CheckInEventArgs>(OnComponentCheckedPre, EventPhases.Initiated);
        EventSystem.Subscribe<Component, PublishEventArgs>(ComponentPublished, EventPhases.Processed);
        EventSystem.Subscribe<StructureGroup, PublishEventArgs>(StructureGroupPublished, EventPhases.Processed);

    }
    /// <summary>
    /// Edit news metadata DateTime values and remove checked button for modify component metadata from content in case if editor checked in this box
    /// </summary>
    /// <param name="component"></param>
    /// <param name="arg"></param>
    /// <param name="phase"></param>
    public void OnComponentCheckedPre(Component component, CheckInEventArgs arg, EventPhases phase)
    {

        if (component.Schema.Title.ToLower().Equals("ks News Private") || component.Schema.Title.ToLower().Equals("ks News Business"))
        {
            EditNewsComponent(component);
        }
    }

    private void EditNewsComponent(Component component)
    {
        try
        {

            ItemFields componentContentItemFields = new ItemFields(component.Content, component.Schema);
            ItemFields componentMetadataItemFields = new ItemFields(component.Metadata, component.MetadataSchema);

            WriteEventLog("comp metadata xml elements before :" + componentMetadataItemFields.ToXml().InnerXml );

            if (componentMetadataItemFields.Contains("Created"))
            {
                DateField createdMetaField = (DateField)componentMetadataItemFields["Created"];
                if (createdMetaField.Value == DateTime.MinValue)
                {
                    createdMetaField.Value = component.RevisionDate;
                }
            }

            if(componentMetadataItemFields.Contains("Modified"))
            { 
                DateField modifiedMetaField = (DateField)componentMetadataItemFields["Modified"];
                if (modifiedMetaField.Value == DateTime.MinValue)
                {
                    modifiedMetaField.Value = DateTime.Now;
                }
            }

            if (componentContentItemFields.Contains("SettModified"))
            {
                DateField modifiedMetaField = (DateField)componentMetadataItemFields["Modified"];
                modifiedMetaField.Value = DateTime.Now;
            }

            if (componentMetadataItemFields.Contains("ValidTo"))
            {
                DateField validToMetaField = (DateField)componentMetadataItemFields["ValidTo"];
                if (validToMetaField.Value == DateTime.MinValue)
                {
                    validToMetaField.Value = DateTime.Now.AddYears(100);
                }
            }
            WriteEventLog("metadata item fields value after checking :" + componentMetadataItemFields.ToXml().InnerXml);

            ((TextField)componentContentItemFields["Ingress"]).Value = "ingress test event system";
            component.Content = componentContentItemFields.ToXml();               

           // component.Content.InnerXml = componentContentItemFields.ToXml().InnerXml;
            WriteEventLog("component content  after to xml: " + component.Content.InnerXml);

            component.Metadata= componentMetadataItemFields.ToXml();

            WriteEventLog("component metadata  xml: " + component.Metadata.InnerXml);
            //Changes visible in log files. Not in Tridion
        }
        catch (Exception ex)
        {
            WriteEventLog("Unable to edit FIKS component " + component.Id + ". Error:" + ex.Message);

        }
    }

I have seen the changes in logs but not in Tridion component. Other events triggered and worked as intended. Old event system uninstall as suggested. Network Service is only one impersonate user visible through SDL Tridion Management console application.

4 Answers 4

12

The Event System is one of two official places where you can (and should) use TOM.NET in Tridion 2011 / 2013 (the other place being C# Templating).

So you should not use Core Services here as they are intended for external applications only (such as the Console application you wrote).

1
  • 2
    Is this still true in 2013? As the GUI itself is now written against core service isn't it? Commented Apr 16, 2013 at 23:29
8

If all you want is to modify component metadata on save (as you mention in a comment) then using the CoreService is complete overkill.

Set your event to run on Component,Save,Initiated

EventSystem.Subscribe<Component,SaveEventArgs>(MyEventHandler, EventPhases.Initiated);

Then do something along these lines:

ItemFields metadata = new ItemFields(component.Metadata, component.Schema);
SingleLineTextField fieldIWantToChange = (SingleLineTextField) metadata["FieldName"];
fieldIWantToChange.Value = "My new value";
component.Metadata = metadata.ToXml();

(Not 100% sure of that code, just wrote it here directly).

However, I did find ONE use case where I had to use the CoreService from the Event System:

  • Performing an action for a user that is not authorized to do it :)

To do this I had to:

  1. Add Tridion.ContentManager.CoreService.Client.dll to the GAC
  2. Create a NetTCP Binding through code instead of using app.config:
var binding = new NetTcpBinding
    {
        MaxReceivedMessageSize = 2147483647,
        ReaderQuotas = new XmlDictionaryReaderQuotas
            {
                MaxStringContentLength = 2147483647,
                MaxArrayLength = 2147483647
            }
    };
var endpoint = new EndpointAddress(Configuration.NetTcpEndpointUrl);
var client = new SessionAwareCoreServiceClient(binding, endpoint);
client.ChannelFactory.Credentials.Windows.ClientCredential = 
    new NetworkCredential(Configuration.AdminUser, Configuration.AdminPassword);
6

Please mind: this applies to accessing ANY WCF (.svc) service from the Tridion Event System (including the core service). We have had requirements to access custom services from the Event system and that is why we have a solutions for this. One could, no should ask whether it is necessary to access the CoreService from the EventSystem, as other API's are available on the CMS server.

Your eventsystem dll can not find its own app.config ("someAssemblyName.dll.config"). .Net assemblies will look for config files in the original calling assembly location. This is the Tridion CMS core, NOT your event system dll.

How to configure it then?

  1. Put your Webservice info in the machine.config or Tridion.ContentManager.config. --> now the calling assembly has access to the config info, and therefore also the eventsystem assembly.
  2. In your eventSystem dll, explicitly point to a custom config file (with the web service settings), and then use a CustomChanelFactory.

We have successfully implemented option 2 recently. We started by reading this article: http://weblogs.asp.net/pabloretyk/archive/2010/01/17/override-wcf-client-settings-for-custom-config-file-using-duplexchannel.aspx

And then implemented our own CustomChanelFactory class:

using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;

namespace ADAMWSConnector
{
    /// <summary>
    /// Custom channel factory to use a non-standard config file
    /// </summary>
    /// <typeparam name="TChannel"></typeparam>
    public class CustomChannelFactory<TChannel> : ChannelFactory<TChannel>
    {
        /// <summary>
        /// Path to the used configuration file
        /// </summary>
        public string ConfigurationPath { get; private set; }

        public CustomChannelFactory(string configurationPath) : base(typeof(TChannel))
        {
            ConfigurationPath = configurationPath;
        }

        internal ServiceEndpoint CreateEndpoint()
        {
            return CreateDescription();
        }

        protected override ServiceEndpoint CreateDescription()
        {
            ServiceEndpoint serviceEndpoint = base.CreateDescription();
            if (ConfigurationPath == null || !File.Exists(ConfigurationPath))
                return base.CreateDescription();

            ExeConfigurationFileMap executionFileMap = new ExeConfigurationFileMap();
            executionFileMap.ExeConfigFilename = ConfigurationPath;
            System.Configuration.Configuration config = ConfigurationManager.OpenMappedExeConfiguration(executionFileMap, ConfigurationUserLevel.None);
            ServiceModelSectionGroup serviceModeGroup = ServiceModelSectionGroup.GetSectionGroup(config);
            ChannelEndpointElement selectedEndpoint = null;

            foreach (ChannelEndpointElement endpoint in serviceModeGroup.Client.Endpoints)
            {
                if (endpoint.Contract == serviceEndpoint.Contract.ConfigurationName)
                {
                    selectedEndpoint = endpoint;
                    break;
                }
            }
            if (selectedEndpoint != null)
            {
                if (serviceEndpoint.Binding == null)
                {
                    serviceEndpoint.Binding = CreateBinding(selectedEndpoint.Binding, serviceModeGroup);
                }

                if (serviceEndpoint.Address == null)
                {
                    serviceEndpoint.Address = new EndpointAddress(selectedEndpoint.Address, GetIdentity(selectedEndpoint.Identity), selectedEndpoint.Headers.Headers);
                }

                if (serviceEndpoint.Behaviors.Count == 0 && !String.IsNullOrEmpty(selectedEndpoint.BehaviorConfiguration))
                {
                    AddBehaviors(selectedEndpoint.BehaviorConfiguration, serviceEndpoint, serviceModeGroup);
                }

                serviceEndpoint.Name = selectedEndpoint.Contract;
            }

            return serviceEndpoint;
        }

        #region private

        /// <summary>        
        /// Configures the binding for the selected endpoint.
        /// </summary>        
        /// <param name="bindingName"></param>
        /// <param name="group"></param>
        /// <returns></returns>
        private Binding CreateBinding(string bindingName, ServiceModelSectionGroup group)
        {
            BindingCollectionElement bindingElementCollection = group.Bindings[bindingName];
            if (bindingElementCollection.ConfiguredBindings.Count > 0)
            {
                IBindingConfigurationElement be = bindingElementCollection.ConfiguredBindings[0];
                Binding binding = GetBinding(be);
                if (be != null)
                {
                    be.ApplyConfiguration(binding);
                }
                return binding;
            }
            return null;
        }

        /// <summary>        
        /// Adds the configured behavior to the selected endpoint        
        /// </summary>        
        /// <param name="behaviorConfiguration"></param>        
        /// <param name="serviceEndpoint"></param>        
        /// <param name="group"></param>        
        private void AddBehaviors(string behaviorConfiguration, ServiceEndpoint serviceEndpoint, ServiceModelSectionGroup group)
        {
            EndpointBehaviorElement behaviorElement = group.Behaviors.EndpointBehaviors[behaviorConfiguration];
            for (int i = 0; i < behaviorElement.Count; i++)
            {
                BehaviorExtensionElement behaviorExtension = behaviorElement[i];
                object extension = behaviorExtension.GetType().InvokeMember("CreateBehavior", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, behaviorExtension, null);

                if (extension != null)
                {
                    serviceEndpoint.Behaviors.Add((IEndpointBehavior)extension);
                }
            }
        }

        /// <summary>        
        /// Gets the endpoint identity from the configuration file        
        /// </summary>        
        /// <param name="element"></param>        
        /// <returns></returns>        
        private EndpointIdentity GetIdentity(IdentityElement element)
        {
            EndpointIdentity identity = null;
            PropertyInformationCollection properties = element.ElementInformation.Properties;
            if (properties["userPrincipalName"].ValueOrigin != PropertyValueOrigin.Default)
            {
                return EndpointIdentity.CreateUpnIdentity(element.UserPrincipalName.Value);
            }

            if (properties["servicePrincipalName"].ValueOrigin != PropertyValueOrigin.Default)
            {
                return EndpointIdentity.CreateSpnIdentity(element.ServicePrincipalName.Value);
            }
            if (properties["dns"].ValueOrigin != PropertyValueOrigin.Default)
            {
                return EndpointIdentity.CreateDnsIdentity(element.Dns.Value);
            }
            if (properties["rsa"].ValueOrigin != PropertyValueOrigin.Default)
            {
                return EndpointIdentity.CreateRsaIdentity(element.Rsa.Value);
            }
            if (properties["certificate"].ValueOrigin != PropertyValueOrigin.Default)
            {
                X509Certificate2Collection supportingCertificates = new X509Certificate2Collection();
                supportingCertificates.Import(Convert.FromBase64String(element.Certificate.EncodedValue));
                if (supportingCertificates.Count == 0)
                {
                    throw new InvalidOperationException("UnableToLoadCertificateIdentity");
                }
                X509Certificate2 primaryCertificate = supportingCertificates[0];
                supportingCertificates.RemoveAt(0);

                return EndpointIdentity.CreateX509CertificateIdentity(primaryCertificate, supportingCertificates);
            }

            return identity;
        }

        /// <summary>        
        /// Helper method to create the right binding depending on the configuration element        
        /// </summary>        
        /// <param name="configurationElement"></param>        
        /// <returns></returns>        
        private Binding GetBinding(IBindingConfigurationElement configurationElement)
        {
            if (configurationElement is CustomBindingElement)
                return new CustomBinding();
            else if (configurationElement is BasicHttpBindingElement)
                return new BasicHttpBinding();
            else if (configurationElement is NetMsmqBindingElement)
                return new NetMsmqBinding();
            else if (configurationElement is NetNamedPipeBindingElement)
                return new NetNamedPipeBinding();
            else if (configurationElement is NetPeerTcpBindingElement)
                return new NetPeerTcpBinding();
            else if (configurationElement is NetTcpBindingElement)
                return new NetTcpBinding();
            else if (configurationElement is WSDualHttpBindingElement)
                return new WSDualHttpBinding();
            else if (configurationElement is WSHttpBindingElement)
                return new WSHttpBinding();
            else if (configurationElement is WSFederationHttpBindingElement)
                return new WSFederationHttpBinding();
            return null;
        }

        #endregion
    }
}

The last step is to connect to the webservice using your custom config file:

    /// <summary>
    /// Uses the configuration to setup the connection.
    /// </summary>
    /// <returns></returns>
    private ConnectorServiceClient GetClient()
    {
        Trace.TraceInformation("GetClient()");

        ConnectorServiceClient client;
        if (String.IsNullOrEmpty(configPath))
        {
            client = new ConnectorServiceClient(bindingName);
        }
        else
        {
            CustomChannelFactory<IConnectorServiceChannel> x = new CustomChannelFactory<IConnectorServiceChannel>(configPath);
            ServiceEndpoint endpoint = x.CreateEndpoint();
            client = new ConnectorServiceClient(endpoint.Binding, endpoint.Address);
        }


        return client;
    }

(we used this to connect to a custom webservice from our EventSystem, this should work for the core service as well. The "configPath" var is the full path to the custom config file)

2

I'm pretty sure we're recommended against using the Core Service with an Event System.

3
  • Well, In order to modify component metadata fields. Commented Apr 16, 2013 at 13:54
  • 1
    You have the full TOM.NET API available in the event system, so all you want to do is possible, just follow the TOM.NET API guide. Commented Apr 16, 2013 at 14:16
  • 2
    You should refer to this link created by Bart - sdltridionworld.com/articles/sdltridion2011/tutorials/… which explains in which scenario to use which API. In event system, you are supposed to use TOM.NET API and not the Core Service. Commented Apr 16, 2013 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.