For example:
public static string GetXPath<TModel, TMember>(this TModel model, Expression<Func<TModel, TMember>> expr)
{
...
}
var result = new MyObject().GetXPath(x=> x.DataList[1].Value);
Console.WriteLine(result);
And this will print out something like this (structure without attributes, just like DataContractSerializer do by default):
/DataList[1]/Value
Related
I am looking to use CsvHelper dynamically by building up Expressions in code which represent property member access for a given type.
The method I am trying to pass these expressions to has the following signature:
public virtual CsvPropertyMap<TClass, TProperty> Map<TProperty>( Expression<Func<TClass, TProperty>> expression )
{
//
}
So you would normally call it, for any given type you want to map, like this (for a type with a property called 'stringProperty'):
mapper.Map(x => x.StringProperty);
Passing in a lambda which is converted internally into an Expression<Func<T, object>>
I have tried to create this expression in code, using Expressions. At compile time it all works fine (in that it returns an Expression<Func<TModel, object>>), but at runtime I get an exception 'not a member access'. Here is the code which takes a PropertyInfo object representing the property I want to map:
private Expression<Func<TModel, object>> CreateGetterExpression( PropertyInfo propertyInfo )
{
var getter = propertyInfo.GetGetMethod();
Expression<Func<TModel, object>> expression = m => getter.Invoke( m, new object[] { } );
return expression;
}
Basically, how do I build that Expression up properly in code?
Just try something looks like this:
public static Expression<Func<T, P>> GetGetter<T, P>(string propName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propName);
return Expression.Lambda<Func<T, P>>(property, parameter);
}
public static Expression<Func<T, P>> GetGetter<T, P>(PropertyInfo propInfo)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propInfo);
return Expression.Lambda<Func<T, P>>(property, parameter);
}
It's example of usages:
private class TestCalss
{
public int Id { get; set; }
}
private static void Main(string[] args)
{
var getter = GetGetter<TestCalss, int>(typeof(TestCalss).GetProperty("Id")).Compile();
Console.WriteLine(getter(new TestCalss { Id = 16 }));
}
#Html.DisplayRowForWithDisplayName(model => model.CustomerDetail.IfNotNullReturnDefault(x => x.Id))
Extension method:
public static TResult IfNotNullReturnDefault<TSource, TResult>(this TSource source, Func<TSource, TResult> function)
where TSource : class
where TResult : struct
{
if (source == null)
{
return default(TResult);
}
MVC builder
public static MvcHtmlString DisplayRowForWithDisplayName<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var model = metaData.Model;
var displayName = metaData.DisplayName;
...
but I get exception here:
var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
Exception:
System.InvalidOperationException: Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
at System.Web.Mvc.ModelMetadata.FromLambdaExpression[TParameter,TValue](Expression`1 expression, ViewDataDictionary`1 viewData, ModelMetadataProvider metadataProvider)
at System.Web.Mvc.ModelMetadata.FromLambdaExpression[TParameter,TValue](Expression`1 expression, ViewDataDictionary`1 viewData)
at System.Web.Mvc.HtmlHelperExtensions.DisplayRowForWithDisplayName[TModel,TValue](HtmlHelper`1 html, Expression`1 expression) in ...
How to heck if is not null in MVC?
For example:
Html.TextBoxFor(x => x.ModelProperty)
If I were to get an expression like this as a method argument, how would I get the referenced property from the expression? My experience with expressions is somewhat limited and based on what I know, I don't get how this works.
You can get property name easily like this:
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var propName = metadata.PropertyName;
Or you can get property and its attributes:
MemberExpression memberExpression = (MemberExpression) expression.Body;
var member = memberExpression.Member as PropertyInfo;
var attributes = member.GetCustomAttributes();
For example you can write a simple method that generates a input element like this:
public static MvcHtmlString TextboxForCustom<TModel, TResult>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TResult>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var propName = metadata.PropertyName;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<input type=\"text\" id=\"{0}\" />", propName);
return MvcHtmlString.Create(sb.ToString());
}
Take a look at my answer here.
I once wrote my own EditorFor, it had the following definition:
public static MvcHtmlString MyHtmlEditorFor<TModel, TProperty>(this HtmlHelper<TModel> h, Expression<Func<TModel, TProperty>> expression)
{
// ...
}
To get the information of the property I used the ModelMetadata class:
ModelMetadata m = ModelMetadata.FromLambdaExpression(expression, h.ViewData);
var value = m.Model;
In my MVC application, I've created a helper which should take an enum from the model and then display radio buttons for all of the other available enums of that type.
For example, you have the enum Satus which has Active, Inactive, Closed and the model for the page has Status = Status.Active so you want to display radio buttons for Inactive and Closed.
Going forward with this example, the MVC view calls helper RadioButtonForEnum:
#Html.RadioButtonForEnum(model => model.Status)
RadioButtonForEnum then gets the list of all Enums of that type and prints them out as radio buttons; however, I'm not sure how to get to the enum that was passed to exclude it from names
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var names = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
foreach (var name in names)
{
var id = string.Format(
"{0}_{1}_{2}",
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
metaData.PropertyName,
name
);
var radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
sb.AppendFormat("<label for=\"{1}\">{0}{2}</label>", radio, id, HttpUtility.HtmlEncode(StringHelpers.PascalCaseToSpaces(name)));
}
return MvcHtmlString.Create(sb.ToString());
}
Something like
var modelValue = expression.Compile()(htmlHelper.ViewData.Model);
...
foreach (var name in names.Where(s => s != modelValue.ToString())
...
You need the model instance to get the current enum value to avoid, you could get to it inside the HtmlHelper by using HtmlHelper.ViewData.Model as above.
I don't think you have the model object, as-is. You could add it as another parameter:
#Html.RadioButtonForEnum(Model, model => model.Status)
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, TModel model, Expression<Func<TModel, TProperty>> expression)
{
string currentStatusName = expression.Compile()(model).ToString();
...
Quick and dirty way:
foreach (var name in names)
{
if (name == metaData.Model.ToString())
continue;
...
}
Completely untested here as I don't have my own computer, but what about changing your signature to:
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Enum Status)
This will allow you to pass in the Status enum and interrogate its value:
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Enum Status){
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var names = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
foreach (var name in names)
{
if (!name.Equals(Status.ToString()){
var id = string.Format(
"{0}_{1}_{2}",
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
metaData.PropertyName,
name
);
var radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
sb.AppendFormat("<label for=\"{1}\">{0}{2}</label>", radio, id, HttpUtility.HtmlEncode(StringHelpers.PascalCaseToSpaces(name)));
}
}
And you would call it from your view like this:
#Html.RadioButtonForEnum(model => model.Status, Model.Status)
For sake of simplicity, imagine the following code:
I want to create a Foo:
public class Foo
{
public string Bar { get; set; }
}
And pass it to a special Html Helper method:
Html.SomeFunction(f => f.Bar);
Which is defined as:
public string SomeFunction<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
I want to get the value of Bar inside of this function, but have absolutely no idea how to get it.
Simply compile the expression and get the value.
Func<TModel, TValue> method = expression.Compile();
TValue value = method(html.ViewData.Model);
// might be a slightly different property, but you can get the ViewModel
// from the HtmlHelper object.
You will need to call Compile() on the expression to get the Func and then execute that.
public string SomeFunction<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
TValue valueOfBar = expression.Compile()(html.Model); // Assumes Model is accessible from html.
// Do stuff
}
Side note: If there isn't any need for the dynamic expressions or expression analysis you might as well pass the Func directly in instead.
For those that are using expression without MVT Model, one would obtain name and value of property in a following way.
public static string Meth<T>(Expression<Func<T>> expression)
{
var name = ((MemberExpression)expression.Body).Member.Name;
var value = expression.Compile()();
return string.Format("{0} - {1}", name, value);
}
use:
Meth(() => YourObject.Property);
in Microsoft.AspNetCore.Mvc.Rendering there is helpfull valuefor method;
public static string ValueFor<TModel, TResult>(this IHtmlHelper htmlHelper, Expression<Func<TModel, TResult>> expression);
public string SomeFunction<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression){
var valueOfExpression = html.ValueFor(expression);
//do your stuff
}
Using Compile() will use the Roslyn compiler-framework and will emit MSIL-code that will be dynamically loaded into your application. This executable code takes up memory, and in contrast to "normal" memory it is not subject to garbage collection nor can you free it yourself. If you do this too frequently (like regularly during SQL generation) you will run out of memory eventually. I ran into this issue and open-sourced my solutions as an open-source library:
https://www.nuget.org/packages/MiaPlaza.ExpressionUtils