So what I really want is somewhat usable tab completion in a PS module.
ValidateSet seems to be the way to go here.
Unfortunately my data is dynamic, so I cannot annotate the parameter with all valid values upfront.
DynamicParameters/IDynamicParameters seems to be the solution for that problem.
Putting these things together (and reducing my failure to a simple test case) we end up with:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace PSDummy
{
[Cmdlet(VerbsCommon.Get, "BookDetails")]
public class GetBookDetails : Cmdlet, IDynamicParameters
{
IDictionary<string, string[]> m_dummyData = new Dictionary<string, string[]> {
{"Terry Pratchett", new [] {"Small Gods", "Mort", "Eric"}},
{"Douglas Adams", new [] {"Hitchhiker's Guide", "The Meaning of Liff"}}
};
private RuntimeDefinedParameter m_authorParameter;
private RuntimeDefinedParameter m_bookParameter;
protected override void ProcessRecord()
{
// Do stuff here..
}
public object GetDynamicParameters()
{
var parameters = new RuntimeDefinedParameterDictionary();
m_authorParameter = CreateAuthorParameter();
m_bookParameter = CreateBookParameter();
parameters.Add(m_authorParameter.Name, m_authorParameter);
parameters.Add(m_bookParameter.Name, m_bookParameter);
return parameters;
}
private RuntimeDefinedParameter CreateAuthorParameter()
{
var p = new RuntimeDefinedParameter(
"Author",
typeof(string),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 0,
Mandatory = true
},
new ValidateSetAttribute(m_dummyData.Keys.ToArray()),
new ValidateNotNullOrEmptyAttribute()
});
// Actually this is always mandatory, but sometimes I can fall back to a default
// value. How? p.Value = mydefault?
return p;
}
private RuntimeDefinedParameter CreateBookParameter()
{
// How to define a ValidateSet based on the parameter value for
// author?
var p = new RuntimeDefinedParameter(
"Book",
typeof(string),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 1,
Mandatory = true
},
new ValidateSetAttribute(new string[1] { string.Empty }/* cannot fill this, because I cannot access the author */),
new ValidateNotNullOrEmptyAttribute()
});
return p;
}
}
}
Unfortunately this tiny snippet causes a lot of issues already. Ordered descending:
I fail to see how I can create a connection between the parameters. If you pick an author, you should only be able to pick a book that matches the author. So far GetDynamicParameters() always seems stateless though: I see no way to access the value of a different/earlier dynamic parameter. Tried keeping it in a field, tried searching MyInvocation - no luck. Is that even possible?
How do you define a default value for mandatory parameter? Doesn't fit the silly example, but let's say you can store your favorite author. From now on I want to default to that author, but having a pointer to an author is still mandatory. Either you gave me a default (and can still specify something else) or you need to be explicit.
Tab completion for strings with spaces seems weird/broken/limited - because it doesn't enclose the value with quotes (like cmd.exe would do, for example, if you type dir C:\Program <tab>). So tab completion actually breaks the invocation (if the issues above would be resolved, Get-BookDetails Ter<tab> would/will expand to Get-BookDetails Terry Pratchett which puts the last name in parameter position 1 aka 'book'.
Shouldn't be so hard, surely someone did something similar already?
Update: After another good day of tinkering and fooling around I don't see a way to make this work. The commandlet is stateless and will be instantiated over and over again. At the point in time when I can define dynamic parameters (GetDynamicParameters) I cannot access their (current) values/see what they'd be bound to - e.g. MyInvocation.BoundParameters is zero. I'll leave the question open, but it seems as if this just isn't supported. All the examples I see add a dynamic parameter based on the value of a static one - and that's not relevant here. Bugger.
I think this works. Unfortunately, it uses reflection to get at some of the cmdlet's private members for your first bullet. I got the idea from Garrett Serack. I'm not sure if I completely understood how to do the default author, so I made it so that the last valid author is stored in a static field so you don't need -Author the next time.
Here's the code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Management.Automation;
using System.Text;
using System.Threading.Tasks;
namespace PSDummy
{
internal class DynParamQuotedString {
/*
This works around the PowerShell bug where ValidateSet values aren't quoted when necessary, and
adding the quotes breaks it. Example:
ValidateSet valid values = 'Test string' (The quotes are part of the string)
PowerShell parameter binding would interperet that as [Test string] (no single quotes), which wouldn't match
the valid value (which has the quotes). If you make the parameter a DynParamQuotedString, though,
the parameter binder will coerce [Test string] into an instance of DynParamQuotedString, and the binder will
call ToString() on the object, which will add the quotes back in.
*/
internal static string DefaultQuoteCharacter = "'";
public DynParamQuotedString(string quotedString) : this(quotedString, DefaultQuoteCharacter) {}
public DynParamQuotedString(string quotedString, string quoteCharacter) {
OriginalString = quotedString;
_quoteCharacter = quoteCharacter;
}
public string OriginalString { get; set; }
string _quoteCharacter;
public override string ToString() {
// I'm sure this is missing some other characters that need to be escaped. Feel free to add more:
if (System.Text.RegularExpressions.Regex.IsMatch(OriginalString, #"\s|\(|\)|""|'")) {
return string.Format("{1}{0}{1}", OriginalString.Replace(_quoteCharacter, string.Format("{0}{0}", _quoteCharacter)), _quoteCharacter);
}
else {
return OriginalString;
}
}
public static string[] GetQuotedStrings(IEnumerable<string> values) {
var returnList = new List<string>();
foreach (string currentValue in values) {
returnList.Add((new DynParamQuotedString(currentValue)).ToString());
}
return returnList.ToArray();
}
}
[Cmdlet(VerbsCommon.Get, "BookDetails")]
public class GetBookDetails : PSCmdlet, IDynamicParameters
{
IDictionary<string, string[]> m_dummyData = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) {
{"Terry Pratchett", new [] {"Small Gods", "Mort", "Eric"}},
{"Douglas Adams", new [] {"Hitchhiker's Guide", "The Meaning of Liff"}},
{"An 'Author' (notice the ')", new [] {"A \"book\"", "Another 'book'","NoSpace(ButCharacterThatShouldBeEscaped)", "NoSpace'Quoted'", "NoSpace\"Quoted\""}} // Test value I added
};
protected override void ProcessRecord()
{
WriteObject(string.Format("Author = {0}", _author));
WriteObject(string.Format("Book = {0}", ((DynParamQuotedString) MyInvocation.BoundParameters["Book"]).OriginalString));
}
// Making this static means it should keep track of the last author used
static string _author;
public object GetDynamicParameters()
{
// Get 'Author' if found, otherwise get first unnamed value
string author = GetUnboundValue("Author", 0) as string;
if (!string.IsNullOrEmpty(author)) {
_author = author.Trim('\'').Replace(
string.Format("{0}{0}", DynParamQuotedString.DefaultQuoteCharacter),
DynParamQuotedString.DefaultQuoteCharacter
);
}
var parameters = new RuntimeDefinedParameterDictionary();
bool isAuthorParamMandatory = true;
if (!string.IsNullOrEmpty(_author) && m_dummyData.ContainsKey(_author)) {
isAuthorParamMandatory = false;
var m_bookParameter = new RuntimeDefinedParameter(
"Book",
typeof(DynParamQuotedString),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 1,
Mandatory = true
},
new ValidateSetAttribute(DynParamQuotedString.GetQuotedStrings(m_dummyData[_author])),
new ValidateNotNullOrEmptyAttribute()
}
);
parameters.Add(m_bookParameter.Name, m_bookParameter);
}
// Create author parameter. Parameter isn't mandatory if _author
// has a valid author in it
var m_authorParameter = new RuntimeDefinedParameter(
"Author",
typeof(DynParamQuotedString),
new Collection<Attribute>
{
new ParameterAttribute {
ParameterSetName = "BookStuff",
Position = 0,
Mandatory = isAuthorParamMandatory
},
new ValidateSetAttribute(DynParamQuotedString.GetQuotedStrings(m_dummyData.Keys.ToArray())),
new ValidateNotNullOrEmptyAttribute()
}
);
parameters.Add(m_authorParameter.Name, m_authorParameter);
return parameters;
}
/*
TryGetProperty() and GetUnboundValue() are from here: https://gist.github.com/fearthecowboy/1936f841d3a81710ae87
Source created a dictionary for all unbound values; I had issues getting ValidateSet on Author parameter to work
if I used that directly for some reason, but changing it into a function to get a specific parameter seems to work
*/
object TryGetProperty(object instance, string fieldName) {
var bindingFlags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public;
// any access of a null object returns null.
if (instance == null || string.IsNullOrEmpty(fieldName)) {
return null;
}
var propertyInfo = instance.GetType().GetProperty(fieldName, bindingFlags);
if (propertyInfo != null) {
try {
return propertyInfo.GetValue(instance, null);
}
catch {
}
}
// maybe it's a field
var fieldInfo = instance.GetType().GetField(fieldName, bindingFlags);
if (fieldInfo!= null) {
try {
return fieldInfo.GetValue(instance);
}
catch {
}
}
// no match, return null.
return null;
}
object GetUnboundValue(string paramName) {
return GetUnboundValue(paramName, -1);
}
object GetUnboundValue(string paramName, int unnamedPosition) {
// If paramName isn't found, value at unnamedPosition will be returned instead
var context = TryGetProperty(this, "Context");
var processor = TryGetProperty(context, "CurrentCommandProcessor");
var parameterBinder = TryGetProperty(processor, "CmdletParameterBinderController");
var args = TryGetProperty(parameterBinder, "UnboundArguments") as System.Collections.IEnumerable;
if (args != null) {
var currentParameterName = string.Empty;
object unnamedValue = null;
int i = 0;
foreach (var arg in args) {
var isParameterName = TryGetProperty(arg, "ParameterNameSpecified");
if (isParameterName != null && true.Equals(isParameterName)) {
string parameterName = TryGetProperty(arg, "ParameterName") as string;
currentParameterName = parameterName;
continue;
}
// Treat as a value:
var parameterValue = TryGetProperty(arg, "ArgumentValue");
if (currentParameterName != string.Empty) {
// Found currentParameterName's value. If it matches paramName, return
// it
if (currentParameterName.Equals(paramName, StringComparison.OrdinalIgnoreCase)) {
return parameterValue;
}
}
else if (i++ == unnamedPosition) {
unnamedValue = parameterValue; // Save this for later in case paramName isn't found
}
// Found a value, so currentParameterName needs to be cleared
currentParameterName = string.Empty;
}
if (unnamedValue != null) {
return unnamedValue;
}
}
return null;
}
}
}
Related
I try to add some new property in existing category for selected NavisWorks ModelItem`s
There is not so many example over network, and it base on same COM approach.
However there special method to add property available in API.
Only issue that objects is locked.
Is there any way to unlock it?
using ANA = Autodesk.Navisworks.Api;
...
private void addProperty(string category, string prop, string value)
{
var oDoc = Autodesk.Navisworks.Api.Application.ActiveDocument;
ModelItemCollection selectionModelItems = new ModelItemCollection();
ANA.Application.ActiveDocument.CurrentSelection.SelectedItems.CopyTo(selectionModelItems);
//Clear the current selection
ANA.Application.ActiveDocument.CurrentSelection.Clear();
try
{
foreach (ModelItem m in selectionModelItems)
{
foreach (PropertyCategory p in m.PropertyCategories)
{
if (p.DisplayName != category) continue;
var property = new DataProperty(prop, prop, new VariantData(value));
p.Properties.Add(property);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
result of execution:
Properties and categories which are created by Navisworks are read-only.
You can not add or modify properties or categories which are created by Navisworks.
You can only create or add user-defined properties - using COM.
See:
https://adndevblog.typepad.com/aec/2012/05/create-attributes-and-properties-for-model-objects-using-net-api.html
https://adndevblog.typepad.com/aec/2012/08/addmodifyremove-custom-attribute-using-com-api.html
https://forums.autodesk.com/t5/navisworks-api/navisworks-api-add-user-data-tab/td-p/2916866
Here is a code snippet (copied from xiaodong.liang forum post mentioned above) which shows how to add a user-defined property using COM:
private void addProperty() {
ComApi.InwOpState10 state;
state = ComApiBridge.ComApiBridge.State;
ModelItemCollection modelItemCollectionIn = new ModelItemCollection(Autodesk.Navisworks.Api.Application.ActiveDocument.CurrentSelection.SelectedItems);
ComApi.InwOpSelection comSelectionOut =
ComApiBridge.ComApiBridge.ToInwOpSelection(modelItemCollectionIn);
ComApi.InwSelectionPathsColl oPaths = comSelectionOut.Paths();
ComApi.InwOaPath3 oPath = (ComApi.InwOaPath3) oPaths.Last();
ComApi.InwGUIPropertyNode2 propn = (ComApi.InwGUIPropertyNode2) state.GetGUIPropertyNode(oPath, true);
ComApi.InwOaPropertyVec newPvec = (ComApi.InwOaPropertyVec) state.ObjectFactory(Autodesk.Navisworks.Api.Interop.ComApi.nwEObjectType.eObjectType_nwOaPropertyVec, null, null);
ComApi.InwOaProperty newP = (ComApi.InwOaProperty) state.ObjectFactory(Autodesk.Navisworks.Api.Interop.ComApi.nwEObjectType.eObjectType_nwOaProperty, null, null);
newP.name = "LXD_Property_Name";
newP.UserName = "LXD_Property_UserName";
newP.value = "LXD_Property_Value";
newPvec.Properties().Add(newP);
propn.SetUserDefined(0, "LXD_PropertyTab_Name", "LXD_PropertyTab_InteralName", newPvec);
}
I am using a PXSmartPanel to display a dialog allowing a user to enter a string. I would like to use a 'Non-persisted field', but that means (I think) that I would have to get the field value by calling the field on the Panel and extracting its value.
The text field's ID is cstFieldSSN and the non-persisted field's ID is UsrSSN
My method looks like this:
(I'm calling the dialog upon clicking a menu item)
// Initialize 'myPanel'
public PXFilter<PX.Objects.CR.Contact> myPanel;
// Make the 'Letters' menu available to 'Automation Steps'
public PXAction<PX.Objects.CR.Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
if (myPanel.AskExt(true) != WebDialogResult.OK) return;
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
Dictionary<string, string> parameters = new Dictionary<string, string>();
parameters["ContactID"] = contact.ContactID.ToString();
/** Here's the issue **/
parameters["SSN"] = myPanel.Current.UsrSSN;
throw new PXReportRequiredException(parameters, reportID, "");
if (ex != null) throw ex;
return adapter.Get();
}
I'm getting
'PX.Objects.CR.Contact' does not contain a definition for 'UsrSSN' and no extension method 'UsrSSN' accepting a first argument of type 'PX.Objects.CR.Contact' could be found (are you missing a using directive or an assembly reference?)
Could someone help me out or point me to a resource?
Thanks to #Brendan, my final code looks like this:
// Initialize 'myPanel'
public PXFilter<PX.Objects.CR.Contact> myPanel;
// Make the 'Letters' menu available to 'Automation Steps'
public PXAction<PX.Objects.CR.Contact> letters;
[PXUIField(DisplayName = "Letters", MapEnableRights = PXCacheRights.Select)]
[PXButton(SpecialType = PXSpecialButtonType.Report)]
protected virtual IEnumerable Letters(PXAdapter adapter, string reportID)
{
// Launch the PXSmartPanel dialog and test result
if (myPanel.AskExt(true) == WebDialogResult.OK)
{
PXReportRequiredException ex = null;
Contact contact = Base.Caches[typeof(Contact)].Current as Contact;
Dictionary<string, string> parameters = new Dictionary<string, string>();
//*** Get the extended class
var myExt = myPanel.Current.GetExtension<ContactExt>();
parameters["ContactID"] = contact.ContactID.ToString();
//*** Get the extended class's custom field value
parameters["SSN"] = myExt.UsrSSN;
throw new PXReportRequiredException(parameters, reportID, "");
if (ex != null) throw ex;
}
return adapter.Get();
}
But I also had to set the CommitChanges property on the text field to True so that the value would be pushed back to the cached Contact, allowing me to use it.
I've been trying to create a new base class for a Windows Forms form. I want to have this base class go through all the tableadapters it has on it and update their connection strings without anyone adding any code to the form. They just put the tableadapters on the form and don't worry about the connection string settings as it's all handled in the base class.
The problem is my reflection code can find the property fine, but it can't set it. How can I fix it?
Below is the code:
public class cFormWS : Form
{
public string ConnectionStringToUse { get; set; }
public cFormWS()
{
Load += cFormWS_Load;
}
void cFormWS_Load(object sender, EventArgs e)
{
InitiliseTableAdapters();
}
private void InitiliseTableAdapters()
{
var ListOfComponents = EnumerateComponents();
foreach (var ItemComp in ListOfComponents)
{
if (ItemComp.ToString().ToLower().EndsWith("tableadapter"))
{
var ItemCompProps = ItemComp.GetType().GetRuntimeProperties();
var TASQLConnection =
ItemCompProps.FirstOrDefault(
w => w.PropertyType == typeof(System.Data.SqlClient.SqlConnection));
if (TASQLConnection != null)
{
var property = typeof(System.Data.SqlClient.SqlConnection).GetProperty("ConnectionString");
// How do I set the value?
string value = "some new connection string";
var ConvertedProperty = Convert.ChangeType(value, property.PropertyType);
// I tried seting a value. It is not working:
// "object does not match target type"
property.SetValue(TASQLConnection, ConvertedProperty, null);
//// I tried using a method. It is not working:
//// "object does not match target type"
//var m = property.SetMethod;
//ParameterInfo[] parameters = m.GetParameters();
//m.Invoke(m, parameters); // m.Invoke(this, parameters); // m.Invoke(ItemComp, parameters);
}
}
}
}
private IEnumerable<Component> EnumerateComponents()
{
return from field in GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
where typeof(Component).IsAssignableFrom(field.FieldType)
let component = (Component)field.GetValue(this)
where component != null
select component;
}
When you do SetValue, you need to pass in the object that you wish to set the property on.
In your first example code, you passed in ItemComp: This is incorrect, since the ConnectionString is a property of the SqlConnection which is a property of ItemComp
In your edited question (and my original answer) you pass in the TASqlConnection. However, this is not the object, but a PropertyInfobased of the object
The correct way is to get the value from the ItemComp object and pass that in:
property.SetValue(TASQLConnection.GetValue(ItemComp), ConvertedProperty, null);
ORIGINAL (INCORRECT) ANSWER:
You're trying to set a ConnectionString property of ItemComp. The ConnectionString is not a property of the TableAdapter, but of the SqlConnection (which is a property of the TableAdapter).
The correct way of setting the property would be this:
property.SetValue(TASQLConnection, ConvertedProperty, null);
I am using Chello (the c# wrapper for the Trello API). I need to pass the argument "createCard" as per the documentation here: https://trello.com/docs/api/card/index.html
And this is the function I am using from Chello:
public IEnumerable<CardUpdateAction> ForCard(string cardId, object args)
{
string queryString = BuildQueryString(args);
return GetRequest<List<CardUpdateAction>>("/cards/{0}/actions?{1}", cardId, queryString);
}
I have tried calling this in this way:
List<CardUpdateAction> cua = chello.CardUpdates.ForCard("5264d37736695b2821001d7a","createCard").ToList();
but I get the error: Parameter Count Mismatch
on this function:
protected static string BuildQueryString(object args)
{
string queryString = String.Empty;
if (args != null)
{
StringBuilder sb = new StringBuilder();
foreach (var prop in args.GetType().GetProperties())
{
sb.AppendFormat("{0}={1}&", prop.Name, prop.GetValue(args, null));
}
if (sb.Length > 0) sb.Remove(sb.Length - 1, 1);
queryString = sb.ToString();
}
return queryString;
}
The problem is the fact that your API you are using expects you to pass in a class that has public properties equal to the tags you want to use.
This is very easy to do using Anonymous Types (I am doing a slightly different example to help illustrate a point)
//This will cause BuildQueryString to return "actions=createCard&action_fields=data,type,date"
var options = new { actions = "createCard", action_fields = "data,type,date" };
List<CardUpdateAction> cua = chello.CardUpdates.ForCard("5264d37736695b2821001d7a",options).ToList();
string is an object. Every type in .NET platform inherits from Object. This is called Unified Type System.
On the other hand, we have the Liskov Substitution Principle, which put simply, says that if B is a subtype of A (B is A), then you should be able to use B, wherever A is used.
Based on these reasons, you can pass string to any method that accepts an object as an argument.
You can test it:
public void DoSomething(object args)
{
}
public void Main()
{
DoSomething("some string argument, instead of the object");
}
It works just fine. No error.
I have a C# app that exports to Excel using ClosedXML. It works fine but just ran into an issue where when i hit the :
var ms = new MemoryStream();
workbook.SaveAs(ms);
I get an exception:
' ', hexadecimal value 0x0B, is an invalid character
Its definitely data related because it I look at certain data it works fine but other data it causes this issue.
how can i figure out which character is causing the issue? Also, once I figure that out, what is the best way of finding where this character is within my data?
Since you have invalid characters in the data / strings you put into the ClosedXML sheet, you have to find them and get them out.
The simplest solution is to add
.Replace((0x0B).ToString(), " ")
to all your strings to get rid of the vertical tabs and replace them with spaces.
Since ClosedXML is an open source project, the simplest way of tracking the error down would be building it from the source *, and then running your code against the library in debug mode.
Once you see the full stack trace, you should be able to identify the spot from which the error is coming. Good chances are that it is a bug in the way the ClosedXML project uses Microsoft XML libraries, because the error that you mentioned is reported by a library outside the ClosedXML project.
* I downloaded the project, and tried building it. Everything in the closedxml-79843.zip package builds correctly.
Since ClosedXML doesn't prevent you from using the 0x0B character in values, you'll either have to scrub your data of it yourself (as suggested by #Raidri), or you could force and exception, or do a string replace when the value is set. I've created a sample program below which uses Castle's Dynamic Proxy to wrap the IXLWorksheet and IXLCell interfaces. Firstly, we proxy the IXLWorksheet values (which returned from adding a new worksheet as in the example below, or by indexing an existing worksheet). This needs to be done manually via a method call; everything else from then on is set up. When accessing cells (via the Cell methods, or the ActiveCell property) a proxied IXLCell value is returned which checks the data being set via the Value property and the SetValue method. The check is done in the ValidateMethodInterceptor as per the comments. This whole mechanism can be left in your codebase and turned on/off via a switch in the Program.Proxy method if you so desire.
As a further alternative, the package EPPlus (which has similar functionality to ClosedXML) doesn't crash when confronted with the VT character. Instead it replaces it with the value _x00B_. Perhaps a switch would be more beneficial?
internal class Program
{
private static void Main(string[] args)
{
var stream = new MemoryStream();
using (stream)
{
using (var workbook = new XLWorkbook())
{
using (var worksheet = Proxy(workbook.Worksheets.Add("Sheet 1")))
{
worksheet.Cell("A1").Value = "This is a test";
worksheet.Cell("A2").Value = "This \v is a test";
workbook.SaveAs(stream);
}
}
}
}
public static IXLWorksheet Proxy(IXLWorksheet target)
{
var generator = new ProxyGenerator();
var options = new ProxyGenerationOptions { Selector = new WorksheetInterceptorSelector() };
return generator.CreateInterfaceProxyWithTarget<IXLWorksheet>(target, options);
}
}
public class WorksheetInterceptorSelector : IInterceptorSelector
{
private static readonly MethodInfo[] methodsToAdjust;
private readonly ProxyCellInterceptor proxyCellInterceptor = new ProxyCellInterceptor();
static WorksheetInterceptorSelector()
{
methodsToAdjust = typeof(IXLWorksheet).GetMethods()
.Where(x => x.Name == "Cell")
.Union(new[] { typeof(IXLWorksheet).GetProperty("ActiveCell").GetGetMethod() })
.ToArray();
}
#region IInterceptorSelector Members
public IInterceptor[] SelectInterceptors(System.Type type, System.Reflection.MethodInfo method, IInterceptor[] interceptors)
{
if (!methodsToAdjust.Contains(method))
return interceptors;
return new IInterceptor[] { proxyCellInterceptor }.Union(interceptors).ToArray();
}
#endregion
}
public class CellInterceptorSelector : IInterceptorSelector
{
private static readonly MethodInfo[] methodsToAdjust = new[] { typeof(IXLCell).GetMethod("SetValue"), typeof(IXLCell).GetProperty("Value").GetSetMethod() };
private ValidateMethodInterceptor proxyCellInterceptor = new ValidateMethodInterceptor();
#region IInterceptorSelector Members
public IInterceptor[] SelectInterceptors(System.Type type, MethodInfo method, IInterceptor[] interceptors)
{
if (method.IsGenericMethod && method.Name == "SetValue" || methodsToAdjust.Contains(method))
return new IInterceptor[] { proxyCellInterceptor }.Union(interceptors).ToArray();
return interceptors;
}
#endregion
}
public class ProxyCellInterceptor : IInterceptor
{
#region IInterceptor Members
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
//Wrap the return value
invocation.ReturnValue = Proxy((IXLCell)invocation.ReturnValue);
}
#endregion
public IXLCell Proxy(IXLCell target)
{
var generator = new ProxyGenerator();
var options = new ProxyGenerationOptions { Selector = new CellInterceptorSelector() };
return generator.CreateInterfaceProxyWithTarget<IXLCell>(target, options);
}
}
public class ValidateMethodInterceptor : IInterceptor
{
#region IInterceptor Members
public void Intercept(IInvocation invocation)
{
var value = invocation.Arguments[0];
//Validate the data as it is being set
if (value != null && value.ToString().Contains('\v'))
{
throw new ArgumentException("Value cannot contain vertical tabs!");
}
//Alternatively, you could do a string replace:
//if (value != null && value.ToString().Contains('\v'))
//{
// invocation.Arguments[0] = value.ToString().Replace("\v", Environment.NewLine);
//}
invocation.Proceed();
}
#endregion
}