MVC Dynamic View Data and Dynamic Views - asp.net-mvc

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.

Related

Is there a way to share data between instances of razor display templates?

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.

What is use of UIHint attribute in ASP.Net MVC

just reading about UIHint from this url What is use of UIHint attribute in MVC
If you annotate a property with UIHint attribute
and use EditorFor or DisplayFor inside your views,
ASP.NET MVC framework will look for the specified
template which you specified through UIHintAttribute.
The directories it looks for is:
For EditorFor:
~/Views/Shared/EditorTemplates
~/Views/Controller_Name/EditorTemplates
For DisplayFor:
~/Views/Shared/DisplayTemplates
~/Views/Controller_Name/DisplayTemplates
the above write up means MVC engine first search view in shared if not found then it will search view in ~/Views/Controller_Name/DisplayTemplates ?
i just got a code but it is not complete so not being able to understand it properly
public class Person {
[UIHint("Poo")]
public string Name { get; set; }
}
#model MyApp.Models.Person
<h2>My Person</h2>
#Html.DisplayFor(m => m.Name)
if i think Poo is a shared view then where is poo related view code?
when this line will execute #Html.DisplayFor(m => m.Name) then what will happen.
see this code
#Html.EditorFor(model => model.ProductViewModel, "yourTemplateName")
where MVC will find the file yourTemplateName.cshtml?
thanks
the above write up means MVC engine first search view in shared if not found then it will search view in ~/Views/Controller_Name/DisplayTemplates ?
That is backwards, the search pattern is (exactly):
(if in an area)
"~/Areas/{2}/Views/{1}/DisplayTemplates/{0}.cshtml",
"~/Areas/{2}/Views/{1}/DisplayTemplates/{0}.vbhtml",
"~/Areas/{2}/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Areas/{2}/Views/Shared/DisplayTemplates/{0}.vbhtml"
then
"~/Views/{1}/DisplayTemplates/{0}.cshtml",
"~/Views/{1}/DisplayTemplates/{0}.vbhtml",
"~/Views/Shared/DisplayTemplates/{0}.cshtml",
"~/Views/Shared/DisplayTemplates/{0}.vbhtml"
Where
0 = Template/Type name
1 = ControllerName
2 = AreaName
(In the event you do not provide a Template name hint, the razor engine default to the type (int, boolean, string and even custom class types you've defined)
if i think Poo is a shared view then where is poo related view code?
In one more more of the locations above. This allows you to create poo specific views per controller and/or a shared poo view. It's however you want to do it.
when this line will execute #Html.DisplayFor(m => m.Name) then what will happen.
The engine will search the above folders for a template. In the event one is not found it then looks for object.cshtml/vbhtml in the same folders. If that file is found it executes it, if not it executes the default internal object display for code.
where MVC will find the file yourTemplateName.cshtml?
In the same directories above. You have to understand this it does the same thing over and over, it is a convention of asp.net-mvc.
What is use of UIHint attribute in ASP.Net MVC
This allows you to override the template used for a given property.
public class Person
{
[UIHint("Age")]
public DateTime Birthday { get; set; }
}
Will attempt to look for 'age.cshtml' in the above locations. Since the UIHintAttribute is not sealed you can also derive your own attribute and create some pretty nifty templates:
public UIDateTimeAttribute : UIHintAttribute
{
public UIDateTimeAttribute(bool canShowSeconds)
: base("UIDateTime", "MVC")
{
CanShowSeconds = canShowSeconds;
}
public bool CanShowSeconds { get; private set; }
}
Then your model might looks like:
public class Person
{
[UIDateTime(false)]
public DateTime Birthday { get; set; }
}
UIDateTime.cshtml
#model DateTime
#{
var format = "dd-MM-yy hh:mm:ss";
// Get the container model (Person for example)
var attribute = ViewData.ModelMetadata.ContainerType
// Get the property we are displaying for (Birthday)
.GetProperty(ViewData.ModelMetadata.PropertyName)
// Get all attributes of type UIDateTimeAttribute
.GetCustomAttributes(typeof(UIDateTimeAttribute))
// Cast the result as UIDateTimeAttribute
.Select(a => a as UIDateTimeAttribute)
// Get the first one or null
.FirstOrDefault(a => a != null);
if (attribute != null && !attribute.CanShowTime)
{
format = "dd-MM-yy hh:mm";
}
}
#Model.ToString(format)

Rendering Multiple (PartialView) Forms On One View Using #foreach

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.

MVC 4 Views, common or specific?

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.

What is the ASP.NET MVC equivalent of displaying a Label conditionally?

I'm currently porting an ASP.NET WebForms application to ASP.NET MVC.
In one of the pages there is an ASP.NET Label control which is displayed conditionally based on a variable in the codebehind. So, something to the effect of
<asp:Label runat="server" Visible="<%# ShowLabel%>">
...
</asp:Label>
Where ShowLabel is a Boolean value in the codebehind. The contents of the label are generated at runtime and will be different pretty much every time.
There's better ways to do this even in ASP.NET, but what would be the best way to do this in ASP.NET MVC? How are you even supposed to render dynamic text in ASP.NET MVC in a way similar to how the ASP.NET Label object worked?
I believe in the Thunderdome principle of having one ViewModel class for each View (unless it is a very simple view).
So I would have a ViewModel class like the following:
public class IndexViewModel
{
public bool labelIsVisible { get; set; }
public String labelText { get; set; }
public IndexViewModel(bool labelIsVisible, String labelText)
{
this.labelIsVisible = labelIsVisible;
this.labelText = labelText;
}
}
In your controller, do something like,
public ActionResult Index()
{
// Set label to be visible in the ViewModel instance
IndexViewModel viewData = new IndexViewData(true, "Simucal rocks!");
return View(viewData);
}
Where Index is a strongly typed view of type IndexViewModel.
Then, in your view simply do something like:
<% if (Model.labelIsVisible) { %>
<%= Model.labelText %>
<% } %>
The main idea in MVC is NOT to pass the strings you want to display; you should pass the relevant objects to your View, and the View, in turn, would decide wether to display that label or not (and this is using a simple if, like in Simucal's sample).
So, instead of doing
if (Model.labelIsVisible) {
One would do
if (Model.Comments == 0) {
For example, if the label would be to show a prompt for a user to comment on an article.
Take your element in and set on hide() function like that:
<div id="label">
#Html.Label("myLabel", "text")
</div>
$("#label").hide();`

Resources