Enforcing a particular number of parameters - c#

I have a logger that records the method name (which I get through reflection) and parameters (which are manually passed to the logger). Here's an example of the proper way to do the logging:
public void Foo() {
// This is correct - the method has no parameters, neither does the logging
Logger.Log();
// Method
}
public void Foo1(int a, int b) {
// Log both of the parameters correctly
Logger.Log(a, b);
// Rest of method
}
However, people will periodically call this incorrectly. For example:
public void Foo1(int a, int b) {
// Didn't record any of the parameters
Logger.Log();
// Rest of method
}
or
public void Foo1(int a, int b, int c) {
// Someone changed the number of parameters but didn't update the logging call
Logger.Log(a, b);
}
The signature of the Log method is:
public void Log(params object[] parameters)
I'd like to have some way of requiring that Logger.Log have the same number of parameters as the method calling it does.
I know how to do this at runtime (just use reflection to get the parameter list for the caller and compare it to what parameters I actually received), but that would be a really bad solution to this I think since the vast majority of the checks would be unnecessary. (It would also mean that you wouldn't know until runtime that you had written it incorrectly, and then only if you happened to execute that particular method).
Right now we're not using FxCop unfortunately (or I'd just write some kind of rule) and I suspect that I wouldn't succeed in changing that fact. Short of writing a compiler plugin of some kind, is there a way to force people to use this method correctly?

You should be able to accomplish this using the new Roslyn API's. You'll want to install the SDK here:
https://marketplace.visualstudio.com/items?itemName=VisualStudioProductTeam.NETCompilerPlatformSDK
Once installed you should go to new project and navigate to Extensibility and you'll see the project type Analyzer with Code Fix (NuGet + VSIX) template. I created a sample project that I used to show the compiler errors:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AnalyzerTest
{
public static class Logger
{
public static void Log(params object[] parameters)
{
}
}
}
namespace AnalyzerTest
{
public class Foo
{
public void Foo1(int a, int b)
{
// Didn't record any of the parameters
Logger.Log();
// Rest of method
}
}
}
I created a separate project for the analyzer and here is the code for the analyzer class:
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
namespace Analyzer1
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class LoggerAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "Logging";
internal const string Title = "Logging error";
internal const string MessageFormat = "Logging error {0}";
internal const string Description = "You should have the same amount of arguments in the logger as you do in the method.";
internal const string Category = "Syntax";
internal static DiagnosticDescriptor Rule =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat,
Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description);
public override ImmutableArray<DiagnosticDescriptor>
SupportedDiagnostics
{ get { return ImmutableArray.Create(Rule); } }
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(
AnalyzeNode, SyntaxKind.InvocationExpression);
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var invocationExpr = (InvocationExpressionSyntax)context.Node;
var memberAccessExpr = invocationExpr.Expression as MemberAccessExpressionSyntax;
if (memberAccessExpr != null && memberAccessExpr.Name.ToString() != "Log")
{
return;
}
var memberSymbol =
context.SemanticModel.GetSymbolInfo(memberAccessExpr).Symbol as IMethodSymbol;
if (memberSymbol == null || !memberSymbol.ToString().StartsWith("AnalyzerTest.Logger.Log"))
{
return;
}
MethodDeclarationSyntax parent = GetParentMethod(context.Node);
if(parent == null)
{
return;
}
var argumentList = invocationExpr.ArgumentList;
Int32 parentArgCount = parent.ParameterList.Parameters.Count;
Int32 argCount = argumentList != null ? argumentList.Arguments.Count : 0;
if (parentArgCount != argCount)
{
var diagnostic = Diagnostic.Create(Rule, invocationExpr.GetLocation(), Description);
context.ReportDiagnostic(diagnostic);
}
}
private MethodDeclarationSyntax GetParentMethod(SyntaxNode node)
{
var parent = node.Parent as MethodDeclarationSyntax;
if(parent == null)
{
return GetParentMethod(node.Parent);
}
return parent;
}
}
}
While in the Analyzer with Code Fix project you can hit F5 (as long as your .Vsix project is the startup project) and it will open up another VS instance and you can choose the project you would like to test the analyzer on.
Here is the result:
It also looks like you will have to install this as a NuGet package instead of a VS Extension, for whatever reason VS Extensions don't affect the build and you will only get the warning:
https://stackoverflow.com/a/39657967/1721372
For a more complete example see here:
https://msdn.microsoft.com/en-us/magazine/dn879356.aspx

Related

Best practice for support check of REST methods

First of all, I am aware of this question:
Best Practices and How to support different versions of REST APIs in C# wrapper on client-side
but i think my problem is a bit different.
We have a piece of software which can be remote controlled via a REST Api.
The users will get an update ~twice a year.
Every update will provide them with some new functionality.
Now we also have a REST client, which is developed in parallel but has different release dates. The client has to support the old versions in addition to the new ones.
The question is now, that i want to know how to build the version check in my REST client code. It is a bit of a luxury design problem...
public void apiStuff(Data input)
{
if (api.Versions < "2.5.3")
throw new Exception("Not supported, please update")
doApiStuffWith(input);
}
or should I put the check and throw part in a private method.
public void apiStuff(Data input)
{
checkForVersionSupport("2.5.3");
doApiStuffWith(input);
}
I think the first approach is more readable as the second one, but it is also redundant code.
Or does anybody have completely different ideas?
UPDATE
Adjusting for "every method needs different api version" solution.
Abstract client logic to an interface:
public interface IApiClient
{
void Foo();
int GetCurrentVersion();
}
Make an attribute to method-by-method API version:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ApiVersionRangeAttribute : Attribute
{
public int MinVersion { get; private set; }
public int MaxVersion { get; private set; }
public ApiVersionRangeAttribute(int minVersion, int maxVersion)
{
MinVersion = minVersion;
MaxVersion = maxVersion;
}
public void Validate(int version)
{
if (version < MinVersion || version > MaxVersion)
{
throw new Exception("Upgrade");
}
}
}
And create a factory that will take that attribute into an account:
//nuget: Install-Package Castle.Core
using System;
using Castle.DynamicProxy;
public class ApiClientFactory
{
public class ApiClient : IApiClient
{
[ApiVersionRange(10, 20)]
public void Foo()
{
Console.Write("Foo");
}
public int GetCurrentVersion()
{
// call to the server here instead :)
return 50;
}
}
public IApiClient CreateClient()
{
var generator = new ProxyGenerator();
var apiClient = generator.CreateInterfaceProxyWithTarget<IApiClient>(
new ApiClient(), new VersionInterceptor());
return apiClient;
}
}
public class VersionInterceptor : StandardInterceptor
{
protected override void PreProceed(IInvocation invocation)
{
var attributes = invocation.MethodInvocationTarget.GetCustomAttributes(
typeof(ApiVersionRangeAttribute), false);
if (attributes != null && attributes.Length == 1)
{
var apiRange = (ApiVersionRangeAttribute)attributes[0];
var proxy = (IApiClient)invocation.Proxy;
apiRange.Validate(proxy.GetCurrentVersion());
}
base.PreProceed(invocation);
}
}
Sample usage:
var apiClient = new ApiClientFactory().CreateClient();
// fail - 50 is not in range 10-20
apiClient.Foo();
IMHO consider Version instead of int and use the AttributeUsage for redundancy.
The above answer has depicted the usage of attributes
public void CheckVersion(Version currentVersion)
{
//Get your version from assembly
Version applicationVersion = new Version(AssemblyInfo.AssemblyFileVersion);
//
if (currentVersion.CompareTo(applicationVersion) == 1)
throw your Exception("Not supported");
}

How attach event to console app on startup

I have application for which I need to add additional hidden logging.
I have put prototype in way.
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Start");
new DummyTest().Report();
Console.WriteLine("End");
Console.ReadKey();
}
}
public class DummyTest
{
public void Report()
{
var reporter = new Reporter();
Console.WriteLine("Reporting");
for (var i =0; i < 155; i++)
{
reporter.Process(i);
}
Console.WriteLine("Reporting end");
}
}
public class Reporter
{
// attach behavior here
public void Process(int requestId)
{
Console.WriteLine("Processing request: {0}" , requestId);
System.Threading.Thread.Sleep(100);
}
}
Now I have new project logger.dll that contains
using System;
namespace logger
{
public class Log
{
public Log()
{
Console.WriteLine("Line executed");
}
}
}
Now I would like to execute this method every time Main gets executed. This however cannot be referenced in any other way except only by referencing the dll.
=Update=
I do not mind to have reference to that dll. But in main code I cannot have any reference to Log. I thought about using reflection in order to make this work. The problem I am trying to solve first is how to attach that to the execution.
Why I cannot call logger from main?
This is supposed to be reporting on usage of the class, monitoring usage, in order to report on performance on bottle necks.
You could do something like this:
void Main()
{
System.Console.SetOut(new CustomTextWriter());
Console.WriteLine("test");
}
public class CustomTextWriter : TextWriter
{
private TextWriter _consoleOut = null;
private Log _logger = null;
public CustomTextWriter()
{
_consoleOut = System.Console.Out;
_logger = new Log();
}
public override void Write(char[] buffer, int index, int count)
{
this.Write(new String(buffer, index, count));
}
public override void Write(string value)
{
_consoleOut.Write(value);
_logger.Write(value);
}
public override void WriteLine(string value)
{
_consoleOut.WriteLine(value);
_logger.WriteLine(value);
}
public override Encoding Encoding
{
get { return System.Text.Encoding.Default; }
}
}
Wasn't sure if you wanted to do logging without actually calling Console.WriteLine() (if yes you'll need to look at Interception) but if that's ok then this should get you through.
Hope this helps.
You could do that with reflection like this:
// load the assembly
Assembly LogDll = Assembly.LoadFile(#"Log.dll");
// get the type of the Log class
Type LogType = LogDll.GetType("logger.Log");
// get instance of the Log class
object LogInstance = Activator.CreateInstance(LogType);
// invoke class member "Log()"
LogType.InvokeMember("Log",
BindingFlags.InvokeMethod |
BindingFlags.Instance |
BindingFlags.Public,
null,
LogInstance,
null);
Although I'm not sure if the constructor "Log()" already gets called by creating the instance. You should probably move your actual log method out of the constructor. To pass arguments you can use the last parameter of InvokeMember which is an array of the type Object.

In a C# 'using' block, how best to access the IDisposable in contained extension method calls?

I am writing extension methods for a class, and would like to access an IDisposable object defined in a using block which will often contain calls to the extension methods.
I do not want to simply pass the IDisposable to the method calls, which would detract from the simplicity of my API's programming model. Accomplishing what I'm after would also make the code work much more like the third-party API with which I'm integrating.
I can imagine one way to go about this: register the IDisposable in some global location, perhaps tied to the current thread ID so it can be looked up in the extension methods via a factory method call or some such thing. The object could unregister itself when the using block is exited and its Dispose() method is eventually called (to make this work I imagine I might need to use a weak reference, though).
That doesn't seem very unclean, but it is a little too much roundabout for my taste. Is there some more direct way of doing this?
Here's what I'd like to do:
public static class ExtensionMethods {
public static void Foo(this Bar b) {
// Access t to enable this extension method to do its work, whatever that may be
}
}
public class Bar {
}
public class Schlemazel {
public void DoSomething() {
using (Thingamabob t = new Thingamabob()) {
Bar b = new Bar();
b.Foo();
}
}
}
EDIT:
Following is a solution implemented using weak references and a simple thread-based registration system. It seems to work and to be stable even under a fair load, but of course on a really overloaded system it could theoretically start throwing errors due to lock contention.
I thought it might be interesting for someone to see this solution, but again, it introduces needless complexity and I am only willing to do this if necessary. Again, the goal is a clean extension of a third-party API, where I can call extension methods on objects created by the third-party API, where the extension methods depend on some context that is messy to create or get for each little extension method call.
I've left in some console output statements so that if you're curious, you can actually plop these classes into a command-line project and see it all in action.
public class Context : IDisposable
{
private const int MAX_LOCK_TRIES = 3;
private static TimeSpan MAX_WRITE_LOCK_TIMEOUT = TimeSpan.FromTicks(500);
private static System.Threading.ReaderWriterLockSlim readerWriterLock = new System.Threading.ReaderWriterLockSlim();
static IDictionary<string, WeakReference<Context>> threadContexts = new Dictionary<string, WeakReference<Context>>();
private bool registered;
private string threadID;
private string ThreadID
{
get { return threadID; }
set
{
if (threadID != null)
throw new InvalidOperationException("Cannot associate this context with more than one thread");
threadID = value;
}
}
/// <summary>
/// Constructs a Context suitable for use in a using() statement
/// </summary>
/// <returns>A Context which will automatically deregister itself when it goes out of scope, i.e. at the end of a using block</returns>
public static Context CreateContext()
{
Console.WriteLine("CreateContext()");
return new Context(true);
}
private Context(bool register)
{
if (register)
{
registered = true;
try
{
RegisterContext(this);
}
catch
{
registered = false;
}
}
else
registered = false;
}
public Context()
{
registered = false;
}
public void Process(ThirdPartyObject o, params string[] arguments)
{
Console.WriteLine("Context.Process(o)");
// Process o, sometimes using the third-party API which this object has access to
// This hides away the complexity of accessing that API, including obviating the need
// to reconstruct and configure heavyweight objects to access it; calling code can
// blithely call useful methods on individual objects without knowing the messy details
}
public void Dispose()
{
if (registered)
DeregisterContext(this);
}
private static void RegisterContext(Context c)
{
if (c == null)
throw new ArgumentNullException();
c.ThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("RegisterContext() " + c.ThreadID);
bool lockEntered = false;
int tryCount = 0;
try
{
while (!readerWriterLock.TryEnterWriteLock(TimeSpan.FromTicks(5000)))
if (++tryCount > MAX_LOCK_TRIES)
throw new OperationCanceledException("Cannot register context (timeout)");
lockEntered = true;
threadContexts[c.ThreadID] = new WeakReference<Context>(c);
}
finally
{
if (lockEntered)
readerWriterLock.ExitWriteLock();
}
}
private static void DeregisterContext(Context c)
{
if (c == null)
throw new ArgumentNullException();
else if (!c.registered)
return;
Console.WriteLine("DeregisterContext() " + c.ThreadID);
bool lockEntered = false;
int tryCount = 0;
try
{
while (!readerWriterLock.TryEnterWriteLock(TimeSpan.FromTicks(5000)))
if (++tryCount > MAX_LOCK_TRIES)
throw new OperationCanceledException("Cannot deregister context (timeout)");
lockEntered = true;
if (threadContexts.ContainsKey(c.ThreadID))
{
Context registeredContext = null;
if (threadContexts[c.ThreadID].TryGetTarget(out registeredContext))
{
if (registeredContext == c)
{
threadContexts.Remove(c.ThreadID);
}
}
else
threadContexts.Remove(c.ThreadID);
}
}
finally
{
if (lockEntered)
readerWriterLock.ExitWriteLock();
}
}
/// <summary>
/// Gets the Context for this thread, if one has been registered
/// </summary>
/// <returns>The Context for this thread, which would generally be defined in a using block using Context.CreateContext()</returns>
internal static Context GetThreadContext()
{
string threadID = System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();
Console.WriteLine("GetThreadContext() " + threadID);
bool lockEntered = false;
int tryCount = 0;
try
{
while (!readerWriterLock.TryEnterReadLock(TimeSpan.FromTicks(5000)))
if (++tryCount > MAX_LOCK_TRIES)
throw new OperationCanceledException("Cannot get context (timeout)");
lockEntered = true;
Context registeredContext = null;
if (threadContexts.ContainsKey(threadID))
threadContexts[threadID].TryGetTarget(out registeredContext);
return registeredContext;
}
finally
{
if (lockEntered)
readerWriterLock.ExitReadLock();
}
}
}
// Imagine this is some third-party API
public static class ThirdPartyApi
{
// Imagine this is any call to the third-party API that returns an object from that API which we'd like to decorate with an extension method
public static ThirdPartyObject GetThirdPartyObject()
{
return new ThirdPartyObject();
}
}
// Imagine this is some class from a third-party API, to which we would like to add extension methods
public class ThirdPartyObject
{
internal ThirdPartyObject() { }
}
public static class ExtensionMethods
{
public static void DoSomething(this ThirdPartyObject o) {
// get the object I need to access resources to do my work
Console.WriteLine("o.DoSomething()");
Context c = Context.GetThreadContext();
c.Process(o);
}
}
You could test it pretty simply, with some code like this:
ThirdPartyObject o;
using (Context.CreateContext())
{
o = ThirdPartyApi.GetThirdPartyObject(); // or a call to my own code to get it, encapsulating calls to the third-party API
// Call the method we've tacked on to the third party API item
o.DoSomething();
}
try
{
// If the registered context has been disposed/deregistered, this will throw an error;
// there is of course no way of knowing when it will happen, but in my simple testing
// even this first attempt always throws an error, on my relatively unburdened system.
// This means that with this model, one should not access the using-block Context
// outside of the using block, but that's of course true in general of using statements
o.DoSomething();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
System.Threading.Thread.Sleep(1000);
try
{
// Should almost certainly see an error now
o.DoSomething();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Pass the t variable to the extension method.
public static class ExtensionMethods {
public static void Foo(this Bar b, Thingamabob t) {
// Access t to enable this extension method to do its work, whatever that may be
}
}
public class Bar { }
public class Schlemazel {
public void DoSomething() {
using (Thingamabob t = new Thingamabob()) {
Bar b = new Bar();
b.Foo(t);
}
}
}

Make MVC Sitemap unique per session, not user

Our MvcSitemap has some DynamicNodeProviders implemented.
We want these to be unique per session. But it appears they are unique per user.
So if a user logs into two different browsers, or computers, they currently share the same sitemap.
We do not want this.
But I can't seem to figure out how to get it to use the User/Session combination for uniqueness.
Is there a way to make this work?
Option 1:
Implement your own ICacheProvider based on session state and inject it using DI.
using System;
using System.Collections.Generic;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Caching;
using System.Web;
public class SessionStateCacheProvider<T>
: ICacheProvider<T>
{
public SessionStateCacheProvider(
IMvcContextFactory mvcContextFactory
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
this.mvcContextFactory = mvcContextFactory;
}
private readonly IMvcContextFactory mvcContextFactory;
protected HttpContextBase Context
{
get
{
return this.mvcContextFactory.CreateHttpContext();
}
}
#region ICacheProvider<ISiteMap> Members
public bool Contains(string key)
{
return (Context.Session[key] != null);
}
public Caching.LazyLock Get(string key)
{
return (LazyLock)Context.Session[key];
}
public bool TryGetValue(string key, out Caching.LazyLock value)
{
value = this.Get(key);
if (value != null)
{
return true;
}
return false;
}
public void Add(string key, LazyLock item, ICacheDetails cacheDetails)
{
// NOTE: cacheDetails is normally used to set the timeout - you might
// need to roll your own method for doing that.
Context.Session[key] = item;
}
public void Remove(string key)
{
Context.Session.Remove(key);
}
public event EventHandler<MicroCacheItemRemovedEventArgs<T>> ItemRemoved;
#endregion
// NOTE: Normally this is called by a callback from the cache when an item exprires.
// It is required to ensure there is no memory leak because a sitemap has circular references
// that need to be broken explicitly. You need to work out how to call this when the user's session
// expires.
protected virtual void OnCacheItemRemoved(MicroCacheItemRemovedEventArgs<T> e)
{
if (this.ItemRemoved != null)
{
ItemRemoved(this, e);
}
}
}
Then inject it like this (StructureMap example shown):
// Setup cache
SmartInstance<CacheDetails> cacheDetails;
this.For<ICacheProvider<ISiteMap>>().Use<SessionStateCacheProvider<ISiteMap>>();
var cacheDependency =
this.For<ICacheDependency>().Use<NullCacheDependency>();
cacheDetails =
this.For<ICacheDetails>().Use<CacheDetails>()
.Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
.Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
.Ctor<ICacheDependency>().Is(cacheDependency);
Option 2:
Append the user name to the siteMapCacheKey in a custom ISiteMapCacheKeyGenerator, and inject it via DI:
public class SessionBasedSiteMapCacheKeyGenerator
: ISiteMapCacheKeyGenerator
{
public UserBasedSiteMapCacheKeyGenerator(
IMvcContextFactory mvcContextFactory
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
this.mvcContextFactory = mvcContextFactory;
}
protected readonly IMvcContextFactory mvcContextFactory;
#region ISiteMapCacheKeyGenerator Members
public virtual string GenerateKey()
{
var context = mvcContextFactory.CreateHttpContext();
var builder = new StringBuilder();
builder.Append("sitemap://");
builder.Append(context.Request.Url.DnsSafeHost);
builder.Append("/?sessionId=");
builder.Append(context.Session.SessionID);
return builder.ToString();
}
#endregion
}
Inject it like this (StructureMap example):
this.For<ISiteMapCacheKeyGenerator>().Use<SessionBasedSiteMapCacheKeyGenerator>();
Note that using an external DI container is required.
Please see my open question here and explain to me why you would want to do this on GitHub, as it renders most of the features useless: https://github.com/maartenba/MvcSiteMapProvider/issues/16#issuecomment-22229604

Filehelpers NullReferenceException when trying to write a null decimal value

When using the FileHelpers library I am getting a NullReferenceException when trying to write a .csv file.
I have narrowed the problem down. Whenever I have a null decimal? it throws this exception. It works fine on reading, just not writing.
I have included a sample that shows the same problem as my app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication11
{
class Program
{
static void Main(string[] args) {
rec record = new rec { id = 1, mydecimal = null };
List<rec> records = new List<rec> { record };
FileHelpers.FileHelperEngine<rec> engine = new FileHelpers.FileHelperEngine<rec>();
Console.WriteLine(engine.WriteString(records));
}
}
[FileHelpers.DelimitedRecord(",")]
public class rec
{
public int id;
public decimal? mydecimal;
}
}
You can use a custom converter.
public class NullableDecimalConverter : FileHelpers.ConverterBase
{
public override object StringToField(string from)
{
return from;
}
public override string FieldToString(object fieldValue)
{
if (fieldValue == null)
return String.Empty;
return fieldValue.ToString();
}
}
You need to modify your record class to add a [FieldConverter()] attribute to any decimal? field.
[FileHelpers.DelimitedRecord(",")]
public class rec
{
public int id;
[FileHelpers.FieldConverter(typeof(NullableDecimalConverter))]
public decimal? mydecimal;
}
Hate to answer my own question, but FileHelpers 2.9.9 fixes this problem. It used to be available on the official site (marked as beta), but can't find it now.
It is however available in NuGet under a package called FileHelpers-stable

Categories