I have the following .cs file;
using System;
using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;
using Sitecore.SecurityModel;
namespace LocationItemEventHandler
{
public class ItemEventHandler
{
private static readonly SynchronizedCollection<ID> MProcess = new SynchronizedCollection<ID>();
/// <summary>
/// This custom event auto-populates latitude/longitude co-ordinate when a location item is saved
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void OnItemSaved(object sender, EventArgs args)
{
var item = Event.ExtractParameter(args, 0) as Item;
if (item != null && !MProcess.Contains(item.ID))
{
if (item.TemplateID.Equals("{E490971E-758E-4A75-9C8D-67EC2C6321CA}"))
{
string errMessage = "";
string responseCode = "";
string address = item.Fields["Address"].Value;
if (1=1)
{
string latitude = "100";
string longitude = "200";
MProcess.Add(item.ID);
try
{
var latlngField = item.Fields["Google LatLng"];
using (new SecurityDisabler())
{
item.Editing.BeginEdit();
latlngField.SetValue(latitude + " - " + longitude, true);
Sitecore.Context.ClientPage.ClientResponse.Alert(
string.Format(
"Fields updated automatically\r\nLatitude: {0}\r\nLongitude: {1}",
latitude, longitude));
item.Editing.EndEdit();
}
}
catch (Exception exception)
{
Log.Error(exception.Message, this);
}
finally
{
MProcess.Remove(item.ID);
}
}
}
}
}
}
}
This is in a code file LocationItemEventHandler.cs in App_Code.
this is in the web.config
<event name="item:saved">
<handler type="LocationItemEventHandler.ItemEventHandler, LocationItemEventHandler" method="OnItemSaved"/>
</event>
When i try to save an item i get a "Could not resolve type name".
What am i missing?
The class name you have configured does not match the actual class name.
Edit:
If the handler is in App_Code, i believe you should leave off the assembly name in the type reference. Better yet, don't use App_Code.
Related
Is there anyway to connect a piece of code to its "using" statement using the Roslyn framework?
For example, given this piece of code:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Serilog;
using System.Collections.Generic;
namespace CodeAnalyzer.Service.CodeAnalysis.CSharp
{
public static class CSharpCodeAnalyser
{
/// <summary>
/// Retrieves all of the using statements from a code page
/// </summary>
/// <param name="codeDocument"></param>
/// <param name="logger"></param>
/// <returns></returns>
public static List<string> IdentifyUsings(string codeDocument, ILogger logger)
{
var usingList = new List<string>();
try
{
SyntaxTree tree = CSharpSyntaxTree.ParseText(codeDocument);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
foreach (var item in root.Usings)
{
usingList.Add(item.Name.ToString());
}
}
catch (System.Exception ex)
{
logger.Error(ex, ex.Message);
throw;
}
return usingList;
}
}
}
Could I know what assembly "CSharpSyntaxTree" belongs to?
So I figured out how to do this.
First I needed to build a workspace containing my entire solution. I did this through the Buildalyzer tool
public static Tuple<AdhocWorkspace, IEnumerable<Project>> GetWorkspace(string solutionFilePath, ILogger logger)
{
Tuple<AdhocWorkspace, IEnumerable<Project>> results;
var projectList = new List<Project>();
AdhocWorkspace workspace = new AdhocWorkspace();
try
{
AnalyzerManager manager = new AnalyzerManager(solutionFilePath);
foreach (var project in manager.Projects)
{
projectList.Add(project.Value.AddToWorkspace(workspace));
}
results = new Tuple<AdhocWorkspace, IEnumerable<Project>>(workspace, projectList);
}
catch (Exception ex)
{
logger.Error(ex, ex.Message);
throw;
}
return results;
}
Once I had a workspace all I needed to do was the following:
var workspace = GetWorkspace(solutionName, _logger);
foreach (var project in workspace.Item1.CurrentSolution.Projects)
{
foreach (var document in project.Documents)
{
_logger.Information("");
_logger.Information(project.Name + "\t\t\t" + document.Name);
var semanticModel = await document.GetSemanticModelAsync();
var root = await document.GetSyntaxRootAsync();
foreach (var item in root.DescendantNodes())
{
var typeInfo = semanticModel.GetTypeInfo(item);
if (typeInfo.Type != null)
{
_logger.Information(String.Format("Node: {0}", item.ToString()));
_logger.Information(String.Format("Type:{0}", typeInfo.Type.Name.ToString()));
_logger.Information(String.Format("ContainingAssembly:{0}", typeInfo.Type.ContainingAssembly));
_logger.Information(String.Format("ContainingNamespace:{0}", typeInfo.Type.ContainingNamespace));
}
}
}
}
I have the following DevExpress control in program (using WPF):
public class EditCustomListsModel : ViewModelBase
{
...
private string _bookIds;
public string BookIds
{
get => _bookIds;
set
{
_bookIds = value;
OnPropertyChanged(nameof(BookIds));
}
}
public EditCustomListsModel(IApplicationService appService, CustomList customerList = null)
{
this.appService = appService;
if (customerList != null)
{
_windowMode = WindowMode.Editing;
CustomList = CloneEntityHelper.CustomListClone(customerList);
BookIds = BookIdCollectionToString(); <--------- line related to element
}
else
{
_windowMode = WindowMode.Adding;
CustomList = new CustomList();
}
}
/// <summary>
/// Метод преобразования коллекции BookId в строку
/// </summary>
/// <returns></returns>
private string BookIdCollectionToString() =>
CustomList?.CustomListDetails?.Any() == true
? string.Join(Environment.NewLine, CustomList.CustomListDetails.Select(p => p.BookId))
: string.Empty;
}
How can I customize a data control so that the following regular expression is used when entering (editing) data:
^[1-9]\d{0,8}(?:\r\n[1-9]\d{0,8})*$
That is, only strings should be displayed in the control, each of which contains an integer and CR + LF character (s), the last (s) may not be.
If write like this:
<dxe:TextEdit Grid.Row="1" Grid.RowSpan="2" Grid.Column="0" TextWrapping="Wrap" AcceptsReturn="True"
VerticalContentAlignment="Top" VerticalScrollBarVisibility="Auto"
EditValue="{Binding BookIds, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MaskShowPlaceHolders="False" Mask="[1-9]\d{0,8}(?:\r\n[1-9]\d{0,8})*" MaskType="RegEx" MaskAutoComplete="None"/>
That in the markup MaskType="RegEx" is displayed by a wavy line and the syntax error is written.
I want to hook the item:renamed event to do some processing. It may take a few minutes though. Are event handlers executed asynchronously or synchronously with normal pipeline execution? Is there a standard Sitecore way to kick this off asynchronously if I need to do that myself?
The only time this handler needs to execute is when an item is renamed in Content Editor.
Sitecore events are executed synchronously. There is a Sitecore Development Toolkit module on Sitecore Marketplace which contains a code for firing events asynchronously which you can easily reuse in your solution Sitecore Development Toolkit.
Here is a part of their code which fires methods asynchronously when the event is fired:
public void OnItemRenamed(object sender, EventArgs args)
{
if (args != null)
{
var item = Event.ExtractParameter(args, 0) as Item;
Assert.IsNotNull(item, "No item in parameters");
var name = Event.ExtractParameter(args, 1) as string;
Assert.IsNotNullOrEmpty(name, "No name in parameters");
DoAsync(() => OnItemRenameAsync(item, name));
}
}
private void OnItemRenameAsync(Item item, string name)
{
var itemRef = new ItemReference(item.Parent);
var itemRefText = itemRef.ToString();
// do some work here
}
Sitecore events are synchronous. You can kick off your long running task as a job. First create a class to handle the event:
namespace MyNamespace
{
public class MyClass
{
public void ItemRenamed (object sender, EventArgs args)
{
Run("LongRenameTask");
}
protected void Run(string methodName, EventArgs args)
{
var item = Event.ExtractParameter(args, 0) as Item;
var name = Event.ExtractParameter(args, 1) as string;
RunJob(methodName, item, name);
}
protected Handle RunJob(string methodName, Item item, string name)
{
var options = new JobOptions(
"Preparing rename job '{0}' for '{1}'".FormatWith(
methodName,
item.ID.ToString()),
"item:renamed",
"shell",
new ItemRenamedManager(item, name),
methodName)
{
WriteToLog = true,
AtomicExecution = true,
};
var job = new Job(options);
JobManager.Start(job);
return job.Handle;
}
}
}
Then create a class to do your work (this will be called on a background thread by Sitecore):
namespace MyNamespace
{
public class ItemRenamedManager
{
protected Item RenamedItem { get; set; }
protected string Name { get; set; }
public ItemRenamedManager(Item item, string name)
{
RenamedItem = item;
Name = name;
}
public void LongRenameTask()
{
// Do your long running task here.
// The property 'RenamedItem' will give you the item
}
}
}
Then patch your event handler in:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<events>
<event name="item:renamed">
<handler type="MyNamespace.MyClass" method="ItemRenamed" />
</event>
</events>
</sitecore>
</configuration>
The above code is cribbed a bit from memory and needs some error handling, but should be pretty close, but this way, your long running task won't block the Content Editor UI.
I try to have my WCF services always throw detailed faults, even when not throwing them explicitly. To achieve it, I implemented:
an ErrorHandler, whose IErrorHandler.ProvideFault wraps the non-fault error as FaultException
a ServiceBehavior extension, attaching this handler AND adding to each operation a fault description of this FaultException, so the client might catch it as such.
I've decorated my service with the error handler attribute (originally I had two distinct implementations of IServiceBehavior, for the ErrorHandler and for the Operation.Faults).
I also made sure the data set into the new FaultDescription is identical to the one I inspected when defining the FaultContract on my contract.
No matter what I try, when using the FaultContract as attribute on my contract, the fault is being properly caught by the client, but when having it attached at runtime through the ApplyDispatchBehavior, only a general FaultException is being caught. Apparently, everything else (error wrapping and throwing) is working, only the addition of a FaultContract to each action at runtime fails.
Please help...
here's the code:
ErrorHandling.cs
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using Shared.Contracts.Faults;
namespace Server.WcfExtensions
{
public class MyErrorHandler : IErrorHandler
{
#region IErrorHandler Members
public bool HandleError(Exception error)
{
return false;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
if (error is FaultException) return;
if (!error.GetType().IsSerializable) return;
FaultException<GeneralServerFault> faultExc = new FaultException<GeneralServerFault>(new GeneralServerFault(error), new FaultReason("Server Level Error"));
MessageFault messageFault = faultExc.CreateMessageFault();
fault = Message.CreateMessage(version, messageFault, faultExc.Action);
}
#endregion
}
class ErrorHandler : Attribute, IServiceBehavior
{
Type M_ErrorHandlerType;
public Type ErrorHandlerType
{
get { return M_ErrorHandlerType; }
set { M_ErrorHandlerType = value; }
}
#region IServiceBehavior Members
public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
IErrorHandler errorHandler;
try
{
errorHandler = (IErrorHandler)Activator.CreateInstance(ErrorHandlerType);
}
catch (MissingMethodException e)
{
throw new ArgumentException("Must have a public empty constructor.", e);
}
catch (InvalidCastException e)
{
throw new ArgumentException("Must implement IErrorHandler.", e);
}
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
channelDispatcher.ErrorHandlers.Add(errorHandler);
}
foreach (ServiceEndpoint ep in serviceDescription.Endpoints)
{
foreach (OperationDescription opDesc in ep.Contract.Operations)
{
Type t = typeof(GeneralServerFault);
string name = t.Name;
FaultDescription faultDescription = new FaultDescription(ep.Contract.Namespace + "/" + ep.Contract.Name + "/" + opDesc.Name + name + "Fault");
faultDescription.Name = name + "Fault";
faultDescription.Namespace = ep.Contract.Namespace;
faultDescription.DetailType = t;
opDesc.Faults.Add(faultDescription);
}
}
}
public void Validate(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
{
}
#endregion
}
}
GeneralServerFault.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Shared.Contracts.Faults
{
[DataContract] //[Serializable]
public class GeneralServerFault
{
[DataMember]
public SerializableException Wrapped
{
get;
private set;
}
public GeneralServerFault()
: base()
{
Wrapped = new SerializableException();
}
public GeneralServerFault(Exception toWrap)
: base()
{
Wrapped = new SerializableException(toWrap);
}
}
[Serializable]
public class SerializableException
{
public string Type { get; set; }
public DateTime TimeStamp { get; set; }
public string Message { get; set; }
public string StackTrace { get; set; }
public SerializableException()
{
this.TimeStamp = DateTime.Now;
}
public SerializableException(string Message)
: this()
{
this.Message = Message;
}
public SerializableException(System.Exception ex)
: this(ex.Message)
{
if (ex == null) return;
Type = ex.GetType().ToString();
this.StackTrace = ex.StackTrace;
}
public override string ToString()
{
return this.Type + " " + this.Message + this.StackTrace;
}
}
}
IContractService.cs
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.ServiceModel;
using Shared.Contracts.Faults;
namespace Shared
{
internal static class Namespaces
{
internal static class Contracts
{
public const string ServiceContracts = "http://mycompany/services";
}
}
[ServiceContract(Namespace = Namespaces.Contracts.ServiceContracts, SessionMode = SessionMode.Required)]
public interface IContactServices
{
[OperationContract]
[FaultContract(typeof(DataNotFoundFault))]
//[FaultContract(typeof(GeneralServerFault))]
void DoSomething();
}
}
ContractService.cs
using System;
using System.Collections.Generic;
using System.ServiceModel;
using Shared;
using Shared.Contracts.Faults;
using Server.WcfExtensions;
namespace Server.Services
{
[ErrorHandler(ErrorHandlerType = typeof(MyErrorHandler))]
public class ContactSevices : IContactServices
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public void DoSomething()
{
throw new InvalidCastException("bla");
}
}
}
I omitted the code of client and host
Sitecore tracks the item changes with last updated by, Created by information.
Is it possible to track changes made to "fields" in an item against the person who changed them? And retrive the list of changes made to fields of an item.
You can create a custom handler and add it to item:saving event in Sitecore events/event configuration:
<sitecore>
<events>
<event name="item:saving">
<handler type="My.Assembly.Namespace.CreateHistoryEntryHandler, My.Assembly" method="OnItemSaving" />
</event>
</events>
</sitecore>
The class below saves the information to the Workflow History Store so you can see it using History menu from ribbon (see screenshot), but you can save it to any other place
namespace My.Assembly.Namespace
{
public class CreateHistoryEntryHandler
{
protected void OnItemSaving(object sender, EventArgs args)
{
Item newItem = Event.ExtractParameter(args, 0) as Item;
if (newItem == null || newItem.Database.DataManager.GetWorkflowInfo(newItem) == null)
{
return;
}
Item originalItem = newItem.Database.GetItem(newItem.ID, newItem.Language, newItem.Version);
newItem.Fields.ReadAll();
IEnumerable<string> fieldNames = newItem.Fields.Select(f => f.Name);
IEnumerable<string> differences = fieldNames.Where(fieldName => newItem[fieldName] != originalItem[fieldName]).ToList();
if (differences.Any())
{
string message = String.Format("Item content changed [{0}]", differences.Aggregate((s1, s2) => s1 + ", " + s2));
AddHistoryEntry(newItem, message);
}
}
public static void AddHistoryEntry(Item item, string text)
{
WorkflowProvider workflowProvider = (item.Database.WorkflowProvider as WorkflowProvider);
if (workflowProvider != null && workflowProvider.HistoryStore != null)
{
string workflowState = GetWorkflowState(item);
workflowProvider.HistoryStore.AddHistory(item, workflowState, workflowState, text);
}
}
private static string GetWorkflowState(Item item)
{
WorkflowInfo info = item.Database.DataManager.GetWorkflowInfo(item);
return (info != null) ? info.StateID : String.Empty;
}
}
}