I am probably going about this completely wrong here, but that is partly what I am asking.
I am creating a blog using MVC3 and I am having some issues. My homepage currently lists each blog post with their corresponding comments and topics correctly. I want it to be limited to a number of posts, so here is my code in the HomeController.
public class HomeController : Controller
{
private MyDB db = new MyDB();
public ActionResult Index()
{
var posts = (from p in db.Set<BlogPost>()
orderby p.DateCreated descending
select new PostViewModel
{
Title = p.Title,
DateCreated = p.DateCreated,
Content = p.Content,
Topics = p.Topics,
Comments = p.Comments,
CommentCount = p.Comments.Count
}).Take(5).ToList();
IEnumerable<Topic> topics = from t in db.Topics
select t;
var blog = new BlogViewModel
{
Post = posts,
Topics = topics.Select(t => new SelectListItem {
Value = Convert.ToString(t.id),
Text = t.Name
})
};
return View(blog);
}
}
This works fine as I've said. I have the topics coming in separately because I want to eventually sort by those (which I also don't know where to start but that's another story).
My main problem is that I would like to have a "Next" and "Previous" button under the 5 selected posts, that when clicked, grab the next 5 or previous 5. I've been told to use...
#Html.ActionLink("Next >>", "ActionName", "Home", Custom arguement?)
type of solution where I write a custom method in my HomeController and grab the next or previous 5. Is this at all correct? I'd like to understand the best use scenario for something like this. I am completely new to MVC3, so I am not looking for shortcuts, and I feel like I maybe already have made a few.
Thanks for your help.
https://github.com/TroyGoode/PagedList
public class HomeController : Controller
{
public ActionResult Index(int? page)
{
var posts = (from p in db.Set<BlogPost>()
orderby p.DateCreated descending
select new PostViewModel
{
Title = p.Title,
DateCreated = p.DateCreated,
Content = p.Content,
Topics = p.Topics,
Comments = p.Comments,
CommentCount = p.Comments.Count
});
var pageNumber = page ?? 1; // if no page was specified in the querystring, default to the first page (1)
posts = posts.ToPagedList(pageNumber, 25); // will only contain 25 posts max because of the pageSize
var blog = new BlogViewModel
{
Post = posts,
Topics = topics.Select(t => new SelectListItem {
Value = Convert.ToString(t.id),
Text = t.Name
})
};
return View(blog);
}
}
I see two best scenarios:
One you could use Ajax/Jquery and do this.
Two you use a 'Partial' View. This way you are not affecting the ENTIRE view when someone presses the action link for the next five. So you would make a partial view for just the posts, then the controller receives a request for the next five and returns a partial view with a model that has five post items.
Choose which one you feel more comfortable with, but if you are learning MVC, choice two may be better for you for practice.
You can take a look at: http://nerddinnerbook.s3.amazonaws.com/Part8.htm .
Also if you want to get the data with an ajax request i recommend you to convert partial view containing the data to string ( Return Razor partial view using JSON (ASP MVC 3)) and use jquery to load it (http://api.jquery.com/load/ ).
I'd request another action method on your controller (either with Ajax, or normal), and just reuse the same View you have been using for your initial page render.
public ActionResult Index(int? pageNumber)
{
if(!pageNumber.HasValue)
return Index();
var posts = ...; // get posts
var set = posts.Skip(pageNumber * itemsPerPage).Take(itemsPerPage);
// or pageNumber - 1 if you want to be 1-index based
return View(...); //or PartialView() if doing ajax, or even Json() if you want to bind on the client side
}
You could create your Controller like the example code below:
public ViewResult List(string category, int page = 1) {
ProductsListViewModel viewModel = new ProductsListViewModel {
Products = repository.Products
.Where(p => category == null ? true : p.Category == category)
.OrderBy(p => p.ProductID)
.Skip((page - 1) * PageSize)
.Take(PageSize),
PagingInfo = new PagingInfo {
CurrentPage = page,
ItemsPerPage = PageSize, //4 for example
TotalItems = category == null ?
repository.Products.Count() :
repository.Products.Where(e => e.Category == category).Count()
},
CurrentCategory = category
};
return View(viewModel);
}
Inside your View, you put this custom HTML Helper:
<div class="pager">
#Html.PageLinks(Model.PagingInfo, x => Url.Action("List",
new {page = x, category = Model.CurrentCategory}))
</div>
Here is the code of the Helper:
public static class PagingHelpers {
public static MvcHtmlString PageLinks(this HtmlHelper html,
PagingInfo pagingInfo,
Func<int, string> pageUrl) {
StringBuilder result = new StringBuilder();
for (int i = 1; i <= pagingInfo.TotalPages; i++) {
TagBuilder tag = new TagBuilder("a"); // Construct an <a> tag
tag.MergeAttribute("href", pageUrl(i));
tag.InnerHtml = i.ToString();
if (i == pagingInfo.CurrentPage)
tag.AddCssClass("selected");
result.Append(tag.ToString());
}
return MvcHtmlString.Create(result.ToString());
}
}
This example was taken from this book
Related
I have a dynamic list of dynamic lists, which have <input />s that need to be POSTed to an MVC controller/action and bound as a typed object. The crux of my problem is I can't figure out how to manually pick out arbitrary POSTed form values in my custom model binder. Details are below.
I have a list of US States that each have a list of Cities. Both States and Cities can be dynamically added, deleted, and re-ordered. So something like:
public class ConfigureStatesModel
{
public List<State> States { get; set; }
}
public class State
{
public string Name { get; set; }
public List<City> Cities { get; set; }
}
public class City
{
public string Name { get; set; }
public int Population { get; set; }
}
The GET:
public ActionResult Index()
{
var csm = new ConfigureStatesModel(); //... populate model ...
return View("~/Views/ConfigureStates.cshtml", csm);
}
The ConfigureStates.cshtml:
#model Models.ConfigureStatesModel
#foreach (var state in Model.States)
{
<input name="stateName" type="text" value="#state.Name" />
foreach (var city in state.Cities)
{
<input name="cityName" type="text" value="#city.Name" />
<input name="cityPopulation" type="text" value="#city.Population" />
}
}
(There is more markup and javascript, but I leave it out for brevity/simplicity.)
All form inputs are then POSTed to server, as so (parsed by Chrome Dev Tools):
stateName: California
cityName: Sacramento
cityPopulation: 1000000
cityName: San Francisco
cityPopulation: 2000000
stateName: Florida
cityName: Miami
cityPopulation: 3000000
cityName: Orlando
cityPopulation: 4000000
I need to capture the form values, ideally bound as a List<State> (or, equivalently, as a ConfigureStatesModel), as so:
[HttpPost]
public ActionResult Save(List<State> states)
{
//do some stuff
}
A custom model binder seems like the right tool for the job. But I don't know how to know which city names and city populations belong to which state names. That is, I can see all the form keys and values POSTed, but I don't see a way to know their relation:
public class StatesBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
//California, Florida
List<string> stateNames = controllerContext.HttpContext.Request.Form.GetValues("stateName").ToList();
//Sacramento, San Francisco, Miami, Orlando
List<string> cityNames = controllerContext.HttpContext.Request.Form.GetValues("cityName").ToList();
//1000000, 2000000, 3000000, 4000000
List<int> cityPopulations = controllerContext.HttpContext.Request.Form.GetValues("cityPopulation")
.Select(p => int.Parse(p)).ToList();
// ... build List<State> ...
}
}
If I could just know the order all values came in in relation to all other form values, that would be enough. The only way I see to do this is looking at the raw request stream, as so:
Request.InputStream.Seek(0, SeekOrigin.Begin);
string urlEncodedFormData = new StreamReader(Request.InputStream).ReadToEnd();
but I don't want to be messing with manually parsing that.
Also note that the order of the list of states and the order of the lists of cities in each state matter, as I persist the concept of display-order for them. So that would need to be preserved from the form values as well.
I've tried variations of dynamic list binding like this and this. But it feels wrong junking up the html and adding a lot of (error-prone) javascript, just to get the binding to work. The form values are already there; it should just be a matter of capturing them on the server.
The only obvious way I see of building a form that will actually represent which cities belong to which state would require that you use the strongly-typed helpers.
So, I'd use something similar to:
#model Models.ConfigureStatesModel
#for (int outer = 0; outer < Model.States.Count; outer++)
{
<div class="states">
#Html.TextBoxFor(m => m.States[outer].Name, new { #class="state" })
for (int inner = 0; inner < Model.States[outer].Cities.Count; inner++)
{
<div class="cities">
#Html.TextBoxFor(m => m.States[outer].Cities[inner].Name)
#Html.TextBoxFor(m => m.States[outer].Cities[inner].Population)
</div>
}
</div>
}
This will create inputs with form names that the default modelbinder can handle.
The part that requires some additional work is handling the re-ordering. I would use something like this, assuming you are using jQuery already:
// Iterate through each state
$('.states').each(function (i, el) {
var state = $(this);
var input = state.find('input.state');
var nameState = input.attr('name');
if (nameState != null) {
input.attr('name', nameState.replace(new RegExp("States\\[.*\\]", 'gi'), '[' + i + ']'));
}
var idState = input.attr('id');
if (idState != null) {
input.attr('id', idState.replace(new RegExp("States_\\d+"), i));
}
// Iterate through the cities associated with each state
state.find('.cities').each(function (index, elem) {
var inputs = $(this).find('input');
inputs.each(function(){
var cityInput = (this);
var nameCity = cityInput.attr('name');
if (nameCity != null) {
cityInput.attr('name', nameCity.replace(new RegExp("Cities\\[.*\\]", 'gi'), '[' + index + ']'));
}
var idCity = cityInput.attr('id');
if (idCity != null) {
cityInput.attr('id', idCity.replace(new RegExp("Cities_\\d+"), index));
}
});
});
});
This last bit probably requires some tweaking, as it's untested, but it's similar to something I've done before. You would call this whenever the items on your view are added/edited/removed/moved.
I came up with my own solution. It's a little bit of a hack, but I feel it's better than the alternatives. The other solution and suggestions all involved altering the markup and adding javascript to synchronize the added markup -- which I specifically said I did not want to do in the OP. I feel adding indexes to the <input /> names is redundant if said <input />s are already ordered in the DOM the way you want them. And adding javascript is just one more thing to maintain, and unnecessary bits sent through the wire.
Anyways .. My solution involves looping through the raw request body. I hadn't realized before that this is basically just a url-encoded querystring, and it's easy to work with after a simple url-decode:
public class StatesBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.HttpContext.Request.InputStream.Seek(0, SeekOrigin.Begin);
string urlEncodedFormData = new StreamReader(controllerContext.HttpContext.Request.InputStream).ReadToEnd();
var decodedFormKeyValuePairs = urlEncodedFormData
.Split('&')
.Select(s => s.Split('='))
.Where(kv => kv.Length == 2 && !string.IsNullOrEmpty(kv[0]) && !string.IsNullOrEmpty(kv[1]))
.Select(kv => new { key = HttpUtility.UrlDecode(kv[0]), value = HttpUtility.UrlDecode(kv[1]) });
var states = new List<State>();
foreach (var kv in decodedFormKeyValuePairs)
{
if (kv.key == "stateName")
{
states.Add(new State { Name = kv.value, Cities = new List<City>() });
}
else if (kv.key == "cityName")
{
states.Last().Cities.Add(new City { Name = kv.value });
}
else if (kv.key == "cityPopulation")
{
states.Last().Cities.Last().Population = int.Parse(kv.value);
}
else
{
//key-value form field that can be ignored
}
}
return states;
}
}
This assumes that (1) the html elements are ordered on the DOM correctly, (2) are set in the POST request body in the same order, and (3) are received in the request stream on the server in the same order. To my understanding, and in my case, these are valid assumptions.
Again, this feels like a hack, and doesn't seem very MVC-y. But it works for me. If this happens to help someone else out there, cool.
I am using the latest version Telerik MVC controls. I am using ASP.NET MVC 3 with razor.
I have a grid that lists all of my grant applications. I am wanting to use a grid that loads these grant applications via AJAX. I also need to create a client template column that has action links. These action links can vary depending on the state of each grant application.
I worked through the article at: http://gedgei.wordpress.com/2011/07/02/telerik-mvc-grid-actionlink-column/. I implemented the code as is and it works, I can create a client template column with a link in it. In my scenario I need to be able to pass in 2 parameters to the helper method, like:
column.ActionLink("Open", "Edit", "GrantApplication", item => new { id = item.Id, applicationStateId = item.GrantApplicationStateType.Id });
How I eventually implement this method in the end will change, but for now I am playing with these 2 input parameters to see how they are passed through and how I can retrieve them in the helper method.
The first question that I have regarding the article, why does the writer do the following:
var builder = factory.Template(x =>
{
var actionUrl = urlHelper.Action(action, controller, routeValues.Compile().Invoke(x));
return string.Format(#"{1}", actionUrl, linkText);
});
I can only assume that this is the server side template that is created? But nothing displays in the grid, so how do I skip this part and go directly to the client template (this is what I actually need).
The following part is also confusing because when the first parameter (id) check comes through then it is of type ParameterExpression so it goes into the true part of the if, but when the second parameter (grant application state id) comes in then it is of another type (not sure what) so then it goes into the false part of the if statement:
switch (argument.NodeType)
{
case ExpressionType.Constant:
value = ((ConstantExpression)argument).Value;
break;
case ExpressionType.MemberAccess:
MemberExpression memberExpression = (MemberExpression)argument;
if (memberExpression.Expression is ParameterExpression)
value = string.Format("<#= {0} #>", memberExpression.Member.Name);
else
value = GetValue(memberExpression);
break;
default:
throw new InvalidOperationException("Unknown expression type!");
}
When the second paramenter values goes into the false part of the if statement it fails here:
value = GetValue(memberExpression);
..and gives the following error message which I have no idea what it is:
variable 'item' of type MyProject.ViewModels.GrantApplicationListViewModel' referenced from scope '', but it is not defined
Here is my view model:
public class GrantApplicationListViewModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullNameDisplay
{
get { return FirstName + " " + LastName; }
}
public DateTime CreatedDate { get; set; }
public GrantApplicationStateType GrantApplicationStateType { get; set; }
}
Here is my partial grid declaration in my view:
#(Html.Telerik()
.Grid<GrantApplicationListViewModel>()
.Name("grdGrantApplications")
.Columns(column =>
{
column.Bound(x => x.Id)
.ClientTemplate(
"<label class=\"reference-number\">" + "<#= Id #>" + "</label>"
)
.Title("Ref #")
.Width(70);
column.Bound(x => x.FullNameDisplay)
.Title("Owner")
.Width(200);
column.Bound(x => x.GrantApplicationStateType.Name)
.Title("Status")
.Width(90);
//column.ActionLink("Edit", "Edit", "GrantApplication", item => new { id = item.Id });
column.ActionLink("Open", "Edit", "GrantApplication", item => new { id = item.Id, applicationStateId = item.GrantApplicationStateType.Id });
})
.DataBinding(dataBinding => dataBinding.Ajax().Select("AjaxGrantApplicationsBinding", "Home"))
.Pageable(paging => paging.PageSize(30))
.TableHtmlAttributes(new { #class = "telerik-grid" })
)
What I am trying to achieve with the above is code is something to the effect of:
if grant application id = 1
then return Edit link and View link
else
then return Details link
How would I do the above? Is the code in that article the only way to do it? Isn't there a more simplar way? I did Google and couldn't find much help on what I want to do. Has any one else come across something like this?
If all you want is the client template to display different content based on the application id, it would be simpler to just put a conditional in the client template.
column.Bound(x => x.Id)
.ClientTemplate("<# if (Id == 1 ) { #> Edit Link and View Link <# } else { #> Details Link <# } #>");
The Edit, View, and Details links would be put in the same way they are put in without the conditional.
I am new to MVC3 and cannot move with this problem.
I am making a simple blog with posts, that are divided into categories and each post may have some tags.
If I show posts to user, I have paging there, where url is like localhost/Posts/1, where "1" is number of the page.
But how can I do this if I want to show posts only from some category or with some tag?
It is in format localhost/Posts/Categories/1, where "1" is id of the category or localhost/Posts/Tags/tag1 where "tag1" is particular tag
I would like to change it all to be in format localhost/Posts/Page/1 or localhost/Posts/Categories/1/Page/1 or localhost/Posts/Tags/tag1/Page/1, but I really cannot find out how to achieve this in controller.
So my question is: How to make methods in controller to accept these complex urls?
I guess it has something to do with routing, but couldn't find any explanation of my problem.
Thanks a lot for any help.
My code:
public ActionResult Tags(string id)
{
Tag tag = GetTag(id);
ViewBag.IdUser = IDUser;
if (IDUser != -1)
{
ViewBag.IsAdmin = IsAdmin;
ViewBag.UserName = model.Users.Where(x => x.IDUser == IDUser).First().Name;
}
return View("Index", tag.Posts.OrderByDescending(x => x.DateTime));
}
public ActionResult Index(int? id)
{
int pageNumber = id ?? 0;
IEnumerable<Post> posts =
(from post in model.Posts
where post.DateTime < DateTime.Now
orderby post.DateTime descending
select post).Skip(pageNumber * PostsPerPage).Take(PostsPerPage + 1);
ViewBag.IsPreviousLinkVisible = pageNumber > 0;
ViewBag.IsNextLinkVisible = posts.Count() > PostsPerPage;
ViewBag.PageNumber = pageNumber;
ViewBag.IdUser = IDUser;
if (IDUser != -1)
{
ViewBag.IsAdmin = IsAdmin;
ViewBag.UserName = model.Users.Where(x => x.IDUser == IDUser).First().Name;
}
return View(posts.Take(PostsPerPage));
}
Create new routes to direct those URL patterns to your controller (or another contoller, as appropriate)
http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/asp-net-mvc-routing-overview-cs
For example, this route definition
routes.MapRoute(
"CategoryPage", // Route name
"Posts/Categories/{CategoryID}/Page/{PageID}", // URL with parameters
new { controller = "Home", action = "ViewPage", CategoryID = "", PageID="" } // Parameter defaults
);
Would be picked up by this action in the HomeController:
public ActionResult ViewPage(int CategoryID, int PageID)
hiya, i have the following code but when i try and create a new IQuerable i get an error that the interface cannot be implemented, if i take away the new i get a not implemented exception, have had to jump back and work on some old ASP classic sites for past month and for the life of me i can not wake my brain up into C# mode.
Could you please have a look at below and give me some clues on where i'm going wrong:
The code is to create a list of priceItems, but instead of a categoryID (int) i am going to be showing the name as string.
public ActionResult ViewPriceItems(int? page)
{
var crm = 0;
page = GetPage(page);
// try and create items2
IQueryable<ViewPriceItemsModel> items2 = new IQueryable<ViewPriceItemsModel>();
// the data to be paged,but unmodified
var olditems = PriceItem.All().OrderBy(x => x.PriceItemID);
foreach (var item in olditems)
{
// set category as the name not the ID for easier reading
items2.Concat(new [] {new ViewPriceItemsModel {ID = item.PriceItemID,
Name = item.PriceItem_Name,
Category = PriceCategory.SingleOrDefault(
x => x.PriceCategoryID == item.PriceItem_PriceCategory_ID).PriceCategory_Name,
Display = item.PriceItems_DisplayMethod}});
}
crm = olditems.Count() / MaxResultsPerPage;
ViewData["numtpages"] = crm;
ViewData["curtpage"] = page + 1;
// return a paged result set
return View(new PagedList<ViewPriceItemsModel>(items2, page ?? 0, MaxResultsPerPage));
}
many thanks
you do not need to create items2. remove the line with comment try and create items2. Use the following code. I have not tested this. But I hope this works.
var items2 = (from item in olditems
select new ViewPriceItemsModel
{
ID = item.PriceItemID,
Name = item.PriceItem_Name,
Category = PriceCategory.SingleOrDefault(
x => x.PriceCategoryID == item.PriceItem_PriceCategory_ID).PriceCategory_Name,
Display = item.PriceItems_DisplayMethod
}).AsQueryable();
In my viewData I have an IList mls.
I want to use this to show in a dropdown. Like so:
<%= Html.DropDownList("ml3Code",
new SelectList(Model.Mls, "Code", "Description", Model.Ml3.Code ?? ""),
Model.T9n.TranslateById("Labels.All"),
new { #class = "searchInput" })%>
This works fine, until there's a myObject.Code == VOC<420 g/l.
I would have expected that an HTML helper would encode its values, but it doesn't.
How should I approach this problem? The only thing I can come up with is first making a dupe list of the objects with encoded values and then feeding it to the selectlist. This would be really bothersome.
P.S. I hope Phill H. and his team will have a long and thorough look at the encoding for asp.net-mvc 2.0...
I'm puzzled. The question "Do ASP.NET MVC helper methods like Html.DropDownList() encode the output HTML?" was asked on SO before, and the answer was "Yes" - and the source-code from the MVC framework was cited to back this assertion up.
Well, you can roll your own Html helper, but if you're like me you won't want to do that.
To me, I see two options here:
Write your select element in plain view without the helper. I've never felt the helpers provide you much save for highlighting an element when an error occurs.
Patch the select box on the client when the page loads, as in:
function encodeHtml(str)
{
var encodedHtml = escape(str);
encodedHtml = encodedHtml.replace(///g,"%2F");
encodedHtml = encodedHtml.replace(/\?/g,"%3F");
encodedHtml = encodedHtml.replace(/=/g,"%3D");
encodedHtml = encodedHtml.replace(/&/g,"%26");
encodedHtml = encodedHtml.replace(/#/g,"%40");
return encodedHtml;
}
window.onload = function()
{
var ml3Code = document.getElementById("ml3Code");
for(var i = 0; i < ml3Code.options.length; ++i)
{
ml3Code.options[i].value = encodeHtml(ml3Code.options[i].value);
}
};
It's a hack, I know. I strongly prefer the first choice.
This is encoded. But dont check with firebug - It shows values decoded.
Check in ViewSource of the Browser and things are encoded.
Controller
public List<CategoryInfo> GetCategoryList()
{
List<CategoryInfo> categories = new List<CategoryInfo>();
categories.Add(new CategoryInfo { Name = "Food<äü", Key = "VOC<420 g/l", ID = 2, Uid = new Guid("C0FD4706-4D06-4A0F-BC69-1FD0FA743B07") });
}
public ActionResult Category(ProductViewModel model )
{
IEnumerable<SelectListItem> categoryList =
from category in GetCategoryList()
select new SelectListItem
{
Text = category.Name,
Value = category.Key
};
model.CategoryList = categoryList;
return View(model);
}
View
<%= Html.DropDownList("Category" , Model.CategoryList) %>
Model
public class ProductViewModel
{
public IEnumerable<SelectListItem> CategoryList { get; set; }
public List<CategoryInfo> Categories { get; set; }
}
HTML
<select id="Category" name="Category"><option value="VOC<420 g/l">Food<äü</option>
</select>