I am building a site in ASP.NET MVC. I intend to replicate the way StackOverflow displays its posts. Are these guys using a repeater control in ASP.NET MVC?
I do want complete control on the markup rendered, but I also want pagination.
What is the best approach in such a case.
I have no idea how StackOverflow implements it, but you don't need to use any asp.net control.
Of course you would need to build your own pager.
Take a look at the NerdDinner tutorial, it has a section showing how you can create a paged list.
http://weblogs.asp.net/scottgu/archive/2009/04/28/free-asp-net-mvc-nerddinner-tutorial-now-in-html.aspx
It is pretty easy and you have full control over all the markup. In fact you pretty much have to provide all the markup almost none of the markup is generated by asp.net.
In MVC, I would suggest using a combination of a LINQ query in your controller (or in a repository, within a function called from the controller), a for loop in your view, and depending on on how complex the items you want to display are, a partial view inside the loop.
For pagination, you might pass start and count parameters to a function to get a section of a list of objects via LINQ. For example:
Function sliceList(ByVal startIndex As Integer, ByVal count As Integer) As Generic.List(Of myObject)
Dim FullObjectList As Generic.List(Of myObject) = GetObjectsFromSomewhere()
Dim returnList As New Generic.List(Of myObject)
returnList = From o In FullObjectList Skip startIndex Take count
Return returnList
End Function
Then your controller passes the returned listed to the view for display, and you loop through it, displaying the items however you please.
Hope this helps.
Related
I'm making the transition from webforms to MVC (I know, 3 years late) and I "get it" for the most part, but there's a few things I'd like advice and clarification on:
First off, what happens if you want to dynamically add inputs to a view? for example, in an old webform for generating invoices I had a button with a server-side click event handler that added an extra 5 invoice item rows. The stateful nature of webforms meant the server handled the POST event "safely" without altering the rest of the page.
In MVC I can't think how I'd do this without using client-side scripting (not a showstopper, but I would like to support clients that don't have scripting enabled).
The second problem relates to the invoices example. If my Model has a List, how should I be generating inputs for it?
I know data binding is a possible solution, but I dint like surrendering control.
Finally, back to the "stateful pages" concept - say I've got a Dashboard page that has a calendar on it (I wrote my own calendar Control class, the control itself is stateless, but can use the webform viewstate to store paging information) - how could a user page through the calendar months? Obviously POST is inappropriate, so it would have to be with GET with a querystring parameter - how can I do this in MVC? (don't say AJAX).
Thanks!
In MVC you design your actions to accommodate your needs. For example, if you wanted to be able to add 5 rows to an invoice NOT using client-side scripting, you'd probably have your GET action for the invoice generation take a nullable int parameter for the number of rows. Store the current number of rows in the view model for the page and generate a link on the page to your GET action that has the parameter value set to 5 more than the current value. The user clicks the link and the GET view generates the page with the requested number of rows.
Controller
[HttpGet]
public ActionResult Invoice( int? rows )
{
rows = rows ?? 5; // probably you'd pull the default from a configuration
...
viewModel.CurrentRows = rows;
return View( viewModel );
}
View
#Html.ActionLink( "Add 5 Lines", "invoice", new { rows = Model.CurrentRows + 5 }, new { #class = "add-rows" } )
You would probably also add some script to the page that intercepts the click handler and does the same thing via the script that your action would do so that in the general case the user doesn't have to do a round trip to the server.
<script type="text/javascript">
$(function() {
$('.add-rows').click( function() {
...add additional inputs to the invoice...
return false; // abort the request
});
});
</script>
Likewise for your calendar. The general idea is you put enough information in your view model to generate all the actions that you want to perform from your view. Construct the links or forms (yes you can have multiple forms!) in your view to do the action. Use parameters to communicate to the controller/action what needs to be done. In the rare case where you need to retain state between actions, say when performing a wizard that takes multiple actions, you can store the information in the session or use TempData (which uses the session).
For things like a calendar you'd need the current date and the current view type (month/day/year). From that you can construct an action that takes you to the next month/day/year. For a paged list you need the current page, the current sort column and direction, the number of items per page, and the number of pages. Using this information you can construct your paging links that call back to actions expecting those parameters which simply do the right thing for the parameters with which they are called.
Lastly, don't fear AJAX, embrace it. It's not always appropriate (you can't upload files with it, for example), but your users will appreciate an AJAX-enabled interface.
In MVC you can store application state in various ways. In your controller you have direct access to the Session object and you can also store state to the database.
your view can contain basic control flow logic, so, if your model has a list you can iterate over it in the view and, for example, render an input control for each item in the list. you could also set a variable in a model to be the maximum number of rows on the viewpage and then render a row in a table for the number specified by the model.
paging is basically the same thing. you can create a partial view (user control in the webform world) that shows page numbers as links, where each link calls an action that fetches the data for that page of results.
i'm not sure what your beef is with ajax or javascript
I'm trying to design a solution in MVC in which a string representation of a class is passed to the controller which should then build a grid with all the data belonging to that class in the DB. (I'm using an ORM to map classes to tables).
//A method in the Model that populates the Item Property
foreach (MethodInfo method in sDRMethods)
{
if (method.Name.Contains(_domainTable))
{
Items = method.Invoke(repositoryObject, null);
break;
}
}
//View uses this Items property of the Model to populate the grid.
public object Items;
//_domainTable is the name of the table/class (in string format).
//repositoryObject is the object that has methods to return IEnumerable<class> collection object of each type.
The problem I have is that I do not know how to cast the "Items" property in my view to iterate through it and build a grid.
I have tried using the "http://mvcsharp.wordpress.com/2010/02/11/building-a-data-grid-in-asp-net-mvc/" but the generic extension method is expecting to know the specific type that it should work with.
I would prefer to use MVC but it looks like I cannot easily have this working(which is very hard to believe).
I really don't like the sound of what you are trying to do. Why convert the table to a string?
The only time you would convert to a string, is when the view gets rendered. And that, in most cases, should be left to the MVC framework.
The code you mentioned uses an HtmlTextWriter which is fine, because it will render straight to the response.
However, it sounds as if you are trying to reinvent the wheel by rendering everything to a string, rather than leaving that to the framework.
Note that in MVC the views are just templates for rendering strings, which is, if I have understood you, exactly what you need.
So, if I have remotely understood what you are trying to do, and it is a big if because your post is not clear, you should pass your class to view as part of the strongly typed model, and then write some basic design logic into the view.
If I am right, which is not certain, I think you have misunderstood how MVC works.
Have a look at a few examples of how to use views to render the data in a model. The model can be any class, it can be an IEnumerable, a list, whatever, and you can use foreach loops in the view to render out what you want, how you want it.
In this sense, MVC is very different to writing custom controls in plain vanilla ASP.NET.
Thanks for your reply awrigley.
The requirement is quite simple. I perhaps made it sound awfully complex in my post.
On an Index view, I have to populate a dropdownlist with all the tables of the application that are system lookup. The "Admin" of the app, selects an item from the dropdownlist which should show the contects of that table in a grid so that the admin can perform CRUD operations using that grid.
What I am trying to do is, pass the selected item (which is the name of the table) to the controller which in turn passes it to the ViewModel class. This class uses reflection to invoke (code shown in my original question) the right method of a repository which has got methods like:
public IEnumerable GetAllTable1Data()
{
.....
}
The problem I have is that when I invoke the method, it returns a type "object" which I cannot cast to anything specific because I don't know the specific type that it should be cast to. When this object is passed to the view, the grid is expecting an IEnumerable or IEnumerable but I do not know this information. I am not able to do this:
(IEnumerable)method.Invoke(repositoryObject, null)
I get: cannot cast IEnumerable to IEnumerable
I (kind of) have the grid now displaying but I am using a Switch statement in the view that goes:
Switch(SLU_Type)
{
case "SLU_Table1": Html.Grid((IEnumerable)Model.Items);
case "SLU_Table2": Html.Grid((IEnumerable)Model.Items);
.....
}
I don't like this at all, it feels wrong but I just cannot find a decent way!
I could have partial views for each of the system look up tables but for that I'll have to add around 30 partial views with almost exactly same code for the Action & View. This does not seem right either!
Hopefully, this gives you a better understanding of what I'm trying to achieve.
This is a noob question, but I will ask it anyway...
I'm wanting to create a page that will do basic CRUD operations on a list of items:
-display the list
-edit an item
-create an item
-delete an item
It is looking like I will need an action for each of this operations. This is good and understandable. My question is regarding the views for interacting with the user.
I want to have in-place editing, so the user clicks on edit and they can edit the details of the item in the list. In my current understanding, i will have to duplicate a great deal of the view between 'display the list' and 'edit an item'. however, this seems to be unnecessary redundancy and will make future updates more time-consuming as I will have to update each view.
Is there an easier way? Am I on the right/wrong track? Any other comments?
Yes, absolutely. You'll want to use the overload of View() that takes a string. The string is the name of the view to render:
public ActionResult MyAction()
{
return View("MyViewName");
}
The View() method can take the name of a view as a parameter, so you can render the same view from several actions. By default (if you don't specify a view name) the framework uses a view named as the current action. See here for details.
I believe what you should be looking into is rendering partial views, which are .ascx pages similar to UserControls in WebForms. They're basically shared partial views that you can use for the same purposes across many views.
If you look in the default project template you can find examples for items such as the Login control.
Edit: And as others have noted, you can also share views between actions. Had my own noob moment there too. :)
I am new to ASP.NET MVC, particularly ajax operations. I have a form with a jquery dialog for adding items to a drop-down list. This posts to the controller action.
If nothing (ie void method) is returned from the Controller Action the page returns having updated the database, but obviously there no chnage to the form. What would be the best practice in updating the drop down list with the added id/value and selecting the item.
I think my options are:
1) Construct and return the html manually that makes up the new <select> tag
[this would be easy enough and work, but seems like I am missing something]
2) Use some kind of "helper" to construct the new html
[This seems to make sense]
3) Only return the id/value and add this to the list and select the item
[This seems like an overkill considering the item needs to be placed in the correct order etc]
4) Use some kind of Partial View
[Does this mean creating additional forms within ascx controls? not sure how this would effect submitting the main form its on? Also unless this is reusable by passing in parameters(not sure how thats done) maybe 2 is the option?]
UPDATE:
Having looked around a bit, it seems that generating html withing the controller is not a good idea. I have seen other posts that render partialviews to strings which I guess is what I need and separates concerns (since the html bits are in the ascx). Any comments on whether that is good practice.
look at the ContentResult you can specify the mime type of what you return (text/html)
You could alternatively make a control that take a IEnumerable of whatever you put in the selectlist, and build it using the view engine. That way you keep the formatting of the html (in this case a list of options) into a view, and not in your code.
<%# Control Language="C#"Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Article>>"%>
<%foreach (var article in Model){%>
<option><%:article.Title %></option>
<%} %>
I think I would go for that second one
From what I understood, the jQuery dialog contains a form that, when submitted, will post to an action which updates the database with some information. You want to get the newly added database information and update the same form that was used to trigger the database update.
If that is the case, then I think the best clean and logical option is to return JSON serialization of the items to be put in the drop down right after you update the database. Then, using jQuery, you would clear the drop down and append option tags into it.
You can also write a new, seperate action that returns the JSON serialization of the database objects you need. You would have jQuery call another post to this action as a callback to your first ajax post (the one used to update the database).
Here is a quick snippet
public ActionResult UpdateDatabase(string something)
{
/// update the database
IEnumerable<Items> items = getItemsFromDatabase(); // or w/e
var vals = items.Select(x=> new { value = x.ID, text = x.Name }); // something similar
return Json(vals);
}
Personally, I would write a separate function that returns JSON. This ensure separation of concerns, and gives me a function I can use in many different places.
Returning a JsonResult with all the items is the most versatile and least-bandwidth intensive solution as long as you are happy to iterate through the list in jQuery and update your drop-down list.
Using a partial view is nice for HTML that you can .load(...) directly into your select, but less versatile.
I would go with the JsonResult.
In your Controller:
public JsonResult UpdateItem(string sItem)
{
// 1. Insert new item into database if not exist...
// {update code here}
// 2. retrieve items from database:
IEnumerable<Item> Items = GetItems();
// 3. return enumerable list in JSON format:
return new JsonResult{ Data = new {Items = Items, Result = "OK" }};
}
On client-side:
Iterate through Items array and add the items to your list.
Main question: Is there a better way to accomplish creating a reusable control?
So the idea was to make a paging control to basically stop from having to keep typing out practically the same markup on multiple views. It's taking this:
<%= Html.ActionLink("First", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.FirstPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Previous", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.PreviousPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Next", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.NextPage, amountToShow = Model.AmountToShow }))%>
|
<%= Html.ActionLink("Last", "Details", new RouteValueDictionary(new { parentForumId = Model.TopicId, pageNumber = Model.LastPage, amountToShow = Model.AmountToShow }))%>
And turning it into this:
<%= Html.Pager("View", "Controller", "RouteName", Model, new Dictionary<String, Object> { {"parentForumId", Model.ParentForumId}}, " ") %>
Where as you can see I pass in the needed view, controller, route name, model, and a dictionary used to add request variables onto the url for the link.
What I found is that I would have to make an extension method for the HtmlHelper class and essentially take what in ASP.Net was a full class (with nice methods like CreateChildControls) and jam it all into one main method that returns a string.
Is this the preferred way of doing this? One nice thing of the ASP.Net way was markup to class as in you have the html markup tag that would translate markup properties to class properties. It generally made for cleaner mark up but admittedly "fake" html. In this situation I have a method with what could be a mile long signature pumping out html. And since I don't have a base WebControl class, every control I make will have to have method calls with the same basic needs like say CssClass or ID.
Now with that being said, I suppose I could pass in an attributes dictionary since the
HtmlHelper.GenerateRouteLink
method that I'm using calls for one anyhow, but this really seems really messy.
Is there a better way to do this?
First, its all ASP.NET...one is MVC, the other is WebForms. Took me a sec to realize what you were saying when you keept saying the "ASP.NET way". :P
The idea with an MVC is that your view is "dumb", without any real behavior outside of the absolute bare bones basics to render data. In WebForms, views were tightly bound to the behavior that rendered them and handled view events. This, while convenient, made WebForms views very difficult to unit test since view content and behavior were linked and sometimes blended.
The reason MVC views use things like HtmlHelper and AjaxHelper is to keep behavior as separated from the view as possible. Unlike a user or server control in WebForms, you can fully unit test an Html.Pager extension method, since the logic is pure code, without blending those UI concerns or being linked to a bunch of non-testable UI level types. The same general rule applies to MVC controllers to...they are just code, without being linked to events or anything like that.
It may be less convenient in the short run, as you are currently used to the old WebForms way of doing things. Give yourself some time, though, and you will likely start to realize the benefits that MVC's preferred way of doing things brings to the table. Writing a Pager extension method on HtmlHelper is indeed the preferred way to do things with MVC.
As for the mile-long signature bit...do a search (try out Bing.com!) for fluent style interfaces and HtmlHelper. The fluent style is starting to take a strong hold in environments like MVC views where you are likely to have huge signatures. The general idea is based on method chaining, kind of like jQuery, and can shorten those long signatures into a series of much shorter and more meaningful chained method calls that set up your html helper, with a final call to a .Render method or something similar.
You could put it in a partial view, instead of creating a helper.
You might want to check out Martijn Boland's Pager control for some inspiration.
Personally, for my reusable grid control I use a class that contains all information needed to generate a grid with paging, sorting, ... and I call partial views to generate the seperate elements (Pager, Column selection, pagesize selection, ...), passing the information they require to them.
This way I can easily extend the grid with custom stuff. For example I can create a Mygrid_editableTable.ascx view to show textboxes instead of just text, and add an extra column with a submit button. This while continuing to use the paging, page selection, ...
We end up using html helpers for paginators as they are easy to unit test. Pagination business requirements can be finicky.
"Show less than 35 links as numbers, then group by 20s unless there are more than 100 pages of results, in which case group by 100s...but on Thursdays, or to GoogleBot, show them as... etc."
Plus our SEO guys keep changing their mind on what shape urls get the most juice. In such a situation, something unit testable is a must!