Simplest Way To Do Dynamic View Models in ASP.NET MVC 3 - linq

Caveat: This might be an inappropriate use of C#'s dynamic keyword and I probably should be using a strongly-typed view model, but...
I'm trying to avoid creating a strongly-typed view model by passing a C# 4 dynamic type to my view. I have this in my controller:
public ActionResult Index()
{
var query =
from fr in db.ForecastRates
join c in db.Codes
on
new { Code = fr.RateCode, CodeType = "ForecastRate" }
equals
new { Code = c.CodeValue, CodeType = c.CodeType }
select new
{
RateCode = fr.RateCode,
RateCodeName = c.CodeName,
Year = fr.Year,
Rate = fr.Rate,
Comment = fr.Comment
};
// Create a list of dynamic objects to form the view model
// that has prettified rate code
var forecastRates = new List<dynamic>();
foreach (var fr in query)
{
dynamic f = new ExpandoObject();
f.RateCode = fr.RateCode;
f.RateCodeName = fr.RateCodeName;
f.Year = fr.Year;
f.Rate = fr.Rate;
f.Comment = fr.Comment;
forecastRates.Add(f);
}
return View(forecastRates);
}
...and this in my view (I'm using MVC 3's Razor view engine):
#inherits System.Web.Mvc.WebViewPage<IEnumerable<dynamic>>
...
<tbody>
#foreach (var item in Model) {
<tr>
<td>#item.RateCodeName</td>
<td>#item.Year</td>
<td>#item.Rate</td>
<td>#item.Comment</td>
</tr>
}
</tbody>
I don't like how I iterate through the LINQ result to form the List of dynamic objects.
I'd like to initialize each ExpandoObject inside the LINQ query, but that doesn't seem to be supported.
I tried casting the the query result as List, but that didn't work because you can't convert anonymous type to dynamic.

Like you said, it's not supported. (I'm not saying dynamic View Models aren't supported - I'm saying what you're trying to do is not)
You could probably neaten up the LINQ query, but in the end your best bet would be to simply create a custom View Model. Seriously, it will take you about 30 seconds to do that.
I know dynamic is new and cool and everything, but your code will be a lot neater and easier to maintain if you just stick with a custom View Model in this case.
I would only go with a dynamic View Model in the very simple scenarios - most of the time you probably want to stick with what we've been doing all along - custom View Models.

Ok, you could do the following, but I wouldn't recommend it. Create a static method similar to the following
public static IHtmlString DisplayProperty(object obj, string property) {
return new HtmlString(TypeDescriptor.GetProperties(obj)[property].GetValue(obj).ToString());
}
Then in your cshtml file make the following call (make sure to using your proper namespace)
<tbody>
#foreach (var item in Model) {
<tr>
<td>#DisplayProperty(x, "RateCodeName")</td>
<td>#DisplayProperty(x, "Year")</td>
<td>#DisplayProperty(x, "Rate")</td>
<td>>#DisplayProperty(x, "Comment")</td>
</tr>
}
</tbody>
I wouldn't recommend this though but it is a solution to your problem that doesn't require a model.

Related

MVC3 Razor Passing ViewModel to Conroller List<> is null

I have a MVC 3 applicaiton in which I pass a vewmodel from the controller to the view. The vewmodel contains a couple of List<> properties.
public ActionResult MainView()
{
var model = GetViewModel();
return View("SignificantEventsView", model);
}
private SignificantEventsViewModel GetViewModel()
{
var viewModel = new SignificantEventsViewModel();
List<County_Codes> countyCodes = GetCountyCodeList();
List<String> stateNames = countyCodes.OrderBy(o=>o.County_st).Select(o => o.County_st ).Distinct().ToList();
viewModel.selectedState = stateNames.FirstOrDefault();
viewModel.CountyCodesList = countyCodes;
viewModel.StateNames = stateNames;
viewModel.SelectedCounties = new String[]{};
viewModel.SelectedCountyCodes = new String[] { };
viewModel.UnSelectedCounties = new String[] { };
viewModel.UnSelectedCountyCodes = new String[]{};
return viewModel;
}
The View looks like this:
#model ServicingPortal.ViewModels.SignificantEventsViewModel
#{
ViewBag.Title = "Significant Events";
}
<h2>SignificantEvents</h2>
#using (Html.BeginForm("RefreshCounties", "SignificantEvents", FormMethod.Post, new { id = "significantEventsForm", Model }))
{
<fieldset>
<span class="SpanTextboxEdit">
#Html.Label("states", "States")
<br />
<br />
#Html.DropDownListFor(o => #Model.selectedState
, new SelectList(Model.StateNames)
, new { id = "stateDropDown", onchange = "submit()", name = "test" })
</span>
</fieldset>
...
}
When the StateDropdownList is changed the veiwmodel is passed back to the controller, but the countyCodes list is always null.
I tried adding #Html.HiddenFor(o => #Model.CountyCodesList) in the view, but it still returns null. The only values that don't seem to be null are the primitive types such as String or String[]. Even the List stateNames is null.
I don't want to rebuild the county code list on each post back because there is substantial overhead involved. I have to create the list from all active loans in the database, of which there are thousands.
How can I get a List<> to persist from the view to the controller?
I should explain what I'm trying to acheive here.
I have a dropdown and a multiselect list box. The dropdown contains states and the listbox contains counties filtered by the selected state.
I need to filter the listbox contents when the selected state changes.
It would make sense to perform this task on the client side, but I have not found a good solution.
I will admit my javascript skills are quite limited.
All the solutions I researched one way or another involved filtering the county list on the server side.
I can accomplish this on the server side easy enough, but I thought that since I have already built the list, why not keep it intact instead of going to the backend each time.
The short answer is that you can't really do what you're trying to do. You're kind of trying to solve the wrong problem. You should look at using caching on the server side to prevent going back to the database to construct the county list every time.
I solved this by using TempData. On the postback action I can get the County List from temp data and set the ViewModel CountyCodeList to this value.

Is Dynamic LINQ still in use and suitable for narrowing-down search results?

I am developing an ASP.NET MVC3 application in C#.
I am trying to implement in my application a "narrow-down" functionality applied the result-set obtained from a search.
In short, after I perform a search and the results displayed in the center of the page, I would like to have on the left/right side of the page a CheckBoxList helper for each property of the search result. The CheckBox of each CheckBoxList represent the distinct values of the property.
For instance if I search Product and it has a Color property with values blue, red and yellow, I create a CheckBoxList with text Color and three CheckBox-es one for each color.
After a research on the Web I found this Dynamic LINQ library made available by Scott Guthrie. Since the most recent example/tutorial I found is from 2009, I was wondering whether this library is actually good (and maintained) or not.
In the latter case is jQuery the best way to implement such functionality?
You can solve it by building the needed predicate expressions dynamically, using purely .NET framework.
See code sample below. Depending on the criteria, this will filter on multiple properties. I've used IQuerable because this will enable both In-Memory as remote scenario's such as Entity Framework. If you're going with Entity Framework, you could also just build an EntitySQL string dynamically. I expect that will perform better.
There is a small portion of reflection involved (GetProperty). But this could be improved by performing caching inside the BuildPredicate method.
public class Item
{
public string Color { get; set; }
public int Value { get; set; }
public string Category { get; set; }
}
class Program
{
static void Main(string[] args)
{
var list = new List<Item>()
{
new Item (){ Category = "Big", Color = "Blue", Value = 5 },
new Item (){ Category = "Small", Color = "Red", Value = 5 },
new Item (){ Category = "Big", Color = "Green", Value = 6 },
};
var criteria = new Dictionary<string, object>();
criteria["Category"] = "Big";
criteria["Value"] = 5;
var query = DoDynamicWhere(list.AsQueryable(), criteria);
var result = query.ToList();
}
static IQueryable<T> DoDynamicWhere<T>(IQueryable<T> list, Dictionary<string, object> criteria)
{
var temp = list;
//create a predicate for each supplied criterium and filter on it.
foreach (var key in criteria.Keys)
{
temp = temp.Where(BuildPredicate<T>(key, criteria[key]));
}
return temp;
}
//Create i.<prop> == <value> dynamically
static Expression<Func<TType, bool>> BuildPredicate<TType>(string property, object value)
{
var itemParameter = Expression.Parameter(typeof(TType), "i");
var expression = Expression.Lambda<Func<TType, bool>>(
Expression.Equal(
Expression.MakeMemberAccess(
itemParameter,
typeof(TType).GetProperty(property)),
Expression.Constant(value)
),
itemParameter);
return expression;
}
}
I don't really get why would you need the Dynamic LINQ here? Are the item properties not known at compile-time? If you can access a given item properties by name, eg. var prop = myitem['Color'], you don't need Dynamic LINQ.
It depends on how you render the results. There is a lot of ways to achieve the desired behavior, in general:
Fully client-side. If you do everything client-side (fetching data, rendering, paging) - jQuery would be the best way to go.
Server-side + client-side. If you render results on the server, you may add HTML attributes (for each property) to each search result markup and filter those client-side. The only problem in this case can be paging (if you do paging server-side, you will be able to filter the current page only)
Fully server-side. Post the form with search parameters and narrow down the search results using LINQ - match the existing items' properties with form values.
EDIT
If I were you (and would need to filter results server-side), I'd do something like:
var filtered = myItems.Where(i => i.Properties.Match(formValues))
where Match is an extension method that checks if a given list of properties matches provided values. Simple as this - no Dynamic LINQ needed.
EDIT 2
Do you need to map the LINQ query to the database query (LINQ to SQL)? That would complicate things a bit, but is still doable by chaining multiple .Where(...) clauses. Just loop over the filter properties and add .Where(...) to the query from previous iteration.
you may have a look at PredicateBuilder from the author of C# 4.0 in a Nutshell
As already pointed out by #Piotr Szmyd probabbly you don't need dynamic Linq. Iterating over all properties of T doesn'require dynamic linq. Dynamic Linq is mainly usefull to build complete queries on the client side and send it in string format to the server.
However now, it become obsolete, since Mvc 4 supports client side queries through Api Controllers returning an IQueryable.
If you just need to iterate over all properties of T you can do it with reflection and by building the LambdaExpressions that will compose the filtering criterion. You can do it with the static methods of the Expression class.
By using such static methods you can build dynamically expressions like m => m.Name= "Nick" with a couple instructions...than you put in and them...done you get and expression you can apply to an exixting IQueryable
LINQ implementation still has not changed so there should be no problem using the dynamic LINQ library. It simply creates LINQ expressions from strings.
You can use AJAX to call action methods that run the LINQ query and return JSON data. JQuery would populate HTML from the returned data.

Linq query is triggered multiple times without any apparent reason

I’m trying to optimize my app, and I notice that one query is triggered multiple times without any apparent reason.
Is a MVC 3 App, razor and I’m using Linq and EF.
I have ViewModel class with a couple of properties.
One of these properties is the model for to view.
This is my controller (I omit all the others properties initialization):
public ActionResult companyDetail(Guid id)
{
companyDetailsViewModel myModel = new companyDetailsViewModel();
myModel.companyDetail = companiesRepository.getCompany(id);
return View(myModel);
}
This is my getCompany method:
public company getCompany(Guid id)
{
return db.companies.Single(c => c.id == id);
}
The view is too long to paste here, but is a simple view.
This is a part for example:
<div id="companyName">
<h2>
#Model.companyDetail.companyName
</h2>
</div>
<div id="companyInfoWapper">
<div class="companyInfo">
<h5>
industry: #Model.companyDetail.industry<br />
revenue: #String.Format("{0:C}", Model.companyDetail.revenue)
</h5>
</div>
</div>
I’m using AnjLab SQL Profiler to view the transactions..
When I call the view, the query it’s
called 3 times.
The Generated SQL is
the exact same on all 3.
The transaction ID is different, and also
the duration varies a little bit.
The rest are pretty much the same.
Any Idea what can be making this query to run multiple times?
Another Question!
Anyone know why db.companies.Single(c => c.id == id) ask for top 2? Like this:
SELECT TOP (2)
[Extent1].[id] AS [id], ….
Thanks in Advance!
Edgar.
Update!
The third call was my fault, and I fix it. However, I find this:
The application is Multi-language, so I write a class that implements Controller.
I trace the problem to this class. The query is triggered the second time at the end of the class when I call the Base:
base.Execute(requestContext);
and of course, the action is called again.
Any Idea how to prevent this?
Another Update!
Linkgoron ask why I call Base.Execute(), the answer is because of the localizedController implementation.
But his question make me think, and there is another part of the code:
public abstract class LocalizedControllerBase : Controller
{
public String LanguageCode { get; private set; }
private String defaultLanguage = "es";
private String supportedLanguages = "en|es|pt";
protected override void Execute(RequestContext requestContext)
{
if (requestContext.RouteData.Values["languageCode"] != null)
{
LanguageCode = requestContext.RouteData.Values["languageCode"].ToString().ToLower();
if (!supportedLanguages.ToLower().Contains(LanguageCode))
{
LanguageCode = defaultLanguage;
}
}
else {
LanguageCode = defaultLanguage;
}
System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CreateSpecificCulture(LanguageCode);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
base.Execute(requestContext);
}
}
My controller are defined like this:
public class companiesController : LocalizedControllerBase
I put a break point in “Base.Execute” and another in the “return View(myModel)” in the controller.
When I call the view companyDetail, the first stop is in base.Execute, the second is in return view, but for some reason there is a third stop in Base.Execute and a fourth in Return View, and finally the view is render.
This is making me crazy!
Anyone know why db.companies.Single(c
=> c.id == id) ask for top 2? Like this:
SELECT TOP (2) [Extent1].[id] AS [id],
….
Single() throws an exception if there is not exactly one match - so the Linq to Entities provider translates that as a top 2 query which is enough data to make a decision - throw an exception if the query returns 2 results or none, return the only result otherwise.
This doesn't make sense. If the query is executed multiple times you must call GetCompany method multiple times. Once you call Single the query is executed and Company instance is materialized so using it multiple times in view will not cause new executions. Those another calls must be caused by different part of your code.
Btw. you can avoid them by using Find (in EF 4.1) or GetObjectByKey (in EFv1 and EFv4) instead of Single. Single always executes query in database whereas Find first checks if the entity with the same entity key was already loaded and returns the instance without executing db query:
This is code for DbContext API (EF 4.1):
public company getCompany(Guid id)
{
// Id must be primary key
return db.companies.Find(id);
}
Code for ObjectContext API is little bit complicated because you first have to build EntityKey which requires entity set name. Here I described full example which works with different key types and names.

Convert Loop To Linq - Model Creation

I'm converting an entity object to a model that can be passed around my application without the extra overhead (As well as generating a couple of extra fields for the view etc.
public IEnumerable<PageModel> GetAllPages()
{
var AllPageO = _session.All<Page>();
IList<PageModel> RetO = new List<PageModel>();
foreach (var AP in AllPageO)
{
RetO.Add(new PageModel(AP));
}
return RetO.AsEnumerable();
}
Can this be converted to a Linq Query, the below does work I get the error
Server Error in '/' Application. Only
parameterless constructors and
initializers are supported in LINQ to
Entities.
public IEnumerable<PageModel> GetAllPages()
{
var AllPageO = _session.All<Page>();
var RetO = from EntityO in AllPageO select new PageModel(EntityO);
return RetO;
}
Resharper actually converts the firt loop into this, which also fails with the same error.
IList<PageModel> RetO = PageO.Select(AP => new PageModel(AP)).ToList();
Thats because entity framework is trying to convert optimize your projection expression into sql.
The easy fix is to enumerate the results before the projection:
var RetO = from EntityO in AllPageO.ToList() select new PageModel(EntityO);

How do I delete records from a child collection in LINQ to SQL?

I have two tables in my database connected by foreign keys: Page (PageId, other data) and PageTag (PageId, Tag). I've used LINQ to generate classes for these tables, with the page as the parent and the Tag as the child collection (one to many relationship). Is there any way to mark PageTag records for deletion from the database from within the Page class?
Quick Clearification:
I want the child objects to be deleted when the parent DataContext calls SubmitChanges(), not before. I want TagString to behave exactly like any of the other properties of the Page object.
I would like to enable code like the following:
Page page = mDataContext.Pages.Where(page => page.pageId = 1);
page.TagString = "new set of tags";
//Changes have not been written to the database at this point.
mDataContext.SubmitChanges();
//All changes should now be saved to the database.
Here is my situation in detail:
In order to make working with the collection of tags easier, I've added a property to the Page object that treats the Tag collection as a string:
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
PageTags.Clear();
foreach (string tag in tags) {
PageTag pageTag = new PageTag();
pageTag.Tag = tag;
PageTags.Add(pageTag);
}
}
}
Basically, the idea is that when a string of tags is sent to this property, the current tags of the object are deleted and a new set is generated in their place.
The problem I'm encountering is that this line:
PageTags.Clear();
Doesn't actually delete the old tags from the database when changes are submitted.
Looking around, the "proper" way to delete things seems to be to call the DeleteOnSubmit method of the data context class. But I don't appear to have access to the DataContext class from within the Page class.
Does anyone know of a way to mark the child elements for deletion from the database from within the Page class?
After some more research, I believe I've managed to find a solution. Marking an object for deletion when it's removed from a collection is controlled by the DeleteOnNull parameter of the Association attribute.
This parameter is set to true when the relationship between two tables is marked with OnDelete Cascade.
Unfortunately, there is no way to set this attribute from within the designer, and no way to set it from within the partial class in the *DataContext.cs file. The only way to set it without enabling cascading deletes is to manually edit the *DataContext.designer.cs file.
In my case, this meant finding the Page association, and adding the DeleteOnNull property:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)]
public Page Page
{
...
}
And adding the DeleteOnNull attribute:
[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)]
public Page Page
{
...
}
Note that the attribute needed to be added to the Page property of the PageTag class, not the other way around.
See also:
Beth Massi -- LINQ to SQL and One-To-Many Relationships
Dave Brace -- LINQ to SQL: DeleteOnNull
Sorry, my bad. That won't work.
It really looks like you need to be doing this in your repository, rather than in your Page class. There, you have access to your original data context.
There is a way to "attach" the original data context, but by the time you do that, it has become quite the code smell.
Do you have a relationship, in your Linq to SQL entity diagram, linking the Page and PageTags tables? If you don't, that is why you can't see the PageTags class from the Page class.
If the foreign key in the PageTags database table is set to Allow Nulls, Linq to SQL will not create the link when you drag the tables into the designer, even if you created a relationship on the SQL Server.
This is one of those areas where OR mapping can get kind of hairy. Providing this TagString property makes things a bit more convenient, but in the long run it obfuscates what is really happening when someone utilizes the TagString property. By hiding the fact that your performing data modification, someone can very easily come along and set the TagString without using your Page entity within the scope of a DataContext, which could lead to some difficult to find bugs.
A better solution would be to add a Tags property on the Page class with the L2S model designer, and require that the PageTags be edited directly on the Tags property, within the scope of a DataContext. Make the TagString property read only, so it can be genreated (and still provide some convenience), but eliminate the confusion and difficulty around setting that property. This kind of change clarifies intent, and makes it obvious what is happening and what is required by consumers of the Page object to make it happen.
Since Tags is a property of your Page object, as long as it is attached to a DataContext, any changes to that collection will properly trigger deletions or insertions in the database in response to Remove or Add calls.
Aaron,
Apparently you have to loop thru your PageTag records, calling DeleteOnSubmit for each one. Linq to SQL should create an aggregate query to delete all of the records at once when you call SubmitChanges, so overhead should be minimal.
replace
PageTags.Clear();
with
foreach (PageTag tag in PageTags)
myDataContext.DeleteOnSubmit(tag);
Aaron:
Add a DataContext member to your PageTag partial class.
partial class PageTag
{
DataClassesDataContext myDataContext = new DataClassesDataContext();
public string TagString {
..etc.
Larger code sample posted at Robert Harvey's request:
DataContext.cs file:
namespace MyProject.Library.Model
{
using Tome.Library.Parsing;
using System.Text;
partial class Page
{
//Part of Robert Harvey's proposed solution.
MyDataContext mDataContext = new TomeDataContext();
public string TagString {
get {
StringBuilder output = new StringBuilder();
foreach (PageTag tag in PageTags) {
output.Append(tag.Tag + " ");
}
if (output.Length > 0) {
output.Remove(output.Length - 1, 1);
}
return output.ToString();
}
set {
string[] tags = value.Split(' ');
//Original code, fails to mark for deletion.
//PageTags.Clear();
//Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached."
foreach (PageTag tag in PageTags) {
mDataContext.PageTags.DeleteOnSubmit(tag);
}
foreach (string tag in tags) {
PageTag PageTag = new PageTag();
PageTag.Tag = tag;
PageTags.Add(PageTag);
}
}
}
private bool mIsNew;
public bool IsNew {
get {
return mIsNew;
}
}
partial void OnCreated() {
mIsNew = true;
}
partial void OnLoaded() {
mIsNew = false;
}
}
}
Repository Methods:
public void Save() {
mDataContext.SubmitChanges();
}
public Page GetPage(string pageName) {
Page page =
(from p in mDataContext.Pages
where p.FileName == pageName
select p).SingleOrDefault();
return page;
}
Usage:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(string pageName, FormCollection formValues) {
Page updatedPage = mRepository.GetPage(pageName);
//TagString is a Form value, and is set via UpdateModel.
UpdateModel(updatedPage, formValues.ToValueProvider());
updatedPage.FileName = pageName;
//At this point NO changes should have been written to the database.
mRepository.Save();
//All changes should NOW be saved to the database.
return RedirectToAction("Index", "Pages", new { PageName = pageName });
}

Resources