I'm not sure what I'm doing wrong here. The default display template for a model I'm using is not being used.
This code is in my main action view:
#if (Model.EmbeddedMediaModels != null)
{
foreach (var mediaItem in Model.EmbeddedMediaModels)
{
BitmapFigureModel bitmap = mediaItem as BitmapFigureModel;
if (bitmap != null)
{
var mm = ModelMetadata.FromLambdaExpression(p => bitmap, this.ViewData);
var modelTypeName = mm.ModelType.Name; // = "BitmapFigureModel"
// Neither resolve the template.
// Html.DisplayFor(m => bitmap);
Html.DisplayFor(m => bitmap, modelTypeName);
}
}
}
The Model.EmbeddedMediaModels property is a collection of EmbeddedMediaModel base types, at present it just contains one object, a BitmapFigureModel which derives from EmbeddedMediaModel.
It's tempting to think that this is confusing matters, but the ModelMetadata instance retrieved is quite able to see the correct BitmapFigureModel model type.
Besides, even if I specify the model type name in the call to DisplayFor it still doesn't work.
And here's proof that a correctly-named display template partial view is in place.
What am I doing wrong?
Contrary to the advice from Brad Wilson (ASP.NET team):
The expression-based versions are primarily used for pulling values
from the model (they are parametrized by the current model, as shown
in the example above). They can also be used for pulling values from
some source other than the model or ViewData (for example, with an
expression like “model => someOtherValue” which ignores the model
entirely). This makes them useful in loops.
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html
It actually seems that its not possible to "ignore the model entirely". In the comments under my question DaveParsons suggests to experiment by just newing-up a model instance and passing it into DisplayFor, this leads to the error:
Templates can be used only with field access, property access,
single-dimension array index, or single-parameter custom indexer
expressions.
So it appears that I should stop being a smarty-pants and just use Html.Partial as Ehsan Sajjad suggests.
do like this:
#Html.DisplayFor(m => mediaItem.Name)
if you want to load the partial view:
#Html.Partial("~/Views/Shared/DisplayTemplates/BitmapFigureModel.cshtml", mediaItem)
or:
#Html.RenderPartial("~/Views/Shared/DisplayTemplates/BitmapFigureModel.cshtml", mediaItem)
In your BitmapFigureModel.cshtml:
#model BitmapFigureModel
Related
This is a weird one. I have the following view file (Views/Search/Submit.cshtml):
#model IEnumerable<KeyValuePair<string, ISearchProvider>>
#foreach (var provider in Model)
{
var results = provider.Value.Results.Take(10);
if (results.Count() > 0)
{
<text><li class="dropdown-header">#provider.Key</li></text>
#Html.DisplayFor(x => results)
}
}
... where results is a System.Collections.Generic.IEnumerable<out T>, and T is ISearchMatch.
I have then defined a display template in Views/Search/DisplayTemplates/SiteSearchMatch.cshtml;
#model SiteSearchMatch
<li>#Html.ActionLink(Model.Name, "details", "site", new { Id = Model.Id }, null)</li>
... and SiteSearchMatch implements ISearchMatch like so;
public class SiteSearchMatch: ISearchMatch
{
public int Id { get; set; }
public string Name { get; set; }
}
I'd expect that my display template gets used; but it doesn't. Instead, the output I see being output is;
<li class="dropdown-header">sites</li>
11147166811481897189813271028
... where that string of numbers is the combination of all the Ids of the ISearchMatch's I wanted to render via the display template.
It seems Razor is simply rendering the ISearchMatch using the first attribute defined in the class; if I remove the definition of the Id property, I instead see the combination of all the Name's of the ISearchMatch's.
Does anyone know why this is happening, and how I can get Razor to use the display template I've specified?
Your expectation is wrong:
I'd expect that my display template gets used; but it doesn't.
The output you see is the ID's simply listed. I suspect your ISearchMatch-interface does only expose the Id-property, but this does not matter. What matters is the actual type of the instance of the result. In your case the following line:
#Html.DisplayFor(x => results)
can be implicitly evaluated as
HtmlHelper<IEnumerable<KeyValuePair<string, ISearchProvider>>>
.DisplayFor<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>>
(Func<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>> expression);
Looks pretty complex, but basically it's just a implicit substitution of your model and expression result. Your model is of type IEnumerable<KeyValuePair<string, ISearchProvider>>. That's also the type for the input of your lampda-expression. The result is of type IEnumerable<ISiteMatch>. And here come's the important thing!
The DisplayFor implementation checks, if the result type is enumerable or not. If not, it searches for a fitting template for the type, otherwise it will iterate through the elements and does this for all elements. 1
Searching for a template works based on the type name. In your case the template uses the name of the enumerated type, which is ISearchMatch. It does not find any display template, so it simply dumps the properties, resulting in what you see:
11147166811481897189813271028
To fix this problem, you need to convert your result set to the correct type first. You can do this in different ways. Either you cast the whole result of your provider results:
var results = provider.Value.Results
.Cast<SiteSearchMatch>()
.Take(10);
or you cast them individually within your lamda expression:
#Html.DisplayFor(x => (SiteSearchMatch)results)
The important thing is, that the scalar result type is the same as the model in your display template.
1 Note that this is a little bit more complex, for example the the extension also keeps track of an index and applys it to the output, so that the model could be bound for postback purposes.
The lame answer is that the "Build Action" on my View file Views/Search/DisplayTemplates/SiteSearchMatch.cshtml was set to "None", rather than "Content".
This meant the code worked fine when running in Debug mode within Visual Studio, but didn't work when any deployment was made.
Just to reiterate; this fix required no code changes. Simply change the "Build Action" back to "Content".
#model Customer
#Html.Partial("_UserProfile", (UserProfile)Model.UserProfile)
When i run this code, i get this error:
The model item passed into the dictionary is of type 'Customer', but this dictionary requires a model item of type 'UserProfile'.
Partial View _UserProfile is strongly typed.
I want to be able to edit these field.
Any suggestions?
Make sure your Model.UserProfile is not null.
I found your post trying to debug the same error, and it turned out I hadn't initialised my "Model.UserProfile" equivalent.
I guess what's happening here, is that if a null model is passed to RenderPartial, it defaults to using the main view's model? Can anyone confirm this?
If Model.UserProfile is null, it will attempt to pass in your customer model.
Two ways to get around this:
#model Customer
#Html.Partial("_UserProfile", (UserProfile)Model.UserProfile, new ViewDataDictionary())
Or:
#model Customer
if (Model.UserProfile != null)
{
#Html.Partial("_UserProfile", (UserProfile)Model.UserProfile)
}
I ran into this problem when dealing with parts of a user profile such as Name and Address records. If the user had an incomplete profile I want the account management view to detect a null Address record and display an Action link to create a new Address or display whatever address data is available.
As described by others when null is passed the overload for Html.RenderPartial gets triggered and the parent View Model is passed through. I ended up converting my partial views to Display and Editor Templates to get around it. Here are some How-To articles from: Hansleman and codeguru
You get better re-usability from this method and it preserves the null values:
In your View:
#Html.DisplayFor( m=> m.Address)
Then handle the null value in the DisplayTemplate.
#model Namespace.Models.MyObject
...
if(#Model != null){
...
}else{
...
}
I have faced the same problem but finally I had figured it out.
There is a type mismatch in passed models .. Your View accepts model of type Customer but you partial view is passing the model Userprofile so what you have to do is pass the same model in both or.... create a model that have all properties of both models. Surely your problem will be solved.
It will fallback on initial model if passed item is null.
Try this:
#Html.Partial("_UserProfile", (UserProfile)Model.UserProfile ?? new UserProfile())
Your trying to case a Customer type object to a UserProfile type object. By default this wont work as the framework has no idea how to cast these objects. If you absolutely must do it this way the only option is to provide explicit cast operator like:
public static explicit operator Digit(byte b) // explicit byte to digit conversion operator
{
Digit d = new Digit(b); // explicit conversion
System.Console.WriteLine("Conversion occurred.");
return d;
}
You can read more about it here.
Add the keyword "virtual" to the UserProfile property on the Customer model.
It is the easyest way overcome the lazy loading, but performance..
I have been reading and reading , and I can't seem to get this to work at all. I am very very new to asp.net MVC - after all the tutorials I read I finally got this much accomplished.
public class EventsController : Controller
{
private EventsDBDataContext db = new EventsDBDataContext();
public ActionResult Index()
{
var a = (from x in db.tblEvents
where x.StartDate >= DateTime.Now
select x).Take(20).ToList();
return View(a);
}
}
This is successfully finding 20 rows (like it is supposed to). Now how do I display these in the view ?? Does it have to be a strongly typed view?? It doesn't seem like it should have to be... I have tried both , I tried typing a whole view, but for now it would be nice to just get one property of tblEvents to show up in the view. This is not working, I have tried many many variations.
#{foreach( var item in Model){
#Html.DisplayFor( item.ID)
}
}
How do I get the results from the controller displayed in the view? Just the ID is good for now - I can go from there.
The problem is that your View doesn't know what type your Model is. Use the #model syntax to define the type of your model.
#model List<YourEventClass>
#foreach( var item in Model )
{
#item.ID<br />
}
See i.e. here for more information
from the root of the web project you should have a directory called Views. Within the views folder create a new folder named Events. In the Events folder create a razor view named Index. put your markup and template code in this file.
You are correct, views do not need to be strongly typed. I find it's a good idea to do so because it provides another compile time check, but it's not required.
when you run the application you will navigate from the root (typically home/index) to Events/Index. there you should see the list of 20 items rendered in the view.
I guess you can not do something like this:
#{foreach( var item in Model){
#Html.DisplayFor(modelItem => item.ID)
}
unless the view knows what Type Model is returned as (still seems weird that .Net can't figure that out on its own. So I fixed the problem and got the ID displayed properly by adding this to the View.
#model IEnumerable<GetEvents.Models.tblEvent>
This works fine for this example - I am returning one table , so the Model type is just the class for the table. But this doesn't seem right - what if I wanted to query and join tables then what would the Model Type be?? Adding this fixed my problem , but if someone has a better answer for this then I will accept that.
I'm trying to create a strongly typed model for one of my Views in MVC. The model is the result of a LINQ GroupBy query so it is the type shown below (grouping employees by first letter of surname).
#model IQueryable<IGrouping<string, Employee>>
I'm unsure why but it doesn't let me have a model of this type. The error message I get is:
An opening "<" is missing the corresponding closing ">". Which is incorrect.
I know I can create a view specific model and populate that instead but I'd like to know why this model doesn't seem to work?
By default, a very limited set of namespaces are available for direct use in razor views. Try to expand it to fully qualified names and see if the problem persists:
#model System.Linq.IQueryable<System.Linq.IGrouping<string, Name.Space.Employee>>
I don't know why you'd be getting this error, since you appear to be using correct Razor code. It's possible that there's actually a bug elsewhere in the page that is being made manifest through this incorrect error message.
A workaround, which may help you determine the real source of the bug, would be to create your own strongly-typed model class, which could have this data as its property:
public class EmployeeListViewModel
{
public IQueryable<IGrouping<string, Employee>> EmployeesByCompanyTitle {get;set;}
}
(There are those who would argue that this is a better approach anyway, since you can now add information to your view model more easily.)
Just came across an interesting effect while debugging a View. The scenario is easy to reproduce - I have a breakpoint in a View, in the Watch window I add ViewBag.ViewData and the value is null. However, if I just add ViewBag and expand the object, I can see the ViewData and it is not null. I can also successfully expand it and see its properties.
Can anyone explain whether it's a bug or what causes this behavior?
EDIT
ViewBag.ViewData is actually null. E.g. if I have this code in the View:
if (ViewBag.ViewData == null)
{
<span>ViewBag.ViewData is null</span>
}
it displays the span. So the weird part is that I can expand it in the watch window and see the properties.
EDIT2
In response to #Darin Dimitrov's answer - I tried to reproduce this behavior with a custom test class and I get a RuntimeBinderException when trying to access the private property: 'SomeClass.SomeProperty' is inaccessible due to its protection level:
public class SomeClass
{
private string SomeProperty;
}
dynamic dynamicObject = new SomeClass();
if (dynamicObject.SomeProperty == null)
{
Console.WriteLine("dynamicObject.SomeProperty is null");
}
In this case, shouldn't I be getting the same exception when accessing ViewBag.ViewData in the View (the line with if (ViewBag.ViewData == null))?
What you see in the debugger/watch window is the private ViewData property of the ViewBag. When you do the test in the view you obviously have no access to this private field and you get null because there is no corresponding public property.
Now do the following test in the view:
#if (null == ViewBag
.GetType()
.GetProperty("ViewData", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(ViewBag, null)
)
{
<span>ViewBag.ViewData is null</span>
}
and you won't see the span.
Of course all this is very funny but when it comes to writing real world and properly architected ASP.NET MVC applications both ViewData and ViewBag have no place. Those two are my worst enemies in ASP.NET MVC application development.
Conclusion: always use view models and strongly typed views and have fun.
I don't have a technical explanation, but I believe it's because ViewBag is dynamic.
It's like doing a watch on an expression, you don't see the contents of the resultset without running the expression.
I guess the ViewBag is behaving the same way, because you're going directly to a property that hasn't been loaded, and you're doing it through a debugger, it simply decides not to load that property.
I could be totally wrong of course :D
Just for arguments sake, does it do the same in quick-watch or the immediate window ?
While your code always runs within the limits of certain scope (class, method, private, etc), the debugger has no such limits
why are you referencing ViewBag.ViewData? Just reference ViewData directly.
This may work because behind the scenes viewbag uses viewdata you may be looking at an internal representation of ViewData, separate than your dynamimc property of "ViewData"
After testing this out, ViewBag shows up as a nonpublic member... which is what I would expect.
System.Web.Mvc.DynamicViewDataDictionary has a non public member ViewData