I use Sitecore 7 and in the code I see this line
public static ID HelpLinks
{
get { return GetIdFromConfig("aer.ProductDetails.HelpLinks"); }
}
Developer define this line with this function
static ID GetIdFromConfig(string key)
{
try
{
return new ID(Sitecore.Configuration.Settings.GetSetting(key));
}
catch (Exception ex)
{
Sitecore.Diagnostics.Log.Warn(String.Format("GetIdFromConfig (key='{0}'): not found ", key), ex, "aed.Classes.ConfigID");
return null;
}
}
I wonder how it define the
aer.ProductDetails.HelpLinks
In order to get Sitecore unique ID and use it in other templates.
is any one know how it define?
Somewhere in settings section in configs (either web.config or some include file from Include folder for your solution) you need to define that key with that name.
Should have something like this:
<sitecore>
<settings>
<setting name="aer.ProductDetails.HelpLinks" value="sitecoreID" />
</settings>
</sitecore>
where sitecoreID is an Sitecore ID format like {DE3A698F-1D7F-4C43-B797-162C5811E270}
Related
Sitecore comes with several standard custom value tokens when creating branch templates (i.e. $name for the name of the new item, $parentid, for the id of the parent).
Is there anyway to add new variables?
Specifically I want a variable that will allow me to access the items path when added?
There is a sitecore blog post for this ADD CUSTOM STANDARD VALUES TOKENS IN THE SITECORE ASP.NET CMS but TBH, it's wrong . I'm not sure why sitecore insist on producing "untested prototype(s)" all the time in these posts. The guy in that blog literally says You can implement a solution based on the following untested prototype o_O
For some reason sitecore are jumping though various hoops to decompile the source and then recreate it (give a man a hammer and everything looks like a nail maybe?). This makes your code very fragile should the default behaviour change and is just totally unnecessary.
You can add a new variable in a few lines of code:
public class NewVariablesReplacer : MasterVariablesReplacer
{
public override string Replace(string text, Item targetItem)
{
//still need to assert these here
Sitecore.Diagnostics.Assert.ArgumentNotNull(text, "text");
Sitecore.Diagnostics.Assert.ArgumentNotNull(targetItem, "targetItem");
string tempTxt = text;
if (text.Contains("$path"))
{
Sitecore.Diagnostics.Assert.ArgumentNotNull(targetItem.Paths, "targetItem.Paths");
Sitecore.Diagnostics.Assert.ArgumentNotNull(targetItem.Paths.FullPath, "targetItem.Paths.FullPath");
tempTxt = text.Replace("$path", targetItem.Paths.FullPath);
}
//Do what you would normally do.
return base.Replace(tempTxt, targetItem);
}
}
This works without decompiling because it retains the base functionality by calling base.Replace(text, targetItem);.
You then need to alter the default behaviour in the xml as in the blog post:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<settings>
<setting name="MasterVariablesReplacer">
<patch:attribute name="value">Sitecore.Sharedsource.Data.NewVariablesReplacer ,Sitecore.Sharedsource</patch:attribute>
</setting>
</settings>
</sitecore>
</configuration>
Out of the box, we have these variables at our disposal:
$name: The item name
$id: The item ID
$parentid: The item ID of the parent item
$parentname: The item name of the parent item
$date: The system date
$time: The system time
$now: The combination of system date and time
Tokens are variables that start with the “$” symbol. When a content item is created in the content tree, the pipeline that gets invoked is:
<expandInitialFieldValue help="Processors should derive from Sitecore.Pipelines.ExpandInitialFieldValue.ExpandInitialFieldValueProcessor">
<processor type="Sitecore.Pipelines.ExpandInitialFieldValue.SkipStandardValueItems, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.ExpandInitialFieldValue.CheckSharedField, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.ExpandInitialFieldValue.ReplaceVariables, Sitecore.Kernel" /></expandInitialFieldValue>
The pipeline that does all the work is:
public override void Process(ExpandInitialFieldValueArgs args)
{
Assert.ArgumentNotNull((object) args, "args");
MasterVariablesReplacer variablesReplacer = Factory.GetMasterVariablesReplacer();
string text = args.SourceField.Value;
if (variablesReplacer == null)
args.Result = text;
else
args.Result = variablesReplacer.Replace(text, args.TargetItem);
}
In the ReplaceVariables processor, you will see that there is a call to another class that does all the work. This class is defined in the section of web.config.
<setting name="MasterVariablesReplacer" value="Sitecore.Data.MasterVariablesReplacer,Sitecore.Kernel.dll" />
Decompile this class and you will see that the execution order is Replace > ReplaceValues > ReplaceWithDefault with Replace being a virtual method while the others are not. Fortunately for us, this means we can easily override the combined logic with a custom subclass of our own.
<!--<setting name="MasterVariablesReplacer" value="Sitecore.Data.MasterVariablesReplacer,Sitecore.Kernel.dll" />-->
<setting name="MasterVariablesReplacer" value="Client.SitecoreUtil.SettingsOverrides.MasterVariablesReplacer,Client.Sitecore" />
In our custom class, we have to override the Replace method with the same or similar code. Then we need two local private versions of ReplaceValues and ReplaceWithDefault. We can use the same or similar code for ReplaceWithDefault but the ReplaceValues method is where you would define your custom tokens and also tell Sitecore what to do with it. For example, let’s say you want to replace the custom “$test” token with the string “hello” this would be the resulting code.
private string ReplaceValues(string text, Func<string> defaultName, Func<string> defaultId, Func<string> defaultParentName, Func<string> defaultParentId)
{
if (text.Length == 0 || text.IndexOf('$') < 0)
return text;
ReplacerContext context = this.GetContext();
if (context != null)
{
foreach (KeyValuePair<string, string> keyValuePair in (SafeDictionary<string, string>)context.Values)
text = text.Replace(keyValuePair.Key, keyValuePair.Value);
}
text = this.ReplaceWithDefault(text, "$name", defaultName, context);
text = this.ReplaceWithDefault(text, "$id", defaultId, context);
text = this.ReplaceWithDefault(text, "$parentid", defaultParentId, context);
text = this.ReplaceWithDefault(text, "$parentname", defaultParentName, context);
text = this.ReplaceWithDefault(text, "$date", (Func<string>)(() => DateUtil.IsoNowDate), context);
text = this.ReplaceWithDefault(text, "$time", (Func<string>)(() => DateUtil.IsoNowTime), context);
text = this.ReplaceWithDefault(text, "$now", (Func<string>)(() => DateUtil.IsoNow), context);
text = this.ReplaceWithDefault(text, "$test", (Func<string>)(() => "hello"), context);
return text;
}
That is all there is to define custom token variables for Sitecore standard values. All the work is done in the ReplaceValues method.
I've added a ComputedIndexFields.config files with the following code:
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<indexConfigurations>
<defaultIndexConfiguration>
<fields hint="raw:AddComputedIndexField">
<field fieldName="AppliedThemes" storageType="yes" indexType="TOKENIZED">be.extensions.AppliedThemes, be.extensions</field>
</fields>
</defaultIndexConfiguration>
</configuration>
</contentSearch>
</sitecore>
</configuration>
I also added a class in said assemlby:
namespace be.extensions
{
class AppliedThemes : IComputedIndexField
{
public string FieldName { get; set; }
public string ReturnType { get; set; }
public object ComputeFieldValue(IIndexable indexable)
{
Item item = indexable as SitecoreIndexableItem;
if (item == null)
return null;
var themes = item["Themes"];
if (themes == null)
return null;
// TODO
}
}
}
I wanted to test the little bit of code i had already written. So i added a breakpoint at the first line of the "ComputeFieldValue(IIndexable indexable)" method and fired up the website ( while debugging ).
I changed several items, saved them en then rebuild the index tree but my breakpoint is never hit.
The class is located in a different project and build into a .dll with the assemblyname "be.extensions"
I'm using sitecore 8 update 2.
Does anyone know what i did wrong or why this code wouldn't be reached ?
( Like is this code send to some Lucene workflow that i simply can not debug )
Your configuration is likely not being patched in due to a change Sitecore made to the structure of the Include file. Namely, the defaultIndexConfiguration node was changed to defaultLuceneIndexConfiguration along with a new type attribute. You can verify that your computed field is being patched correctly using the /sitecore/admin/showconfig.aspx utility page. Also, please note that the storageType and indextype for each computed index field is now defined in the <fieldMap><fieldNames> section, not where you have it now.
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
<contentSearch>
<indexConfigurations>
<defaultLuceneIndexConfiguration type="Sitecore.ContentSearch.LuceneProvider.LuceneIndexConfiguration, Sitecore.ContentSearch.LuceneProvider">
<fields hint="raw:AddComputedIndexField">
<field fieldName="yourname">be.extensions.AppliedThemes, be.extensions</field>
</fields>
</defaultLuceneIndexConfiguration>
</indexConfigurations>
</contentSearch>
</sitecore>
</configuration>
I followed the steps here to create a new custom rule and add it to the ruleset in VSStudio 2013:
http://blog.tatham.oddie.com.au/2010/01/06/custom-code-analysis-rules-in-vs2010-and-how-to-make-them-run-in-fxcop-and-vs2008-too/
However, despite all my efforts, the custom rule does not show up in the ruleset file.
If I add the rule in the FXCop Editor, it shows up and analyzes the target project correctly.
This is the Rule File, which is an embedded resource in the project:
<?xml version="1.0" encoding="utf-8" ?>
<Rules FriendlyName="PSI Custom FxCop Rules">
<Rule TypeName="EnforceHungarianNotation" Category="PSIRules" CheckId="CR0001">
<Name>Enforce Hungarian Notation</Name>
<Description>Checks fields for compliance with Hungarian notation.</Description>
<Resolution>Field {0} is not in Hungarian notation. Field name should be prefixed with '{1}'.</Resolution>
<MessageLevel Certainty="100">Error</MessageLevel>
<FixCategories>Breaking</FixCategories>
<Url />
<Owner />
<Email />
</Rule>
</Rules>
This is my RuleSet:
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="New Rule Set" Description=" " ToolsVersion="10.0">
<RuleHintPaths>
<Path>C:\App\PSI\Development\Source\JHA.ProfitStars.PSI\JHA.ProfitStars
.PSI.FxCop\bin\Debug</Path>
</RuleHintPaths>
</RuleSet>
I even tried adding the line below, but now it shows an Unknown rule in the ruleset:
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis"
RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CR0001" Action="Error" />
</Rules>
Could someone please help me understand what I am doing wrong here?
Edited:
BaseClass for rules:
internal abstract class BaseFxCopRule : BaseIntrospectionRule
{
protected BaseFxCopRule(string ruleName)
: base(ruleName, "JHA.ProfitStars.PSI.FxCop.Rules", typeof(BaseFxCopRule).Assembly)
{ }
}
Rules Class:
internal sealed class EnforceHungarianNotation : BaseFxCopRule
{
public EnforceHungarianNotation()
: base("EnforceHungarianNotation")
{
}
public override TargetVisibilities TargetVisibility
{
get
{
return TargetVisibilities.NotExternallyVisible;
}
}
public override ProblemCollection Check(Member member)
{
Field field = member as Field;
if (field == null)
{
// This rule only applies to fields.
// Return a null ProblemCollection so no violations are reported for this member.
return null;
}
if (field.IsStatic)
{
CheckFieldName(field, s_staticFieldPrefix);
}
else
{
CheckFieldName(field, s_nonStaticFieldPrefix);
}
// By default the Problems collection is empty so no violations will be reported
// unless CheckFieldName found and added a problem.
return Problems;
}
private const string s_staticFieldPrefix = "s_";
private const string s_nonStaticFieldPrefix = "m_";
private void CheckFieldName(Field field, string expectedPrefix)
{
if (!field.Name.Name.StartsWith(expectedPrefix, StringComparison.Ordinal))
{
Resolution resolution = GetResolution(
field, // Field {0} is not in Hungarian notation.
expectedPrefix // Field name should be prefixed with {1}.
);
Problem problem = new Problem(resolution);
Problems.Add(problem);
}
}
}
Looks like your path is kind of shaky, remove some spacing and unwanted characters:
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="New Rule Set" Description=" " ToolsVersion="10.0">
<RuleHintPaths>
<Path>C:\App\PSI\Development\Source\JHA.ProfitStars.PSI\JHA.ProfitStars.PSI.FxCop\bin\Debug</Path>
</RuleHintPaths>
</RuleSet>
Also adding the rulesdll to Microsoft Visual Studio [Version]\Team Tools\Static Analysis Tools\FxCop\Ruleslocation would solve the issue of having to use Rulehintpaths.
As I can't detect anything wrong with your custom rules, see if you have selected the option to show all rules:
Also, using the following BaseRule might help:
protected BaseRule(string name)
: base(
// The name of the rule (must match exactly to an entry
// in the manifest XML)
name,
// The name of the manifest XML file, qualified with the
// namespace and missing the extension
typeof(BaseRule).Assembly.GetName().Name + ".Rules",
// The assembly to find the manifest XML in
typeof(BaseRule).Assembly)
{
}
Close your solution. Use the Source Control Explorer to locate your Rule Set File. Doubleclick onto your ruleset. The Rule Set Editor now should show all your custom rules. If this still doesn't work, try to use a relative path in the Path tag of the RuleHintPaths section.
Have a look at the LoadFromFile() method of the Microsoft.VisualStudio.CodeAnalysis.dll:
public static RuleSet LoadFromFile(string filePath, IEnumerable<RuleInfoProvider> ruleProviders)
{
RuleSet ruleSet = RuleSetXmlProcessor.ReadFromFile(filePath);
if (ruleProviders != null)
{
string relativePathBase = string.IsNullOrEmpty(filePath) ? (string) null : Path.GetDirectoryName(ruleSet.FilePath);
Dictionary<RuleInfoProvider, RuleInfoCollection> allRulesByProvider;
Dictionary<string, IRuleInfo> rules = RuleSet.GetRules((IEnumerable<string>) ruleSet.RuleHintPaths, ruleProviders, relativePathBase, out allRulesByProvider);
foreach (RuleReference ruleReference in (Collection<RuleReference>) ruleSet.Rules)
{
IRuleInfo ruleInfo;
if (rules.TryGetValue(ruleReference.FullId, out ruleInfo))
{
if (ruleInfo.AnalyzerId == ruleReference.AnalyzerId)
ruleReference.RuleInfo = ruleInfo;
else
CATrace.Info("RuleSet.LoadFromFile: Rule {0} was listed under analyzer id {1} in the rule set, but the corresponding IRuleInfo returned analyzer id {2}", (object) ruleReference.FullId, (object) ruleReference.AnalyzerId, (object) ruleInfo.AnalyzerId);
}
}
}
return ruleSet;
}
If the relativePathBase is calculated wrong, the rule DLLs will not be found.
I'm holding a custom configuration section in machine.config of following structure:
<CustomSettings>
<add key="testkey" local="localkey1" dev="devkey" prod="prodkey"/>
</CustomSettings>
Now, i want to be able to override the same key settings by storing the overrides in app.config like this:
<CustomSettings>
<add key="testkey" dev="devkey1" prod="prodkey1"/>
</CustomSettings>
So that when i read it in code i'll get - dev="devkey1", prod="prodkey1", local="localkey1"
Problem is that when i read my custom config section like this:
CustomConfigurationSection section = ConfigurationManager.GetSection("CustomSettings") as CustomConfigurationSection;
i get an error stating that the key has already been added:
"The entry 'testkey' has already been added."
I modified making the ConfigElementCollection.Add function to check if the same key already exists but it didn't work.
Any ideas?
you should delete the key first, try
<CustomSettings>
<remove key="testkey"/>
<add key="testkey" dev="devkey1" prod="prodkey1"/>
</CustomSettings>
That should do the trick
I ended up overriding BaseAdd in the ConfigurationElementCollection:
protected override void BaseAdd(ConfigurationElement element)
{
CustomConfigurationElement newElement = element as CustomConfigurationElement;
if (base.BaseGetAllKeys().Where(a => (string)a == newElement.Key).Count() > 0)
{
CustomConfigurationElement currElement = this.BaseGet(newElement.Key) as CustomConfigurationElement;
if (!string.IsNullOrEmpty(newElement.Local))
currElement.Local = newElement.Local;
if (!string.IsNullOrEmpty(newElement.Dev))
currElement.Dev = newElement.Dev;
if (!string.IsNullOrEmpty(newElement.Prod))
currElement.Prod = newElement.Prod;
}
else
{
base.BaseAdd(element);
}
}
I hope it helps...
Is there any way to load settings from a different file other than the default App.config file at runtime? I'd like to do this after the default config file is loaded.
I use the Settings.Settings GUI in Visual Studio to create my App.config file for me. The config file ends up looking like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="SnipetTester.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</sectionGroup>
</configSections>
<applicationSettings>
<SnipetTester.Properties.Settings>
<setting name="SettingSomething" serializeAs="String">
<value>1234</value>
</setting>
</SnipetTester.Properties.Settings>
</applicationSettings>
</configuration>
In code, I'm able to access the settings like this:
Console.WriteLine("Default setting value: " + Properties.Settings.Default.SettingSomething);
The idea is that when the application is run, I should be able to specify a config file at run time and have the application load the config file into the Properties.Settings.Default object instead of using the default app.config file. The formats of the config files would be the same, but the values of the settings would be different.
I know of a way to do this with the ConfigurationManager.OpenExeConfiguration(configFile);. However, in the tests that I've run, it doesn't update the Properties.Settings.Default object to reflect the new values from the config file.
After thinking about this a bit longer, I've been able to come up with a solution that I like a little better. I'm sure it has some pitfalls, but I think it'll work for what I need it to do.
Essentially, the Properties.Settings class is automatically generated by Visual Studio; it generates the code for the class for you. I was able to find where the code was generated and add a few function calls to load a config file on its own. Here's my addition:
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
//Parses a config file and loads its settings
public void Load(string filename)
{
System.Xml.Linq.XElement xml = null;
try
{
string text = System.IO.File.ReadAllText(filename);
xml = System.Xml.Linq.XElement.Parse(text);
}
catch
{
//Pokemon catch statement (gotta catch 'em all)
//If some exception occurs while loading the file,
//assume either the file was unable to be read or
//the config file is not in the right format.
//The xml variable will be null and none of the
//settings will be loaded.
}
if(xml != null)
{
foreach(System.Xml.Linq.XElement currentElement in xml.Elements())
{
switch (currentElement.Name.LocalName)
{
case "userSettings":
case "applicationSettings":
foreach (System.Xml.Linq.XElement settingNamespace in currentElement.Elements())
{
if (settingNamespace.Name.LocalName == "SnipetTester.Properties.Settings")
{
foreach (System.Xml.Linq.XElement setting in settingNamespace.Elements())
{
LoadSetting(setting);
}
}
}
break;
default:
break;
}
}
}
}
//Loads a setting based on it's xml representation in the config file
private void LoadSetting(System.Xml.Linq.XElement setting)
{
string name = null, type = null, value = null;
if (setting.Name.LocalName == "setting")
{
System.Xml.Linq.XAttribute xName = setting.Attribute("name");
if (xName != null)
{
name = xName.Value;
}
System.Xml.Linq.XAttribute xSerialize = setting.Attribute("serializeAs");
if (xSerialize != null)
{
type = xSerialize.Value;
}
System.Xml.Linq.XElement xValue = setting.Element("value");
if (xValue != null)
{
value = xValue.Value;
}
}
if (string.IsNullOrEmpty(name) == false &&
string.IsNullOrEmpty(type) == false &&
string.IsNullOrEmpty(value) == false)
{
switch (name)
{
//One of the pitfalls is that everytime you add a new
//setting to the config file, you will need to add another
//case to the switch statement.
case "SettingSomething":
this[name] = value;
break;
default:
break;
}
}
}
}
The code I added exposes an Properties.Settings.Load(string filename) function. The function accepts a config filename as a parameter. It will parse the file and load up any settings it encounters in the config file. To revert back to the original configuration, simply call Properties.Settings.Reload().
Hope this might help someone else!
Look at using ExeConfigurationFileMap and ConfigurationManager.OpenMappedExeConfiguration.
See Cracking the Mysteries of .Net 2.0 Configuration
The ExeConfigurationFileMap allows you to specifically configure the
exact pathnames to machine, exe, roaming and local configuration
files, all together, or piecemeal, when calling
OpenMappedExeConfiguration(). You are not required to specify all
files, but all files will be identified and merged when the
Configuration object is created. When using
OpenMappedExeConfiguration, it is important to understand that all
levels of configuration up through the level you request will always
be merged. If you specify a custom exe and local configuration file,
but do not specify a machine and roaming file, the default machine and
roaming files will be found and merged with the specified exe and user
files. This can have unexpected consequences if the specified files
have not been kept properly in sync with default files.
It depends on the type of the application:
Web Application & Windows Application - use the configSource xml attribute if you are willing to store the config files in the same folder (or subfolders) as the application
Create a settings provider and also implement IApplicationSettingsProvider. Samples here and here. You might also need to use the IConfigurationManagerInternal interface to replace the default .NET configuration manager. When implementing the provider don't forget to make a difference between user settings and application settings and the roaming profiles.
If you want to get started quickly just decompile the LocalFileSettingsProvider class (the default settings provider) and change it to your needs (you might find some useles code and might need to replicate all of the classes on which it depends).
Good luck
You can include the types so you don't need to manually update the source every time.
`private void LoadSetting(System.Xml.Linq.XElement setting)
{
string name = null, type = null;
string value = null;
if (setting.Name.LocalName == "setting")
{
System.Xml.Linq.XAttribute xName = setting.Attribute("name");
if (xName != null)
{
name = xName.Value;
}
System.Xml.Linq.XAttribute xSerialize = setting.Attribute("serializeAs");
if (xSerialize != null)
{
type = xSerialize.Value;
}
System.Xml.Linq.XElement xValue = setting.Element("value");
if (xValue != null)
{
if (this[name].GetType() == typeof(System.Collections.Specialized.StringCollection))
{
foreach (string s in xValue.Element("ArrayOfString").Elements())
{
if (!((System.Collections.Specialized.StringCollection)this[name]).Contains(s))
((System.Collections.Specialized.StringCollection)this[name]).Add(s);
}
}
else
{
value = xValue.Value;
}
if (this[name].GetType() == typeof(int))
{
this[name] = int.Parse(value);
}
else if (this[name].GetType() == typeof(bool))
{
this[name] = bool.Parse(value);
}
else
{
this[name] = value;
}
}
}`