Tuesday, December 21, 2010

Logging using Enterprise Library - Change the log file Name and Path to the My Documents folder

When using Enterprise Library Logging Application Block as the logging tool, here is how to change the file path and the name of the trace file during run time. What I'm doing here is update the listener's "filename" (loggingConfiguration->listeners) in the App.config.


using System.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;

/// <summary>
/// Change the log file path and name.
/// </summary>
public void SetTraceLogPath()
{
// Log file path.
string logFilePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) +
@"\MyLogs\" + DateTime.Now.ToString("yyyy-MM-dd") + ".log";

ConfigurationFileMap objConfigPath = new ConfigurationFileMap();

// App config file path.
string appPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
objConfigPath.MachineConfigFilename = appPath;

Configuration entLibConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

LoggingSettings loggingSettings = (LoggingSettings)entLibConfig.GetSection(LoggingSettings.SectionName);

TraceListenerData traceListenerData = loggingSettings.TraceListeners.Get("FlatFile TraceListener");
FlatFileTraceListenerData objFlatFileTraceListenerData = traceListenerData as FlatFileTraceListenerData;

objFlatFileTraceListenerData.FileName = logFilePath;

entLibConfig.Save();
}

Then I can use following method to log exceptions to the specified path. In this case log file will be created in the My Documents folder of the current user.


///<summary>
/// Log exceptions.
///</summary>
public void LogException(Exception ex)
{
string strMessage = string.Empty;
strMessage += ex.Message + "\r\n";
strMessage += ex.StackTrace;

LogEntry le = new LogEntry();
le.Categories.Add(Constants.LoggingCategory.Exception.ToString());
le.Severity = TraceEventType.Error;
le.Message = strMessage;
le.Title = ex.Message;
le.Priority = 1;

Logger.Write(le);
}

This is how my app.Config file looks. There should be a
FlatFileTraceListener configured as follows.

  <configSections>
<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<loggingConfiguration name="Logging Application Block" tracingEnabled="true"
defaultCategory="General" logWarningsWhenNoCategoriesMatch="true">
<listeners>
<add fileName="trace.log" header="----------------------------------------"
footer="----------------------------------------" formatter=""
listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
traceOutputOptions="None" filter="All" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="FlatFile TraceListener" />
</listeners>
<formatters>
<add template="Timestamp: {timestamp}&#xD;&#xA;Message: {message}&#xD;&#xA;Category: {category}&#xD;&#xA;Priority: {priority}&#xD;&#xA;EventId: {eventid}&#xD;&#xA;Severity: {severity}&#xD;&#xA;Title:{title}&#xD;&#xA;Machine: {machine}&#xD;&#xA;Application Domain: {appDomain}&#xD;&#xA;Process Id: {processId}&#xD;&#xA;Process Name: {processName}&#xD;&#xA;Win32 Thread Id: {win32ThreadId}&#xD;&#xA;Thread Name: {threadName}&#xD;&#xA;Extended Properties: {dictionary({key} - {value}&#xD;&#xA;)}"
type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Text Formatter" />
</formatters>
<categorySources>
<add switchValue="All" name="General">
<listeners>
<add name="FlatFile TraceListener" />
</listeners>
</add>
</categorySources>
<specialSources>
<allEvents switchValue="All" name="All Events" />
<notProcessed switchValue="All" name="Unprocessed Category" />
<errors switchValue="All" name="Logging Errors &amp; Warnings">
<listeners>
<add name="FlatFile TraceListener" />
</listeners>
</errors>
</specialSources>
</loggingConfiguration>

Wednesday, December 1, 2010

SharePoint - CRM Integration: Create a SharePoint Document Library on CRM Opportunity Creation

In CRM 4.0 there is no way to maintain a set of related documents for Opportunities created within the CRM. Therefore it is a good idea to integrate CRM and SharePoint here, so that for each Opportunity creation in CRM a document library will get created in a specified SharePoint site automatically. We can achieve this task by registering a plugin to execute in Opportunity Create step.

Plugin implementation

Add a Web Reference to the Lists service of the SP site (or sub-site) located in "/_vti_bin/Lists.asmx"

Document library creation method could be like this.


///<summary>
/// Create a SP document library for a specified Opportunity.
///</summary>
private string CreateDocLibrary(string opportunityName, string spSiteUrl)
{
ListsService.Lists listService = new ListsService.Lists();
listService.Url = String.Concat(spSiteUrl, "/_vti_bin/Lists.asmx");
listService.Credentials = CredentialCache.DefaultCredentials;

XmlNode xnodeList = listService.AddList("Opportunity-" + opportunityName,
opportunityName, 101);

// Get document library URL name.
string rootFolder = xnodeList.Attributes.GetNamedItem("RootFolder").Value;
return rootFolder.Remove(0, rootFolder.LastIndexOf("/"));
}

Call document library creation method with proper parameters and maintain information about new document libraries in another custom SharePoint list.


public void Execute(IPluginExecutionContext context)
{
string opportunityName;
string stateCode;

if (context.MessageName.Equals("Create"))
{
opportunityName = ((DynamicEntity)context.PostEntityImages["Target"]).
Properties["name"].ToString();
stateCode = ((DynamicEntity)context.PostEntityImages["Target"]).
Properties["statecode"].ToString();
string docLibraryUrl = CreateDocLibrary(opportunityName,
"http://prasadmoss2010/sites/CRMTest");
AddConfigurationData(opportunityName, "http://prasadmoss2010/sites/CRMTest");
}
}

///<summary>
/// Update configuartion data custom list.
///</summary>
private void AddConfigurationData(string listName, string spSiteUrl)
{
ListsService.Lists listService = new ListsService.Lists();
listService.Url = String.Concat(spSiteUrl, "/_vti_bin/Lists.asmx");
listService.Credentials = CredentialCache.DefaultCredentials;

// Get Name attribute values (GUIDs) for list and view.
XmlNode node = listService.GetListAndView("CustomListName", "");
string listID = node.ChildNodes[0].Attributes["Name"].Value;
string viewID = node.ChildNodes[1].Attributes["Name"].Value;

// Add configuration entry - Construct a Batch element and its attributes.
XmlDocument doc = new XmlDocument();
XmlElement batch = doc.CreateElement("Batch");

batch.SetAttribute("OnError", "Continue");
batch.SetAttribute("ListVersion", "1");
batch.SetAttribute("ViewName", viewID);

// Specify update method for the batch post using CAML,
batch.InnerXml = "<Method ID='1' Cmd='New'>" +
"<Field Name='Title'>" + listName + "</Field>" +
"<Field Name='ProposalStatus'>Open</Field></Method>";

// Update SP list item using the list GUID.
try
{
XmlNode xnodeList = listService.UpdateListItems(listID, batch);
}
catch (Exception) {}
}

In addition, Opportunity status maintained in the custom SharePoint list can be updated when the opportunity is marked as a closed opportunity. For that purpose same plugin can be extended to execute on Win or Lose messages

(Ex: "context.MessageName.Equals("Lose")")

by registering plugin for the "Win" and "Lose" steps. In order to capture revert updates (Reopen opportunity), plugin should be registered for the "SetStateDynamicEntity" step as well.