Question background:
I have a two page MVC 4 web app. The first page's view currently has its data manually typed into the View as its static i.e nothing is being passed to the View from its associated Controller method, as shown:
Index controller method:
Public ActionResult Index()
{
return View();
}
Index.cshtml View:
<div class="titleHeader">Welcome To This Test Example</div>
Best practice:
Is the above example OK to use, or should I generate a ViewModel object on the Index controller, populate the static data then return it to the View? As Shown:
Index Controller Index method with ViewModel:
Public ActionResult Index()
{
HomePageVM homepageVM = new HomePageVM
{
WelcomeMessage = "Welcome To This Test Example";
};
return View(homepageVM);
}
Index.cshtml now bound to the HomePageVM ViewModel:
#model TestPage.Models.HomePageVM
<div class="titleHeader">#Model.WelcomeMessage</div>
I think (opinion), it's best to use Resource files for static data.
With these Resource file you will be able to easily port to a multi language website.
Basically you'll need to create resx files, make the entries public.
In your cshtml you can access it by:
<div class="titleHeader">#Resources.WelcomeMessage</div>
Depending on the UIThread's culture info it is able to select the appropriate language.
You can find a tutorial here:
http://www.codeproject.com/Articles/778040/Beginners-Tutorial-on-Globalization-and-Localizati
As for your options:
1) There is really no need to create a ViewModel for static data unless you'll expect it to be dynamic in the future (although it's bad habit to develop now for future requirements).
2) Hard coded strings in the cshtml, it possible, but not suited for multi-language. There is 1 benefit I would like to mention: it's easier for non-developers to alter the html.
Based on the principle of not gold plating your code.
You don't need the view model (right now) so don't add it. If you want a view model later then it's simple to add one. Your goal should be to create the simplest solution possible. For me that means the view model (or any other solution that's not pure HTML) only add's unnecessary complexity.
The answer from stefan about resources again seems gold plating to me. It's based on the:
will be able to easily port to a multi language website.
Is your site multi language? Is it ever likely to be? If no, this is gold-plating.
I am not really a code guru but:
Why use viewbag or even viewmodel when you just want to show one line of static text? You don't usually use calculator when you want to add 2 + 2 don't you ?
<div> seems fine to me in this one
Related
I'm trying to have cleaner views for readability and the ease of modification.
I usually have for each view a couple of variables that represents the action URLs used in it:
#model IndexViewModel
#{
// controllers
string newsControllerName = nameof(NewsController).ToName();
// calls to actions
string removeNewsActionUrl = Url.Action(nameof(NewsController.RemoveNews));
string getNewsActionUrl = Url.Action(nameof(NewsController.GetNews));
string indexActionUrl = Url.Action(nameof(NewsController.Index));
}
So I can use them inside the view's data attributes for ajax calls in JavaScript:
<div id="newsContainer"
data-remove-url="#removeNewsActionUrl"
data-view-url="#getNewsActionUrl">
...
</div>
Then a friend of mine suggested that I shouldn't declare variables in a view because it's a bad practice and why not declare them in the ViewModel of that view in this case the IndexViewModel.
I know that views shouldn't have complex logic and should have the minimum of C# code, but is what I'm doing a bad practice? If so why? and is there a disadvantage or an advantage to have variables declared in the ViewModel of that view for this purpose?
From Microsoft docs views and view model:
Views are responsible for presenting content through the user interface. They use the Razor view engine to embed .NET code in HTML markup. There should be minimal logic within views, and any logic in them should relate to presenting content. If you find the need to perform a great deal of logic in view files in order to display data from a complex model, consider using a View Component, ViewModel, or view template to simplify the view.
I couldn't find anything related to this on this site.
I wouldn't want this stuff in the view model. It's superfluous there as there is nothing to be communicated between model and controller.
I don't see an issue with this - it's minimal logic and it is purely concerned with the view itself. Putting it anywhere else would be polluting another part of the codebase for no good reason.
That said, I don't really see any good reason for the code block - why not just inline it in the HTML attributes?
Let's say I have a theoretical MVC framework that uses a ViewData object to pass data from the controller to the view. In my controller, let's say I have some code like this (in pseudocode):
function GetClientInfo()
{
// grab a bunch of data from the database
var client = Database.GetClient();
var clientOrders = Database.GetClientOrders();
var clientWishList = Database.GetClientWishList();
// set a bunch of variables in the ViewData object
ViewData.set("client", client);
ViewData.set("clientOrders", clientOrders);
ViewData.set("clientWishList", clientWishList);
showView("ClientHomePage");
}
And then in my ClientHomePage view, I display the data like so:
<p>Welcome back, [ViewData.get("client").FirstName]!</p>
<p>Your order history:</p>
<ul>
[Html.ToList(ViewData.get("clientOrders")]
</ul>
<p>Your wishlist:</p>
<ul>
[Html.ToList(ViewData.get("clientWishList")]
</ul>
This is what I understand MVC to be like (please correct me if I'm wrong). The issue I'm having here is those magic strings in the view. How does the view know what objects it can pull out of the ViewData object unless it has knowledge of what the controller is putting in there in the first place? What if someone does a refactor on one of the magic strings in the controller, but forgets to change it in the view, and gets a runtime bug instead of a compile-time error? This seems like a pretty big violation of separation of concerns to me.
This is where I'm thinking that a ViewModel might come in handy:
class ClientInfo
{
Client client;
List clientOrders;
List clientWishList;
}
Then the controller creates an instance of ClientInfo and passes it to the view. The ViewModel becomes the binding contract between the controller and the view, and the view does not need to know what the controller is doing, as long as it assumes that the controller is populating the ViewModel properly. At first, I thought this was MVVM, but reading more about it, it seems like what I have in mind is more MVC-VM, since in MVVM, the controller does not exist.
My question is, what am I not understanding here about MVC vs. MVVM? Is referring to variables in the ViewData by magic strings really not that bad of an idea? And how does one insure that changes made in the controller won't adversely affect the view?
Your understanding of MVC is wrong, it stands for Model View Controller but you are missing the Model in your example. This is the typed entity that gets passed back to the View to do the rendering. In ASP.Net MVC you would use typed Views that also type the Model within the View so it is checked at compile time. This eliminates the need for magic strings (second part of your question).
In MVVM you have Model View ViewModel. This is a way of binding a ViewModel directly to the UI layer via a View which is used a lot in WPF. It replaces the need for a controller and it's generally a 1-to-1 mapping with the UI. It's just an alternative mechanism that solves the same problem (of abstraction and seperation of concerns) but better suited to the technology.
Theres some useful info here which might help understand the difference.
Best approach to use strongly typed views
Models:
public class ContentPage
{
public string Title { get; set; }
public string Description { get; set; }
}
public class ContentPagesModel
{
public ContentPage GetAboutPage()
{
var page = new ContentPage();
page.Title = "About us";
page.Description = "This page introduces us";
return page;
}
}
Controller:
public ActionResult About()
{
var model = new ContentPagesModel();
var page = model.GetAboutPage();
return View(page);
}
View:
#model Experiments.AspNetMvc3NewFeatures.Razor.Models.ContentPage
#{
View.Title = Model.Title;
}
<h2>About</h2>
<p>
#Model.Description
</p>
for more detail check out here
I case of using string as keys of ViewData - yes, it will be a lot of exceptions if someone will refactor code.
It sounds like your understanding of MVC is very old, probably based on MVC 1. Things have changed tremendously in the last couple of years.
Now we have strongly typed view models, and we have the ability to use expressions in the view, which by default aren't compile-time validated, but they can be for debug purposes (though it slows down the build a great deal).
What's more, we don't pass model data through ViewDate anymore (well, not directly anyways.. it's still passed that way but the framework hides it).
Suppose you have a list of People A and a list of People B in a page. And these two are seperate classes in L2S, representing two different tables. Therefore, you cannot pass a single model as follows:
...
#model PeopleA
...
#foreach(var peopleA in Model.People) ...
#foreach(var peopleB in //what?)
Accordingly, I guess, I have three options to follow.
The first one is to devide the page into partial views so that I can pass a model through RenderAction helper. Since I will use these partial views only once this option does not seem attracting to me.
The second option would be to use ViewBags which I don't want to since I prefer strongly typed models.
The last one, finally, which I was about to use but wanted to ask before doing so, is to create a model as the following:
ModelMyPage.cs
public List<PeopleA> peopleA { get; set; }
public List<PeopleB> peopleB { get; set; }
MyController.cs
...
ModelMyPage m = new ModelMyPage();
m.peopleA = // query
m.peopleB = // another query
return(m);
And you got the idea. Is this the valid way to accomplish my task or is there a better c# way to do what I want?
Creating a ViewModel specific to the page, as your option 3 is the way I would do it.
I believe this is also the recommended approach.
No, there is not any better idea. In asp.net MVC, M stands for ViewModels, not the Business, Domain models. It is recommended to create ViewModels for your views and it's not reccomended to use Business Models. You should design your ViewModels to fit the need of controller interactions with Domain, and from controller to view interactions
Your first and third options seem both OK.
ad 1) "only using them once" is not a good argument-against. Use Partial views to organize views.
ad 2) Use the Viewbag to add small items like a lookup list.
ad 3) ViewModels are (becoming) common in MVC. This is probably the best approach.
I would do it the third way. Additionally, if you are going to render identical html for each person in both arrays, I would concat them before foreach:
var person in Model.PeopleA.Concat(Model.PeopleB)
I usually create a Model for the page, and name it as such, eg AccountDetailsPageModel. Then other models can be properies of this for complex pages.
So in order accomplish what I asked in this post I did the following:
[iPhone]
[ActionName("Index")]
public ActionResult IndexIPhone()
{
return new Test.Areas.Mobile.Controllers.HomeController().Index();
}
[ActionName("Index")]
public ActionResult Index()
{
return View();
}
Which still serves the same view as the Index action method in this controller. Even though I can see it executing the Test.Areas.Mobile.Controllers.HomeController().Index() action method just fine. What's going on here? And how do I serve the Index view from Mobile area without changing the request URL (as asked in the original post referenced above)?
You have a few options:
Redirect to the Action you'd like to return: return RedirectToAction("Action-I-Want").
Return the View by name: return View("The-View-I-Want").
Note that with the 2nd approach you'd have to put your view in the "Shared" folder for all controllers to be able to find it and return it. This can get messy if you end up putting all your views there.
As a side note: The reason your work doesn't find the view is because default view engine looks for the view in the folder that "belongs" to the current executing controller context, regardless of what code you're calling.
Edit:
It is possible to group all "mobile" views in the same folder. On your Global.asax (or where ever you're setting up your ViewEngine, just add the path to your mobile View in the AreaViewLocationFormats. Mind you, you'll still have to name your views differently.
You can also write your own view engine. I'd do something like detecting the browser and then serving the right file. You could setup a convention like View.aspx, and View.m.aspx.
Anyhow, just take a look at WebFormViewEngine and you'll figure out what works best for you.
The easiest way to send a request to a view handled by another controller is RedirectToAction("View-Name", "Controller-Name").
There are overloads of View() that take route information that might work as well, but they'd require more effort to set up.
Well actually the easiest way is to make one version of your site programmed on standards instead of browser detection :D -- however in direct response to accomplish what it in a more of a ASP.NET mvc fashion, using:
RedirectToAction("ViewName", "ControllerName");
is a good method however I have found it is more practical if you feel you must program for different browser standards to create a primary view and an alternate "mobile" view under your controllers views. Then instead of writing special code on every controller, instead extend the controller like so.
public class ControllerExtended : Controller
{
private bool IsMobile = false;
private void DetectMobileDevices(){ .... }
}
Then modify your controller classes to instead say ControllerExtended classes and just add the one line to the top of each Action that you have alternate views of like so:
public class ApplicationsController : ControllerExtended
{
// GET: /Applications/Index
public ActionResult Index() {
this.DetectMobileDevices();
if(this.IsMobile){
return RedirectToAction("MobileIndex");
} else {
// actual action code goes here
return View();
}
}
}
Alternately you can use return View("ViewName"); but from my experience you want to actually perform different actions as opposed to just showing the result in a different view as in the case of presenting an HTML table as opposed to a Flex table to help iPhone users since there is no flash support in the iPhone, etc. (as of this writing)
All my controllers are based off of a BaseController, to share properties between them and override OnActionExecuting to set some values based on the route.
I'm creating a BaseViewData class to do the same for all my view data.
At the moment I'm populating the view data like so (C#):
var viewData = new BaseViewData
{
Name = "someName",
Language = "aLanguage",
Category = "aCategoryName"
};
I do this in every action that requires the view data. Some of the properties are common, need to be set throughout every action. Is there a way to set some of the properties on a more global scale?
If I instantiate the BaseViewData class in the OnActionExecuting method on the BaseController, how do I access the BaseViewData properties from the action in the regular controllers (derived from the BaseController)?
Update in response to Dennis Palmer:
I'm essentially doing this because of a nagging issue I'm having with ViewData["lang"] not being populated randomly on some requests. ViewData["lang"] contains "en" if the language is English, and "ja" if it is Japanese (well, it's supposed to anyway). I populate ViewData["lang"] inside OnActionExecuting on the BaseController.
In my view, I make a call to some partial views based on the language:
<% Html.RenderPartial(ViewData["lang"] + "/SiteMenu"); %>
But I'm randomly getting errors thrown that state "Cannot find /SiteMenu", which points to the fact that ViewData["lang"] has no value. I just cannot find any reason why ViewData["lang"] would not get populated. So, I'm rewriting the site to use ONLY strongly typed view data (and setting some hard defaults). But if another method is better, I'll go that way.
Thank you!
I'm not sure I follow exactly what you're trying to do, but if your view is using values in the route to display certain information, it seems like adding your own extension methods for HtmlHelper would be a better way to go.
Are Name, Language and Category contained in your routes? If so, then HtmlHelper will have access to the route info and can determine what to display via the extension methods. What is the correlation between your routes and what your views need to know?
Update: Is lang part of your route? If so, then I would still contend that you could write an HtmlHelper extension method that looks at the route data directly and determines which partial view to render. That way your controller wouldn't even need to worry about setting the ViewData["lang"]. The view would always know how to render based on the route.
Update 2: I think dismissing use of an HtmlHelper extension method because it re-evaluates the route data might be a case of premature optimization. Your controller inheritance scheme sounds overly complex and you asked the question because the way you were setting ViewData was unreliable. I doubt that pulling the value from route data would be much, if any, less efficient than setting and reading from ViewData.
From your comment:
In the controller I use the lang value
to determine which view to show as
well.
That only makes me think that there are more pieces of your system that I'd need to see in order to give better advice. If you have separate views for each language then why does the view need to be told which language to use?
Another alternative to consider would be using nested master pages. You could have a single master page for your site layout and then a nested master page for each language that just contains a hard coded lang value.
Perhaps instead of this inheritance scheme you have, you can just use action filters to add the data you need.