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.
Related
I'm developing a hobby project that communicates with external hardware (a robot), and am stuck with a few compiler errors that I can’t resolve.
Now before a mod closes this because other questions with this error have been asked, I have read numerous posts about other users CS1061 errors (e.g. Error CS1061 'object' does not contain a definition for and no accessible extension method accepting a first argument of type 'object' could be found), as well as relevant support documents (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs1061?f1url=%3FappId%3Droslyn%26k%3Dk(CS1061)). However, the level of expertise/experience in these previous questions is beyond my ability to comprehend and apply to my specific problem (or maybe I'm just daft and can't figure this out). I am in need of specific guidance explaining how to address my particular problem.
Ok, getting down to it. The errors are:
error CS1061: 'object' does not contain a definition for 'Connection' and no accessible extension method 'Connection' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
error CS1061: 'object' does not contain a definition for 'Devices' and no accessible extension method 'Devices' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
and occur on lines 7, 9, 15, 30, and 32 in the 1st code block below.
They seem to be related to data in the Xml (Connection and Devices) not being correctly recognized/stored in the object "value" (or value2). For background, the Xml file (3rd code block below) is read into a dictionary using the XmlSettings class, which I've provided in the 2nd code block below.
XmlSettings xmlSettings;
xmlSettings = new XmlSettings();
xmlSettings.Load();
if (xmlSettings.TryGetValue("Cambus", out var value))
{
string text;
text = this.CameraConnection ?? value.Connection;
this.systemConfiguration = new List<RaspDeviceConfiguration>();
if (string.Empty.Equals(value.Devices))
{
return;
}
try
{
foreach (dynamic item in (IEnumerable<object>)value.Devices)
{
Type type;
type = Type.GetType((string)item.Impl, throwOnError: true);
if (typeof(IRaspComponent).IsAssignableFrom(type))
{
this.systemConfiguration.Add(new RaspDeviceConfiguration(type, item.Role, text, item.Address, item.Configuration));
}
}
}
catch (Exception)
{
this.systemConfiguration = null;
throw;
}
}
if (xmlSettings.TryGetValue("Mountarm", out var value2) && ((!string.IsNullOrEmpty(value2.Connection)) ? true : false))
{
this.ServoConnection = value2.Connection;
}
XmlSettings is a public class that loads an Xml file with various hardware settings. TryGetValue is a function within the XmlSettings class, and value is of type object.
Below is the XmlSettings code:
public class XmlSettings
{
private readonly ILog log = LogManager.GetLogger(typeof(XmlSettings));
private ExpandoObject expandobj;
public void Load()
{
string path = Path.Combine(Path.GetDirectoryName(typeof(XmlSettings).Assembly.Location), "RaspCamModuleSettings.xml");
Load(path);
}
public void Load(string path)
{
log.DebugFormat("Loading settings from {0}", path);
expandobj = _getExpandobjFromXml(path);
}
public bool TryGetValue(string property, out object value)
{
if (expandobj != null)
{
return ((IDictionary<string, object>)expandobj).TryGetValue(property, out value);
}
value = null;
return false;
}
private static dynamic _getExpandobjFromXml(string file, XElement node = null)
{
if (string.IsNullOrWhiteSpace(file) && node == null)
{
return null;
}
node = ((!string.IsNullOrWhiteSpace(file)) ? XDocument.Load(file).Root : node);
IDictionary<string, object> dictionary = new ExpandoObject();
PluralizationService pluralizationService = PluralizationService.CreateService(CultureInfo.CreateSpecificCulture("en-us"));
foreach (XElement gn in node.Elements())
{
bool flag = gn.HasElements && ((gn.Elements().Count() > 1 && gn.Elements().All((XElement e) => e.Name.LocalName.ToLower() == gn.Elements().First().Name.LocalName)) || gn.Name.LocalName.ToLower() == pluralizationService.Pluralize(gn.Elements().First().Name.LocalName).ToLower());
List<XElement> obj = (flag ? gn.Elements().ToList() : new List<XElement> { gn });
List<object> list = new List<object>();
foreach (XElement item in obj)
{
list.Add(item.HasElements ? _getExpandobjFromXml(null, item) : _extractNodeValue(item));
}
dictionary[gn.Name.LocalName] = (flag ? list : list.FirstOrDefault());
}
return dictionary;
}
private static object _extractNodeValue(XElement node)
{
object obj = node.Value.Trim();
XAttribute xAttribute = node.Attribute("clr-type");
if (xAttribute != null)
{
string text = xAttribute.Value.Trim();
if (!text.Contains('.'))
{
text = "System." + text;
}
Type type = Type.GetType(text, throwOnError: true, ignoreCase: true);
obj = Convert.ChangeType(obj, type);
}
return obj;
}
}
Lastly, to help contextualize, the Xml settings file (RaspCamModuleSettings.xml):
<?xml version="1.0" encoding="utf-8" ?>
<DeviceConfig>
<Cambus>
<Connection></Connection>
<Devices>
<Device>
<Role>camera1</Role>
<Address clr-type="int32">15</Address>
<Impl>Rasp.Core.RaspCamComponent,Rasp.Core</Impl>
<Configuration>
<![CDATA[
<?xml version="1.0" encoding="utf-8" ?>
<CamConfigurations>
<CamConfiguration TargetProductNumber="711">
<Diagflags0Mask>2024</Diagflags0Mask>
<OptionToggles>0</OptionToggles>
</CamConfiguration>
</CamConfigurations>
]]>
</Configuration>
</Device>
<Device>
<Role>camera2</Role>
<Address clr-type="int32">18</Address>
<Impl>Rasp.Core.RaspCamComponent,Rasp.Core</Impl>
<Configuration>
<![CDATA[
<?xml version="1.0" encoding="utf-8" ?>
<CamConfigurations>
<CamConfiguration TargetProductNumber="711">
<Diagflags0Mask>2024</Diagflags0Mask>
<OptionToggles>0</OptionToggles>
</CamConfiguration>
</CamConfigurations>
]]>
</Configuration>
</Device>
<Device>
<Role>camera3</Role>
<Address clr-type="int32">23</Address>
<Impl>Rasp.Core.RaspCamComponent,Rasp.Core</Impl>
<Configuration>
<![CDATA[
<?xml version="1.0" encoding="utf-8" ?>
<CamConfigurations>
<CamConfiguration TargetProductNumber="711">
<Diagflags0Mask>2024</Diagflags0Mask>
<OptionToggles>0</OptionToggles>
</CamConfiguration>
</CamConfigurations>
]]>
</Configuration>
</Device>
</Devices>
</Cambus>
<ProcedureBasePath></ProcedureBasePath>
</DeviceConfig>
This is a compiler error. The compiler has no way of knowing the structure of your XML document. This has to be done at run time.
You can have the compiler generate code to do it at run time by using the dynamic compile-time type. Using var in the fourth line of your code sample causes the compile-time type of the variable to be object. Instead, try
if (xmlSettings.TryGetValue("Cambus", out dynamic value))
//...
That will fix the compiler error.
Incidentally, if you declare expandobj as dynamic instead of ExpandoObject, you can use expandobj.Cambus instead of ((IDictionary<string, object>)expandobj).TryGetValue("Cambus", out value). However, that will throw if "Cambus" is missing, so you might want to stick with TryGetValue. If you do, though, you'll still want to change the key line to ((IDictionary<string, dynamic>)expandobj).TryGetValue("Cambus", out value).
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}
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 have this file for my settings.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!--Settings document for System Gazatter-->
<Settings>
<OutputState>2</OutputState>
<SystemPrefix>XCL</SystemPrefix>
<MaxNumSystems>2147483647</MaxNumSystems>
<OpenCluster>0</OpenCluster>
<GardenWorld>False</GardenWorld>
<StellarVariance>False</StellarVariance>
<StellarMassOverride allow="True">
<MinStellarMass>1.00</MinStellarMass>
<MaxStellarMass>2</MaxStellarMass>
</StellarMassOverride>
<NumberOfStarsOverride allow="False">1</NumberOfStarsOverride>
</Settings>
I want to write a function that is something like
public static T getSettingElementValue(XDocument settings, string elementName) {
return (T)(settingDoc.Element("Settings")
.Select(x=>x.Element(elementName))
.First()
.Value);
}
Now, I know this won't work. (It's also missing any checks on calls.) Is there some way to do this? Or should I fall back to something like.
public static string getSettingElementStringValue(...)
public static string getSettingElementDoubleValue(...)
This works for me:
public static T getSettingElementValue<T>(XDocument settings, string elementName) {
return (T)Convert.ChangeType(settings.Element("Settings").Element(elementName).Value, typeof(T));
}
void Main()
{
var xml = XDocument.Load(#"C:\abc\blah.xml");
Console.WriteLine(getSettingElementValue<bool>(xml, "GardenWorld"));
}
...but I would still follow the advice of not having your data like this and just using the App.config or something easier. This'll also only work if T implements IConvertible, so you should probably add that type constraint on T.
You already have an answer, but I wrote an XML library (See here) a while back that does this in it with the following:
XElement settings = XElement.Load(file); // or .Parse(string)
bool flag = settings.Get("GardenWorld", false); // false is a default and does the generic type for you
One of the ways it does it is similar to the answer, but it also uses other methods if they don't work, such as trying for a TryParse(string) on the type and checking to see if the type has a string constructor.