Index into or find property values of anonymous type - model-view-controller

I created an HtmlHelper method called DisplayListBoxFor that will need display a textual representation of the selected items in a MultiSelectList as an unordered list (UL/LI) in the browser. I have specific reasons to keep the signature between the standard ListBoxFor and my custom DisplayListBoxFor exactly the same, which means that my method needs to accept a lambda expression. I have the following code in my helpers class that is modeled off of the Html.ListBoxFor method:
public static MvcHtmlString DisplayListBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList)
{
TagBuilder tag = new TagBuilder("ul");
foreach (var value in ((MultiSelectList)selectList).SelectedValues)
{
TagBuilder itemTag = new TagBuilder("li");
itemTag.SetInnerText(value.ToString());
tag.InnerHtml += itemTag.ToString();
}
return new MvcHtmlString(tag.ToString());
}
I can then call my custom method like so:
#Html.DisplayListBoxFor(x => Model.MySelectedValues, Model.MyAvailableValues)
The types I used are as follows:
Model.MySelectedValues = List<Object>
Model.MyAvailableValues = MultiSelectList
This works just fine, with the exception that the output only lists the ID value and not the name of the selected value because that is all that is stored in the MultiSelectList.SelectedValues property upon creation. I am trying to find a way in my foreach loop to match up the selected value to the value name, but since everything within the method is an anyonymous type, I can't cast it to any non-concrete values or even use an Indexer to find the id and name properties. How would I be able to get these values from the anonymous types passed to this method?

I figured out a solution to this, but I posted in a separate post where I "dumbed down" the question. See MVC Method to output unordered list from the ListBoxFor method

Related

Whats the recommended way to cache 'expensive' queries in an MVC HTML Helper?

I am trying to use output caching on an HTML Helper. However, even with the attribute set, this code block is always entered when the Helper method is called. Since the outputcache attribute won't work in this scenario, what would be the recommended way of caching "expensive" queries in Html Helpers?
[OutputCache(Duration = 60)]
public static MvcHtmlString CountryDropDownListFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object selectedValue)
{
var doc = new XmlDocument();
doc.Load(HttpContext.Current.Server.MapPath("~/App_Data/countries.xml"));
var items = new Dictionary<string, string>();
foreach (XmlNode node in doc.SelectNodes("//country"))
{
items.Add(node.InnerText, node.InnerText);
}
return html.DropDownListFor(expression, new SelectList(items, "key", "value", selectedValue));
}
Output caching lets you store the output of an action method in memory on the Web server. For example, if the action method renders a view, the view page will be cached. This cached page is then available to the application for subsequent requests. Output caching saves your application the time and resources it would take to re-create the result of the action method.
In ASP.NET MVC, you can use the OutputCacheAttribute attribute to mark action methods whose output you want to cache. If you mark a controller with the OutputCacheAttribute attribute, the output of all action methods in the controller will be cached.
details http://msdn.microsoft.com/en-us/library/system.web.mvc.outputcacheattribute(v=vs.108).aspx
You use this attribure for not action method
Correct Example
[OutputCache(Duration = 50000)]
public ActionResult CountryDropDownListFor()
{
// Code
}
And in your view you can use Html.PartialAction to render one

hiddenFor helper is pulling id from action parameter, not the viewModel

I've stumbled upon a really weird situation with ASP.Net MVC, passing an "id" as an action parameter and an "id" hidden form element. I have an action that has an "id" parameter. This id value represents a project. I have a controller action where I'm creating a data entry form for assigning an employee to the passed in project (I create a drop down list where administrator selects an employee to be assigned to passed project). By assigning an employee to a project, I create a ProjectEmployee record (project id, employee id, and an id that represents the combination which is an identity column in the database). The identity column (which is named "id") is also a hidden form element. This is necessary because I need to be able to edit the project/employee assignment at a later date.
Anyways, when creating a new employee assignment to a project, the project id (which is the "id" being passed to the action) is being applied to the "id" hidden form element.
I'm passing 117 into the action. 117 is the projectId. It's being set to the "id" hidden field which should be 0 because 0 represents a new project/employee assignemnt.
View Model
id - unique id that represents the Project/Employee combination
projectId - the "Id" being passed to action
EmployeeId - what the admin is selecting from drop down
rate
startdate
endDate
...
Data Entry Form
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.ProjectId)
...
Id: #Model.Id <br />
ProjectId: #Model.ProjectId<br />
So, #Html.HiddenFor(m => m.Id) renders a hidden form element with a value of 117. #Model.Id renders 0 to the UI. Stepping through the code I can visually see the value of the Id property to be 0. How come HiddenFor is getting it's wires crossed and pulling the value of 117?
This bug has made it's way into production so I have a mess on my hands with data getting messed up because instead of creating a new record in the database table, I'm actually UPDATING existing records because the "Id" property is erroneously getting set from 0 (which represents a new record) to 117 (which is the projectId) and therefore am updating a different record.
How come HiddenFor is getting it's wires crossed and pulling the value of 117?
That's by design. All HTML helpers such as TextBoxFor and HiddenFor first use the value of ModelState when binding and then the value of the model. I presume your controller action looks like this:
public ActionResult Foo(int id)
{
SomeModel model = ...
// At this stage model.Id = 0
return View(model);
}
The thing is that the default model binder adds a value into the ModelState with the key id which is used by the helper and the model property value is ignored. This is not a bug. It is how the HTML helpers are designed. It's a bit confusing in the beginning when you don't know it, but once you get accustomed to this behavior you don't fall in the trap a second time.
One possibility to fix the problem is to remove this value from ModelState:
public ActionResult Foo(int id)
{
ModelState.Remove("id");
SomeModel model = ...
// At this stage model.Id = 0
return View(model);
}
Or rename the Id property inside your model to something else to avoid the conflict.
As an alternative to changing your field names, you can make a new helper method that doesn't use the route values when evaluating what to put in the value attribute.
I created this helper for the extremely common id case:
public static MvcHtmlString HiddenIdFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression)
{
var value = ModelMetadata.FromLambdaExpression(expression, html.ViewData).Model.ToString();
var builder = new TagBuilder("input");
builder.MergeAttribute("type", "hidden");
builder.MergeAttribute("name", ExpressionHelper.GetExpressionText(expression));
builder.MergeAttribute("value", value);
return MvcHtmlString.Create(builder.ToString(TagRenderMode.SelfClosing));
}
Then you can use this in the template:
#Html.HiddenIdFor(m => m.Id)
#Html.HiddenIdFor(m => m.ProjectId)

turn off data-val-* attribute on primitive types

Does anybody know a way to turn off MVC3 automatically decorating primitive types with a data-val-* attribute.
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
removes the data-val-required attribute, but I can't seem to find a way to turn off primitive types eg: data-val-number
I have a lot of hidden int fields which don't required validating on a form, but because of these attributes they are getting validated, causing my app to appear frozen.
I imagine that the hidden int fields have the [Required] data annotations defined on them in the viewmodel? If so then I believe you just need to remove the data annotation to prevent the data-val-required attribute from being displayed.
I could be wrong, but I suspect you will then say that the field is required when that viewmodel is used in some other views?
If this is the case, then rather than turning off the data annotations (which is essentially a work around) then you need to define your view models correctly. Ideally, each view model should be specific for the view that it is defined (see pattern 3 of the following link). This will avoid the issues where you have fields that are required on some views and are not required on others.
I couldn't seem to find a way to turn this off, so created my own HtmlHelper as a way to get around this issue.
public static IHtmlString HiddenInputFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
var memberExpression = (MemberExpression)expression.Body;
string fullID = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(memberExpression.Member.Name);
var builder = new TagBuilder("input");
builder.MergeAttribute("type", "hidden");
var value = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
builder.MergeAttribute("value", value.ToString());
string fullName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
builder.MergeAttribute("name", fullName);
builder.GenerateId(fullID);
var tag = builder.ToString(TagRenderMode.SelfClosing);
return new HtmlString(tag);
}
I've noticed that if you load a partial view from an ajax request, the validations (data-val-*) inside the partial view are not automatically added. So I finally changed my code to load from ajax the heavy form data that doesn't need validations.
You can specify data-val="false" in the HTML input which you are creating on the page, for example:
<input type="checkbox" name="foo" value="#item.foo" class="input-validation-error"
data-val="false">

MVC3 DropDownListFor not setting selected property

In MVC3, when using DropDownListFor is it necessary for the first parameter to be a string? I have the following setup:
#Html.DropDownListFor(m => m.MyListItemId, Model.MyListItems,
new Dictionary<string, object>
{
{ "style", "width:120px" },
{ "data-type", "myList" }
})
where m.MyId is an int on my viewmodel. I'm having an issue where when I change the selected item in my drop down list, and inspect the rendered html, the "selected" property is not set to the newly selected item. This is a problem as i'm using jquery clone function to copy that row and i need the list with the new selected item to be copied to my new row. Ideas?
Update - Changing the property on the viewmodel to a string makes no difference.
Is this a bug with mvc dropdownlistfor? I've read quite a few posts on similar issues, but can't seem to find a solution that works in this instance. This is how my list is setup in my code:
var myListItems = _myRepository.GetAll();
model.MyListItems = new SelectList(myListItems, "Id", "Name", lineItem.myListItemId);
model.MyListItemId = lineItem.myListItemId;
where lineItem is passed into this method
No, the selected value property does not need to be a string, it can be an int. As long as the value is convertible to a string, it should work (so selected value type could be a Guid, int, bool, etc).
I have sometimes found issues when my route for the page has a route parameter with the same name as the selected value model property. For example, consider this:
route: "/establishments/{establishmentId}/edit"
Model:
public class MyViewModel
{
public int EstablishmentId { get; set; }
public SelectListItem[] Establishments { get; set; }
}
View:
#Html.DropDownListFor(m => m.EstablishmentId, Model.Establishments)
With this code, the selected value of the drop down list would always be whatever establishmentId is in the route. So if the route were /establishments/12/edit, then value 12 would be selected in the dropdown. It doesn't matter that the route parameter and model property capitalization doesn't match.
I figured this out by downloading the MVC source code, making my own copy of the DropDownListFor (named MyDropDownListFor), then stepping through the code to see what happened. If you are still having trouble with MVC3, I suggest you do the same. You need to figure out whether this is an issue with the server code, or your jquery clone stuff.

How to set a hidden value in Razor

I know that what I'm trying to do is bad idea, but I have specific constrains for now.
I have multiple sites, using one and the same MVC3 code base. For one of them the requirement is to hide some required fields from the form.
I know that the best approach is to modify the controller to set the defaults for these fields, but I'd like to achieve this modifying only the view for this particular site w/o changing the code.
So, how do I set a particular model property to a default value in the view? The ideal should be something like:
#Html.HiddenFor(model => model.RequiredProperty)
#model.RequiredProperty = "default"
EDIT: more explanation
So, actually this is in a sub-view, which is used by 2 different main views. I need these properties set only if one particular main view is used, and not the others.
So, I guess the setting to default need to go to that particular "main" view. Looks like I can not use HiddenFor in the sub-view, and then Html.Hidden in the main.
Is there a way to check in the sub-view which is the outer view?
If I understand correct you will have something like this:
<input value="default" id="sth" name="sth" type="hidden">
And to get it you have to write:
#Html.HiddenFor(m => m.sth, new { Value = "default" })
for Strongly-typed view.
There is a Hidden helper alongside HiddenFor which lets you set the value.
#Html.Hidden("RequiredProperty", "default")
EDIT Based on the edit you've made to the question, you could do this, but I believe you're moving into territory where it will be cheaper and more effective, in the long run, to fight for making the code change. As has been said, even by yourself, the controller or view model should be setting the default.
This code:
<ul>
#{
var stacks = new System.Diagnostics.StackTrace().GetFrames();
foreach (var frame in stacks)
{
<li>#frame.GetMethod().Name - #frame.GetMethod().DeclaringType</li>
}
}
</ul>
Will give output like this:
Execute - ASP._Page_Views_ViewDirectoryX__SubView_cshtml
ExecutePageHierarchy - System.Web.WebPages.WebPageBase
ExecutePageHierarchy - System.Web.Mvc.WebViewPage
ExecutePageHierarchy - System.Web.WebPages.WebPageBase
RenderView - System.Web.Mvc.RazorView
Render - System.Web.Mvc.BuildManagerCompiledView
RenderPartialInternal - System.Web.Mvc.HtmlHelper
RenderPartial - System.Web.Mvc.Html.RenderPartialExtensions
Execute - ASP._Page_Views_ViewDirectoryY__MainView_cshtml
So assuming the MVC framework will always go through the same stack, you can grab var frame = stacks[8]; and use the declaring type to determine who your parent view is, and then use that determination to set (or not) the default value. You could also walk the stack instead of directly grabbing [8] which would be safer but even less efficient.
While I would have gone with Piotr's answer (because it's all in one line), I was surprised that your sample is closer to your solution than you think. From what you have, you simply assign the model value before you use the Html helper method.
#{Model.RequiredProperty = "default";}
#Html.HiddenFor(model => model.RequiredProperty)
How about like this
public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, object htmlAttributes)
{
return HiddenFor(htmlHelper, expression, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString HiddenFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, IDictionary<string, object> htmlAttributes)
{
return htmlHelper.Hidden(ExpressionHelper.GetExpressionText(expression), value, htmlAttributes);
}
Use it like this
#Html.HiddenFor(customerId => reviewModel.CustomerId, Site.LoggedInCustomerId, null)

Resources