Say I've got five different unique forms (i.e. Form1, Form2, etc.), and a View called ManyForms. Each of the five forms has its own PartialView (call them _Form1, _Form2, etc.)
I want to be able to render these PartialViews on ManyForms, and I know I can use #Html.RenderPartial to do this.
However, I want to be able to dynamically render multiple forms on the ManyForms view (say, for instance, three _Form3s, two _Form4s, and one _Form5).
Does my case warrant the usage of #foreach? If so, how would I use #foreach to accomplish this, and what (roughly speaking) should the ViewModel look like - assuming that I might face some very annoying dictionary errors?
I ended up getting this working using a ViewModel that looks something like this:
public class MainViewModel
{
public List<Form1> Form1{ get; set; }
public List<Form2> Form2 { get; set; }
public List<Form3> Form3{ get; set; }
// etc.
public MainViewModel() {
Form1 = new List<Form1>();
Form2 = new List<Form2>();
Form3 = new List<Form3>();
// etc.
}
And on the ManyForms View:
#model Forms.Models.ViewModels.MainViewModel
<div class="container-fluid">
<div id="MainFormAccordion">
#foreach (var x in Model.Form1.ToList())
{
Html.RenderPartial("_Form1", x);
}
#foreach (var x in Model.Form2.ToList())
{
Html.RenderPartial("_Form2", x);
}
// etc.
The _Form PartialViews each use as their models MainViewModel.FormX. I don't know if I am following good practices here, but I guess this is how one could get it working with #foreach.
Related
I have a Razor view that iterates over a collection property on the view model, and a display template for a single item on that collection. In that single item, I then do the same thing again, so that under the hood I have nested loops that render instances of the same type a number of times on the page.
In the display template for the leaf type, I'd like to find out how many similar items have already been rendered to the page.
I tried to add a property to the ViewBag and increment it on each iteration, but that didn't work.
Model
public class FooViewModel
{
public IEnumerable<Bar> Bars { get; set; }
}
public class BarViewModel
{
public IEnumerable<Baz> Bazes { get; set; } // how do you pluralize baz?
}
public class BazViewModel
{
}
Index.cshtml
#model FooViewModel
#{
ViewBag.RenderCount = 0;
}
#Html.DisplayFor(m => m.Bars);
DisplayTemplates/BarViewModel.cshtml
#model BarViewModel
#Html.DisplayFor(m => m.Bazes);
DisplayTemplates/BazViewModel.cshtml
#model BazViewModel
// how many BazViewModels have been rendered before this one?
#ViewBag.RenderCount // is 0 every time
#{
ViewBag.RenderCount++;
}
As suggested by Liam in the comments, I ended up moving this processing into the Controller (or, actually, into the mapping from my data entities into view models). This entailed adding a property to the view model where I needed the count, and setting that property as part of the mapping process.
This is my first time using MVC, first time writing a web application as well.
So far, I managed to have a view for a list of employees, and an edit view for the Employee model.
If I had 25 models that I need displayed as lists, and edited, will I have to create 50 different views?
or is there a way to have one common List View and one common Edit View?
(edit below)
Solved the List View issue.
Sorry for the long code.
I created a ModelPropertyInfo class that describes model properties. For now I only added the Label, but I might add more properties like "Format", "InputType", ...
// Model field information class. Used by views to display model info properly
public class ModelPropertyInfo
{
public ModelPropertyInfo() { }
public string Name { get; set; }
public string Label { get; set; }
}
Then the ShowInListAttribute attribute class to decorate only model properties that I want to appear in the list view
// Attribute class used to specify Labels for model fields
public class ShowInListAttribute : Attribute
{
public ShowInListAttribute(string header)
{
Header = header;
}
public string Header { get; set; }
}
And a ModelBase class that all my models will inherit. This class will give the ability to get any property value from the class by passing its name as string
// Base class for all models
public class ModelBase
{
public static List<ModelPropertyInfo> ModelProperties(Type modelType)
{
List<ModelPropertyInfo> result = new List<ModelPropertyInfo>();
foreach (PropertyInfo pi in modelType.GetProperties())
{
ShowInListAttribute att = (ShowInListAttribute)pi.GetCustomAttributes(typeof(ShowInListAttribute), true).FirstOrDefault();
if (att != null)
result.Add(new ModelPropertyInfo { Label = att.Header, Name = pi.Name });
}
return result;
}
public object GetPropertyValue(string propName)
{
return this.GetType().GetProperty(propName).GetValue(this, null);
}
}
Now, here's my Employee model class
[Table("Employee")]
public class Employee : ModelBase
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public decimal ID { get; set; }
[ShowInList("First Name")]
public string FirstName { get; set; }
[ShowInList("Last Name")]
public string LastName { get; set; }
public decimal DepartmentID { get; set; }
[ShowInList("Department")]
[DatabaseGeneratedAttribute(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
public string DepartmentName { get; set; }
}
So, to put all the above to use, here's the Index method in my EmployeeController
public ActionResult Index()
{
ViewBag.Columns = ModelBase.ModelProperties(typeof(Employee));
ViewBag.Title = "Employee List";
return View("ListShared", db.Employees.ToList());
}
Finally, the result, the SharedListView that I will use to display a list of any model I want
#using SharedListView.Models
#model IEnumerable<ModelBase>
<h2>#ViewBag.Title</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
#foreach (ModelPropertyInfo col in ViewBag.Columns)
{
<th>
#col.Label
</th>
}
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
#foreach (ModelPropertyInfo col in ViewBag.Columns)
{
<td width='100px'>
#item.GetPropertyValue(col.Name).ToString()
</td>
}
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.GetPropertyValue("ID") }) |
#Html.ActionLink("Details", "Details", new { id=item.GetPropertyValue("ID") }) |
#Html.ActionLink("Delete", "Delete", new { id=item.GetPropertyValue("ID") })
</td>
</tr>
}
</table>
Still stuck on a common Edit view, any help would be appreciated.
Again, sorry for the long edit.
You don't need to do that. The ASP.NET MVC supports ContentFor method and EditorFor method. So in your case, you only need to designing your view models, and afterwards on your view, you can use its like
#Html.ContentFor(Model.Employee) // for display - that mean, it should be read-only
#Html.EditorFor(Model.Employee) // for editing.
You can see the post about that topic here
I would suggest you have this structure for each model object:
ListView: Display the list of item. And create a viewmodel class for each item in the view
CreateView: Used when creating new object. Also having ViewModel class for this
EditView: the same as CreateView, except it is for edit mode
This structure will create many views with ViewModel that look similar. However, they are not since in the nature they are different for different purposes. The structure will improve the code in term of separation of concern, help in maintenance. Easy to work with.
checkout Knockout.js. I have written applications like what you are talking about, a view for a collection of data and a view for editing the individual records. knockout makes it fairly easy to integrate the editing portion of the views into the collection viewing. It does help to have some understanding of WPF and Silverlight style data bindings. All of my views now use knockout and I integrate the editing functionality in the collection views with proper data binding using the visible binding to a editor area.
Your view will use a model to display or edit the data. If you have 25 different models, every view should have a different model.
If you want to use just one model, mainly because they share similar properties, this can be done but it is not ideal. The way it can be done is if all the models have similar properties, you can include all the properties in one model. Then you can just use the properties you need in other views. This is not the ideal way of doing it. Every view should have its own model.
You could create some sort of class that generates the html for all the different types of "form inputs" you will need in your app. Then add ability to the class to receive data from the models (ie. takes a string from the model and creates a TEXT input with the value set to that string .... or a SELECT dropdown can receive all it's OPTIONs from a model, etc).
Then all these form inputs can be generated from within the models (utilizing your class) and can be pumped into an array that is passed to your single view. The view would contain all the surrounding divs and other html, but somewhere in the view you would put a small "for loop" that outputs your array's contents. This is valid MVC, in that you are only using a simple for-loop in your view. And your models, to some extent may be partially responsible in deciding how the data if formatted coming out of the database (in this case, form inputs). To style the form inputs, you can keep the styling in a css file or at the top of the view.
Ultimately it depends on the future of your app. This is an idea if your app and models fit in a nice repetitive structure. But if you suspect parts of your app might evolve, where sometimes you might need a view to look much differently, or you want more control over how you arrange each of those generated "form inputs" in the views, then you will probably be creating a higher number of views.
I have an application that shows a grid/table of questions and each question has a status dropdown. There are around 1-200 questions and each status drop down has about 50 choices that are the same for every row of the grid.
My controller passes the following model to a view:
IEnumerable<Question.Grid>
Then in my view I have the following code that prints out the detail lines of a grid table:
<tbody class="grid">
#if (Model != null) {
foreach (var item in Model) {
#Html.DisplayFor(model => item, "QuestionDetail")
}
}
</tbody>
Each of the grid lines has a status dropdown and I would like to pass the data for the dropdown (same for every row) to the QuestionDetail view. What's the best way for me to send this additional information so that in my view I can have something like the following:
#Html.DropDownList("Question.Status", Status, new { id = "StatusID"})
First of all, don't pass IENumerable of view models. Rather define one that has IEnumerable as property like this:
public class EnumViewModel
{
public IEnumerable<Question.Grid> Questions { get; set; }
public int MyAdditionalFieldIWantedToPassAlong { get; set; }
}
public class Question.Grid
{
public string MyExistingFields { get; set; }
// as many as you had
public string MyAdditionalFields { get; set; }
// as much as you want
}
and have your view receive one model instead of list of models as #model EnumViewModel.
At this point it must be very easy for you to add any additional information as your heart pleases inside the EnumViewModel should this information belong there. If its more specific to Questions,put it there and enjoy your items inside every model in the list containing that StatusID.
Think of it as just a container for you to hold data your view needs to
display and
post back to controller
Hope this helps.
Traditionally, I have built MVC applications using view models with Data Annotations attributes, and I dynamically render the views using editor templates. Everything works great, and it really cuts down on the time it takes me to build new views. My requirements have recently changed. Now, I can't define the view model at design time. The properties that will be rendered on the view are decided at run time based on business rules. Also, the validation rules for those properties may be decided at run time as well. (A field that is not required in my domain model, may be required in my view based on business rules). Also, the set of properties that will be rendered is not known until run time - User A may edit 6 properties from the model, while user B may edit 9 properties.
I am wondering if it is possible to create a model metadata provider that will supply my own metadata from business rules for an untyped view model like a collection of property names and values. Has anyone solved this problem?
I solved a similar problem by creating a more complex model, and using a custom editor template to make the model be rendered to look like a typical editor, but using the dynamic field information:
public class SingleRowFieldAnswerForm
{
/// <summary>
/// The fields answers to display.
/// This is a collection because we ask the MVC to bind parameters to it,
/// and it could cause issues if the underlying objects were being recreated
/// each time it got iterated over.
/// </summary>
public ICollection<IFieldAnswerModel> FieldAnswers { get; set; }
}
public interface IFieldAnswerModel
{
int FieldId { get; set; }
string FieldTitle { get; set; }
bool DisplayAsInput { get; }
bool IsRequired { get; }
bool HideSurroundingHtml { get; }
}
// sample implementation of IFieldAnswerModel
public class TextAreaFieldAnswer : FieldAnswerModelBase<TextAreaDisplayerOptions>
{
public string Answer { get; set; }
}
EditorTemplates/SingleRowFieldAnswerForm.cshtml:
#helper DisplayerOrEditor(IFieldAnswerModel answer)
{
var templateName = "FieldAnswers/" + answer.GetType().Name;
var htmlFieldName = string.Format("Answers[{0}]", answer.FieldId);
if (answer.DisplayAsInput)
{
#Html.EditorFor(m => answer, templateName, htmlFieldName)
// This will display validation messages that apply to the entire answer.
// This typically means that the input got past client-side validation and
// was caught on the server instead.
// Each answer's view must also produce a validation message for
// its individual properties if you want client-side validation to be
// enabled.
#Html.ValidationMessage(htmlFieldName)
}
else
{
#Html.DisplayFor(m => answer, templateName, htmlFieldName)
}
}
<div class="form-section">
<table class="form-table">
<tbody>
#{
foreach (var answer in Model.FieldAnswers)
{
if (answer.HideSurroundingHtml)
{
#DisplayerOrEditor(answer)
}
else
{
var labelClass = answer.IsRequired ? "form-label required" : "form-label";
<tr>
<td class="#labelClass">
#answer.FieldTitle:
</td>
<td class="form-field">
<div>
#DisplayerOrEditor(answer)
</div>
</td>
</tr>
}
}
}
</tbody>
</table>
</div>
So I populate my SingleRowFieldAnswerForm with a series of answer models. Each answer model type has its own editor template, allowing me to customize how different types of dynamic "properties" should be displayed. For example:
// EditorTemplates/FieldAnswers/TextAreaFieldAnswer.cshtml
#model TextAreaFieldAnswer
#{
var htmlAttributes = Html.GetUnobtrusiveValidationAttributes("Answer", ViewData.ModelMetadata);
// add custom classes that you want to apply to your inputs.
htmlAttributes.Add("class", "multi-line input-field");
}
#Html.TextAreaFor(m => m.Answer, Model.Options.Rows, 0, htmlAttributes)
#Html.ValidationMessage("Answer")
The next tricky part is that when you send this information to the server, it doesn't inherently know which type of IFieldAnswerModel to construct, so you can't just bind the SingleRowAnswerForm in your arguments list. Instead, you have to do something like this:
public ActionResult SaveForm(int formId)
{
SingleRowAnswerForm form = GetForm(formId);
foreach (var fieldAnswerModel in form.FieldAnswers.Where(a => a.DisplayAsInput))
{
// Updating this as a dynamic makes sure all the properties are bound regardless
// of the runtime type (since UpdateModel relies on the generic type normally).
this.TryUpdateModel((dynamic) fieldAnswerModel,
string.Format("Answers[{1}]", fieldAnswerModel.FieldId));
}
...
Since you provided MVC with each dynamic "property" value to bind to, it can bind each of the properties on each answer type without any difficulty.
Obviously I've omitted a lot of details, like how to produce the answer models in the first place, but hopefully this puts you on the right track.
You can use The ViewData Property in your ViewModel, View and Controller, it is dynamic, so it can be resolved at runtime.
I have a list of blog post categories(~20) in a look up table.
I want to display them on multiple pages as list of hyperlinks that user can click.
I also want to display them in a dropdown list in 2 or more places(different view pages)
The follow works & I see categories as a menu/list of hyperlinks.
But this will cause me modify multiple controller where I need to show the categories.
What is the best practice to handle this so that I have minimal code change?
//#1 I added new class in one of my model:
namespace MyApp.Models
{
...
public class ShowPostModel
{
public Post Post { get; set; }
public IEnumerable<Category> Categories { get; set; }
}
public class Category
{
public string _id { get; set; }
public string Name { get; set; }
}
}
//#2 Populating the controller
namespace MyApp.Controllers
{
public class BlogController : Controller
{
public ActionResult ShowPost()
{
ShowPostModel viewModel = new ShowPostModel();
viewModel.Post = ReadBlogPostFromDB();
viewModel.Categories = ReadCategoriesFromDB();
return View(viewModel);
}
}
}
//#3 This is from my main view for showing the Post:
#Html.Partial("_Categories", Model.Categories)
//#4 This is my _Categories partial view:
#model IEnumerable<MyApp.Models.Category>
<section>
<header><b>Categories</b></header>
<ul style="padding:0;margin:0;">
#foreach (var cat in Model)
{
<li>
#cat.Name
</li>
}
</ul>
</section>
Thanks for reading
Edit:
I made these changes and it seems working as well.
Any comments or improvements I can make here?
//#1 deleted this line from public class ShowPostModel (model is now DRY)
public IEnumerable<Category> Categories { get; set; }//deleted
//#2 created a base controller and inherit from it
public abstract class BlogBaseController : Controller
{
public BlogBaseController()
{
ViewBag.Categories = ReadCategoriesFromDB();
}
}
//#3 force all controller where I need categories to inherit from base controller
public class BlogController : BlogBaseController
//#4 change how I read in my views
#Html.Partial("_Categories", (IEnumerable<MyApp.Models.Category>)#ViewBag.Categories)
If you use the categories in enough places, you can encapsulate this into a base controller class, and override OnActionExecuted.
I would then put the Categories into a property on the ViewBag and pass it into your partial view from there, and leave your view's model alone.
i wonder why no one has suggested using RenderAction. you can write this Action method in you base controller. this will make it available in all derived controller. this way you can have your categories view strongly typed. Moreover, you should put your Categeories view in Views/Shared directory so every controller has access to this view. Doing so will keep you DRY and you still have the benefits of having strongly typed view.
EDIT By the way you don't have to have base controller to use renderaction. Although above approach is valid and i prefer doing like this but you can also have a nvaigation controller like
Public NavigationController:Controller()
{
public ActionResult Categories()
{
var Categories = FetchFromDB();
return View(Categoires);
}
}
Now you can call this action method using renderAction on anywhere in your application
You might want to try creating 2 display for templates, one to display in link and one to display in dropdown. Depending on the page you tell the view to use the specific template.
You can create a Filter that populates your categories and adds it to ViewData/ViewBag. You can then apply this filter to the controllers/actions that require the categories.
For displaying, you can use EditorTemplates or Partials to keep your UI code DRY...
HTH.