new HTMLHelper with generic type - asp.net-mvc

So I tried searching, but I'm not exactly sure what to search for. I'm trying to make an HtmlHelper extension that takes a model that is of type MyModel. However, the View takes a model type List(Of MyModel). In my extension, which was originally created to take just MyModel, when the View Model is a List(of MyModel) and I use
For Each Item in Model
Html.MyHelper(Item)
Next
I get an exception 'The model item passed into the dictionary is of type 'System.Collections.Generic.List1[MyModel]'`, but this dictionary requires a model item of type 'MyModel'.'
My original Helper is
Public Function FormatTable(Of T As Class)(helper As HtmlHelper, model As T) as OutModel(Of T)
and i want something like
Public Function FormatTable(Of List(of T As Class))(helper As HtmlHelper, model As T,
tableType As RenderType, Optional columns As Integer = 2) As FormatTable(Of T)
or should i create a new helper in the view to pass?

Although I'm not sure how to do a Function MyExtension(of List(Of T as Class))(model as T) or if that's even possible, I did solve my situation. I needed to create an HtmlHelper of a different class type and use a different model and found MvcUtil which does just that.
I came across this post: Stronglytyped html helper with different model for get and post
In my view i create a new htmlhelper of the new model type to call my htmlextension
For Each Item in Model
#Html.HtmlHelperFor(Of MyType)().MyExtension(Item)
Next
and now have a check
If(IsNothing(helper.ViewData.Model)) Then
helper = helper.HtmlHelperFor(Of T)(model)
End If
This solved my problem when looping through a list in my View.

Related

HTML Helper how to use IEnumerable

I want to create a custom HTML Helper where I can pass a LINQ expression as a parameter, like this:
#Html.GetBackgroundColor(model => model.RiskAssessment)
I want to use it to display some custom css in an MVC view, depending on what the RiskAssessment property is.
So I created a helper method like this:
public static string GetBackgroundColor<T, TResult>(this HtmlHelper<T> htmlHelper, Expression<Func<T, TResult>> expression)
{
...
}
However, that won't compile, the error is IEnumerable does not contain a definition for 'RiskAssessment'
So I changed the method to
public static string GetBackgroundColor<T, TResult>(this HtmlHelper<IEnumerable<T>> htmlHelper, Expression<Func<T, TResult>> expression)
{
...
}
which compiles, so now I presumably have all the objects in the collection but I have no idea how to get the object I want as I can't use use the expression on the IEnumerable, there is no Where() method available. I would have thought I could do something like this:
IEnumerable<T> collection = htmlHelper.ViewData.Model;
T obj = collection.Where(expression)
but I just don't know what I am doing wrong here.
Figured it out, simple mistake. The table header row is set up with #Html.DisplayNameFor(model => modelType), and I was trying to call my custom HTML helper with these parameters. I should have been calling the method on each table row, using #Html.GetBackgroundColor(modelItem => item.RiskAssessment), and this works because I can use htmlHelper.ValueFor(expression) within the method to get the property value.
That said, I have no idea how the header row is generated as Html.DisplayNameFor uses the same method signature as my custom method but Intellisense reports that one of the Expression types is unknown. But that is not an issue for me.
Thanks.

MVC - Inherited view model members [duplicate]

My Post call does not return the correct Model type. It always use the baseObject instead of the correct derived object that I passed in from the Get
RestaurantViewModel.cs
public class RestaurantViewModel{
public Food BaseFoodObject{get;set;}
}
Food.cs
public class Food{
public string Price{get;set;)
}
Bread.cs -- Inherit from Food
public class Bread:Food{
public int Unit{get;set;}
}
Milk.cs -- Inherit from Food
public class Milk:Food{
public string Brand{get;set}
}
Editor For Template for Bread. Display the unit and allow user to edit
Index.html
#Model RestaurantViewModel
#using(Html.BeginForm("SaveFood", "Food"))
{
#Html.EditorFor(m=>m.BaseFoodObject)
<input type="submit" value="Process"/>
}
Bread.cshtml
#Model Bread
<div>
#Html.TextboxFor(bread=>bread.Unit)
</div>
FoodController.cs
public ActionResult Index(){
Bread bread = new Bread(){
Price = "$10",
Unit = 1
}
RestaurantViewModel viewModel = new RestaurantViewModel(){
BaseFoodObject = bread
}
return View(viewModel);
}
public ActionResult Post(RestaurantViewModel viewModelPost)
{
// When I inspect the viewModelPost, there is no attribute for unit
}
Final Result:
1. The display looks correct. EditorFor is smart enough to pick the correct editor template and display the value correctly
2. The Save does not work. The Unit attribute of Bread Object does not get passed in with the RestaurantViewModel. The reason for that is the RestaurantViewModel used the Food object instead of Bread
I hope there is away to modify the EditorFor and tell it to use the Model in the View or the Object Type that I passed in when I display it.
Thanks
Update 1: I solved this problem by using the custom binder and using a factory to decide which object I really want. This helps construct the correct Model which I want
MVC is stateless. A couple of references.
There's a couple of statements in your question that conflict with this, and how MVC binding works eg:
My Post call does not return the correct Model type.
Possibly just terminology, but your Post call does not 'return a model type' - it goes into the model that's defined in the post action, in this case RestaurantViewModel.
instead of the correct derived object that I passed in from the Get
because it is stateless, it knows nothing about the model you passed in from the get... absolutely nothing.
The final html rendered via the getaction+view.cshtml+model is not linked to the postaction. You could just as easily take the rendered html, save it, reboot your PC, reload the rendered html and it will work exactly the same way.
a way to modify the EditorFor and tell it to use the Model in the View or the Object Type that I passed in when I display it
When you use EditorFor it sets an ID and name attribute based on the model it was bound to, so it already does this, but perhaps you are not binding to the model you want to bind to to get the correct id.
So, to the question, if, in 'normal' C# code you were to instantiate a new instance of RestaurantViewModel, what would you expect the type of BaseFoodObject to be?
This is what the ModelBinder is doing - it's creating a new RestaurantViewModel.
As your post action method's signature does not include anything to do with Bread - all the bread properties are ignored.
Some options:
Check for the food properties after binding and read them manually (probably the quickest+easiest but not very "mvc-ish")
public ActionResult Post(RestaurantViewModel viewModelPost)
{
if (!string.IsNullOrEmpty(Request.Form["Unit"]))
// it's a bread form
to make this easier, you could provide a hidden field with the type
if (Request.Form["Type"] == typeof(Bread).Name)
{
var bread = new Bread { Unit = Request.Form["Unit"] }
Add bread to the action so it's bound
public ActionResult Post(RestaurantViewModel viewModelPost, Bread bread)
but then, obviously, it won't work for milk.
So could extend this using an ActionNameSelector to select the correct action
public ActionResult PostBread(RestaurantViewModel viewModelPost, Bread bread)
public ActionResult PostMilk(RestaurantViewModel viewModelPost, Milk milk)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class FoodSelectorAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
... check if provided parameters contains bread/milk
(related link but not a solution to this specific case)
Another option might be to change the Restaurant type to a generic, but would require a few more changes (and ideally use of interfaces), and more details (provided here as an idea, rather than a solution)
The basics would be:
public class RestaurantViewModel<T>
where T: Food
{
}
public ActionResult Post(RestaurantViewModel<Bread> viewModelPost)
public ActionResult Post(RestaurantViewModel<Milk> viewModelPost)
but I've not confirmed if the default ModelBinder would work in this case.
The problem comes with the post. Once you post, all you have is a set of posted data and a parameter of type, RestaurantViewModel. The modelbinder sets all the appropriate fields on Food because that's all it knows. Everything else is discarded. There's nothing that can be done about this. If you need to post fields related to Bread then the type of your property must be Bread. That's the only way it will work.

Two models in a single view without using a VM-class

I'm trying to put two partialviews together but I keep getting errors.
I've got a Subject-model and a Category-model, and I've made partial views which seem to work perfectly, but when I put them together in a single view this pops up:
Error 1 'Myproject.Models.Subject' is a 'type', which is not valid in the given context
this is the only code in my view:
#{Html.RenderPartial("_CategoryPartial", Myproject.Models.Category);}
#{Html.RenderPartial("_SubjectPartial", Myproject.Models.Subject);}
I guess I will have to create a seperate viewmodel if all else fails, but I thought on checking on here first
The problem is that you are passing in the type of Model rather than an object.
void HtmlHelper.RenderPartial(string partialViewName, object model)
You could do this if you changed your code to;
#{Html.RenderPartial("_CategoryPartial", new Myproject.Models.Category());}
#{Html.RenderPartial("_SubjectPartial", new Myproject.Models.Subject());}
Then you would need to populate the properties within each object.
An alternative is to change your code to call a class which returns the populated objects, e.g.
#{Html.RenderPartial("_CategoryPartial", Myproject.Models.CategoryRepository.GetCategory(id));}
#{Html.RenderPartial("_SubjectPartial", Myproject.Models.SubjectRepository.Get(id));}
You need to be passing instances of those models to the partials, whereas you are just specifying the types, hence the error.
So yes, for this view, I would create a view model, something like:
public class ExampleViewModel
{
public Category Category {get;set;}
public Subject Subject {get;set;}
}
and then do:
#{Html.RenderPartial("_CategoryPartial", Model.Category);}
#{Html.RenderPartial("_SubjectPartial", Model.Subject);}

Using LLBL as Model in MVC

I have settled on trying to use ASP.NET MVC but the first part I want to replace is the Model. I am using LLBL Pro for the model.
I have a table called "Groups" that is a simple look up table. I want to take thhe results of the table and populate a list in MVC. Something that should be very simple... or so I thought.... I've tried all kinds of things as I was getting errors like:
The model item passed into the dictionary is of type 'System.Collections.Generic.List1[glossary.EntityClasses.GroupEntity]', but this dictionary requires a model item of type 'System.Collections.Generic.IEnumerable1[glossary.CollectionClasses.GroupCollection]'.
private GroupCollection gc = new GroupCollection();
public ActionResult Index()
{
gc.GetMulti(null);
return View( gc.?????? );
}
This is all I am trying to do, I've tried lots of variations, but my goal is simply to take the data and display it.
Not sure if this would work, but you could try wrapping the EntityCollection into a ViewModel class and passing it to the View like so:
public class GroupsViewModel()
{
public GroupCollection Groups { get; set; }
// other items in your view model could go here
}
then convert your controller method to
public ActionResult Index()
{
GroupCollection gc = new GroupCollection();
gc.GetMulti(null);
GroupsViewModel vm = new GroupsViewModel();
vm.Groups = gc;
return View(vm);
}
I like this approach because each ViewModel is an object in-and-of itself.
You can use the AsEnumerable extension where your ????? are or change the type of your ViewUserControl(in the markup) to be of type System.Collections.Generic.List. Basically what you need to correct is the mismatch between the type of the View and the Model being passed in.
I'm not sure about your exact error, but I'd venture a guess that one of two things are happenging:
You are making some sort of invalid / illegal call on your LLBLGen object. If this is the case make sure you are setting it up right / calling right method / property etc.
The model you are passing to the veiw is too hairy for it to deal with. In this case, and in general, you should create a light 'View Model' class with just the data you want displayed and populate it from your LLBLGen object first then pass it to the view, which will be able to easily handle your view model class.
Here are some references:
http://stephenwalther.com/blog/archive/2009/04/13/asp.net-mvc-tip-50-ndash-create-view-models.aspx
http://nerddinnerbook.s3.amazonaws.com/Part6.htm
http://www.codinginstinct.com/2008/10/view-model-inheritance.html
Stemming off what Yuriy said, it looks like your view is strongly typed to a "collection" of a collection of your groupentity, and you are trying to pass just the collection of your groupentities. Make sure your "collection" type (IEnumerable, IList, etc) matches what type of collection you are sending in your controller, along with the type of the actual object in the collection.
View:
System.Collections.Generic.List1[glossary.EntityClasses.GroupEntity]
Controller:
System.Collections.Generic.List1[glossary.EntityClasses.GroupEntity]
Just a thought

Dynamic Object Properties in a MVC Helper on a Entity Framework object

I am new to vb9 and the .NET MVC. I want to build a MVC helper function for which I pass a Entity Framework object and have it build a select. Generally speaking I tried something like this:
Public Function RenderSelect(ByVal helper As HtmlHelper, ByVal sSelectName As String, ByVal aItmes As Array, Optional ByVal sTitleKeyName As String = "name", Optional ByVal sValueKeyName As String = "id") As String
' open select
For Each Object In aItmes
' here i would like to do something like:
Dim OptionValue = Object.(sValueKeyName)
' NOTE: I have a var with the property name
Dim OptionTitle = Object.(sTitleKeyName)
. then add the option structure to the select
Next
' close select
Return String
End Function
However, it isn't working. It would be great to have a way to do this and pass the current entity object. The data types are beating me up. Thanks in advance.
What isn't working? Does your code run? Does it generate a string?
You've got at least one typo that will cause the generated string to not work in an HTML form. Should be <option value={0}>{1}</option>
Edit 2: To get the IDictionary(Of Object, String) from your EF objects, I would write partial class implementations that add a GetSelectOptions method to each of your objects. Or create an interface with that method in it that each of your EF objects implement. Then you'd just call the RenderSelect method and pass in EFObject.GetSelectOptions as the SelectOptions parameter.
Edit: Here's how I would do this. Make your calling code responsible for reading the key/value pairs from your EF object. Then your RenderSelect extension method can be much cleaner. You don't want your view helper methods to be dependent on the structure of your model objects. And you certainly don't want your helper method to be dependent on the fact that you're using EF objects.
Public Function RenderSelect(ByVal helper As HtmlHelper, _
ByVal name As String, _
ByVal SelectOptions As IDictionary(Of Object, String), _
ByVal SelectedKey As Object, _
ByVal htmlAttributes As IDictionary(Of String, Object)) As String
Dim result = <select name=<%= name %>/>
Dim optElement As XElement
For Each opt In SelectOptions
optElement = <option value=<%= opt.Key.ToString %>><%= opt.Value %></option>
If opt.Key.Equals(SelectedKey) Then
optElement.#selected = "1"
End If
result.Add(optElement)
Next
If htmlAttributes IsNot Nothing Then
For Each attr In htmlAttributes
result.SetAttributeValue(attr.Key, attr.Value)
Next
End If
Return result.ToString
End Function
For a complete set of overloaded DropDownList functions in VB.NET, check out this file from the vbmvc.codeplex.com project from which the above code was copied and modified.
http://vbmvc.codeplex.com/sourcecontrol/changeset/view/19233?projectName=VBMVC#331689
That code has a return type of XElement, but just use result.ToString to get the string representation of the element if that's what you want.
Ok, so the introspection in 2.0 is poor, but it has improved in 3.5. So I all but gave up on this until I started messing around with GetType, and I stumbled on this:
Imports System.Runtime.CompilerServices
Imports System.IO
Public Module HtmlCtrlHelper
<System.Runtime.CompilerServices.Extension()> _
Public Function RenderSelect(ByVal helper As HtmlHelper, ByVal sName As String, ByVal Itmes As Object, Optional ByVal sValueName As String = "id", Optional ByVal sDisplayName As String = "name") As String
Dim wOutputSrting As StringWriter = New StringWriter()
Dim wHtml As HtmlTextWriter = New HtmlTextWriter(wOutputSrting)
wHtml.RenderBeginTag(HtmlTextWriterTag.Select)
wHtml.AddAttribute("name", sName)
wHtml.AddAttribute("id", sName)
For Each thing As Object In Itmes
wHtml.RenderBeginTag(HtmlTextWriterTag.Option)
wHtml.AddAttribute("value", thing.GetType().GetProperty(sValueName).GetValue(thing, Nothing).ToString())
wHtml.Write(thing.GetType().GetProperty(sDisplayName).GetValue(thing, Nothing).ToString())
wHtml.RenderEndTag()
Next
wHtml.RenderEndTag()
Return wOutputSrting.ToString()
End Function
End Module
Now I can just call it like <%=Html.RenderSelect(Model)%> on my view if I so wish. Then if the entity Model object doesn't have 'id' or 'name' attributes, I can specify them like:
<%=Html.RenderSelect(Model, "nameofvalue", "nameofdisplay")%>
The ultimate shortcoming if VB seems is it's verbose introspection. If you have a string with the name of a property there is no good way to get the value of that property on a random object. Making something work requires touching each class. Dennis Palmer's method is probably the best way to allow for generic select creation.
So I pose the question: Will the Entity Framework team build something useful in for this in future versions?

Resources