Display template not used for interface - asp.net-mvc

I'm having a problem with display templates and dealing with interfaces and objects which implement the interface. In the example I have many objects, which I want to be rendered in a fixed way, I decided to create an interface and reference this in the view which I've decided to put into the shared display templates folder. DisplayFor doesn't seam to work for objects passed to it which implement the interface in the view, does any one know a solution to this.
Its probably easier to explain via code so I've wrote a quick example. The base interface and two classes which inherit from it:
public interface IPet
{
String Name { get; }
}
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
The example display template in shared display templates
#model IPet
<div>#Model.Name</div>
The example view model to be passed to the view
public class VM
{
public IPet Master { get; set; }
public IEnumerable<IPet> Minions { get; set; }
}
The controller (in this case to create mock information)
public ActionResult Index()
{
var viewModel = new VM();
viewModel.Master = new Cat(){Name = "Fluffy"};
var minions = new List<IPet>();
minions.Add(new Dog(){Name = "Dave"});
minions.Add(new Dog(){Name = "Pete"});
minions.Add(new Cat(){Name = "Alice"});
viewModel.Minions = minions;
return View(viewModel);
}
and finally the view which I would expect DisplayFor to work
#model ViewInheritance.Models.VM
<h2>Master</h2>
#Html.DisplayFor(x => x.Master)
<h2>Minions</h2>
#Html.DisplayFor(x => x.Minions)
Given that all the objects are are defined in the view model as the interfaces, howcome it fails to use the display template?
One solution I have found is to simply use the code
#Html.DisplayFor(x => x.Master, "IPet")
To recap, the question is:
Why does this happen?
Is there a way to make DisplayFor correctly work out that a type of Cat which implements IPet should in fact be looking at the common shared view IPet.cshtml?
Thanks

Starting a new MVC application and fixing the code to actually compile the view renders fine. It also renders fine when moving the view into the shared folder.
I Added setter to IPet:
public interface IPet
{
String Name { get; set; }
}
I updated implementation and added public accessors:
public class Dog : IPet
{
public String Name { get; set; }
}
public class Cat : IPet
{
public String Name { get; set; }
}
I left your VM alone and also did not change any code in your View.
Pressing F5, running the MVC application rendered the results as expected (See image).

Unfortunately, I don't think ASP.NET MVC currently supports automatically selecting templates based on implemented interfaces. I think this makes sense because a class could implement multiple interfaces, so if you had templates for more than one of those interfaces, which one should the framework choose?
You could use a base class instead of an interface if your design can cope with it:
Change IPet to a (possibly abstract) class.
Change IPet.cshtml to Pet.cshtml.
Otherwise I think you'll just need to explicitly tell the framework which template to use. Here are some options:
Decorate the view model properties with [UIHint].
Specify the template in your calls to your HtmlHelper methods such as DisplayFor.
Make your own ModelMetadataProvider and change the TemplateHint property of the resulting ModelMetadata.

Related

MVC Razor helpers do not render proper ID and Name attributes for fields of interface derived classes

I have a class which looks like this:
public class ApplicationFormModel
{
protected ApplicationFormModel()
{
CurrentStep = ApplicationSteps.PersonalInfo;
PersonalInfoStep = new PersonalInfo();
}
public PersonalInfo PersonalInfoStep { get; set; }
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
where IEducationalBackground, IAboutYou, and IOther are interfaces. I do not use this class directly, but I use derived classes of this one which upon instantiation create the proper instances of EducationalBackgroundStep, AboutYouStep, and OtherStep.
In my view, I am using Razor Helpers such as
#Html.TextBoxFor(model => (model.EducationalBackgroundStep as ApplicationFormModels.EducationalBackgroundAA).University, new {#class = "form-control", type = "text", autocomplete = "off"})
The field 'University', for example, is NOT part of the Interface and I therefore need the cast to access it. Everything is fine for properties of the interface itself, but those which I need to cast for do not end up having the correct ID and Name properties.
For example, instead of EducationalBackgroundStep_University as ID, I only get University. This causes the form to not include this value when submitting it.
I did not have this issue before when I used a base class instead of an interface, but then I had to include the EducationalBackgroundStep, AboutYouStep, and OtherStep in each derived class (and have it then of the correct derived type), but that is what I wanted to avoid.
Is there any way around this? Thank you very much!
The issue with the ID generation is because you are using casting (x as y) and the TextBoxFor expression handler can't determine what the original model property was (more to the point, it doesn't make sense to use the original model property as you're not using it any more, you're using the cast property)
Example fiddle: https://dotnetfiddle.net/jQOSZA
public class c1
{
public c2 c2 { get; set; }
}
public class c2
{
public string Name { get; set; }
}
public ActionResult View(string page, bool pre = false)
{
var model = new c1 { c2 = new c2 { Name = "xx" } };
return View(model);
}
View
#model HomeController.c1
#Html.TextBoxFor(x=>Model.c2.Name)
#Html.TextBoxFor(x=>(Model.c2 as HomeController.c2).Name)
The first textboxfor has ID c2_Name while the second has just Name
You have two options:
1) use concrete classes rather than interfaces for your viewmodel
2) don't use TextBoxFor and instead use TextBox and specify the ID manually (but then you'll lose refactoring)
#Html.TextBox("c2_Name", (Model.c2 as HomeController.c2).Name)
This will give you the ID you're expecting, but as #StephenMuecke rightly points out, this might not bind correctly when you do the POST - so you may still be stuck... but at least it answers the question.
#freedomn-m explained to me why my code wouldn't work and he put me on the right track to find a solution, so he gets the accepted answer.
The workaround I used is the following - so I now have the following classes:
public class ApplicationFormViewModel {
public PersonalInfo PersonalInfoStep { get; set; }
// constructors which take the other classes and
// initialize these fields in an appropriate manner
public IEducationalBackground EducationalBackgroundStep { get; set; }
public IAboutYou AboutYouStep { get; set; }
public IOther OtherStep { get; set; }
}
// in our case, XX can be one of 3 values, so we have 3 classes
public class ApplicationFormXX {
public PersonalInfo PersonalInfoStep { get; set; }
// constructor which take the ApplicationFormViewModel and
// initialize these fields in an appropriate manner
public EducationalBackgroundXX EducationalBackgroundStep { get; set; }
public AboutYouXX AboutYouStep { get; set; }
public OtherXX OtherStep { get; set; }
}
To the main View I send the ApplicationFormViewModel and for each of the fields, I call a separate Partial View.
The Partial views render the common fields which are present in the Interfaces and then, depending on the type of the object held by the interface, it calls a different partial view which accepts the correct Model.
Example:
In the main View I have (NOTE: The actions return a partial view):
#model Applications.Models.ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#Html.Action("RenderEducationalBackgroundStep", "ApplicationFormsLogic", routeValues: new {model = Model})
In the Partial View of for the EducationalBackgroundStep, I have:
#model ApplicationFormModels.ApplicationFormViewModel
// CODE, CODE, CODE
#{
var educationalBackgroundType = Model.EducationalBackgroundStep.GetType();
if (educationalBackgroundType == typeof(EducationalBackgroundXX))
{
<text>#Html.Partial("~\\Views\\Partials\\ApplicationForm\\Partials\\ApplicationSteps\\EducationalBackground\\_EducationalBackgroundXX.cshtml", new ApplicationFormModels.ApplicationFormModelXX { EducationalBackgroundStep = Model.EducationalBackgroundStep as EducationalBackgroundXX })</text>
}
// OTHER ELSE IF CASES
}
And then, the _EducationalBackgroundXX.cshtml partial view expects a model like this:
#model ApplicationFormModels.ApplicationFormModelXX
This way, no casting is required and everything works fine with the ModelBinder. Again, thank you #freedomn-m for setting me on the right track.
NOTE: In practice I need more fields than the ones presented here (for navigation and some custom logic), so actually all of these classes inherit an abstract base class (this makes it redundant to have the PersonalInfoStep declared in each of the classes, for example, because it can be inherited from the abstract base class). But for the intents and purposes of this method, what's present here suffices.

Am I using viewModels correctly (MVC4)?

I have an object that I would like to display in a Details view. The object has a bunch of properties that the view needs.
The object also has parents and grandparents, which I need to display in the view.
What I have for my object viewModel is:
public class ObjectViewModel
{
// Used when creating a new object under a parent object
[HiddenInput(DisplayValue = false)]
public int? ParentObjectId { get; set; }
[Required]
public Object Object { get; set; }
// Info that only the view needs, which is defined in the Controller based on some logic
public string ActiveTitle { get; set; }
// A bre
public IList<Object> ParentObjects { get; set; }
}
I then use this in my Detail controller method:
public ActionResult Detail(int objectId)
{
// TODO: Make this a service call
var object = _db.Objects.FirstOrDefault(s => s.ObjectId == objectId);
if (object == null)
{
return View("Error");
}
var model = new SetViewModel() {
ActiveTitle = object.Name,
Object = object,
ParentObjectId = object.ParentObject.ObjectId,
ParentObjects = _objectService.GetParentObjects(set.ParentObject)
};
return View(model);
}
Does this look right? Or should I be pulling the required fields from the Object model into the viewModel, and not the objects themselves?
To have an object type in your view model is super vague and your code would be hard to support if you are not the original programmer. I would Add the class type to the actual model or use generics to specify the class type as shown below:
public class ObjectViewModel<T>
{
// Used when creating a new object under a parent object
[HiddenInput(DisplayValue = false)]
public int? ParentObjectId { get; set; }
[Required]
public T Object { get; set; }
// Info that only the view needs, which is defined in the Controller based on some logic
public string ActiveTitle { get; set; }
// A bre
public IList<T> ParentObjects { get; set; }
}
Either option will work, and you will often have a mixture of both techniques in a given application.
The key idea is that your view model should contain what the view needs in order to display the data to the user.
If your view merely displays the individual, primitive fields in simply controls, e.g. a series of labels or textbox controls, then your view model should probably specify only the fields, and not the parent object.
However, it's very possible for your view to include a templated or custom control that "knows how" to display a complex object in its entirety. In that case, your view model would need to include the entire object. (In practice I find myself doing this much more often in WPF than ASP-MVC but I've done both).
It looks like the answer will be contextual. Many teams using layered architectures might adopt architectural conventions whereby layers below X should not be referenced directly by your views, and a data access class might be a likely candidate for such a restriction. In your case, it does look like you will be binding your view's structure to the structure directly to your database schema (assuming, since you're using "_db"), which might be considered unreasonably tight coupling.
Also, I'm assuming you're using "object" to represent "any general thing" rather than literally a System.Object, since your objects appear to have an ObjectId property in your lambda expression.

Asp.net mvc how to use htmlhelper to generate complex type?

I have a complex type License as a view model.
public class License
{
public string Name { get; set; }
// Other Properties
public List<Function> Functions { get; set; }
}
public class Function
{
public string Name { get; set; }
// Other Properties
public List<Unit> Units { get; set; }
}
public class Unit
{
public string Name { get; set; }
// Other Properties
}
Both the Function's view template and Unit's view template are dynamiclly rendered. So the html looks like this:
<!-- LicenseView -->
#model License
#Html.TextBoxFor(m => m.Name) // this is OK
#for(int i=0; i<Model.Functions.Count; i++)
{
#Html.Partial(Model.Functions[i].Name, Model.Functions[i])
}
and the FunctionView may look like this
#model Function
#Html.TextBoxFor(m => m.Name) // the generated html element's name is just 'Name'
#for(int i=0; i < Model.Units.Count; i++)
{
#Html.Partial(Model.Units[i].Name, Model.Units[i])
}
and this is UnitView
#model Unit
#Html.TextBoxFor(m => m.Name) // the generated html element's name is just 'Name'
So my question is, what should I do the make the Name attribute correct?
Thanks a lot
The only change you need to make in the above code is to use Editor instead of partial view.
So basically all you code will look similar to the following
#model License
#Html.TextBoxFor(m => m.Name)
// Editor will take care of the repetition and u don't need to explicitly pass in the name
// Since the model already have the attribute
#Html.EditorFor(Model.Functions)
Then create your editor template folder, "EditorTemplates", under "Shared" folder and name your view file as "Function"
Do the same for Unit class and you will get what you want.
As #Jack said... you can do this using Editors instead of PartialViews.
BUT... if you really want to use PartialViews, you can do it, but the model to pass should be the top one (License). This way is similar of what David Jessee proposed, but splitting the one view in several.
Pardon me for guessing at the problem, but are you asking for the DisplayName attribute?
It will define how the html helpers display your field lables
public class License
{
[DisplayName("License Name")]
public string Name { get; set; }
// Other Properties
public List<Function> Functions { get; set; }
}
public class Function
{
[DisplayName("Fun Name")]
public string Name { get; set; }
// Other Properties
public List<Unit> Units { get; set; }
}
public class Unit
{
[DisplayName("Unit Name")]
public string Name { get; set; }
// Other Properties
}
be sure to have
using System.ComponentModel;
in your model code.
If you want to be able to create all of the inputs for a complex object graph and have the entire graph be reconstituted by the model binder, the easiest way to approach it is to create a single view or partial view that renders the entire graph:
#for(int i=0;i<Functions.Length;i++){
#for(int j=0;j<Units.Length;j++){
#Html.EditorFor(Functions[i].Length[j].Unit)
}
}
The other option would be to find a way to pass the index of your element to the partial views for each leaf on your object graph.
Granted, a lot of people dont like the idea of rendering a complex model inside of a single view. However, your other option is to make the smaller child views for Units, etc. be dependent on having additional data either injected or provided by the context. 6 of one, half dozen of the other. Just about every time I've done the "academically correct" approach of making exactly one view or partial view for each type in an object graph, I ended up with a whole bunch of views that were not reusable to begin with and the only advantage I got was the ability to say, "Look! Lots of small files.....that are totally dependent on each other...why did I do that?"

Using the method in two model in one view of asp.net mvc 2.0

I have one view in my ASP.net MVC 2.0 project, I want to list the list of employee that I create method GetProfileCustomer() in CustomerModels and GetTransaction() in TransactionModels.
How can I import two different of models in a single view?
I'm fairly new to MVC as well, and I've struggled with the similar issues if I understand the question correctly.
I've found that you get much cleaner controller code if you design your ViewModels to be as close as possible to the data that the view is using. Your ViewModels can contain lists of other things including other model objects. Something like:
public class TransactionViewModel
{
public string dataelement1 { get; set; }
public int dataelement2 { get; set; }
//and so on...
//The Lists
public IList<Employee> EmpList { get; set; }
public IList<OtherModel> SomethingElse { get; set; }
//and so on...
}
In your controller, you construct and initialize your ViewModel
something like...
TransactionViewModel TVM = new TransactionViewModel();
//assign basic attributes here..
//make a list
TVM.Emplist = (from blah in context select blah).ToList();
//send it to the view
return View(TVM);
Hope this helps and happy to hear any feedback...

Solid approach to loading reference data into view models in ASP.NET MVC

I want a way to separate the loading of reference data into a view model from the controller. At the moment I have a view model with a property for the selected value and the reference data:
public IEnumerable<SelectListItem> DayTypes { get; set; }
public int DayTypeId { get; set; }
and the data is populated from the relevant repository in the controller action:
model.DayTypes = _dayTypeRepository.GetAll().ToSelectList(d => d.Description, d => d.Identifier.ToString());
I would like to change this because it pollutes the controller with lots of repositories and code that is not core to its concerns. All of these dependencies make unit testing the controller a pain.
One possible approach to solving this would be to make the view model class do the loading which would require a custom model binder to instantiate them using the IoC container to provide the repository dependency. Is this a good option?
Another approach that I think would be good is hinted at in CodeCampServer but is incomplete and commented out involving attributes on the field in the view model:
[SelectListProvided(typeof(AllDaysSelectListProvider))]
public IEnumerable<SelectListItem> DayTypes { get; set; }
however I am struggling to figure out how this could be implemented in a way that would not require some major replumbing of the MVC framework.
How do you solve this problem?
EDIT: I want to keep with strongly typed views and avoid stuffing the data into view data.
FURTHER EDIT: I would also like a solution that is ideally model independent, by which I mean if the same reference data is needed by multiple view models this can be achieved with a single piece of code. Matt's approach is interesting but is tightly coupled to the view model.
I would use a service layer which would return me a POCO object that I would map to a view model. So my controller action would look like this:
public ActionResult Index(int id)
{
var model = _service.GetModel(id);
var viewModel = Mapper.Map<Model, ViewModel>(model);
return View();
}
I also like using action filters to avoid the mapping code all over again so:
[AutoMap(typeof(Model), typeof(ViewModel))]
public ActionResult Index(int id)
{
var model = _service.GetModel(id);
return View(model);
}
This way only the service talks with the CRUD repositories and the controller talks to the service and the mapping layer.
You could write a new ActionFilter that you can decorate an action method with; this action filter will load the reference data into the viewdata, which you can access from your view.
There is more on action filters here.
EDIT: Based on the users comments, this now includes a strongly typed option.
Firstly, you need to create the SharedViewModel to contain the shared data.
public class SharedViewModel
{
public List<string> Days { get; set; }
public List<string> Months { get; set; }
public List<string> Years { get; set; }
}
Next, we create the view model to be used by the Index view, which uses this shared view model.
public class HomeViewModel
{
public string ViewName { get; set; }
public SharedViewModel SharedViewModel { get; set; }
}
The next step is important, it implements an action filter called SharedData(), which will apply the shared data.
public class SharedDataActionFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var currentModel = ((HomeViewModel) filterContext.Controller.ViewData.Model);
currentModel.SharedViewModel = new SharedViewModel
{
Days = new List<string> {"Mon"},
Months = new List<string> {"Jan"},
Years = new List<string> {"2011"}
};
base.OnActionExecuted(filterContext);
}
}
At the moment, it just applies the whole shared data, but you can added parameters into the method to be selective.
When the action has been executed, this method takes the current model and adds the shared data.
Here is the controller action.
[SharedDataActionFilter]
public ActionResult Index()
{
return View("Index", new HomeViewModel { ViewName = "HomePage" });
}
You can access the data like any other strongly typed view, and the shared data wont affect the data already in the model (in this case "ViewName"). You can also use action filters across controllers, and globally across the site with mvc 3.
Hope this helps, Matt.

Resources