MVC3 Razor Editor/Display templates and generics - asp.net-mvc-3

There were a few questions regarding mvc templates and generics however there seems to be none related to what I'm looking for. Consider the following models:
namespace MyNamespace
{
public class ModelBase { /* Not important for example */ }
public class MyModel : ModelBase
{
public string Name { get; set; }
}
public class MyViewModel
{
public IEnumerable<ModelBase> Data { get; set; }
}
}
And a controller:
public class HomeController : Controller
{
public ActionResult Index
{
return View(new MyViewModel { Data = new List<MyModel>() })
}
}
A Razor view Views/Home/Index.cshtml would look like:
#model MyNamespace.MyViewModel
#Html.EditorFor(m => m.Data)
Nothing special there. If I want a display or editor template for that I can create a file under Views/Shared/EditorTemplate or under Views/Home/EditorTemplates called MyModel.cshtml and it finds it correctly.
What if I want to do something different for each implementation of ModelBase when showing a list? Mvc view finder will find List'1.cshtml template correctly in any of above paths. However what I need to do is do a template for List`1[MyModel].cshtml
I can't seem to get the correct file naming. What I've tried so far (relative to this example naming):
List`1[MyModel].cshtml
List`1[[MyModel]].cshtml
List`1[MyNamespace.MyModel].cshtml
List`1[[MyNamespace.MyModel]].cshtml
If possible I want to avoid writing a custom view finder. The only alternative I can think of for now if I can't get above stuff to work is to just have List`1.cshtml call a partial with naming deduced from List.

A very late response, useful if someone else bump in this very same question (as I did a few moments ago trying to remember how to do this)
You can use the UIHintAttribute to define the name of the editor
public class MyViewModel
{
[UIHint("MyModel")]
public IEnumerable<ModelBase> Data { get; set; }
}

I haven't checked this code but I would create different Views for each subtype and do something dumb like:
return View(MyModel.GetType().Name, new MyViewModel { Data = new List<MyModel>() })
So that your View matches the name of your type.

You could do this in the main view:
#model MyViewModel
#Html.EditorFor(x => x.Data)
and then have:
~/Views/Shared/EditorTemplates/MyModel.cshtml:
#model MyModel
...
~/Views/Shared/EditorTemplates/MyOtherModel.cshtml (where obviously MyOtherModel derives from ModelBase):
#model MyOtherModel
...
and so on ... ASP.NET MVC will take care of looping through the Data property and pick the correct template based on the runtime type of each element of this collection.

Related

Best way to bind the constant values into view (MVC3)

I have a constants values such as "Required","Optional", and "Hidden". I want this to bind in the dropdownlist. So far on what I've done is the below code, this is coded in the view. What is the best way to bind the constant values to the dropdownlist? I want to implement this in the controller and call it in the view.
#{
var dropdownList = new List<KeyValuePair<int, string>> { new KeyValuePair<int, string>(0, "Required"), new KeyValuePair<int, string>(1, "Optional"), new KeyValuePair<int, string>(2, "Hidden") };
var selectList = new SelectList(dropdownList, "key", "value", 0);
}
Bind the selectList in the Dropdownlist
#Html.DropDownListFor(model => model.EM_ReqTitle, selectList)
Judging by the property EM_RegTitle I'm guessing that the model you're using is auto-generated from a database in some way. Maybe Entity Framework? If this is the case, then you should be able to create a partial class in the same namespace as your ORM/Entity Framework entities and add extra properties. Something like:
public partial class MyModel
{
public SelectList MyConstantValues { get; set; }
}
You can then pass your SelectList with the rest of the model.
There are usually hangups from using ORM/EF entities through every layer in your MVC app and although it looks easy in code examples online, I would recommend creating your own View Model classes and using something like AutoMapper to fill these views. This way you're only passing the data that the views need and you avoid passing the DB row, which could contain other sensitive information that you do not want the user to view or change.
You can also move the logic to generate your static value Select Lists into your domain model, or into a service class to help keep reduce the amount of code and clutter in the controllers.
Hope this helps you in some way!
Example...
Your View Model (put this in your "Model" dir):
public class MyViewModel
{
public SelectList RegTitleSelectList { get; set; }
public int RegTitle { get; set; }
}
Your Controller (goes in the "Controllers" dir):
public class SimpleController : Controller
{
MyViewModel model = new MyViewModel();
model.RegTitle = myEfModelLoadedFromTheDb.EM_RegTitle;
model.RegTitleSelectList = // Code goes here to populate the select list.
return View(model);
}
Now right click the SimpleController class name in your editor and select "Add View...".
Create a new view, tick strongly typed and select your MyViewModel class as the model class.
Now edit the view and do something similar to what you were doing earlier in your code. You'll notice there should now be a #model line at the top of your view. This indicates that your view is a strongly typed view and uses the MyViewModel model.
If you get stuck, there are plenty of examples online to getting to basics with MVC and Strongly Typed Views.
You would prefer view model and populate it with data in controller.
class MyViewModel
{
public string ReqTitle { get; set; }
public SelectList SelectListItems { get; set; }
}
Then you can use:
#Html.DropDownListFor(model => model.EM_ReqTitle, model.SelectListItems)

MVC3 IEnumerable of model not finding correct editor template

I have the following model:
public class Filter
{
public string Field { get; set; }
public string Operator { get; set; }
public string Value { get; set; }
}
And the following controller:
public class FilterController
{
public ActionResult Index()
{
IList<Filter> model = new List<Filter>() {
new Filter(),
new Filter()
};
return View(model);
}
}
And the following view:
#model IEnumerable<Filter>
#Html.EditorForModel()
This should look for my EditorTemplate Filter.cshtml, and render the template for each element in the list, right?
Using Glimpse, I notice that MVC is looking for IEnumerable`1.cshtml instead of Filter.cshtml
The same thing happens when I use
#Html.EditorFor(model => model)
When I do this:
#Html.EditorFor(model => model, "Filter")
I get an error saying that Filter.cshtml is expecting a model of type Filter but it received a model of type IEnumerable<Filter>
Am I doing this correctly? Do I need to do anything else to get the list of models to render correctly, using the correct editor template?
I've definitely had issues in the past with EditorTemplates, but I think it was mostly user error.
One possible workaround is to encapsulate your collection in a single, view model class and pass that to the view
public class MySweetFilterViewModel
{
public IEnumerable<Filter> Filters { get; set; }
}
Then you could use a single view to pick apart the collection
#model Project.Models.MySweetFilterViewModel
#Html.EditorFor(x => x.Filters)
Just make sure your controller encapsulates
public ActionResult Index()
{
//...
return View(new MySweetFilterViewModel() { Filters = model });
}
To answer your question regarding why... let's experiment with a few things.
What happens if you wrote your code this way:
return View(new List<Filter>{ new Filter(), new Filter() });
It could be that since you are using an intermediate IList rather than a List, that something is getting confused. What would be happening (in my theory) is that passing an IList<Filter> is causing a standard IEnumerable rather than an IEnumerable<Filter> to be passed to the view.
You could also try model.AsEnumerable<Filter>()

html.TextBoxFor and html.Textbox, POSTing values, model in parameters

Alright guys, Need some help!
Im working with asp.net mvc3 razor (and am fairly new to it but did lots of web forms)
Okay so onto the problem
My question revolves around submitting a view.
I have a very complicated model that my view is based off (strongly typed).
I want to return the model into the arguments in the HttpPost method of the controller. do basically:
public ActionResult Personal()
{
DataModel dataModel = new DataModel();
FormModel model = new FormModel();
model.candidateModel = dataModel.candidateModel;
model.lookupModel = new LookupModel();
return View(model);
}
[HttpPost]
public ActionResult Personal(FormModel formModel)
{
if (ModelState.IsValid)
{
//stuff
}
return View(formModel);
}
Now...
I'm having trouble getting values into the formModel parameter on the post method.
This works (meaning i can see the value)but is tedious as i have to write exactly where it sits in a string every single field:
#Html.TextBox("formModel.candidateModel.tblApplicant.FirstName", Model.candidateModel.tblApplicant.FirstName)
It renders like this:
<input name="formModel.candidateModel.tblApplicant.FirstName" id="formModel_candidateModel_tblApplicant_FirstName" type="text" value="Graeme"/>
This doesn't work:
#Html.TextBoxFor(c => c.candidateModel.tblApplicant.FirstName)
It renders like this:
<input name="candidateModel.tblApplicant.FirstName" id="candidateModel_tblApplicant_FirstName" type="text" value="Graeme"/>
Now I'm assuming the problem lies in the discrepancy of the id's
So please answer me this:
Am i going about this the right way
Why doesn't textboxfor get the right value/id, and how do i make it get the right value/id so i can retrieve it in a POST(if that is even the problem)?
Additionally, it seems that textboxfor is restrictive, in the manner that if you have a date time, how do you use the .toshortdate() method? This makes me think textboxfor isn't useful for me.
Quick clarification:
when i say textboxfor isn't working, it IS getting values when i GET the form. So they fill, but on the POST / submission, i can't see them in the formModel in the parameters.
Another side note:
None of the html helpers work, this is the problem. They aren't appearing in modelstate either.
Thanks everyone for the help
Answer:
html.TextBoxFor and html.Textbox, POSTing values, model in parameters
It was a problem in my view somewhere, i replaced all the code with the snippet in this answer and it worked.
Thank you again
Am i going about this the right way
Yes.
Why doesn't textboxfor get the right value/id, and how do i make it get the right value/id so i can retrieve it in a POST(if that is even the problem)?
There is something else in your code that makes this not work. It's difficult to say since you haven't shown all your code. Here's a full working example which illustrates and proves that there's something else going on with your code:
Model:
public class FormModel
{
public CandidateModel candidateModel { get; set; }
}
public class CandidateModel
{
public Applicant tblApplicant { get; set; }
}
public class Applicant
{
public string FirstName { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new FormModel
{
candidateModel = new CandidateModel
{
tblApplicant = new Applicant
{
FirstName = "fn"
}
}
});
}
[HttpPost]
public ActionResult Index(FormModel formModel)
{
// the username will be correctly bound here
return View(formModel);
}
}
View:
#model FormModel
#using (Html.BeginForm())
{
#Html.EditorFor(c => c.candidateModel.tblApplicant.FirstName)
<button type="submit">OK</button>
}
Additionally, it seems that textboxfor is restrictive, in the manner
that if you have a date time, how do you use the .toshortdate()
method? This makes me think textboxfor isn't useful for me.
I agree that TextBoxFor is restrictive. That's why I would recommend you always using EditorFor instead of TextBoxFor. It will allow you to simply decorate your view model property with the [DisplayFormat] attribute and voilĂ . You get any format you like.
For example:
public class MyViewModel
{
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime CreatedAt { get; set; }
}
and in the view:
#model MyViewModel
#Html.EditorFor(x => x.CreatedAt)
will format the date exactly as you expect.
the model binder uses the name to bind the values to the model, and the html helpers e.g. Html.TextBoxFor uses the body of the lambda expression to set the name, however you can specify the name yourself which you are doing by using the Html.TextBox( helper
#Html.TextBoxFor(x=>x.candidateModel.tblApplicant.FirstName),
new{#Name="formModel.candidateModel.tblApplicant.FirstName"})
If your view is strongly typed, try the helper bellow, instead call each helper on each property
#Html.EditorForModel()
#Html.EditorFor(m => m.candidateModel)
#Html.EditorFor(m => m.lookupModel)
Update:
Well, have tried to use viewmodel to simplify this task? And when you get back the data you can map your real models. keep your views clean will give you less headaches in the future. Additionally you could use AutoMapper to help you.
Here a example if you think that will help you.
http://weblogs.asp.net/shijuvarghese/archive/2010/02/01/view-model-pattern-and-automapper-in-asp-net-mvc-applications.aspx

add a section with specific name to my index

I need to add a section with specific name, something like this:
#string test="test";
#section #test
{
#* my view code *#
}
How could I get this work.
You can use the DefineSection method. Have a look here at examples of how this can be useful: http://blogs.msdn.com/b/marcinon/archive/2010/12/15/razor-nested-layouts-and-redefined-sections.aspx
You could use a partial which could be included using the Html.Partial helper at any place:
#Html.Partial("test")
which would render the Test.cshtml partial view which could contain any markup you like.
If you want to handle some more complex scenarios where this partial will contain dynamic data coming from some data store you could use the Html.Action helper. You would start by defining a model:
public class MyModel
{
public string SomeProperty { get; set; }
}
then a controller:
public class MySectionController: Controller
{
public ActionResult Index()
{
MyModel model = ...
return View(model);
}
}
and have a corresponding view:
#model AppName.Models.MyModel
<div>#Model.SomeProperty</div>
and finally you would include it like this:
#Html.Action("index", "mysection")
You could include it in your layout thus allowing you to include dynamic widgets completely decoupled from your main controller logic and each of them following their natural workflow.

ASP.NET MVC 3 _Layout.cshtml Controller

Can anyone help me with the subject? I'm using Razor view engine and I need to pass some data to _Layout. How can I do it?
As usual you start by creating a view model representing the data:
public class MyViewModel
{
public string SomeData { get; set; }
}
then a controller which will fetch the data from somewhere:
public class MyDataController: Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
SomeData = "some data"
};
return PartialView(model);
}
}
then a corresponding view (~/Views/MyData/Index.cshtml) to represent the data:
#{
Layout = null;
}
<h2>#Model.SomeData</h2>
and finally inside your _Layout.cshtml include this data somewhere:
#Html.Action("index", "mydata")
You could use the ViewBag to pass data.
In your controller:
ViewBag.LayoutModel = myData;
Access in you layout:
#ViewBag.LayoutModel
It is a dynamic object, so you can use any property name you want.
The ViewBag method is the easiest. However if you need advanced and typed features, you can also try taking that part to a partial view (the part that'll render the dependent section) with a common controller (if the value can be calculated on it's own and doesn't need input from other controllers), and call RenderPartial on it from _Layout.
If you'd like I can give you some more info about it...

Resources