MVC View interacting directly with Model - good or bad? - c#

I have a pretty basic web application showing the content of a table. The user can change some parameters by clicking on links.
To simplify let's consider only a simple one, the name of the column used to order rows.
User can either click on the column or select the name of the column from a right sidebar to change the ordering.
I use an <option> element to show the list of available column name.
Is it correct to take those names directly from the Model object or should I somehow pass them from the Controller? This is the code by now
<select>
#{
// get list of all properties names
List<string> EtlProceduresNames = (((new MIPortal.Models.EtlProcedure()).GetType().GetProperties()).Select(item => item.Name)).ToList();
// display an option item for each property name
foreach (string EtlProceduresName in EtlProceduresNames) {
<option value=#EtlProceduresName >#Html.DisplayName(EtlProceduresName)</option>
}
}
</select>
This way the View interact directly with Model. Is this a conceptual error? Should I put that code on the Controller and then store the name list on a ViewBag object?

You should not access the Model directly from the view as that is against the MVC pattern (separation of concern), and you also should NOT be putting this data into the view bag.
View bag should only be used for the simplest of things to pass around and not when you have objects full of data.
Look into using Viewmodels, that is the best way of coding asp.net MVC.
Example of such-
http://www.codeproject.com/Articles/826417/Advantages-of-ViewModel-in-MVC-Model-View-Controll

I can suggest you to use so called ViewBag injection.
You need to declare an action filter attribute class for your data.
public class EtlProceduresNamesAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//you can add some caching here
filterContext.Controller.ViewBag.EtlProcedures = (new MIPortal.Models.EtlProcedure()).GetType().GetProperties()).Select(item => item.Name)).ToList();
}
}
All you to annotate your view action with this attribute, for example
[EtlProceduresNames]
public ActionResult Action()
{
return View();
}
You need to replace you view code with
<select>
#foreach (string EtlProceduresName in ViewBag.EtlProceduresNames)
{
<option value=#EtlProceduresName >#Html.DisplayName(EtlProceduresName)</option>
}
</select>

Related

Pass Initialized model through View

Simple question, but I cannot find solution anywhere.
I have 1 Get ActionResult, 1 Post ActionResult and 1 View.
In the Get method I initialize some part from the model.
After that in the View I initialize the other part.
When the model comes in Post method, it is not initialized well.
How to pass the data throught View and Methods?
I don't want to use Temporary objects.
Example:
[HttpGet]
public ActionResult Register(Guid product_id)
{
RegistrationModel model = new RegistrationModel();
model.Product = **someProduct**;
return View(model);
}
[HttpPost]
public string Register(RegistrationModel model, Product Product)
{
Here the objects comes initialized only with the parts from the view...
}
You need to understand how the MVC works.
There is a concept called Model Binding which is responsible for Mapping a Model with suitable HTML.
When I say suitable I mean that it needs to be formatted according to specific rules.
In Order to simplify the things, MVC has inbuilt HtmlHelpers which handles the transition between a Model's property to an HTML Element.
From your [HttpGet] method you may return:
1) a model-less view return View();
2) a view with a Blank model return View(new Product());
3) a view with a model which contains some data return View(product);
Inside the View, you should decide:
1) If you only want to display the model you could use (may not be wrapped in a form):
HtmlHelpers like #Html.DisplayFor(x => x.Name)
Simple Model calls like <h1>#Model.Name</h1>
2) If you want some data to be posted back to your [HttpPost] you should
Take care to "Map" a Model's property to an HTML element specifically (everything wrapped inside a form ):
Through HtmlHelpers like #Html.TextBoxFor(x => x.Price), which will generate a "suitable" HTML output:<input id="Price" name="Price" value="">52.00</input> (recommended)
Through well formatted HTML (suitable ^_^ ):
<select id="SelectedLanguageId" name="SelectedLanguageId">
<option value="1">English</option>
<option value="2">Russian</option>
</select>
To summarize:
1)If you want to receive something back to a [HttpPost] method you should have a suitable HTML element inside your .
2)If you want only to display some data you could simply call model's property
3)Use HTML helpers instead of raw HTML code.
Note!
Model binding on complex Models is achieved through EditorTemplates, Hidden inputs inside Loops, Different kind of Serialization etc.

How do you put a ViewBag item into a Text Box?

I have the following code and I get an error saying:
has no applicable method named 'TextBoxFor' but appears to have an extension method by that name.
My Code:
#Html.TextBoxFor(ViewBag.taglist)
Why don't you use strongly typed model in your view instead of ViewBag. This will make your life easier.
In fact, you must use a model to with TextBoxFor, otherwise it just won't work. See the definition of TextBoxFor - as a second parameter it takes a lambda expression that takes a property form a model.
If you want just a text box, two options:
#Html.TextBox("NameOfTheTextbox", (String)ViewBag.SomeValue)
or just go
<input type="text" value="#ViewBag.SomeValue" />
No complex solutions required.
I agree with other suggestions of using a strongly-typed model, because the compile-time error support is so much better than debugging exceptions. Having said that, in order to do what you want, you can use this:
#Html.TextBox("NameOfTextBox", (string)ViewBag.taglist)
Update: A Simple Example
Now that you've provided some details in your comments, I've taken a guess at what you might be doing, in order to provide a simple example.
I'm assuming you have a list of tags (like SO has per question) that you'd like to display neatly in a textbox, with each tag separated by a space. I'm going to assume your Tag domain model looks something like this:
public class Tag
{
public int Id { get; set; }
public string Description { get; set; }
}
Now, your view will need a list of the tags but will likely need some other information to be displayed as well. However, let's just focus on the tags. Below is a view model to represent all the tags, taking into account that you want to display them as a string inside a textbox:
public class SomeViewModel
{
public string Tags { get; set; }
// Other properties
}
In order to get the data you want you could grab all of the tags like this:
public ActionResult Index()
{
using (YourContext db = new YourContext())
{
var model = new SomeViewModel();
model.Tags = string.Join(" ", db.Tags.Select(t => t.Description).ToList());
return View(model);
}
}
Notice how I'm directly passing model to the view.
The view is now very simple:
#model SomeViewModel
#Html.EditorFor(m => m.Tags)
The model directive is what signifies that a view is strongly-typed. That means this view will expect to receive an instance of SomeViewModel. As you can see from my action code above, we will be providing this view the type that it wants. This now allows us to make use of the strongly-typed HtmlHelper (i.e. Html.XxxFor) methods.
In this particular case, I've used Html.EditorFor, as it will choose an appropriate input element to render the data with. (In this case, because Description is a string, it will render a textbox.)
You cannot use Html.TextBoxFor without explicitly setting a type for your model within the view. If you don't specify a type it defaults to dynamic. If you want to do model binding then you must use an explicit type rather than a dynamic type like ViewBag. To use Html.TextBoxFor you must define a model type that defines the property that you wish to bind. Otherwise you have to use Html.TextBox and set the value manually from ViewBag. As others have said, you will make your life much easier if you use a statically typed model and take advantage of the inbuilt MVC model binding.
You have to use a lambda expression to select the property, plus you will have to cast the ViewBag member to the correct type.
#Html.TextBoxFor(model => (string)ViewBag.taglist)

asp.net mvc navigation on master page best practices

Trying to create a strongly typed master page with multi level navigation and would love to hear your opinion.
i'm using the sample recommended by MS here:
http://www.asp.net/mvc/tutorials/passing-data-to-view-master-pages-vb
so i have an ApplicationController that gets all the categories and all the other controller inherits it. and it return a LIST and stores it in ViewData["Nav"]
The master page as a partial view that gets the NAV model and creates the menu.
The roues for the category
Category/{CategoryId}/{CategoryName}/{Page}
The question is how can i display the selected category or sub category as selected when i renders it inside the partialView.
I see some options:
1. Create another property in the applicatin controller :
public class CategoryController : AppliactionController
{
//
// GET: /Category/
public ActionResult Index(string categoryId, string categoryName, int page)
{
base.ActiveCategoryId=int.parse(categoryId);
return View();
}
Check the current action URL in the partial view when creating the menu and set the category as selected if it produces the same action URL (not sure if i can get the categoryid from the action)
Any suggestions?
If you're going to use the Master Controller pattern, then option 1 is a suitable solution. For even better separation, you may consider moving that logic in the action into an action filter.
I would avoid option 2 completely, because you don't want that kind of logic in your view.
A third option you can try is to not use the Master Controller pattern, and instead set up your view to call RenderActions on stuff that is orthogonal to the view's main concern. In this case, your view would look something like Html.RenderAction("Menu", Model.CurrentCategoryId)
Regarding your seggestion:
"you may consider moving that logic in the action into an action filter."
I could do that but is it possible to access the controller case controller from the action filter?
should it be something like
public void OnActionExecuting(ActionExecutingContext filterContext)
{
((AppController)filterContext.Controller.base)).ActiveCategoryId=int.parse( filterContext.ActionParameters["CategoryId"])
Didn't check the code, just wanted to hear your thoughts, is this what you suggested?
Thanks

Passing PaginatedList to ActionResult MVC C#

I wanted to create a simple paginated search for my project. I'm having trouble with my 'advanced search' page, where I have several textbox inputs that the user would fill with the appropriate data (basically just several filters).
My view is strongly-typed with the paginatedList class similar to the NerdDinner tutorial.
In my controller, I wanted to pass the PaginatedList as a parameter since my view contains several bits of info from the PaginatedList model. The PaginatedList was null (as parameter), then I changed added the route; the object itself is not null anymore, but the values are.
View:
<%= Html.TextBox("Manufacturers", Model.Manufacturers) %>
<%= Html.TextBox("OtherFilters", Model.FilterX) %>
//...etc etc
Controller:
public ActionResult AdvancedSearchResults(PaginatedList<MyModel> paginatedList) {
//...
}
Any ideas? Am I even going about this correctly? Should I create a ViewModel that encapsulates the paginatedList info as well as the additional info I need instead?
You may want to create SearchCriteria class which contains user's input data for filtering.
Controller will have the Action as below:
public ActionResult AdvancedSearchResults(SearchCriteria criteria)
{
PaginatedList<MyModel> result = SearchService.Search(criteria);
return View(result);
}

Passing ViewModel in ASP.Net MVC from a View to a different View using Get

I have a List View which has a strongly typed ViewModel which includes the entity list I am working with together with some other session-type stuff I am carrying aruound.
On clicking an item in the list (an Html.ActionLink) to go to the Details view I can easily pass the entity id. But I also want to pass the rest of the ViewModel from the View.
I can build the ActionLink with various QueryString parameters and then a custom ModelBinder can pick them up and hydrate the ViewModel object for me again. However, I donĀ“t like this.
I can get the custom ViewModel to rehydrate when it is POSTed back to the same page etc., but how can I get the ViewModel into a Controller Action using a GET to another View without using a ModelBinder and by simply placing the ViewModel object as a parameter in the target Action method?
I don't think you can do what you want, which from what I gather is the following:
While rendering the List action, you want to create a link to another action (potentially on another controller, but that's not key here)
This action should, when fired, have access to the original ViewModel that existed when the ActionLink method was first executed.
Unfortunately, items #1 and #2 are completely disconnected from each other and so there is no real mechanism to pass the current ViewModel to a link that will be executed in a different session.
That's not to say there aren't workarounds, of course:
You can generate the action link like so:
<%=
Html.ActionLink(
"Label",
"Action",
"Controller",
new {Parameter1 = Model.Data1, Parameter2 = Model.Data2},
null
)
%>
Within your linked action method, you can instantiate the ViewModel using the parameters passed to that action method.
I just tried this, and it seemed to work. Also tried the without a form and it worked also. Not sure if this is exactly what you wanted though.
Action
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Index(TestModel model)
{
ViewData["Message"] = model.Test1;
return View();
}
Model
public class TestModel
{
public string Test1 { get; set; }
public string Test2 { get; set; }
}
View
<% using (Html.BeginForm("Index","Home",FormMethod.Get))
{ %>
<%=Html.TextBox("Test1")%>
<%=Html.TextBox("Test2")%>
<input type=submit value=submit />
<% }%>

Categories