I need to change a function that accepts one Expression with one property inside and give it the ability to work with 2 properties at least.
I have the following base class that contains nested ElementHelper class
public class DomainObjectViewModel<TModel> where TModel : DomainObject
{
public class ElementHelper
{
public static void Create<T1>(TModel model, Expression<Func<TModel, T1>> expression)
{
var getPropertyInfo = GetPropertyInfo(expression);
//Do cool stuff
}
private static PropertyInfo GetPropertyInfo<T1>(Expression<Func<TModel, T1>> propertyExpression)
{
return (PropertyInfo)((MemberExpression)propertyExpression.Body).Member;
}
}
}
-ElementHelper class contains a Create function that gets the propertyInfo of the expression and only works if you pass one property in the expression.
Then I have the following inherited class that uses the helper function in the constructor.
public class ProductViewModel : DomainObjectViewModel<ProductEditViewModel>
{
public ProductViewModel(ProductEditViewModel model)
{
//It works for one property in the Expression
ElementHelper.Create(model, x => x.LaunchDate);
//Give the ability to pass multiple paramenters in the expression
ElementHelper.Create(model, x => new { x.LaunchDate, x.ApplyLaunchDateChanges });
}
}
I think I can use NewExpression (new { x.LaunchDate, x.ApplyLaunchDateChanges }) in order to pass it a collection of properties, but I cannot make it work.
Would you use same approach?
How you can split the passed Expression so you can get the propertyinfo of each properties found in the NewExpression object?
Well, since ElementHelper.GetPropertyInfo is your own method, you can decide what is allowed to pass, and then handle it appropriately inside that method.
Currently you handle only MemberExpression, so that's why it works only with single property accessor. If you want to be able to pass new { ... }, you need to add support for NewExpression like this:
private static IEnumerable<PropertyInfo> GetPropertyInfo<T1>(Expression<Func<TModel, T1>> propertyExpression)
{
var memberExpression = propertyExpression.Body as MemberExpression;
if (memberExpression != null)
return Enumerable.Repeat((PropertyInfo)memberExpression.Member, 1);
var newExpression = propertyExpression.Body as NewExpression;
if (newExpression != null)
return newExpression.Arguments.Select(item => (PropertyInfo)((MemberExpression)item).Member);
return Enumerable.Empty<PropertyInfo>(); // or throw exception
}
Related
I have this call on one view model
ShowViewModel<MyViewModel>(
new MyParams { ... }
);
On MyViewModel I have this Init method which works perfect
public void Init(MyParams params)
{
if (params != null)
{
// some logic
}
else
{
// some other logic
}
}
On another view model I have
ShowViewModel<MyViewModel>();
I expect to receive null on MyViewModel init method, instead of that I get an instance of 'MyParams'. That's generating problems since I have specific logic to handle the call with no parameters
I have custom presenter logic that might responsible for this, but at first sight I couldn't identify any custom logic as responsible. Is this the standard behavior for complex params?
Unfortunately, no there isn't a way to pass null using a parameters object.
The reason is that when Mvx creates the ViewModel and attempts to call the Init method, it will first convert your object instance into a simple dictionary (key/value pairs). If you use the no arg version, then it creates an empty dictionary. At this point, it creates an MvxBundle which includes the dictionary.
When Mvx is finally ready to call your Init method, it takes this dictionary and attempts to create an actual object.
It's this method that creates the instance to pass to Init.
MvxSimplePropertyDictionaryExtensionMethods.Read()
https://github.com/MvvmCross/MvvmCross/blob/8a824c797747f74716fc64c2fd0e8765c29b16ab/MvvmCross/Core/Core/Platform/MvxSimplePropertyDictionaryExtensionMethods.cs#L54-L72
public static object Read(this IDictionary<string, string> data, Type type)
{
var t = Activator.CreateInstance(type);
var propertyList =
type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy).Where(p => p.CanWrite);
foreach (var propertyInfo in propertyList)
{
string textValue;
if (!data.TryGetValue(propertyInfo.Name, out textValue))
continue;
var typedValue = MvxSingletonCache.Instance.Parser.ReadValue(textValue, propertyInfo.PropertyType,
propertyInfo.Name);
propertyInfo.SetValue(t, typedValue, new object[0]);
}
return t;
}
Notice how it calls Activator.CreateInstance(type) which will always return an instance.
So that is why you'll never get an null value in Init.
My recommendation is to simply add a property to your MyParams object and set that in your no-arg version. Then in Init you can check the property to determine what to do.
Something like:
ShowViewModel<MyViewModel>(new MyParams { HasNoParams = true });
public void Init(MyParams myParams)
{
if (myParams.HasNoParams)
{
// do null flow here
}
else
{
// do non-null flow here
}
}
You can use Dictionary< TKey, TValue> as your params.
The website that I'm working on is heavily depending on ajax/json and knockout.js.
I would like to have a lot of my Controllers return view-tailored 'json objects', without wrapping them in a JsonResult when returning the method.
This would mean I could easily composite multiple calls into one parent object, but still be able to call the Actions separately too.
Simplified example:
public object Main(int groupId)
{
var viewModel = new
{
Persons = Employees(groupId),
Messages = AllMessages()
};
return viewModel;
}
public object Employees(int groupId)
{
return DatabaseContext.Employees.Where(e => e.GroupId == groupId).ToList();
}
public object AllMessages()
{
return DatabaseContext.Messages.ToList();
}
I was hoping I could capture the returned object in OnActionExecuted and at that point wrap the whole result up in a final JsonResult.
The result is already converted to a string and captured in a ContentResult though.
Any ideas? :) Thanks,
A good approach on this is to create helper methods for your entity calls. Or if you have those methods already somewhere, they can actually serve as the helper methods. In that manner you can return a list of strongly-typed Messages and Employees as well as returning your desired parent object. You can then have individual controller methods that returns json objects. In addition, you can extend the parent viewmodel to return additional fields.
The Parent ViewModel
public class ParentModel {
public Employee Persons {get;set;}
public Message Messages {get;set;}
}
The Helper Methods
The beauty of using helper methods similar to what is defined here is that you can apply a few more logic to your query, and more, and you don't have to change anything in your controller methods.
public ParentModel GetMain(int groupId)
{
var viewModel = new ParentModel
{
Persons = Employees(groupId),
Messages = AllMessages()
};
return viewModel;
}
public IEnumerable<Employee> Employees(int groupId)
{
return DatabaseContext.Employees.Where(e => e.GroupId == groupId).ToList();
}
public IEnumerable<Message> AllMessages()
{
return DatabaseContext.Messages.ToList();
}
The Controller Methods
public ActionResult GetParent(int groupId){
return Json(helperinstance.GetMain());
}
public ActionResult GetEmployees(int groupId){
return Json(helperinstance.Employees());
}
public ActionResult GetMessages(int groupId){
return Json(helperinstance.AllMessages());
}
Thanks for the answer. I'm not going for the solution of von v. because I like to keep the boilerplate as small as possible.
In the end I am trying out the following approach. It seems to work pretty well for now, but I still have to test it in real production.
If anyone has some (security) concerns with this, I'm happy to hear them in the comments.
// BaseController
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var returnType = ((ReflectedActionDescriptor)filterContext.ActionDescriptor).MethodInfo.ReturnType;
// is the returnType not deriving from ActionResult? Automatically wrap it in a JsonResult
if ( !typeof(ActionResult).IsAssignableFrom(returnType) )
{
var result = filterContext.ActionDescriptor.Execute(filterContext, filterContext.ActionParameters);
filterContext.Result = Json( result );
}
}
I have the following custom type with a method on it to determine if one span of time overlaps another
public struct DateTimeSpan
{
public DateTime? Start { get; set; }
public DateTime? End { get; set; }
public bool Overlaps(DateTimeSpan overlap)
{
//....
}
}
I am trying to write a custom HQL generator so that when I use this method within my data access LINQ queries it will generate to appropriate SQL when querying the database.
This is the start of my BaseHqlGeneratorForMethod that attempts to compare the End property of one DateTimeSpan with the other
public class DateSpanOverlapsDateTimeSpanHqlGenerator : BaseHqlGeneratorForMethod
{
public DateSpanOverlapsDateTimeSpanHqlGenerator()
{
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeSpan>(x => x.Overlaps(new DateTimeSpan()))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder builder, IHqlExpressionVisitor visitor)
{
var endTargetProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
Expression endTargetExpression = Expression.MakeMemberAccess(targetObject, endTargetProperty);
var endArgumentProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
Expression endArgumentExpression = Expression.MakeMemberAccess(arguments[0], endArgumentProperty);
return builder.GreaterThanOrEqual(visitor.Visit(endTargetExpression).AsExpression(), visitor.Visit(endArgumentExpression).AsExpression());
}
}
I have proven that the End property in the targetObject is being evaluated fine but no matter what I do I cannot get it to evaluate the End property in arguments[0]. The above code is just one example of what I have tried (and seems the most obvious given it works for the targetObject) with most things I try ending up with the exception Antlr.Runtime.NoViableAltException
One obvious different between targetObject and arguments[0] is that targetObject is of type PropertyExpression and arguments[0] is of type ConstantExpression. I assume this means there needs to be different ways to access them but I cannot work out what it is!
treeBuilder.Constant(arg.SubProperty); will cache your object so you will end up to have problem when you will run your query.
you should add an overload into your DateTimeSpan
public bool Overlaps(DateTime? start, DateTime? end)
match the signature into your DateSpanOverlapsDateTimeSpanHqlGenerator
SupportedMethods = new[]
{
ReflectionHelper.GetMethodDefinition<DateTimeSpan>(span => span.Overlaps(default(DateTime?), default(DateTime?)))
};
and get the values in this way:
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder builder, IHqlExpressionVisitor visitor)
{
var startProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.Start);
var endProperty = ReflectionHelper.GetProperty<DateTimeSpan, DateTime?>(x => x.End);
MemberExpression targetStartExpression = Expression.MakeMemberAccess(targetObject, startProperty);
MemberExpression targetEndExpression = Expression.MakeMemberAccess(targetObject, endProperty);
HqlExpression startDateExpression = visitor.Visit(arguments[0]).AsExpression();
HqlExpression endDateExpression = visitor.Visit(arguments[1]).AsExpression();
....
}
after that business as usual:
builder.LessThanOrEqual(visitor.Visit(targetStartExpression).AsExpression(), endDateExpression)
I don't know if you ever resolved this or are still interested in an answer, however I stumbled across this question myself while looking for a solution to this exact problem, so thought I would add my solution for others.
While debugging I noticed that the target object parameter is passed in as an expression of type PropertyExpression, whereas my argument was passed in as a type of ConstantExpression, so while it's perfectly valid to add to PropertyExpression using Expression.Property, that doesn't work with a constant value.
Instead I extract the value of the constant and access the values of sub-properties directly, creating new constant expressions which I can then use to build the query expression.
I don't know if this is the best, or most elegant way to do it, but it worked for me. I hope it helps.
public class MyObject
{
public double SubProperty { get; set; }
}
private HqlTreeNode GenerateHQL(
MethodInfo method,
Expression targetObject,
ReadOnlyCollection<Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
MyObject arg = (MyObject)((ConstantExpression)arguments[0]).Value;
HqlConstant argExpression = treeBuilder.Constant(arg.SubProperty);
...
...
}
I'm using SPMetal in order to generate entity classes for my sharepoint site and I'm not exactly sure what the best practice is to use when there are multiple content types for a single list. For instance I have a task list that contains 2 content types and I'm defining them via the config file for SPMetal. Here is my definition...
<List Member="Tasks" Name="Tasks">
<ContentType Class="LegalReview" Name="LegalReviewContent"/>
<ContentType Class="Approval" Name="ApprovalContent"/>
</List>
This seems to work pretty well in that the generated objects do inherit from WorkflowTask but the generated type for the data context is a List of WorkflowTask. So when I do a query I get back a WorkflowTask object instead of a LegalReview or Approval object. How do I make it return an object of the correct type?
[Microsoft.SharePoint.Linq.ListAttribute(Name="Tasks")]
public Microsoft.SharePoint.Linq.EntityList<WorkflowTask> Tasks {
get {
return this.GetList<WorkflowTask>("Tasks");
}
}
UPDATE
Thanks for getting back to me. I'm not sure how I recreate the type based on the SPListItem and would appreciate any feedback.
ContractManagementDataContext context = new ContractManagementDataContext(_url);
WorkflowTask task = context.Tasks.FirstOrDefault(t => t.Id ==5);
Approval a = new Approval(task.item);
public partial class Approval{
public Approval(SPListItem item){
//Set all properties here for workflowtask and approval type?
//Wouldn't there be issues since it isn't attached to the datacontext?
}
public String SomeProperty{
get{ //get from list item};
set{ //set to list item};
}
Linq2SharePoint will always return an object of the first common base ContentType for all the ContentTypes in the list. This is not only because a base type of some description must be used to combine the different ContentTypes in code but also it will then only map the fields that should definitely exist on all ContentTypes in the list. It is however possible to get access to the underlying SPListItem returned by L2SP and thus from that determine the ContentType and down cast the item.
As part of a custom repository layer that is generated from T4 templates we have a partial addition to the Item class generated by SPMetal which implements ICustomMapping to get the data not usually available on the L2SP entities. A simplified version is below which just gets the ContentType and ModifiedDate to show the methodology; though the full class we use also maps Modified By, Created Date/By, Attachments, Version, Path etc, the principle is the same for all.
public partial class Item : ICustomMapping
{
private SPListItem _SPListItem;
public SPListItem SPListItem
{
get { return _SPListItem; }
set { _SPListItem = value; }
}
public string ContentTypeId { get; internal set; }
public DateTime Modified { get; internal set; }
public virtual void MapFrom(object listItem)
{
SPListItem item = (SPListItem)listItem;
this.SPListItem = item;
this.ContentTypeId = item.ContentTypeId.ToString();
this.Modified = (DateTime)item["Modified"];
}
public virtual void MapTo(object listItem)
{
SPListItem item = (SPListItem)listItem;
item["Modified"] = this.Modified == DateTime.MinValue ? this.Modified = DateTime.Now : this.Modified;
}
public virtual void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
{
SPListItem originalItem = (SPListItem)originalListItem;
SPListItem databaseItem = (SPListItem)databaseObject;
DateTime originalModifiedValue = (DateTime)originalItem["Modified"];
DateTime dbModifiedValue = (DateTime)databaseItem["Modified"];
string originalContentTypeIdValue = originalItem.ContentTypeId.ToString();
string dbContentTypeIdValue = databaseItem.ContentTypeId.ToString();
switch(mode)
{
case RefreshMode.OverwriteCurrentValues:
this.Modified = dbModifiedValue;
this.ContentTypeId = dbContentTypeIdValue;
break;
case RefreshMode.KeepCurrentValues:
databaseItem["Modified"] = this.Modified;
break;
case RefreshMode.KeepChanges:
if (this.Modified != originalModifiedValue)
{
databaseItem["Modified"] = this.Modified;
}
else if (this.Modified == originalModifiedValue && this.Modified != dbModifiedValue)
{
this.Modified = dbModifiedValue;
}
if (this.ContentTypeId != originalContentTypeIdValue)
{
throw new InvalidOperationException("You cannot change the ContentTypeId directly");
}
else if (this.ContentTypeId == originalContentTypeIdValue && this.ContentTypeId != dbContentTypeIdValue)
{
this.ContentTypeId = dbContentTypeIdValue;
}
break;
}
}
}
Once you have the ContentType and the underlying SPListItem available on your L2SP entity it is simply a matter of writing a method which returns an instance of the derived ContentType entity from a combination of the values of the base type and the extra data for the missing fields from the SPListItem.
UPDATE: I don't actually have an example converter class as we don't use the above mapping extension to Item in this way. However I could imagine something like this would work:
public static class EntityConverter
{
public static Approval ToApproval(WorkflowTask wft)
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = wft.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = wft.SPListItem["field-name"];
return a;
}
}
Or you could put a method on a partial instance of WorkflowTask to return an Approval object.
public partial class WorkflowTask
{
public Approval ToApproval()
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = this.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = this.SPListItem["field-name"];
return a;
}
public LegalReview ToLegalReview()
{
// Create and return LegalReview as for Approval
}
}
In either situation you would need to determine the method to call to get the derived type from the ContentTypeId property of the WorkflowTask. This is the sort of code I would normally want to generate in one form or another as it will be pretty repetitive but that is a bit off-topic.
I'm trying and failing to use an ExpressionVisitor to modify an expression that calls a method. I have a SearchService that encapsulates the search logic and want to be able to amend the search arguments passed.
The class in which the SearchFunc should be modified and run:
public class SearchService
{
public Expression<Func<string, string, List<int>>> SearchFunc { get; set; }
public void Run()
{
SearchModifier modifier = new SearchModifier();
Expression<Func<string, string, List<int>>> newFunc = (Expression<Func<string, string, List<int>>>)modifier.Modify(SearchFunc);
}
}
SearchModifier is defined as:
public class SearchModifier : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
Debug.Print(string.Format("VisitMethodCall: {0}", node.ToString()));
//VisitMethodCall: value(ExpressionTree_test.MainWindow)._adminRepository.SearchUsers("orig val", "orig val2")
//trying to use the Update method to create an amended MethodCallExpression
List<ConstantExpression> newargs = new List<ConstantExpression>();
newargs.Add(Expression.Constant("my new arg 1", typeof(string)));
newargs.Add(Expression.Constant("my new arg 2", typeof(string)));
MethodCallExpression methodCallExpression = node.Update(node, newargs);
//causes exception
//Method 'System.Collections.Generic.List`1[System.Int32] SearchUsers(System.String, System.String)' declared
//on type 'ExpressionTree_test.AdminRepository' cannot be called
//with instance of type 'System.Collections.Generic.List`1[System.Int32]'
Debug.Print(string.Format("Amended VisitMethodCall: {0}", methodCallExpression.ToString()));
return base.VisitMethodCall(node);
}
The Run method is called like this:
_searchService = new SearchService();
_searchService.SearchFunc = (t, s) => _adminRepository.SearchUsers("orig val", "orig val2");
I can't find much information on using the MethodCallExpression.Update method so am not sure I'm doing this correctly. How to I change the values of the arguments in the method?
Of course there may be a better way of doing this and any suggestions gratefully received...
You're not using the result of the Update method. You should pass it to base.VisitMethodCall instead of node:
return base.VisitMethodCall(methodCallExpression);
EDIT
Sorry, I misread the question... The first argument to Update is not the expression node being visited, it's the instance on which the method is called. So the code should be:
node.Update(node.Object, newargs);