I have an extension generic method
public static void AddError<TModel>(
this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression,
string resourceKey,
string defaultValue)
{
// How can I get a reference to TModel object from expression here?
}
I need to get the reference to TModel object from expression.
This method called by the following code:
ModelState.AddError<AccountLogOnModel>(
x => x.Login, "resourceKey", "defaultValue")
You cannot get to the TModel object itself without passing it into the method. The expression you are passing in is only saying "take this property from a TModel". It isn't actually providing a TModel to operate on. So, I would refactor the code to something like this:
public static void AddError<TModel>(
this ModelStateDictionary modelState,
TModel item,
Expression<Func<TModel, object>> expression,
string resourceKey,
string defaultValue)
{
// TModel's instance is accessible through `item`.
}
Then your calling code would look something like this:
ModelState.AddError<AccountLogOnModel>(
currentAccountLogOnModel, x => x.Login, "resourceKey", "defaultValue")
I imagine you really want the text "Login" to use to add a new model error to the ModelStateDictionary.
public static void AddError<TModel>(this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression, string resourceKey, string defaultValue)
{
var propName = ExpressionHelper.GetExpressionText(expression);
modelState.AddModelError(propName, GetResource("resourceKey") ?? defaultValue);
}
Assume you have some resource factory/method that returns null if the resource isn't found, that's just for illustration.
Related
My Html helper method looks like following
public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper,
string propertyName, LayoutHelper layout, TemplateType templateType = TemplateType.Screen)
{
//...
}
I want to convert my property name into following
Expression<Func<TModel, string>> expression
Any help will be much appreciated
It looks like you want to call ModelMetadata.FromLambdaExpression, not FromStringExpression. You can create an expression like
x => x.PropertyName
from scratch, like this:
// Get a reference to the property
var propertyInfo = ExpressionHelper.GetPropertyInfo<TModel>(propertyName);
var model = ExpressionHelper.Parameter<TModel>();
// Build the LINQ expression tree backwards:
// x.Prop
var key = ExpressionHelper.GetPropertyExpression(model, propertyInfo);
// x => x.Prop
var keySelector = ExpressionHelper.GetLambda(typeof(TModel), propertyInfo.PropertyType, model, key);
To make the code more readable, the nitty-gritty expression tree manipulation is moved into this helper class:
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
}
Building the expression tree from scratch gives you the most flexibility in creating the lambda expression. Depending on the target property type, it may not always be an Expression<Func<TModel, string>> - the last type could be an int or something else. This code will build the proper expression tree no matter the target property type.
Referring to the following for reference
Creating Expression Trees by Using the API
Expression<Func<TModel, string>> GetPropertyExpression<TModel>(string propertyName) {
// Manually build the expression tree for
// the lambda expression model => model.PropertyName.
var parameter = Expression.Parameter(typeof(TModel), "model");
var property = Expression.Property(parameter, propertyName);
var expression = Expression.Lambda<Func<TModel, string>>(property, parameter);
return expression;
}
Which would allow you to derive the expression in the helper
public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper, string propertyName,
LayoutHelper layout, TemplateType templateType = TemplateType.Screen) {
Expression<Func<TModel, string>> expression = GetPropertyExpression<TModel>(propertyName);
var propertyMetadata = ModelMetadata.FromStringExpression(expression, helper.Html.ViewData);
//...other code...
}
Expression is just a wrapper around the lambda that creates a tree-style data structure. Things like HTML helpers need this so they can introspect the lambda to determine things like the name of the property. The meat of the type is in the Func<TModel, string>, which indicates that it requires a lambda that takes a class instance of some type (generic) and returns a string (the property value). In other words:
m => m.Foo
Where m is parameter to the lambda, and would likely be executed by passing in your model. The m, here, is analogous to a typed param to a normal method, so it can be named anything any other variable could be named. The return value, then, is Model.Foo, where Foo is the property you're accessing.
I have the following enum
public enum StatusEnum
{
Open= 1,
SemiOpen = 2,
Closed= 3
}
I pass it in my ASP.NET 5 view to my custom HTML Helper
#Html.EnumDropDownListFor(m => m.SwitchStatus, typeof (StatusEnum), "- Please select Item -")
which is a method that uses Generic enum as parameter
public static IHtmlContent EnumDropDownListFor<TModel, TResult, TEnum>(
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TResult>> expression,
TEnum enumValue,
string optionLabel)
{
// bunch of logic omitted as not relevant to error
//calling another method passing the TEnum
return null;
}
which works.
However I need to pass the enum type between a number of methods as I need to treat it as an TEnum and not a Type which typeof will pass.
From the above I add a where clause to tell the method the TEnum is an enum.
public static IHtmlContent EnumDropDownListFor<TModel, TResult, TEnum>(
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TResult>> expression,
TEnum enumValue,
string optionLabel) where TEnum : struct, IConvertible, IFormattable
{
// bunch of logic omitted as not relevant to error
//calling another method passing the TEnum
return null;
}
However then I get a red line in my view under
#Html.EnumDropDownListFor
in Visual Studio
showing the error message
Error CS0453 The type 'Type' must be a non-nullable value type in order to use it as parameter 'TEnum' in the generic type or method EnumDropDownListFor
Basically as I understand it I need to pass it a TEnum and not a Type
However if I remove the typeof from my line
#Html.EnumDropDownListFor(m => m.SwitchStatus, StatusEnum, "- Please select Item -")
I understandably get
StatusEnum is a type which is not valid in the given context
What I basically want to do is do is call typeof inside the method like this
public static IHtmlContent EnumDropDownListFor<TModel, TResult, TEnum>(
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TResult>> expression,
TEnum enumValue,
string optionLabel) where TEnum : struct, IConvertible, IFormattable
{
Type enumValue2 = typeof(StatusEnum);
// bunch of logic omitted as not relevant to error
//calling another method passing the TEnum
return null;
}
and for that I need the MVC View to look something like
#Html.EnumDropDownListFor(m => m.SwitchStatus, Before_typeof(StatusEnum), "- Please select Item -")
so I am allowed to pass it and do the typeof in the method.
Is this possible?
Take note: I can get it working with the non generic version
i.e.
public static IHtmlContent EnumDropDownListFor<TModel, TResult>(
this IHtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TResult>> expression,
Type enumValue,
string optionLabel)
{
//logic
return null;
}
which works perfectly
I just wanted to know if the generic version is possible somehow
When you pass in typeof (StatusEnum) as second parameter, the TEnum becomes a Type, which isn't what you want. It seems to me you want to put in the value of an enum in there, since the field is named enumValue. So you should pass in StatusEnum.Okay, or something like that, not a type.
You already have the type from the type parameter, so if it is to do about the type, you don't need to pass in that parameter at all.
Is it possible to get the instance of TModel from Expression in the following method?
public void FooBar<TModel, TProperty> MyMethod(
Expression<Func<TModel, TProperty>> expression)
where TModel : ViewModel
{
ViewModel model = ???;
}
There is no instance, and as such there is no way to get the instance that doesn't exist.
The expression is merely an object that says, "If you give me a model, I know how to give you back a property of it." It doesn't actually have a model until you give it a model.
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
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..