Problem with using a ToExpando method - asp.net-mvc

Hi I i try to use a ToExpando solution to use anonymous classes in razor views.
I use this solution -> Dynamic Anonymous type in Razor causes RuntimeBinderException
I'll write what i did:
I added a file Extensions.cs where i put following code:
public static class Extensions
{
public static ExpandoObject ToExpando(this object anonymousObject)
{
IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
IDictionary<string, object> expando = new ExpandoObject();
foreach (var item in anonymousDictionary)
expando.Add(item);
return (ExpandoObject)expando;
}
}
I wrote a query that receive a tuples from database in controller method:
IEnumerable<dynamic> articles = (from p in db.Articles.Where(p => p.user_id == 2)
select new
{
p.article_id,
p.title,
p.date,
p.category,
AverageScore = db.Articles_Scores
.Where(o => o.user_id == p.user_id && p.article_id == o.article_id)
.Average(m => m.score)
}).AsEnumerable()
.Select(r => r.ToExpando());
int ii = 0;
foreach(var it in articles) {
// HERE I CAN READ EVERYTHING
ii = it.article_id;
}
return View(articles);
In view I declare a model:
#model IEnumerable<dynamic>
And I try to get every tuples:
#foreach (dynamic item in Model) {
// some code
#item.article_id // HERE IS EXCEPTION
}
In the foreach line I got an Exception:
RuntimeBinderException: 'System.Dynamic.ExpandoObject' does not contain a definition for 'article_id'
What did I do wrong?

You need to call .AsEnumerable() or .ToList() first to force ToExpando to run on the client.

Try:
dynamic articles = (from p in db.Articles.Where(p => p.user_id == 2_
select new
{
p.article_id,
p.title,
p.date,
p.category,
AverageScore = db.Articles_Scores
.Where(o => o.user_id == p.user_id && p.article_id == o.article_id)
.Average(m => m.score)
}).AsEnumerable()
.Select(r => r.ToExpando());
Edit: Make sure you declare dynamic not var
Edit 2: In your for look, you're declaring var again. Change it to:
#foreach (dynamic item in Model) {
// some code
#item.article_id // HERE IS EXCEPTION
}

Ok, building on the first two answers, I added another extension method that I'm surprised I don't see here:
public static List<ExpandoObject> ToExpandoList<T>(this IEnumerable<T> ie) {
return ie.Select(o => o.ToExpando()).ToList();
}
And now in my view I have code like this, and it works just fine using Razor.
var peoples = from f in "tom dick susan roberto".Split(' ')
select new { FirstName = f, Age = f.Length };
ViewBag.People = peoples.ToExpandoList();

The problem is HtmlHelper.AnonymousObjectToHtmlAttributes(), it's replacing
'_' with '-' for the property name. Look at the comment for the method.

Related

Orderby clause not working with drop down list in mvc

I am populating a drop down list using Linq and the orderby clause doesn't seem to work.
public List<Hello> getManagers()
{
var que = (from man in db.Table1
where man.Role == "Manager"
orderby man.Name
select new Hello
{
Managers = man.Name
}).Distinct().ToList();
return que;
}
Controller Class:
public ActionResult Index()
{
rp = new RequestProcess();
ViewBag.ID = fillSelectedList("", "ID", rp);
ViewBag.Managers = fillSelectedList("", "Managers", rp);
return View(""); //View 1
}
public static List<SelectListItem> fillSelectedList(string selValue, string type, RequestProcess rp )
{
List<SelectListItem> list = new List<SelectListItem>();
SelectListItem obj = new SelectListItem();
if (type == "Managers") {
var tempList= rp.getManagers();
tempList.ForEach(x =>
{
obj = new SelectListItem();
obj.Text = x.Managers;
obj.Value = x.Managers;
obj.Selected = x.Managers == selValue ? true : false;
list.Add(obj);
});
}
return list;
}
I am still receiving an un-ordered list. Any fixes?
The result is not ordered, because method Distinct does not return ordered results. What you need to do instead is to first call Disctinct, and only then OrderBy:
var que = (from man in db.Table1
where man.Role == "Manager"
select new Hello
{
Managers = man.Name
}).Distinct() // <- First distinct ...
.OrderBy(x => x.Managers) // <- ... then order by
.ToList();
As mentioned in the answer above, you need to sort the result after Distinct().
Also note that you are mixing Lambda expression and LINQ to Entities Queries... you may want to consider choosing one of them for consistency (though there is no syntax error if you mix them). This is the same query using lambda expression:
var que = _context.Table1
.Where(m => m.Role == "Manager")
.Select(h => new Hello { Managers = h.Name })
.Distinct()
.OrderBy(o => o.Managers)
.ToList();

IQueryable changed in foreach loop not displaying in view

I been trying to change the value of RouteAttr.RoutedForRole if it is equal to
SHead but I checked in run time and the query was not changed even though it went to the foreach loop and there were valid entries. I also tried adding the foreach loop in the view but it didn't change anything.
public ViewResult Index()
{
IQueryable<ServiceRequestViewModel> query;
query = from c in context.ServiceRequests
select new ServiceRequestViewModel
{
ServiceRequestId = c.ServiceRequestId,
ServiceDescription = c.ServiceDescription,
RequestNumber = c.RequestNumber,
Title = c.Title,
RouteAttr = c.RouteAttr,
LogAttr = c.LogAttr
};
foreach (var item in query)
{
if (item.RouteAttr.RoutedForRole == WorkflowRole.SHead)
{
item.RouteAttr.RoutedForRole = WorkflowRole.HRManager;
}
}
return View(query);
}
Below is my gridview.
#Html.Grid(Model).Columns(col =>
{
col.Add(o => o.ServiceRequestId)
.Encoded(false)
.Sanitized(false)
.Filterable(true)
.Titled("SRF No.")
.SetWidth(150)
.RenderValueAs(o => Html.ActionLink(o.RequestNumber, "Details", new { id = o.ServiceRequestId }));
col.Add(o => o.Title)
.Filterable(true)
.SetWidth(400)
.Titled("Title");
col.Add(o => o.LogAttr.CreatedBy)
.Filterable(true)
.Titled("Requestor");
col.Add(o => o.RouteAttr.RoutedForRole)
.Filterable(true)
.Titled("Status");
}).WithPaging(10).Sortable(true)
I've been told in the comments why it's not returning so now I want to know how to update an item in iqueryable and return it in view.
Just like what #Enigmativity said in the comments, I changed the query into an array.
var data = query.ToArray();
He suggested I return data but it didn't work since ServiceRequestViewModel requires an IQueryable type. So I changed it back to IQueryable type so I can return it to view.
var queryable = data.AsQueryable();

Dynamically apply filters on Entity Framework's entity using lambda expression

I need to have a method like this, where I can apply Where(x =>x. ...) and Include(x => x.RelatedEntity) and OrderBy(x=>x. ...) on a given entity.
Something like this:
public List<TEntity> ApplyFilter<TEntity>(TEntity entity,
List<filters> filters /* List of filters: 'filters' */)
where TEntity : BaseEntity
{
using (var db = new MyDbContext()){
var query = db.Set<TEntity>().AsQueryable;
//apply filters to 'query'
query.include(/*multiple related entities*/);
query.applyfilters(/*filters*/);
return query.ToList();
}
}
And I need to pass what I need to be filtered/included as lambda expressions.
NOTE: I searched a lot about how I can do it but I really wasn't able to find anything. I'm new to this part of C# / Entity Framework and I really didn't even know what keywords to search for.
Thank you for the help
You'll want to use a LINQ expression
public List<TEntity> ApplyFilter<TEntity>(
Expression<Func<TEntity, bool>> filter,
Expression<Func<TEntity, object>> orderBy,
params Expression<Func<TEntity, object>>[] includes) where TEntity : BaseEntity
{
using (var db = new MyDbContext())
{
var query = db.Set<TEntity>().AsQueryable();
query = query.Where(filter);
query = query.OrderBy(orderBy);
if (includes != null)
{
foreach (var include in includes)
{
query = query.Include(include);
}
}
return query.ToList();
}
}
To use the method:
ApplyFilter<TestObject>(
x => x.Prop1 == "foo",
x => x.Prop2,
x => x.Prop3, x => x.Prop4);
Like this?
var result = Repository.PurchaseProposalItem.GetDbSet();
if (filters.FilterByBrand) result = result.Where(p => p.GS_Product.GS_ProductBrand.PBr_Id == filters.BrandId);
if (filters.FilterByFamily) result = result.Where(p => p.GS_Product.GS_ProductFamily.PFa_Id == filters.FamilyId);
if (filters.FilterBySubFamily) result = result.Where(p => p.GS_Product.GS_ProductSubFamily.PSu_Id == filters.SubFamilyId);
if (filters.FilterByProductType) result = result.Where(p => p.GS_Product.Pro_Type == filters.ProductTypeEnum);
return result;

List of class properties

I have ASP.NET MVC 4 application with one view model class and about 20 views representing this view model. This views differs only by fields which user can edit. I want to merge all that views to one and define list of properties available to editing in strongly-typed manner. Ideally, I want something like this:
// Action
public ActionResult EditAsEngineer(int id)
{
//...
viewModel.PropertiesToChange = new List<???>()
{
v => v.LotNumber,
v => v.ShippingDate,
v => v.Commentary
};
return View(viewModel);
}
// View
if (#Model.PropertiesToChange.Contains(v => v.LotNumber)
{
#Html.TextBoxFor(m => m.LotNumber)
}
else
{
#Model.LotNumber
}
Is it possible to do something like this? Or is there a better solution?
Thank you.
Why note something like this (its pseudo code)
public class Prop{
string PropertyName {get;set;}
bool PropertyEditable {get;set;}
}
public ActionResult EditAsEngineer(int id)
{
viewModel.PropertiesToChange = new List<Prop>()
{
new Prop{PropertyName = LotNumber, PropertyEditable = true}
};
return View(viewModel);
}
#foreach (var pin Model.PropertiesToChange)
{
if(p.PropertyEditable){
#Html.TextBoxFor(p)
}else{
#Html.DisplayFor(p)
}
}
This will solve HALF of your problem. You will also need to create a IEqualityComparer<Expression> for your code to work (the default is to check for ref-equals).
return from p in typeof(T).GetProperties()
let param = System.Linq.Expressions.Expression.Parameter(typeof(T), "x")
let propExp = System.Linq.Expressions.Expression.Property(param, p)
let cast = System.Linq.Expressions.Expression.Convert(propExp, typeof(object))
let displayAttribute = p.CustomAttributes.OfType<System.ComponentModel.DataAnnotations.DisplayAttribute>()
.Select(x => x.Order).DefaultIfEmpty(int.MaxValue).FirstOrDefault()
orderby displayAttribute
select System.Linq.Expressions.Expression.Lambda<Func<T, object>>(cast, new [] {param});
This will list out ALL the properties for T. You would also probabily want to use Expression<Func<T, object>> as the type for defining your list of properties.
This will allow you to create a generic view over all properties.
Also you will want to wrap this in some kind of a cache, as this code is SLOW.

MVC with Kendo treeview - remote data JSON or XML

I'm trying to create a Kendo treeview that gets populated from my controller (PropertyController).
The part I'm stuck at is how to format my data in the controller. How could I create a treeview three items deep and pass that to my view to be displayed in the treeview?
#(Html.Kendo().TreeView()
.Name("treeview")
.Events(events => events
.DragStart("PartnershipPage.OnDragStart")
.Drop("PartnershipPage.OnDrop")
.DragEnd("PartnershipPage.OnDragEnd")
)
.HighlightPath(true)
.DragAndDrop(true)
.DataSource(dataSource => dataSource
.Read(read => read
.Action("Index","Tree")
)
)
)
I've included the controller to see if I'm doing it correctly. All that is happening so far is the JSON is being displayed on the screen as text.
Controller:
public ActionResult Index()
{
var org = new Entities();
var eList = new List<Entity>();
var entity1 = new Entity
{
Id = 1,
Name = "LLC-A",
parentId = 0
};
eList.Add(entity1);
var entity2 = new Entity
{
Id = 2,
Name = "LLC-B",
parentId = 0
};
eList.Add(entity2);
var entity3 = new Entity
{
Id = 1,
Name = "LLC-C",
parentId = 2
};
eList.Add(entity3);
var entity4 = new Entity
{
Id = 1,
Name = "LLC-D",
parentId = 2
};
eList.Add(entity4);
org.Entity = eList;
var test = from x in org.Entity
where (x.Name != null)
select new
{
Id = x.Id,
Name = x.Name,
parentId = x.parentId
};
;
return Json(test, JsonRequestBehavior.AllowGet);
}
From: Kendo Site
<%= Html.Kendo().TreeView()
.Name("TreeView")
.BindTo(Model, mapping => mapping
.For<Customer>(binding => binding
.Children(c => c.Orders) // The "child" items will be bound to the the "Orders" property
.ItemDataBound((item, c) => item.Text = c.ContactName) // Map "Customer" properties to TreeViewItem properties
)
.For<Order<(binding => binding
.Children(o => null) // "Orders" do not have child objects so return "null"
.ItemDataBound((item, o) => item.Text = o.OrderID.ToString()) // Map "Order" properties to TreeViewItem properties
)
)
%>
You could put your entities into a list called for example, "myEntities" and return that from the controller to the view:
public ActionResult Index()
{
var ents = getMyEntities(); // some method you have to return the list of your entities
return ents;
}
Then in your view, you can loop through all the Entities in your Model:
#(Html.Kendo().TreeView()
.Name("TreeView")
.Items(treeview =>
{
foreach (var entity in Model)
{
var entityName = entity.Name;
var children = entity.Children;
treeview.Add().Text(entityName ).Expanded(false).Items(branch =>
{
if (children != null)
{
foreach (var child in children)
{
branch.Add().Text(child);
}
}
});
}
}
)
)
I used children because I found it easier to use than parent and so I would change my Entities to something like this:
var entity4 = new Entity
{
Id = 1,
Name = "LLC-D",
Children = <list of children names ... >
};
You can see how I did mine here: Populate KendoUI Treeview with RavenDB Documents
Hope this helps.
Edit in response to: How can I display children of children?
I ran into the same problem as you describe (displaying children of children). Here is how I did it after I had the problem of displaying children of children:
I used EntityFramework:
The DB context class:
public class EntityDBContext : DbContext
{
public DbSet<MyEntity> Entities { get; set; }
}
Controller:
public JsonResult EntitiesForTreeView(int? id)
{
// Here I am using EntityFramework
var entitiesContext = new EntityDBContext();
var myEntity= from e in entitiesContext.Entities
where (id.HasValue ? e.Parent == id : e.Parent == null)
select new
{
id = e.Id,
Name = e.Name,
hasChildren = e.Id
};
return Json(myEntity, JsonRequestBehavior.AllowGet);
}
And the View:
#(Html.Kendo().TreeView()
.Name("treeview")
.DataTextField("Name")
.LoadOnDemand(true)
.HighlightPath(true)
.DataSource(dataSource => dataSource
.Read(read => read
.Action("EntitiesForTreeView", "SiteMap")
)
)
.Checkboxes(true)
)
Hope this helps. Sorry if there is something stupid - I've only been doing web development for 3 months so unmark as answer if needed. Let me know if I can help further. PS: I should mention that after implementing the solution I have shown above, I noticed that the nodes on my tree all have the "+" sign... regardless of whether or not they have any children. If you know how to fix this, please let me know :)

Resources