What is the best way to access a model from a view when the model is optional? - asp.net-mvc-3

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>
<% } %>

Related

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.

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.

Using Page.User.Identity.Name in MVC3

In MVC2 I have used Page.User.Identity.Name using the <%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
How can I use the same in MVC3?
You can always do something like:
#Html.ViewContext.HttpContext.User.Identity.Name
but don't.
Normally a view shouldn't try to fetch such information. It is there to display whatever information is passed by the controller. It should be strongly typed to a model class which is passed by a controller action.
So in the controller action rendering this view:
[Authorize]
public ActionResult Index()
{
var model = new MyViewModel
{
Username = User.Identity.Name
}
return View(model);
}
Now inside the view feel free to use this information:
#Model.Username
MVC 2
<%: this.Page.User.Identity.Name %>
MVC 3
#this.User.Identity.Name
I had the same problem. I used this tutorial to solve this issue.
In short, in your view, put this code:
#Context.User.Identity.Name
Just make sure that the project is set to authenticate with windows.

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.

Passing values between View and Controller in MVC 2

I'm constantly confused about how to pass values between Views and Controllers in MVC. I know I can set ViewData in the Controller and use that in the View, but what about the other way around?
What I have found is I can use a hidden field and then access it through Request.Form["name"] like this:
<% using (Html.BeginForm("Upload", "Customers", FormMethod.Post, new { enctype = "multipart/form-data" }))
{%>
<br />
<input id="currentDir" type="hidden" name="currentDir" value="" />
<input type="file" name="fileTextbox" id="fileTextbox" />
<br />
<br />
<input type="submit" value="Send" />
<% } %>
What complicates it even more is that the value originally comes from a jquery script, so that's why the input field was the only way I could think of. But it still feels wrong... Maybe it isn't but I'd basically like to know if there are other more "proper" established ways to pass values between the View and Controller (both ways). Should one use querystrings instead? If so, how would they look in the html.beginform htmlhelper?
Also, what I'm trying to do here is enable upload possibilities for my application. And I'm trying to make the whole application as "Ajaxy" as possible. But this form will make a complete post. Is there another way to do this and not have to reload the entire page for this upload?
Let's ignore the "AJAX-y" aspects for a moment (because that's a different issue) and just look at passing data between views and controllers. I would first recommend that you check out the NerdDinner Tutorial which provides some good insights into how MVC works and how you use some of the features of MVC.
To address your specific question of how data is passed from View to Controller and back, there are a few ways to do this. However, the one that tends to make sense to most people is the idea of using strongly-typed views.
Let's say you have a model called Person. For now, don't worry about how we store Person data - we just have a Person class in the Models folder inside your MVC project.
public class Person {
public string FirstName;
public string LastName;
public Person() {
FirstName = "John";
LastName = "Doe";
}
}
When we want to display data about Person in a View, we make a request to a specific controller. In this case (and for clarity) we'll call this controller the MainController. This will go in the Controllers folder and will be called MainController. Let's call the Action (an action is really just a specialized method) we want to get data from Index. Due to how ASP.NET MVC routing works, the path to our server will be: http://localhost/Main/Index. Notice the Controller (minus the "Controller" name), and the Action make up the path. (The first part is your server name, of course.)
Let's look at your controller - I'm going to keep it very simple for now:
public class MainController : Controller {
public ActionResult Index() {
Person person = new Person();
return View(person);
}
}
What we have going on inside the Index Action is that it is returning a View (which, by default, has the same name as the Action) and a model to correspond with that view. Now, we have to create our view.
The important part here is that you want to strongly-type the model that is being returned in the controller to your view. You do that with this line (which is first in your aspx file).
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewUserControl<Project.Namespace.Person>" %>
Notice the "Inherits" attribute and notice that your Person model makes up that attribute.
Now, just code the rest of your view as normal. Let's say we want to display the current Person name, and allow someone to change the name. The page would look like this (I'm not making this pretty):
<% using (Html.BeginForm()) { %>
<%: Html.LabelFor(model => model.FirstName) %>
<%: Html.TextBoxFor(model => model.FirstName) %>
<%: Html.LabelFor(model => model.LastName) %>
<%: Html.TextBoxFor(model => model.LastName) %>
<input type="submit" value="Submit" name="submitButton" />
<% } %>
This is the important part about getting data back and forth between Controllers and Views. What we are doing here is that we are taking your strongly-typed view (which is typed with the Person class) and using helper methods (like LabelFor and TextBoxFor) to tie the model together with its data and, ultimately, together with the actions contained in the controller (which we have to finish developing here in one moment).
So, you can now see the data. But, if a user changes the name and clicks submit - we want the page to display the new name. This means we need to add one more action to MainController - the one that receives the data.
[HttpPost]
public ActionResult Index(Person person) {
// Do whatever you want with the Person model. Update a database, or whatever.
return View(person);
}
This action looks very similar to the other action we just developed. However, this one takes a person object (from the form that is being submitted) and it gives the controller an opportunity to do whatever needs to be done with that object. Once this is done, you can choose to redirect to a different page, redisplay the page (useful if there are errors), or do any number of other things.
Again, this is ALL covered (and much more) in the NerdDinner Tutorial. I highly recommend you read and follow through that.
As for the AJAX-y aspects you discussed, the premise is still the same (although there is a little bit of JavaScript/jQuery work that goes on in there). I won't go into it now, but the basics are also covered in the NerdDinner tutorial.
I hope this gets you started. I remember being a bit confused too when I first started working with web technologies, so I hope this helps you out!

Resources