Get Attributes of a Func - c#

I need to check an incoming Func<TIn, TOut> for a specific attribute.
So far I have:
var methodInfo = cachedMethod.GetMethodInfo();
var isCachable = methodInfo.CustomAttributes.
Any(x => x.AttributeType == typeof(CachedAttribute));
However I'm finding that the CustomAttributes property is empty.
What's the proper way to find the attributes applied to an incoming func? In my case the func is a static method in another class.
Update
Here's a small sample showing how I'm trying to use this:
The HTML helper:
public static IHtmlString CachedPartial<TModel>(this HtmlHelper helper,
string partialName,
TModel model,
Func<string, string> cachedMethod)
{
var methodInfo = cachedMethod.GetMethodInfo();
var isCachable = methodInfo.IsDefined(typeof (CachedAttribute));
if (!isCachable)
{
throw new Exception("...");
}
return new MvcHtmlString(cachedMethod("foo"));
}
The cached method
[Cached]
public static string GenrateSiteMapGraphHtml(string siteCode)
{
return "Foo";
}
This is being called from a Razor view:
#Html.CachedPartial("Foo",
Model,
HtmlHelperExtensions.GenrateSiteMapGraphHtml)

I may not quite understand what is your problem, but I'll suggest two things:
You need to be sure that CustomAttributes isn't empty, use
if (methodInfo.CustomAttributes!=NULL) //or whatever is analogue of NULL for that type of property
{/some action/}
You can't figure out why is this property empty. The first thing to check is visibility.
In my case the func is a static method in another class
So go and check out if that class is visible for the area of code where you are trying to invoke that static method

Related

Can I create a HtmlHelper specifically for IEnumerable properties?

I would like to create a HtmlHelper that can be used on IEnumerable properties.
The aim is to use it like this:
#Html.DisplayForEnumerable(m => m.EnumerableItemsProperty, "ViewTemplateName");
If possible I would like to use the m => m.Items lambda syntax (as opposed to passing through Model.Items).
This is my best effort so far. But I'm not sure how to get the items variable from the expression parameter.
I suspect I may have to use something like IEnumerable<TValue> as the return type of the expression, but I'm quite new to generics and I've no idea how to implement this.
public static MvcHtmlString DisplayForEnumerable<TModel>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable>> expression, string templateName, object additonalViewData = null)
{
var sb = new StringBuilder();
// how to get items variable?
foreach (var item in items)
{
var item1 = item;
sb.Append(html.DisplayFor(m => item1, templateName, additonalViewData));
}
return MvcHtmlString.Create(sb.ToString());
}
Update
To clarify - I am taking this approach because I would like to be able so specify differnt templates for the same model. And the normal DisplayFor() enumeration does not occur if you specify a particular template.
I know I could just enumerate through manually, but I'd rather use this method unless someone more knowledgable advises otherwise.
You helper will need to be
public static MvcHtmlString DisplayForEnumerable<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, string templateName, object additionalViewData = null)
{
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
IEnumerable collection = metaData.Model as IEnumerable;
if (collection == null)
{
return helper.DisplayFor(expression, templateName, additionalViewData );
}
StringBuilder html = new StringBuilder();
foreach (var item in collection)
{
html.Append(helper.DisplayFor(m => item, templateName, additionalViewData).ToString());
}
return MvcHtmlString.Create(html.ToString());
}
Note the code allows you to pass either a single T or IEnumerable<T> (although the method name now does not really make sense). If you wanted to limit it to only IEnumerable<T> you could throw an InvalidCastException if collection == null
Note that this approach will not work if you wanted to generate form controls for a collection (for example a EditorForEnumerable() method) because the required collection indexers will not be added to the generate name attributes. A better approach is to use the built-in DisplayFor() and EditorFor() methods which will generate the correct html for both T and IEnemerable<T>
Assuming you have a Person.cs class, create a partial view in /Views/Shared/DisplayTemplates/Person.cshtml (note the name of the file must match the name of the class) and in the view simply use
#Html.DisplayFor(m => m.yourCollectionProperty)
You can also create specific display and editor templates for each controller, for example /Views/yourControllerName/DisplayTemplates/Person.cshtml. This allows you to use one template in /Persons/Index and another template in /Organisation/Details/1 which might display a list of Person associated with an Organisation
And finally, if you do need 2 different templates for Person in the same controller, you can use view models, for example class PersonVM and class AssociatedPersonVM and create an EditorTemplate for each

RazorEngine extension methods in a template

I am using the Razor engine: https://github.com/Antaris/RazorEngine.
I am creating a model dynamically. I'm trying to include a extension method in a template but it inst recognising the extension class. I am receiving the following error message " 'string' does not contain a definition for 'ToUpperFirstLetter' "
Model Creation
dynamic model = new ExpandoObject();
((IDictionary<string, object>)model).Add("Forename", "john");
Template
#using Namespace.Extensions
#{
ViewBag.Title = "Title";
}
Hello #Model.Forename.ToUpperFirstLetter()
Extension Class
namespace Namespace.Extensions
{
public static class StringExtensions
{
public static string ToUpperFirstLetter(this string source)
{
return ....removed for abbreviation
}
}
}
Razor Parsing
ITemplate template = Razor.Resolve(template, model);
string result = template.Run(new ExecuteContext());
Edit
I also wanted to create an extension method on the Dynamic ExpandoObject. To see if a value inside the Model exists. I am receiving the following error message "System.Dynamic.ExpandoObject' does not contain a definition for 'HasValue'"
Extension Class
public static class ExpandoObjectExtensions
{
public static bool HasValue(this ExpandoObject source, string key)
{
return ((IDictionary<String, object>)source).ContainsKey(key);
}
}
Extension Use
#if(Model.HasValue("Hello"))
{
#Model.Hello
}
EDIT TWO
The below works and goes into the extension method
var o = new object();
var bool = o.HasValue("value");
The dynamic model still throws an exception - RuntimeBinderException: 'System.Dynamic.ExpandoObject' does not contain a definition for 'HasValue'
dynamic model = new ExpandoObject();
var bool = model.HasValue("value");
The problem you're seeing is that you're trying to execute a method ToUpperFirstLetter(this string source) when in reality, the Forename property is dynamic. I would imagine if you did something like:
#(((string)Model.Forename).ToUpperFirstLetter())
... it would probably work. This is all down to the use of dynamic. Dynamic is handled at runtime through late binding, but as RazorEngine is compiling your view, it's trying to statically link the method that expects a string where you're providing dynamic. The compiler doesn't know that Forename is a string.
You also cannot have a dynamic first argument to an extension method, e.g.:
public static string ToUpperFirsLetter(this dynamic source) { ... }
Because extension methods are resolved at compile time to explicit method calls, but the compiler doesn't know how to handle dynamic first arguments in extension methods.
EDIT
Based on your response, if you want to achieve these sort of extension methods where dynamic might be the type, you'll be better of using object and testing for types, e.g.:
public static class StringExtensions
{
public static string ToUpperFirstLetter(this object val)
{
string str = val as string;
if (string.IsNullOrWhitespace(str)) return str;
return str.Substring(0, 1).ToUpper() + str.Substring(1);
}
}
public static class ExpandoObjectExtensions
{
public static bool HasValue(this object val, string key)
{
var expando = val as ExpandoObject;
if (expando == null) return false;
return ((IDictionary<string, object>)expando).ContainsKey(key);
}
}
... Not ideal, but it is still a typesafe way of using extension methods on dynamic.

How to refer to a method?

I'm trying to create a router for a web application (yes, I know solutions already exist).
Thus far I've got this:
class Route
{
public static RegexOptions DefaultOptions = RegexOptions.IgnoreCase;
Regex regex;
Type controller;
MethodInfo action;
public Route(string pattern, Type controller)
{
this.regex = new Regex(string.Format("^(?:{0})$", pattern), DefaultOptions);
this.controller = controller;
}
}
And this:
Route[] routes = {
new Route(#"/user:(?<id>\d+)", typeof(UserController))
};
When the URL matches that regex, it should call the method action in class controller. I'm thinking typeof() is the only way I can pass the class around, but how about the method?
I think MethodInfo is the object I want, because with that I should be able to invoke it, but from an API perspective, what should the 3rd argument to Route constructor be, and how should we call it?
I prefer a strongly-typed solution rather than some string shenanigans.
I don't think there is a way to refer to a instance method in c sharp without an instance. Unless you want the user of the API to define a MethodInfo object to pass in, string name may be the best way.
static class Program
{
static void Main()
{
Route r = new Route("pattern", typeof(UserController), "Action");
}
}
public class Route
{
public Route(string pattern, Type type, string methodName)
{
object objectToUse = Activator.CreateInstance(type, null);
MethodInfo method = type.GetMethod(methodName);
string[] args = new string[1];
args[0] = "hello world";
method.Invoke(objectToUse, args);
}
}
public class UserController
{
public void Action(string message)
{
MessageBox.Show(message);
}
}
You're looking for the non-existent infoof operator.
Unfortunately, it doesn't exist.
The simplest answer is to take a string.
Alternatively, you can take an expression tree.
The disadvantage to taking an expression tree is that your caller must pass all parameters.
You could work that into your framework (by taking an Expression<Func<parameters, ResultType>>), and perhaps even compile the expression tree to call the actions. (which will result in much faster calls than reflection)
You can create something like this:
MethodInfo GetMethodInfo(LambdaExpression expression)
{
var call = expression.Body as MethodCallExpression;
if(call == null) throw new ArgumentException("Must be a method call","expression");
return call.Method;
}
MethodInfo GetMethodInfo<T>(Expression<Action<T>> expression)
{
return GetMethodInfo(expression);
}
MethodInfo GetMethodInfo<T, TResult>(Expression<Func<T, TResult>> expression)
{
return GetMethodInfo(expression);
}
And use it like this:
MethodInfo action = GetMethodInfo((UserController c) => c.ActionMethod());
// or
MethodInfo action = GetMethodInfo((UserController c) =>
c.ActionMethodWithParams(default(int)));
This won't call the method immediately because it is an expression tree, i.e., a syntactic tree representing the method call.

How to Create a Generic Method and Create Instance of The Type

I want to create a helper method that I can imagine has a signature similar to this:
public static MyHtmlTag GenerateTag<T>(this HtmlHelper htmlHelper, object obj)
{
// how do I create an instance of MyAnchor?
// this returns MyAnchor, which has a MyHtmlTag base
}
When I invoke the method, I want to specify a type of MyHtmlTag, such as MyAnchor, e.g.:
<%= Html.GenerateTag<MyAnchor>(obj) %>
or
<%= Html.GenerateTag<MySpan>(obj) %>
Can someone show me how to create this method?
Also, what's involved in creating an instance of the type I specified? Activator.CreateInstance()?
Thanks
Dave
You'd use Activator.CreateInstance<T>:
public static MyHtmlTag GenerateTag<T>(this HtmlHelper htmlHelper, object obj)
{
T value = Activator.CreateInstance<T>();
// Set properties on value/ use it/etc
return value;
}
There's existing functionality in MvcContrib you may want to check out called "FluentHtml". It looks like this:
<%=this.TextBox(x => x.FirstName).Class("required").Label("First Name:")%>
<%=this.Select(x => x.ClientId).Options((SelectList)ViewData["clients"]).Label("Client:")%>
<%=this.MultiSelect(x => x.UserId).Options(ViewModel.Users)%>
<%=this.CheckBox("enabled").LabelAfter("Enabled").Title("Click to enable.").Styles(vertical_align => "middle")%>
You need to add the generic constraint of new() to T, then do the following:
public static MyHtmlTag GenerateTag<T> (this HtmlHelper helper, T obj) where T : MyHtmlTag, new()
{
T val = new T ();
return val;
}

Add property to anonymous type after creation

I use an anonymous object to pass my Html Attributes to some helper methods.
If the consumer didn't add an ID attribute, I want to add it in my helper method.
How can I add an attribute to this anonymous object?
The following extension class would get you what you need.
public static class ObjectExtensions
{
public static IDictionary<string, object> AddProperty(this object obj, string name, object value)
{
var dictionary = obj.ToDictionary();
dictionary.Add(name, value);
return dictionary;
}
// helper
public static IDictionary<string, object> ToDictionary(this object obj)
{
IDictionary<string, object> result = new Dictionary<string, object>();
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(obj);
foreach (PropertyDescriptor property in properties){
result.Add(property.Name, property.GetValue(obj));
}
return result;
}
}
I assume you mean anonymous types here, e.g. new { Name1=value1, Name2=value2} etc. If so, you're out of luck - anonymous types are normal types in that they're fixed, compiled code. They just happen to be autogenerated.
What you could do is write new { old.Name1, old.Name2, ID=myId } but I don't know if that's really what you want. Some more details on the situation (including code samples) would be ideal.
Alternatively, you could create a container object which always had an ID and whatever other object contained the rest of the properties.
If you're trying to extend this method:
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues);
Although I'm sure Khaja's Object extensions would work, you might get better performance by creating a RouteValueDictionary and passing in the routeValues object, add your additional parameters from the Context, then return using the ActionLink overload that takes a RouteValueDictionary instead of an object:
This should do the trick:
public static MvcHtmlString MyLink(this HtmlHelper helper, string linkText, string actionName, object routeValues)
{
RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
// Add more parameters
foreach (string parameter in helper.ViewContext.RequestContext.HttpContext.Request.QueryString.AllKeys)
{
routeValueDictionary.Add(parameter, helper.ViewContext.RequestContext.HttpContext.Request.QueryString[parameter]);
}
return helper.ActionLink(linkText, actionName, routeValueDictionary);
}
public static string TextBox(this HtmlHelper html, string value, string labelText, string textBoxId, object textBoxHtmlAttributes, object labelHtmlAttributes){}
This would accept the id value the textbox should have and the label should refer to.
If the consumer now doesn't include the "id" property in the textBoxHtmlAttributes, the method will create an incorrect label.
I can check through reflection if this attribute is added in the labelHtmlAttributes object. If so, I want to add it or create a new anonymous object that has it added.
But because I can't create a new anonymous type by walking through the old attributes and adding my own "id" attribute, I'm kind of stuck.
A container with a strongly typed ID property and then an anonymous typed "attributes" property would require code rewrites that don't weigh up to the "add an id field" requirement.
Hope this response is understandable. It's the end of the day, can't get my brains in line anymore..

Categories