Is the sentence formatting I do here view logic or not? - asp.net-mvc-3

I have a page containing a partial view with a list and a partial view with three filter options. If the list is empty, I show a special partial view instead of the list with some tips for the user. The tip will instruct the user to widen the filter options that are not already set to it's most generic form. I could just present a bulleted list of all the options the user could change, doing something like this (LocalizedText is generated from a resource file):
<%# Import Namespace="Resources" %>
<%# Import Namespace="System.Web.Mvc" %>
<%# Import Namespace="MyProject.Models" %>
<%# Control Language="C#" Inherits="ViewUserControl<EmptyResultViewModel>" %>
<div class="warning">
<ul>
<% if (!string.IsNullOrEmpty(Model.SelectedCustomerNumber)) %>
<% { %>
<li><%: LocalizedText.TipCustomerNumber %> </li>
<% } %>
<% if (Model.SelectedPeriodOption != PeriodOption.EntirePeriod) %>
<% { %>
<li><%: LocalizedText.TipPeriod %> </li>
<% } %>
<% if (Model.SelectedFilterOption != FilterOption.None) %>
<% { %>
<li><%: LocalizedText.TipFilter %> </li>
<% } %>
</ul>
</div>
I think this would be considered OK view logic. However, I want it to be one grammatically correct sentence. I now have four extra tip resources:"
Tip0 = "No results found";
Tip1 = "No results found, consider choosing {0}.";
Tip2 = "No results found, consider choosing {0} or {1}.";
Tip3 = "No results found, consider choosing {0}, {1} or {2}.";
TipCustomerNumber = "another customer number";
TipPeriod = "a different period";
TipFilter = "a more generic filter";
And my view becomes this:
<%# Import Namespace="Resources" %>
<%# Import Namespace="System.Web.Mvc" %>
<%# Import Namespace="MyProject.Models" %>
<%# Control Language="C#" Inherits="ViewUserControl<EmptyResultViewModel>" %>
<% var tips = new List<string>();
// Add the tips of the filter items that are not set to the most general search criteria to a list.
if (!string.IsNullOrEmpty(Model.SelectedCustomerNumber))
{
tips.Add(LocalizedText.TipCustomerNumber);
}
if (Model.SelectedPeriodOption != PeriodOption.EntirePeriod)
{
tips.Add(LocalizedText.TipPeriod);
}
if (Model.SelectedFilterOption != FilterOption.None)
{
tips.Add(LocalizedText.TipFilter);
}
// Depending on how many tips there are, choose the correct tip format.
string message;
switch (tips.Count)
{
case 0:
message = LocalizedText.Tip0;
break;
case 1:
message = string.Format(LocalizedText.Tip1, tips[0]);
break;
case 2:
message = string.Format(LocalizedText.Tip2, tips[0], tips[1]);
break;
case 3:
message = string.Format(LocalizedText.Tip3, tips[0], tips[1], tips[2]);
break;
default:
message = LocalizedText.Tip0;
break;
} %>
<div class="warning">
<%: message %>
</div>
Now I see so much code in my view compared to only a tiny bit of html. This made me worry about whether I'm putting too much logic in my view. However, in a sense all the logic is around formatting the message.
I just wanted to check whether I'm doing this in the right way or not.
Thanks in advance.

What stops you from passing a pair, [Model, Message] as an actual model to view? Or even create extra wrapper class to pass message together with model. Consider:
class ModelWithMessage
{
public object Model { get; set; }
public string Message { get; set; }
}
as your view model.
This way you can do all the formatting logic in controller and view can remain as simple as it gets.
My point being, all the logic you do in your view currently is view-independant - it rings a bell it should not be there.
Edit in regards to discussion in comments:
Before putting any logic in view, it's good to ask yourself a question: can this be done in my controller/model? Often the answer is yes. Having properly designed model/controller will result in very simple and lighweight views, purely for content display, which is what view should do - accepts necessary information from the controller and renders a user interface to display that (wiki).
Minimalistic view will benefit you later, for example when you realize you need to display same content in other places aswell, perhaps using new view. In your original approach, you'll have to copy logic from the old view into the new view. Copying stuff from one file to another might not sound like a big deal, but let's remember about DRY principle. What if you have 15 views? Do you want to have same formatting logic in all of them? What if your manager comes and tells you formatting has changed? Do you want to update 15 view files, or 1 controller...?
When approaching such problems, it's worth to take a minute and answer yourself questions like those.

Related

Rails 3 each method and Ajax

I'm trying to implement some Ajax in my app. A strange behaviour occurs!
It's a daycare application. When you show a specific daycare of a specific date you can add some children.
Originally a list of the children of the database is generated and when you click on one of them the page reload and a new child appears in the attendance list of the daycare. It's working fine, i just wanna add some ajax to be more userfriendly !
When you click on child to add him to the daycare, a daycare_item is created ( join table, an id of the child and the id of the daycare ).
I make the changes to make it ajax ready:
partial for the list
format.js in the daycare_item controller
remote true on the link.
It works, no more reload! But the list is updated only when you click a second time on the children list ( the last child added doesn't appears yet ). The js transaction works and if you refresh manually the page, the missing child appears.
I tried few things and here are my results:
In my partial there are
<% #daycare.daycare_items.each do |c| %>
<li><%= c.child.firstname ></li>
<% end %>
This produce the "lag" effect with one children who is not showing ( until a full refresh )
But if i put a
<%= #daycare.daycare_items.count %>
the code is update in time !
I see nothing strange in the logs.
I'm asking why the .each method make a difference?
A var was stoping a part of the code to be executed, the var wasn't wrong and doesn't generate an error.
The elements involved:
daycare_items controller
def create
#daycare = Daycare.find(params[:daycare_id]) # for Ajax
#daycare_item = DaycareItem.new(params[:daycare_item])
....
end
the create.js.erb
$('#children-list').html(" <%=j render partial: 'daycares/daycare', locals: { daycare: #daycare } %> ");
the view daycares#show
<div id="children-list">
<%= render #daycare %>
</div>
the partial ( a part of ) _daycare.html.erb
<p>counting test:<%= daycare.daycare_items.count %></p>
# here was something like this <%= #waiting.count %> #waiting is define in the daycare controller but not in the daycare_items controller
<p>
<% daycare.daycare_items.each do |dci| %>
<%= dci.enfant.prenom %>
<% end %>
</p>

What is the best way to access a model from a view when the model is optional?

I have a single view that is used by two Controller Actions.
My Controller:
AccountController {
...
ActionResult LogOn() {
return View()
}
[HttpPost]
ActionResult LogOn(LogOnModel model) {
return View(model)
}
}
My View:
<% if (!String.IsNullOrEmpty(Model.UserName)) { %>
<div class="username"><% Response.Write(Model.UserName); %></div>
<% } %>
Of course, the problem with the above code is that I get an error "Object Reference not set to an instance of an object" on the line that references Model.UserName.
What is the best way to my view to support both Actions? I can replace the HttpGet LogOn method with:
ActionResult LogOn() {
result View(new LogOnModel());
}
That works, but it doesn't seem very elegant and I'm sure an empty model will break some more complex models that I will need to create later on. Is there a better way? Thanks!
What is the best way to my view to support both Actions?
If a view is strongly typed to a view model (which is what absolutely all your views should be) you should make sure that all your controller actions serving those views (which is pretty much all your controller actions in your application) are passing a view model to those views.
Which is pretty much bla-bla which translated into source code pretty basically means this:
ActionResult LogOn() {
return View(new LogOnModel())
}
That works, but it doesn't seem very elegant and I'm sure an empty
model will break some more complex models that I will need to create
later on.
That's the correct way. Don't worry. You ain't gonna break anything. It's by not providing a view model to your views that you are breaking stuff.
Also as a side note you should absolutely never use Response.Write in a view. So instead of:
<% Response.Write(Model.UserName); %>
you totally want:
<%= Html.DisplayFor(x => x.UserName) %>
or:
<%: Model.UserName %>
but personally I prefer the DisplayFor helper since it will take care of any metadata.
You can either return a different view or as you have done, return a new model.
As noted by other commenters, your Controller should support the strong-typeness of your view. But if you insist on proceeding with the anti-pattern. Replace this:
<% if (!String.IsNullOrEmpty(Model.UserName)) { %>
<div class="username"><% Response.Write(Model.UserName); %></div>
<% } %>
with this:
<% if (Model!= null && !String.IsNullOrEmpty(Model.UserName)) { %>
<div class="username"><% Response.Write(Model.UserName); %></div>
<% } %>

Razor Partial View not Rendering

If I use the following Controller method:
public ActionResult Menu()
{
// do stuff...
return PartialView("viewName", navLinks);
}
calling the partial view in _Layout.cshtml like this:
<div id="categories">
#{ Html.Action("Menu", "Nav"); }
</div>
With the following ASCX partial view:
<%# Control Language="C#"
Inherits="ViewUserController<IEnumerable<MyDataType>>" %>
<% foreach(var link in Model) { %>
<%: Html.Route.Link(link.Text, link.RouteValues) %>
<% } %>
everything works fine. Yay.
BUT, if I use either of the following RAZOR partial views:
#model IEnumerable<MyDataType>
#foreach(var link in Model){
Html.RouteLink(link.Text, link.RouteValues);
}
or...
#model IEnumerable<MyDataType>
#{
Layout = null;
}
#foreach(var link in Model){
Html.RouteLink(link.Text, link.RouteValues);
}
I get nothing. there's no exception thrown, I just don't get anything rendered. I know the problem isn't with the controller method (it works just great with the ASCX partial view).
What's going on here?
Try changing this:
#foreach(var link in Model){
Html.RouteLink(link.Text, link.RouteValues);
}
to this:
#foreach(var link in Model){
#Html.RouteLink(link.Text, link.RouteValues);
}
It looks like without the # the method is being called, but the return value is just being dscarded. Putting the # causes it to be written in the response.
The RenderAction method writes the action directly to the view and returns void.
The Action method returns the action's contents but doesn't write anything to the view.
Writing #something will print the value of something to the page.
You cannot write #Html.RenderAction, since RenderAction doesn't return anything.
Writing Html.Action(...) (without #) calls the method normally, but doesn't do anything with its return value.
OK, Changing the way the it was called from _Layout.cshtml worked...
<div id="categories">
#Html.Action("Menu", "Nav");
</div>
It is important to note, that #Html.RenderAction DOES NOT work for me. I'd really love some explanation here, because right now, learning Razor is frustrating me as there is little documentation, and problems like these which should take minutes to resolve, are eating up way too much of my time.

MVC Transfer Data Between Views

I just started to learn MVC and am trying to understand how it works.
I don't want to send users to different views for all edit, insert and list operations.
In my sample application a View contains a list of items and below the list there is a form (for inserting new items) with action "{Controller}/Create" but there is no Create View.
When a user inserts a new item it posts to the Create action with httpverb post and creates the item and returns back to the List action with RedirectToAction method.
But I can not show any message(error, information etc) to the user in this style because I can not pass data between Create action and List action. How can I do that?
You need to use Post Redirect Get PRG pattern.
Please read this Use PRG Pattern for Data Modification section in this blog post by Kazi Manzur Rashid.
http://weblogs.asp.net/rashid/archive/2009/04/01/asp-net-mvc-best-practices-part-1.aspx
This approach uses TempData to maintain ModelState data between redirects.
[HttpPost, ValidateAntiForgeryToken, ExportModelStateToTempData]
public ActionResult Create(FormCollection form)
{
Product p = new Product();
if (TryUpdateModel<IProductModel>(p))
{
productRepository.CreateProduct( p );
}
else
{
// add additional validation messages as needed
ModelState.AddModelError("_generic", "Error Msg");
}
return RedirectToAction("Index");
}
And here is your Index action method.
[ImportModelStateFromTempData]
public ActionResult Index()
{
IList<Product> products = productRepository.GetAll();
return View("Index", products);
}
And here is your Index view.
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IList<Product>>" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Products</h2>
<% foreach (var p in Model) { %>
<div><%= Html.Encode( p.ProductName ) %></div>
<% } %>
<%= Html.ValidationSummary("Please correct the errors", new { id = "valSumCreateForm" }) %>
<% using (Html.BeginForm("Create", "Product")) { %>
Product Name: <%= Html.TextBox("ProductName") %>
<%= Html.AntiForgeryToken() %>
<% ViewContext.FormContext.ValidationSummaryId = "valSumCreateForm"; %>
<% } %>
</asp:Content>
The ImportModelStateFromTempData
and ExportModelStateToTempData
attributes helps transfer model
state errors between redirects. This
<% ViewContext.FormContext.ValidationSummaryId = "valSumCreateForm"; %> associates the MVC Form with its corresponding Validation Summary.
You can check another answer by me on this here as well.
ViewModel with SelectList binding in ASP.NET MVC2
Let me know if you have any question.
-Soe
Most MVC frameworks have the ability to temporarily store a small bit of data just through the next request, for just this purpose. In ASP.NET MVC its called TempData, in Rails it's called :flash, etc.
This article explains how to use TempData:
One of the more annoying things to
deal with in Web programming is errors
on a form. More specifically, you want
to display error messages, but you
want to keep the previously entered
data. We've all had the experience of
making a mistake on a form that has 35
fields, only to be presented with a
bunch of error messages and a new,
blank form. The MVC Framework offers TempData as a place to store the previously entered information so that the form can be repopulated. This is
something that ViewState actually made
very easy in Web Forms, since saving
the contents of controls was pretty
much automatic. ... TempData is a dictionary,
much like the untyped ViewData.
However, the contents of TempData only
live for a single request and then
they're deleted.

MVC Multiple ViewPage items in aspx required

I need to pass in 2 objects to a page to access Model.NewsItems and Model.Links
(the first is a class of news item objects with heading, content etc and the Links are a set of strings for hyperlink depending on whether the system is up or down.
This is the page declaration
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<Tolling.Models.NewsItems>" %>
If I refer to Model.Items - I am fine.
However, if I refer to Model.HyperLink1, I am not.
How can you pass in multiple objects into the page?
I have tried importing both namespaces without success - i.e.
<%# Import Namespace="Tolling.Models.News" %>
<%# Import Namespace="Tolling.Models.HyperLinks" %>
Create a ViewModel class that contains both of your model collections and then pass that too the view:
Sample Controller:
var myNewModel = new MyNewModel()
{
NewsItems = new List<NewItem>(),
HyperLinks = new List<HyperLink>()
}
return View(myNewModel);
View page declaration:
<%# Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<MyNewModel>" %>
Then you can access them in your view with your new ViewModels properties:
<%= Model.NewsItems %>
<%= Model.Hyperlinks %>
You can pass extra data into the View by using ViewData, TempData, Session or Cache. I'd suggest you use ViewData. As described by MSDN:
Gets or sets a dictionary that
contains data to pass between the
controller and the view.

Resources