MVC Multiple ViewPage items in aspx required - model-view-controller

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.

Related

OutputCache for partialView not working?

Hi,
I have a ASP.NET MVC 3 website where I have created a action that returns a partial view. This view have the following :
<%# OutputCache Duration="10000" VaryByParam="none" %>
And to render this from my main view I use this :
<% Html.RenderAction("ImageWall", "Image"); %>
The problem is that it is not caching at all? When setting a breakpoint I can see that the expensive drawing is running everytime?
For this, Use Action Attribute
<OutputCache(Duration:=10000, VaryByParam:="any")>
Function ImageWall() As PartialViewResult
Return PartialView()
End Function
For Vb.Net sample, sorry

MVC 3: Add usercontrol to Razor view

I have a DLL that contain a user control inside, in the Web Form view i can easily use it by using
<%# Register Assembly = "..." Namespace = "..." TagPrefix = "..." %>
But how to do it in Razor view?
You can't add server side controls to Razor views. In general it is very bad practice to do so anyways in an ASP.NET MVC application. Due to the heritage of WebForms view engine you could violate this rule but in Razor things have been made clearer.
This being said you could still do some pornography in Razor and include a WebForms partial which will contain the user control (totally not recommended, don't even know why I am mentioning it but anyway):
#Html.Partial("_Foo")
where in _Foo.ascx you could include server side controls:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<%# Register Assembly="SomeAssembly" Namespace="SomeNs" TagName="foo" %>
<foo:SomeControl runat="server" ID="fooControl" />
Also, not recommended, but you can render a control in code, like in an HTML Helper:
public static string GenerateHtmlFromYourControl(this HtmlHelper helper, string id)
{
var yourControl = new YourControl();
yourControl.ID = id;
var htmlWriter = new HtmlTextWriter(new StringWriter());
yourControl.RenderControl(htmlWriter);
return htmlWriter.InnerWriter.ToString();
}
and then you can reference it from your view:
Html.GenerateHtmlFromYourControl("YourControlId")
Just make sure you set up/reference your namespaces correctly to do this.
Caveat
FYI, I'm pretty sure there are some severe limitations regarding the Page Lifecycle here...

Is the sentence formatting I do here view logic or not?

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.

Why can't I use the ASP.NET MVC 3 Razor engine (with the new #-syntax) in a UserControl?

I am creating a UserControl that will read data from an XML file and populate a ListBox. I've open up the .ascx file and would like to use #foreach (etc..) but this isn't valid?
Can someone explain why this isn't possible? Or maybe confirm if UserControls are deprecated in MVC 3?
.ascx is WebForms. # is Razor, meaning that if you want to write #foreach(...) you need a Razor template: Foo.cshtml:
#model IEnumerable<AppName.Models.Foo>
#foreach (var item in Model)
{
...
}
And if you want WebForms user control Foo.ascx:
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<AppName.Models.Foo>>"
%>
<% foreach(var item in Model) { %>
...
<% } %>

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.

Resources