Newbie MVC usual practices for hiding and showing content - asp.net-mvc

I am a long time WebForms developer but am finally getting around to learning MVC by converting one of my WebForms sites, which needs updating, to MVC3.
I know the basics of MVC from reading but am struggling in the real world on day one with what are probably simple things, and I would also like to know how best to do them and best practices.
For this answer I’m not looking for code (although a little might help), just enough information to put me on the right path. I have already looked at quite a few examples and tutorials but none seem to show something relevant for my situation, so here I am asking on SO.
So, the first page I am working on is a search results page. It’s a little more complex than a simple search page. It has an area for suggestions if it finds words spelt incorrectly, an area for if no results are found, and an area for the search results themselves, if any.
Because it searches two database tables (views actually) I have a model that contains both table models and a priority field I use for ordering results by most relevant. The model is like this:
public class SearchResult
{
public Table1 { get; set; }
public Table2 { get; set; }
public int Priority { get; set; }
}
In WebForms I use panels to contain each of the areas so I can turn them on and off, something like this:
<asp:Panel ID=”panSuggest” runast=”server” Visible=”false”>
…
</asp:Panel>
<asp:Panel ID=”panNoResults” runat=”server” Visible=”false”>
…
</asp:Panel>
<asp:Panel ID=”panResults” runat=”server”>
<asp:Repeater ID=”repResults” runat=”server”>
…
</asp:Repeater>
</asp:Panel>
In my present WebForms code behind logic I look to see if there are any misspelt works and if so display panSuggest and hide all other panels. If there are no errors then I hide show panResults/panNoResults as necessary.
So how is this kind of thing usually done in MVC? Do I set a ViewBag item in my Controller for whether PanSuggest should be shown which I look for in my View and then choose to hide/show based on that, and check if my Model has any items to determine whether panResults/panNoResults should be shown. Something like my code below or is this not the proper way to do it?
#{ if (ViewBag.Suggest == true) {
<div>
Suggest
</div>
} else {
#{ if (Model.Count == 0) {
<div>
No Results
</div>
} else {
<div>
#foreach (var result in Model) {
#result.Table1.Whatever etc etc
}
</div>
}
}
Update.
I have been reading more and a there is a lot of advice to avoid ViewBag.
So instead should I change my model to include the extra data I need? Something like this
Public class ViewModel
{
public string Suggest { get; set; }
public List<SearchResult> Result { get; set; }
}
public class SearchResult
{
public Table1 { get; set; }
public Table2 { get; set; }
public int Priority { get; set; }
}
Then in my view I can check if ViewModel.Suggest is not empty, and check ViewModel.Result to see if any items (search results) exists.
Is this a better solution?

If I have 2 different tables that needs to be shown, I would to the same, the only thing I would change is
#{
if (ViewBag.Suggest == true)
{
<div>
Suggest
</div>
} else {
<table>
<thead>
<th>Header column</th>
</thead>
<tbody>
#{ if (Model != null && Model.Any()) {
<tr>
#foreach (var result in Model)
{
<td>
#result.Table1.Whatever etc etc
</td>
}
</tr>
} else {
<tr>
<td>
No Results
</td>
</tr>
}
</tbody>
</table>
}
}

My approach would be essentially the same as yours. My rationale is to put all the business logic into the controller, and to pass a simple indicator to the view in the ViewBag that indicates any options for displaying the data. Typically that would be a simple boolean value such as your ViewBag.Suggest value.

Related

Implementing pagin / sorting / filtering in View with POST Form

I have an application in MVC 4 / C# / Visual Studio 2010 SP1. It is to create UI for storing collections of books. The information I want to store are: a name of a collection, a date on creation, and the list of books. A number of books is to be added from the database that stores all the books. Actually, another view is to edit books themselves.
So far, I have designed my View such that it shows form fields for name of collection and date on creation. But underneath I included list of all books to be selected.
Selecting them in the edit / create view means they are added to collection. I thought I could implement paging / sorting / filtering for the list of books as the number may become too large to show it on one page. My idea is to add PartialView with a list of books. The PartialView can be invoked by jQuery by .post() that is trggered by events like clicking on a page number, table column etc. The PartialView would store a page no., a sort criterium, filter criteria in some hidden fields and based on their values it would generate portion of the list. Hidden fields would be updated from the model but would also pass paging / sorting back to action.
I run into problem of how to put everything together in POST form. I would like a user to click page numbers while the previously selected books would still be selected. I don't know how to refresh a PartialView and keep books' state. I hope it is possible. If not, what would you recommend?
Thanks
Below is my application.
The model of a book:
// Entity
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
public DateTime DatePublished { get; set; }
}
ViewModels:
// BookToSelect view model
public class BookToSelect : Book
{
public bool Isselected { get; set; }
public static IList<BookToSelect> MapBooksToBooksToSelect(IList<Book> list, bool isselected = false)
{
return list.Select(x => new BookToSelect() { //...})
}
public static IList<Book> MapBooksToSelectToBooks(IList<BookToSelect> list)
{
return list.Select(x => new Book() { //... })
}
}
// List of books view model
public class ListOfBooks
{
public IList<BookToSelect> Books { get; set; }
public DateTime DayOnCreationThe { get; set; }
public string CollectionName { get; set; }
public static IList<Book> GetListOfBooks()
{
return new List<Book>() {
// ... set of new Books() { },
};
}
}
Controller / Action:
public class TestCollectionController : Controller
{
[HttpGet, ActionName("Edit")]
public ActionResult Edit_GET()
{
ListOfBooks ViewModel = new ListOfBooks();
ViewModel.Books = ListOfBooks.GetListOfBooksToSelect();
ViewModel.DayOnCreation = DateTime.Today;
ViewModel.CollectionName = "List of random books";
return View(ViewModel);
}
[HttpPost, ActionName("Edit")]
public ActionResult Edit_POST(ListOfBooks ViewModel)
{
return View(ViewModel);
}
}
and View:
#using MvcDbContext.ViewModels
#model ListOfBooks
#{
ViewBag.Title = Model.CollectionName;
}
<h2>#Model.CollectionName</h2>
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.CollectionName)
#Html.EditorFor(m => m.DayOnCreation)
<table>
<tr>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().Isselected)</th>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().Title)</th>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().Author)</th>
<th class="display-label">#Html.DisplayNameFor(m => m.Books.FirstOrDefault().DatePublished)</th>
</tr>
#for (int i = 0; i < Model.Books.Count(); i++)
{
<tr>
#Html.EditorFor(m => m.Books[i])
</tr>
}
<tr>
<td colspan="3"><input type="submit" name="SaveButton" value="Save" /></td>
</tr>
</table>
}
As you've already determined, if you switch out the HTML with the next page, all the inputs, included their state, are replaced as well. As a result, the only way to handle this is to offload the state into an input outside of the replacement.
The simplest way to handle this would most likely be creating a hidden input that will consist of a comma-delimited string of ids of selected items. Just add some JS that will watch the checkboxes or whatever and add or remove items from this hidden input. You can then just post this string back and use Split to turn it into a list of ids that you can use to query the appropriate books and add them to the collection on the entity.

Looping through model of RSS feeds in mvc razor

I'm generating a list of rss links in my model that I want to show on my webpage. The model works fine and the display of the links works fine on the view. My question is this, I'd like to display the links in 2 side by side columns. The first 5 links in the first column and the next 5 links in the second column. Right now I'm showing all 10 links in each column. I'm sure there is an easy way to do this, but I'm just not sure how. Any help would be appreciated.
Here is my model:
namespace OA.Models
{
public class Rss1
{
public string Link { get; set; }
public string Title { get; set; }
public string Description { get; set; }
}
public class Rss1Reader
{
private static string _blogURL = "http://www.test.com/blogs/news/feed/";
public static IEnumerable<Rss1> GetRss1Feed()
{
XDocument feedXml = XDocument.Load(_blogURL);
var feeds = from feed in feedXml.Descendants("item")
select new Rss1
{
Title = feed.Element("title").Value,
Link = feed.Element("link").Value,
Description = Regex.Match(feed.Element("description").Value, #"^.{1,180}\b(?<!\s)").Value
};
return feeds;
}
}
}
Here is my view:
#model IEnumerable<OA.Models.Rss1>
<table style="width: 80%;margin-left: auto;margin-right: auto;margin-top:10px;margin-bottom:25px;">
<tr>
<td>
<div id="RSSCOL1">
<span style="font-size:.9em;">
#foreach (var item in Model)
{
#item.Title<br />
}
</span>
</div>
</td>
<td>
<div id="RSSCOL2">
<span style="font-size:.9em;">
#foreach (var item in Model)
{
#item.Title<br />
}
</span>
</div>
</td>
</tr>
</table>
The quickest way to accomplish this is to update your first and second loops to this...
#foreach (var item in Model.Take(Model.Count()/2))
...
#foreach (var item in Model.Skip(Model.Count()/2))
However, you might want to consider one loop that displays an unordered list, then using css to lay it out into columns. What if someone is looking at your page on a phone vs a 27-inch screen? You may want the columns to display differently. Good luck! See this link: How to display an unordered list in two columns?

Best way to get table header names in MVC 4

I'm trying to display a list of objects in a table. I can iterate over each individual item to find it's value (using an for loop or a DisplayTemplate), but how do I abitriarily pick one to display headers for the whole group.
Here's an simplified example:
Model:
public class ClientViewModel
{
public int Id { get; set; }
public List<ClientDetail> Details { get; set; }
}
public class ClientDetail
{
[Display(Name="Client Number")]
public int ClientNumber { get; set; }
[Display(Name = "Client Forname")]
public string Forname { get; set; }
[Display(Name = "Client Surname")]
public string Surname { get; set; }
}
View
#model MyApp.ViewModel.ClientViewModel
#{ var dummyDetail = Model.Details.FirstOrDefault(); }
<table>
<thead>
<tr>
<th>#Html.DisplayNameFor(model => dummyDetail.ClientNumber)</th>
<th>#Html.DisplayNameFor(model => dummyDetail.Forname)</th>
<th>#Html.DisplayNameFor(model => dummyDetail.Surname)</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.Details.Count; i++)
{
<tr>
<td>#Html.DisplayFor(model => model.Details[i].ClientNumber)</td>
<td>#Html.DisplayFor(model => model.Details[i].Forname)</td>
<td>#Html.DisplayFor(model => model.Details[i].Surname)</td>
</tr>
}
</tbody>
</table>
Notice: I'm using var dummyDetail = Model.Details.FirstOrDefault(); to get a single item whose properties I can access in DisplayNameFor.
What would be the best way to access those headers ?
Will this break if the collection is null?
Should I just replace them with hard coded plain text labels?
The Problem
As Thomas pointed out, Chris's answer works in some cases, but runs into trouble when using a ViewModel because the nested properties don't enjoy the same automatic resolution. This works if your model type is IEnumerable<Type>, because the DisplayNameFor lambda can still access properties on the model itself:
However, if the ClientDetail collection is nested inside of a ViewModel, we can't get to the item properties from the collection itself:
The Solution
As pointed out in DisplayNameFor() From List in Model, your solution is actually perfectly fine. This won't cause any issues if the collection is null because the lambda passed into DisplayNameFor is never actually executed. It's only uses it as an expression tree to identify the type of object.
So any of the following will work just fine:
#Html.DisplayNameFor(model => model.Details[0].ClientNumber)
#Html.DisplayNameFor(dummy => Model.Details.FirstOrDefault().ClientNumber)
#{ ClientDetail dummyModel = null; }
#Html.DisplayNameFor(dummyParam => dummyModel.ClientNumber)
Further Explanation
If we want to see some of the fancy footwork involved in passing an expression, just look at the source code on DisplayNameFor or custom implementations like DescriptionFor. Here's an simplified example of what happens when we call DisplayNameFor with the following impossible expression:
#Html.DisplayNameFor3(model => model.Details[-5].ClientNumber)
Notice how we go from model.Details.get_Item(-5).ClientNumber in the lambda expression, to being able to identify just the member (ClientNumber) without executing the expression. From there, we just use reflection to find the DisplayAttribute and get its properties, in this case Name.
Sorry, your question is a little hard to understand, but I think the gist is that you want to get the display names for your properties, to use as headers, without requiring or first having to pick a particular item out of the list.
There's already built-in support for this. You simply just use the model itself:
#Html.DisplayNameFor(m => m.ClientNumber)
In other words, just don't use a particular instance. DisplayNameFor has logic to inspect the class the list is based on to get the properties.

MVC design question

I'm building a questionnaire. The questionnaire have multiple sections, each section has multiple questions, and each question can have one to many answers. Each question can be a different type (radio buttons, checkbox, text...).
I put my tables in the model and loop through the sections table to display sections, loop through questions to display questions, loop through answerOptions to populate answers:
<fieldset>
<legend>Fields</legend>
<%foreach (var s in Model.Sections)
{ %>
<h3><%=s.SCTN_TXT %></h3>
<% var QuestsInSect = Model.GetQuestionsBySectionID(s.SCTN_ID);%>
<%foreach (var q in QuestsInSect){%>
<h4><%=q.QSTN_TXT %><%=q.QSTN_ID.ToString() %></h4>
<% var answers = Model.GetAnswerOptionByQuestionID(q.QSTN_ID); %>
<%if (q.QSTN_TYP_ID>= 3)
{%>
<%:Html.TextBox(q.QSTN_ID.ToString())%>
<%}
else if (q.QSTN_TYP_ID == 1)
{ %>
<%var answerOptions = Model.GetDropDownListAnswerOptionByQuestionID(q.QSTN_ID);%>
<%:Html.DropDownList(q.QSTN_ID.ToString(), answerOptions)%>
<%}
else
{ %>
<% foreach (var ao in answers)
{ %>
<br />
<%:Html.CheckBox(q.QSTN_ID.ToString())%>
<%=ao.ANS_VAL%>
<% }
}
}
} %>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
In my controller I loop through collection.Allkeys to figure out the answer for each question:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
try
{
// TODO: Add insert logic here
List<ASSMNT_RESP> arList = new List<ASSMNT_RESP>();
foreach (string key in collection.AllKeys)
{
QSTN q = _model.GetQuestionByQuestionID(int.Parse(key));
IEnumerable<ANS_OPTN> aos = _model.GetAnswerOptionByQuestionID(int.Parse(key));
ASSMNT_RESP ar = new ASSMNT_RESP();
ar.QSTN_ID = int.Parse(key);
ar.ASSMNT_ID = 1;
if (q.QSTN_TYP_ID == 1)//dropdown
{
//do something
}
else if (q.QSTN_TYP_ID == 2)//checkboxlist
{
//do something
}
else
{
//do something
}
//_model.AddAssessmentResponse(ar);
System.Diagnostics.Trace.WriteLine(key + "---"+ collection[key]);
}
//_model.Save();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
It works, but I just don't think it's a very good design. It seems like I have too much logic in the view. I would like to move the logic in the view and controller to the model. Can you recommend an easier/cleaner way to do this?
Thanks.
I'm not an expert on MVC, but I think you'd get a lot of benefit out of using a strongly-typed view based on a custom model class that you build. The properties exposed by such a model can be nested (i.e., the top-level model can consist of properties each of which are also custom classes). Conceptually, something like:
public class MyTopLevelModel
{
public MySubModel1 SubModel1 { get; set; }
public MySubModel2 SubModel2 { get; set; }
}
public class MySubModel1
{
public string AProperty { get; set; }
public int AnotherProperty { get; set; }
}
You can include collections in the class definitions, too. And then you can decorate the individual properties with with attributes specifying whether a particular property is required, a range of permissible values, etc.
It's a big subject, though, and this only scratches the surface. FWIW, I've gotten a LOT out of Steven Sanderson's Pro ASP.NET MVC2 Framework book.
Make your viewmodel more explicit.
public class ViewModel
{
public IList<SectionViewModel> Sections {get;set;}
}
public class SectionViewModel
{
public IList<QuestionViewModel> Questions {get;set;}
}
public class QuestionViewModel
{
public IList<AnswerViewModel> Answers {get;set;}
}
In your view you can then do something like this (I'm using razor):
#foreach(var section in Model.Sections)
{
<h3>#section.Name</h3>
foreach(var question in section.Questions)
{
<h4>#question.Name</h4>
foreach(var question in section.Questions)
{
#Html.EditorFor(x=> question.Answers)
}
}
}
Then create an EditorTemplate for your AnswerViewModel.

ASP.NET MVC Generic templating and collections

Is is possible to apply an attribute to a collection, and then detect this when iterating through the collection members using ViewData.ModelMetadata.Properties ?
I would like to apply an attribute to the collection to specify whether items in the collection should be displayed in their entirety or not. I want to then detect this in the Object.ascx (that deals with the display of objects of unknown type) to decide what level of detail to display.
(Please see brad wilson's post for background on this generic templating approach)
For Example:
public class Parent
{
[SomeAttributeIWantToDetect]
public IList<Child> Children{ get; set; }
}
public class Child
{
public string Name { get; set; }
public string Details { get; set; }
}
object.ascx:
(Note, this code is from the ASP.NET MVC team, not mine)
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<% if (Model == null) { %>
<%= ViewData.ModelMetadata.NullDisplayText %>
<% } else { %>
<table cellpadding="0" cellspacing="0" border="0">
<% foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm))) { %>
<% if (prop.HideSurroundingHtml) { %>
<%= Html.Display(prop.PropertyName) %>
<% } else { %>
<tr>
<td>
<div class="display-label" style="text-align: right;">
<%= prop.GetDisplayName() %>
</div>
</td>
<td>
<div class="display-field">
<!-- *********** HERE ***************-->
<% if (prop.AdditionalValues.ContainsKey(SomeAttributeIWantToDetectAttribute))
//Do something else.....
else
%>
<%= Html.Display(prop.PropertyName) %>
<% } %>
</div>
</td>
</tr>
<% } %>
<% } %>
</table>
<% } %>
This is not MVC, it's closer to classic ASP templates
Choose which camp you want to be in, and stay there
To use MVC you need to make a ViewModel which expresses the Model in terms of a particular render destination
Your ViewModel should be built using just the logic commands from above. i.e.
if (Model == null)
{
x = ViewData.ModelMetadata.NullDisplayText
}
else
{
foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForDisplay && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
x.Items1.Add(prop.PropertyName));
}
else
{
prop.GetDisplayName()
if (prop.AdditionalValues.ContainsKey(SomeAttributeIWantToDetectAttribute))
{
x.Items2.Add( { Text = prop.zzz, Highlight = true} ));
}
else
{
x.Items2.Add( { Text = prop.PropertyName } ));
}
}
}
}
The code above is obviously wrong, I am just try to show that you should take the complex code and logic and use it to build a ViewModel, it should never be in the view
The ViewModel has a simple construct relating to the rendering technology you are using (like html attributes etc)
The view should just contain simple iterators and layout selectors, fed from the ViewModel, never the actual Model
#UpTheCreek, you're coming across as awfully confrontational for wanting people to help you, but I'll give my 2 cents anyway.
My understanding is that you want to be able to have a child collection on a model. On the child model you're going to be including some [ScaffoldColumn("false")] attributes, but you also want to be able to put an attribute on the parent model that would cause the renderer to ignore the ScaffoldColumn attributes and just show everything. If my understanding is correct, I think you are going about this the wrong way. You should be creating separate view models for cases where you want different properties to be shown.
Also, I'm note quite clear if this is your intent because in your code sample you would only be hiding the input field and not the label itself. Perhaps you are trying to put some sort of notice that the field is hidden?
You've linked http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html several times with the comment "I can't use ViewModels because this is generic templating".
I don't understand why you believe this. TFD and Ryan have it exactly right. Create two different ViewModels to wrap your model, and put the ScaffoldColumn attributes on your ViewModel (or better, omit those fields entirely).
Object.ascx then detects the attribute (or of course the presence or absence of the field) on your ViewModel and displays (or doesn't) the field appropriately.
In fact, the author of the post you linked suggests doing exactly that:-
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-4-custom-object-templates.html#comment-6a00e54fbd8c4988340120a6396c7a970b
"Personally, my recommendation for
people who want strict SoC (like I do)
is to use ViewModels and only place
the annotations on the view model.
There are other issues with directly
model binding to things like LINQ to
SQL or LINQ to Entities (for example,
if you're not careful, you can destroy
your associations or inadvertently let
a bad guy bind data into something
that wasn't originally shown in the
editor), so I generally always
recommend view models anyway."
So:-
public class Parent
{
public IList<Child> Children{ get; set; }
}
public class Child
{
public String Name { get; set; }
public String Details { get; set; }
}
// Pass this one to your "Admin" view.
public class ParentAdminViewModel
{
private Parent _parent;
public ParentAdminViewModel(Parent parent) { this._parent = parent; }
public IEnumerable<Child> Children
{
get
{
return _parent.Children.Select(x => new ChildAdminViewModel(x));
}
}
}
public class ChildAdminViewModel
{
private Child _child;
public ChildAdminViewModel(Child child) { this._child = child; }
public String Name { get { return _child.Name; } }
public String Details { get { return _child.Details; } }
}
// Pass this one to your "User" view.
public class ParentUserViewModel
{
private Parent _parent;
public ParentUserViewModel(Parent parent) { this._parent = parent; }
public IEnumerable<Child> Children
{
get
{
return _parent.Children.Select(x => new ChildUserViewModel(x));
}
}
}
public class ChildUserViewModel
{
private Child _child;
public ChildAdminViewModel(Child child) { this._child = child; }
public String Name { get { return _child.Name; } }
// ChildUserViewModel doesn't have a Details property,
// so Object.ascx won't render a field for it.
}
Obviously you'll need to wire up setters as well if you want to edit.
Could you not use reflection like so: http://msdn.microsoft.com/en-us/library/z919e8tw.aspx ?

Resources