Im at a loss for words, as I must be missing something. Just finished ASP.NET MVC 1.0 (WROX) and Im trying to implement a view that performs a simple search then renders the results in a table. I would then like to be able to page thru the results.
So I have a search action from ListingsController, takes some values from FormCollection and filters the results accordingly:
//
//POST: /Listings/Search
// /Listings/Page/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Search(FormCollection collection,int? page)
{
var listings = listingRepository.GetListings();
//filter
if (collection["TypeOfHouse"] != null)
{
string[] typeList = collection["TypeOfHouse"].Split(',');
foreach (string type in typeList)
{
listings = from l in listings
where l.TypeOfHouse == type
select l;
}
}
//display the first page of results
int pageSize = 25;
var paginatedListings = new PriviledgeV1.Helpers.PaginatedList<Listing>(listings, 0, pageSize);
return View("Results", paginatedListings);
}
Initially the Results view will be rendered with the first 25 records for page 1. Then I have a Results action that handles the "pagination":
public ActionResult Results(int? page)
{
int pageSize = 25;
var listings = listingRepository.GetListings();
var paginatedListings = new PriviledgeV1.Helpers.PaginatedList<Listing>(listings, page ?? 0, pageSize);
return View(listings);
}
Trouble is because I no longer have the FormCollection, I cannot properly filter results. So if I tried to move from say page 1 to page 2 using /Listings/Results?page=2, the results action would fire and it would return ALL results instead of the filtered result set from the Search action.
I'm really confused as to what to do here, and as to why there are no blogs/tutorials explaining this, which normally flags me that I am missing something.
Thanks!
I suppose there are a few ways to try and accomplish this.
Pass the search parameters via query string instead of post. Understandably, this could be complicated and messy for advanced search parameters
Store the results of the POST to hidden elements. Have your paging control POST to the same action each time, instead of the separate Results action.
Store the query parameters in an object that is persisted in your session.
I'm sure we could get more creative from there, but that should give you a start. It seems like you only care about one field from the search form, TypeOfListing. You should be able to persist that via query string pretty easily, so that'd be method #1 above.
Update
Here's something simple I put together to maintain your search at the client. The technique involves three parts:
Maintain the form between page requests.
Manage the page state with a hidden element in the form.
Have JavaScript intercept your Paging links, update the page number hidden element, and resubmit the form.
Here's the code for all the various pieces. Note that I use jQuery, in case you prefer something else. I fudged the data source, just sub in real data. Also, I included PagedList and PaginationHelper. Substitute with your own if you wish.
\Controllers\HomeController.cs (Search is the relevant part):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication2.Controllers
{
[HandleError]
public class HomeController : Controller
{
List<String> _data;
public HomeController()
{
_data = new List<String>();
_data.Add("Merry");
_data.Add("Metal");
_data.Add("Median");
_data.Add("Medium");
_data.Add("Malfunction");
_data.Add("Mean");
_data.Add("Measure");
_data.Add("Melt");
_data.Add("Merit");
_data.Add("Metaphysical");
_data.Add("Mental");
_data.Add("Menial");
_data.Add("Mend");
_data.Add("Find");
}
public ActionResult Search()
{
Int32 pageNumber, pageSize = 5, total, first;
String typeOfListing;
PagedList<String> results;
if (Request.HttpMethod == "GET")
{
return View();
}
if (!Int32.TryParse(Request.Form["PageNumber"], out pageNumber)) pageNumber = 1;
typeOfListing = Request.Form["TypeOfListing"];
first = (pageNumber - 1) * pageSize;
total = (from s in _data
where s.Contains(typeOfListing)
select s).Count();
results = new PagedList<String>(
(from s in _data
where s.Contains(typeOfListing)
select s)
.Skip(first)
.Take(pageSize),
total, pageNumber, pageSize);
return View(results);
}
}
}
\Helpers\PaginationHelper.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using System.Web.Routing;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace MvcApplication2.Helpers
{
public static class PaginationHelper
{
public static String Pager(this HtmlHelper helper, Int32 pageSize, Int32 pageNumber, Int32 total, String actionName, RouteValueDictionary values)
{
StringBuilder output = new StringBuilder();
Int32 totalPages = (Int32)Math.Ceiling((Double)total / pageSize);
if (values == null)
values = helper.ViewContext.RouteData.Values;
if (pageNumber > 1)
output.Append(CreatePageLink(helper, values, "< Previous ", pageNumber - 1, pageSize));
for (Int32 i = 1; i <= totalPages; i++)
{
if (i == pageNumber)
output.Append(i);
else
output.AppendFormat(CreatePageLink(helper, values, i.ToString(), i, pageSize));
if (i < totalPages)
output.Append(" | ");
}
if (pageNumber < totalPages)
output.Append(CreatePageLink(helper, values, " Next >", pageNumber + 1, pageSize));
return output.ToString();
}
private static String CreatePageLink(HtmlHelper helper, RouteValueDictionary values, String text, Int32 pageNumber, Int32 pageSize)
{
RouteValueDictionary routeDictionary = new RouteValueDictionary(values);
routeDictionary.Add("page", pageNumber);
routeDictionary.Add("pageSize", pageSize);
return helper.ActionLink(text, null, routeDictionary);
}
}
}
\PagedList.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MvcApplication2
{
public class PagedList<T> : List<T>
{
public Int32 TotalCount { get; protected set; }
public Int32 PageNumber { get; protected set; }
public Int32 PageSize { get; protected set; }
public PagedList(IEnumerable<T> items, Int32 total, Int32 pageNumber, Int32 pageSize)
: base(items)
{
TotalCount = total;
PageNumber = pageNumber;
PageSize = pageSize;
}
}
}
\Views\Home\Search.aspx:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<PagedList<String>>" %>
<%# Import Namespace="MvcApplication2" %>
<%# Import Namespace="MvcApplication2.Helpers" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Search
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script type="text/javascript">
$(function() {
var results = $("#searchResults");
if (results && results.children().length > 2) {
$("#searchForm").hide();
$("#searchResults .pager>a").click(submitForm);
}
});
function submitForm() {
var m = this.href.match(/page=(\d+)/i);
if (m) {
$("#PageNumber").attr("value", m[1]);
$("#searchForm").submit();
}
return false;
}
</script>
<form id="searchForm" action="<%= Url.Action("Search") %>" method="post">
<input type="hidden" value="1" id="PageNumber" name="PageNumber" />
<fieldset>
<legend>Search</legend>
<label for="TypeOfListing">Type of Listing</label>
<%= Html.TextBox("TypeOfListing", Request.Form["TypeOfListing"]) %>
<input type="submit" id="btnSubmit" name="btnSubmit" value="Search" />
</fieldset>
</form>
<% if (Model != null)
{
%>
<div id="searchResults">
<div class="results-count">Displaying <%=this.Model.Count %> of <%=this.Model.TotalCount %> results. <%=Html.ActionLink("Start a new search", "Search") %>.</div>
<%
foreach (String result in Model)
{
%>
<div class="result"><%=result %></div>
<% }
%>
<div class="pager"><%= Html.Pager(Model.PageSize, Model.PageNumber, Model.TotalCount, null, null) %></div>
</div>
<%
}
%>
</asp:Content>
Basically change your form to do an HTTP GET as suggested by others, then download PagedList with Nuget, and use Model Binding in your action method to the submit button and page number coming from the PagedList's HTML helper function (and set the route values using a RouteValuesDictionary). This way everything gets persisted in the querystring, which is what you want with a search page anyway most likely. MVC will do the coersion to and from your ViewModel class. I prefer to use a ViewModel class instead of a bunch of arguments because I think it is cleaner that way, but just my $.02.
I have an example that I blogged about here.
Also, I went ahead and posted the code on a different thread
Related
I know how to hook up ajax paging to a grid or a webgrid in asp.net mvc. But how can I accomplish ajax paging, using custom paging for large data sets for another format outside of a table grid.
Is that even possible using an mvc helper or mvc.pagedlist?
I used to be a webforms guys and it was so easy to hook up a listview where you could use divs to create whatever layout you want for individual items, you could then hook up a datapage and wrap it all in an update panel.
Basically I want a list of items that I can page through via ajax but with having large data sets I can just pull down all the items and page via jquery, I need to do custom paging on the server side and only return the items for a specific page.
By reusing a partial view and some ajax, this is very easily done in MVC.
Add this model as a property to your page's ViewModel to handle the pagination:
namespace Models.ViewModels
{
[Serializable()]
public class PagingInfoViewModel
{
public int TotalItems { get; set; }
public int ResultsPerPage { get; set; }
public int CurrentPage { get; set; }
public int TotalPages {
get { return Convert.ToInt32(Math.Ceiling(Convert.ToDecimal(this.TotalItems) / this.ResultsPerPage)); }
}
public string LinkTextShowMore { get; set; }
public string LinkTextShowingAll { get; set; }
/// <summary>
/// Paging url used by the jQuery Ajax function
/// </summary>
public string UrlGetMore { get; set; }
public PagingInfoViewModel(string linkTextShowMore, string linkTextShowingAll, int resultsPerPage)
{
this.LinkTextShowMore = linkTextShowMore;
this.LinkTextShowingAll = linkTextShowingAll;
this.ResultsPerPage = resultsPerPage;
}
}
}
Add the following code to your partial view to handle the pagination:
//Start Pagination
//determine the value for the X for "Showing X of Y"
{
int currentTotal = 0;
if ((Model.PagingInfo.CurrentPage * Model.PagingInfo.ResultsPerPage) < Model.PagingInfo.TotalItems) {
//the current max item we are displaying is less than the total number of policies
//display the current max item index\
currentTotal = Model.PagingInfo.CurrentPage * Model.PagingInfo.ResultsPerPage;
} else {
//the current is greater than the total number of policies
//display the total number of policies
currentTotal = Model.PagingInfo.TotalItems;
}
if (Model.PagingInfo.TotalPages == 0 || Model.PagingInfo.CurrentPage == Model.PagingInfo.TotalPages)
{
#<li>
<h3>#Model.PagingInfo.LinkTextShowingAll</h3>
<p><strong>Showing #currentTotal Of #Model.PagingInfo.TotalItems</strong></p>
</li>
} else {
#<li id="GetMore">
<a href="#" id="lnkGetMore">
<h3>#Model.PagingInfo.LinkTextShowMore</h3>
<p><strong>Showing #(currentTotal) Of #Model.PagingInfo.TotalItems</strong></p>
</a>
</li>
#<script type="text/javascript" lang="javascript">
$('#lnkGetMore').click(function () {
$.ajax({
url: "#Model.PagingInfo.UrlGetMore",
success: function (data) {
$('#ProducerList li:last').remove();
$('#ProducerList').append(data);
$('#ProducerList').listview('refresh');
}
});
return false;
});
</script>
}
}
Now, the javascript at the end is specifically for a UI that uses ul's and li's, but can easily be customized for your needs.
The UrlGetMore property is set on the back end when the model is passed to the view. I am sure there is a more elegant way of doing this. Here is the code I used:
//build paging url used by the jQuery Ajax function
view.PagingInfo.UrlGetMore == Url.RouteUrl("RouteItemList", new { page = view.PagingInfo.CurrentPage + 1 })
And finally, here is the action that handles both the initial View and the subsequent Partial View (ajax call)
public ActionResult List(UserModel user, ViewModel view, int page = 1)
{
IQueryable<model> models = this.RetrieveModels(user, view);
if ((models != null) && models.Count > 0) {
view.PagingInfo.CurrentPage = page;
view.PagingInfo.ResultsPerPage = user.Preferences.ResultsPerPage;
view.PagingInfo.TotalItems = models.Count;
view.items = models.Skip((page - 1) * user.Preferences.ResultsPerPage).Take(user.Preferences.ResultsPerPage).ToList();
//build paging url used by the jQuery Ajax function
view.PagingInfo.UrlGetMore = Url.RouteUrl("RouteList", new { page = view.PagingInfo.CurrentPage + 1 });
}
if (page == 1) {
return View(view);
} else {
return PartialView("ListPartial", view);
}
}
HTH.
You could create simple HtmlHelper simillar to this:
public static class HtmlPaginHelper
{
public static MvcHtmlString PagerNoLastPage(this AjaxHelper ajaxHelper,
int page,
int pageSize,
bool isLastPage,
Func<int, string> pageUrl,
Func<int, AjaxOptions> pageAjaxOptions)
{
var result = new StringBuilder();
var firstPageAnchor = new TagBuilder("a");
firstPageAnchor.SetInnerText("<<");
var prevPageAnchor = new TagBuilder("a");
prevPageAnchor.SetInnerText("<");
var nextPageAnchor = new TagBuilder("a");
nextPageAnchor.SetInnerText(">");
var currentPageText = new TagBuilder("span");
currentPageText.SetInnerText(string.Format("Page: {0}", page));
if (page > 1)
{
firstPageAnchor.MergeAttribute("href", pageUrl(1));
firstPageAnchor.MergeAttributes(pageAjaxOptions(1).ToUnobtrusiveHtmlAttributes());
prevPageAnchor.MergeAttribute("href", pageUrl(page - 1));
prevPageAnchor.MergeAttributes(pageAjaxOptions(page - 1).ToUnobtrusiveHtmlAttributes());
}
if (!isLastPage)
{
nextPageAnchor.MergeAttribute("href", pageUrl(page + 1));
nextPageAnchor.MergeAttributes(pageAjaxOptions(page + 1).ToUnobtrusiveHtmlAttributes());
}
result.Append(firstPageAnchor);
result.Append(prevPageAnchor);
result.Append(currentPageText);
result.Append(nextPageAnchor);
return MvcHtmlString.Create(result.ToString());
}
}
... and then use it in your Razor view:
grid results go here...
#Ajax.PagerNoLastPage(Model.Query.Page,
Model.Query.PageSize,
Model.Data.IsLastPage,
i => Url.Action("Index", RouteValues(i)),
i => new AjaxOptions
{
UpdateTargetId = "content",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
Url = Url.Action("Grid", RouteValues(i))
})
where RouteValues(i) is defined for example like this:
#functions {
private object PageRouteValues(int i)
{
return new
{
payId = Model.Query.PayId,
clientCode = Model.Query.ClientCode,
fromDate = Model.Query.FromDate,
tillDate = Model.Query.TillDate,
payNum = Model.Query.PayId,
checkNum = Model.Query.CheckNum,
payType = Model.Query.PayType,
payStatus = Model.Query.PayStatus,
page = i,
pageSize = Model.Query.PageSize
};
}
}
Is that even possible using an mvc helper or mvc.pagedlist?
Yes, but of course you have to coordinate the client-side requests with server-side actions to handle the actual data paging. In that sense, it's not as simple as as WebForms, but it's still possible.
Here's an example of using PagedList to render each returned item in its own table, separated by horizontal rules. You should easily be able to modify the HTML in the example to produce any rendering you want.
I'm experiencing current error in my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ProjectenII.Models.Domain.StudentModel>"%>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
IndexStudents
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>IndexStudents</h2>
<%using (Html.BeginForm()) { %>
<%=Html.ListBoxFor(model => model.NormalSelected, new MultiSelectList(Model.NormalStudentsList, "StudentNummer", "Naam", Model.NormalSelected), new { size = "6" }); %>
<input type="submit" name="add"
id="add" value=">>" /><br />
<input type="submit" name="remove"
id="remove" value="<<" />
<%=Html.ListBoxFor(model => model.NoClassSelected, new MultiSelectList(Model.StudentsNoClassList, "StudentNummer", "Naam", Model.NoClassSelected)); %>
<% } %>
<%=Html.HiddenFor(model => model.Save) %>
<input type="submit" name="apply" id="apply" value="Save!" />
</asp:Content>
It gives me an error at the listboxfor() method... saying ") expected".
But I close all the opening tags... very strange though!
What I want to use it for: I want to move items from one listbox to the other and then update the database. So I'd like to do it using formCollection, unless there is another way?
Students have a field named "classID", when I update the database, that value needs to change from the current value to "0". I think the best way is using formCollections? Isn't it?
This is my StudentModel
public class StudentModel
{
public IEnumerable<Student> NormalStudentsList { get; set; }
public IEnumerable<Student> StudentsNoClassList { get; set; }
public string[] NormalSelected { get; set; }
public string[] NoClassSelected { get; set; }
public string Save { get; set; }
}
Controller:
public ActionResult IndexStudents(Docent docent, int id, int klasgroepid)
{
var studentModel = new StudentModel
{
NormalStudentsList = docent.GeefStudenten(id, klasgroepid),
StudentsNoClassList = docent.GeefStudenten(id, klasgroepid)
};
return View(studentModel);
}
I have two questions: how can I fix the error? AND how can I update the database?
I suggest using "UpdateModel()" ... ?
Thanks in advance!!
Not sure what your second question is because you didn't include the code you're using to persist your model to the database.
The ")" expected error is because you have a semicolon at the end of your ListBoxFor method call.
It should look like this:
<%=Html.ListBoxFor(model => model.NormalSelected, new MultiSelectList(Model.NormalStudentsList, "StudentNummer", "Naam", Model.NormalSelected), new { size = "6" }) %>
When you use <%= you don't need the semicolon.
I am having some trouble getting CascadingDropDown lists to work, its displaying the Method 500 error inside the list. I have tried all the common solutions to this and still no luck.
I was originally getting the following error:
System.ArgumentException: Invalid
method name 'getcategories',
method names are case sensitive. The
method name 'GetCategories'
with the same name but different
casing was found. Parameter name:
methodName
Which is odd because I am definately setting the method name in the correct case but it was sending it in lowercase (even though chrome showed the page as sending it in the correct case). Anyhow I worked around this by changing the method name itself to lowercase. This now brings up a new error:
System.InvalidOperationException:
Missing parameter:
knownCategoryValues.
If anyone could shed any light on either of these problems that would be a great help, I've spent way too long on this problem.
Thanks.
UPDATED Code:
<ajaxToolkit:ToolkitScriptManager EnablePageMethods="true" ID="ToolkitScriptManager1" runat="server">
</ajaxToolkit:ToolkitScriptManager>
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown1"
runat="server"
TargetControlID="mmCategory"
Category="Category"
PromptText="Select a category"
ServicePath="~/DropDownLists.asmx"
ServiceMethod="GetCategories" />
<ajaxToolkit:CascadingDropDown
ID="CascadingDropDown2"
runat="server"
TargetControlID="mmTemplate"
ParentControlID="mmCategory"
PromptText="Select a template"
ServiceMethod="GetTemplates"
ServicePath="~/DropDownLists.asmx"
Category="Template" />
Category: <asp:DropDownList ID="mmCategory" runat="server"/><br/>
Template: <asp:DropDownList ID="mmTemplate" runat="server"/><br/>
Web Service File:
using System;
using System.Web;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Web.Services;
using System.Web.Services.Protocols;
using AjaxControlToolkit;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Linq;
/// <summary>
/// Summary description for TemplateData
/// </summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService()]
public class DropDownLists : System.Web.Services.WebService
{
public DropDownLists()
{
//Uncomment the following line if using designed components
//InitializeComponent();
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] getcategories(string knownCategoryValues, string category)
{
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
values.Add(new CascadingDropDownNameValue("test 1", "1"));
values.Add(new CascadingDropDownNameValue("test 2", "2"));
return values.ToArray();
/*using (MiscDataContext dc = new MiscDataContext())
{
var CatQuery = from D in dc.Templates
select new { D.Category }
into CatQueryResults
select CatQueryResults;
foreach (var CatResult in CatQuery)
{
string CatReturn = CatResult.Category;
values.Add(new CascadingDropDownNameValue(CatReturn, CatReturn));
}
return values.ToArray();
}*/
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] GetTemplates(string knownCategoryValues, string category)
{
StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);
string varCat;
varCat = kv["Category"];
List<CascadingDropDownNameValue> values = new List<CascadingDropDownNameValue>();
values.Add(new CascadingDropDownNameValue("test 3", "3"));
values.Add(new CascadingDropDownNameValue("test 4", "4"));
return values.ToArray();
/*using (MiscDataContext dc = new MiscDataContext())
{
var CatQuery = from D in dc.Templates
where(D.Category == varCat)
select new { D.ID, D.Name }
into CatResult
select CatResult;
foreach (var CatResult in CatQuery)
{
int ID = (int)CatResult.ID;
string Name = CatResult.Name;
values.Add(new CascadingDropDownNameValue(Name, Convert.ToString(ID)));
}
return values.ToArray();
}*/
}
}
In order to be usable with a CascadingDropDown, your web method's prototype must be:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] GetCategories(
string knownCategoryValues, string category)
{
}
If you assign something to the ContextKey property of the extender, that prototype becomes:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public CascadingDropDownNameValue[] GetCategories(
string knownCategoryValues, string category, string contextKey)
{
}
The knownCategoryValues parameter represents the current selection in the master dropdown. You should pass it to CascadingDropDown.ParseKnownCategoryValuesString() to obtain its actual value.
I have a timesheet application that has a View where the user can select customers and tasks and add them to a dynamic table. This table is filled with the tasks and input fields for filling in hours worked.
For adding the new tasks in the dynamic table I use jQuery, so the savenewtask button is not a submit button. Instead I have a proper submit button for saving the hours when filled in.
The View is strongly typed to a model called TimesheetViewModel (see below). The controller passes the model to the View, and then the input fields are bound to properties in the model.
However, when I submit with the submit button and try to update the model in the Controller it doesn't update. It seemed from the Nerddinner tutorial (which I am using to learn MVC) that the model should automatically be updated using the values from the forms fields it had been bound to when you use UpdateModel(). But it doesn't. What am I doing wrong?
Here is all the relevant code:
View:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Hook onto the MakeID list's onchange event
$("#CustomerId").change(function () {
//build the request url
var url = "Timesheet/CustomerTasks";
//fire off the request, passing it the id which is the MakeID's selected item value
$.getJSON(url, { id: $("#CustomerId").val() }, function (data) {
//Clear the Model list
$("#TaskId").empty();
//Foreach Model in the list, add a model option from the data returned
$.each(data, function (index, optionData) {
$("#TaskId").append("<option value='" + optionData.Id + "'>" + optionData.Name + "</option>");
});
});
}).change();
});
</script>
<h2>Index</h2>
<% using (Html.BeginForm())
{%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div>
<label for="Customers">
Kund:</label>
<%:Html.DropDownListFor(m => m.Customers, new SelectList(Model.Customers, "Id", "Name"), "Välj kund...", new { #id = "CustomerId" })%>
<label for="Tasks">
Aktiviteter:</label>
<select id="TaskId">
</select>
</div>
<p>
<input type="button" value="Save new task" id="savenewtask" />
</p>
<table width="100%">
<%--<% foreach (var task in Model.Tasks)--%>
<% foreach (var task in Model.WeekTasks)
{ %>
<tr>
<td>
<%: task.Customer.Name %>
</td>
<td>
<%: task.Name %>
</td>
<td>
<% foreach (var ts in task.TimeSegments)
{ %>
<input class="hourInput" type="text" size="2" id="<%: ts.Task.CustomerId + '_' + ts.TaskId + '_' + ts.Date %>"
value="<%: ts.Hours %>" />
<% } %>
</td>
</tr>
<% } %>
</table>
<input type="submit" value="Save hours" id="savehours" />
</fieldset>
<% } %>
</asp:Content>
From the Controller:
private TimesheetViewModel _model;
public TimesheetController()
{
_model = new TimesheetViewModel();
}
public ActionResult Index()
{
return View(_model);
}
[HttpPost]
public ActionResult Index(FormCollection collection)
{
try
{
UpdateModel(_model);
_model.Save();
return View(_model);
//return RedirectToAction("Index");
}
catch
{
return View();
}
}
The ViewModel:
public class TimesheetViewModel
{
private TimesheetContainer _model; //TimesheeContainer is an Entity Framework model
public TimesheetViewModel()
{
_model = new TimesheetContainer();
}
public IList<Customer> Customers
{ get { return _model.Customers.ToList(); } }
public IList<Task> Tasks
{ get { return _model.Tasks.ToList(); } }
public IList<Task> WeekTasks
{
get
{
//Get the time segments for the current week
DateTime firstDayOfWeek = DateTime.Parse("2010-12-05");
DateTime lastDayOfWeek = DateTime.Parse("2010-12-13");
List<TimeSegment> timeSegments = new List<TimeSegment>();
foreach (var timeSegment in _model.TimeSegments)
{
if(timeSegment.DateTimeDate > firstDayOfWeek && timeSegment.DateTimeDate < lastDayOfWeek)
timeSegments.Add(timeSegment);
}
//Group into tasks
var tasks = from timeSegment in timeSegments
group timeSegment by timeSegment.Task
into t
select new { Task = t.Key };
return tasks.Select(t => t.Task).ToList();
}
}
public IList<TimeSegment> TimeSegments
{ get { return _model.TimeSegments.ToList(); } }
public void Save()
{
_model.SaveChanges();
}
public void AddTimeSegments(Task task)
{
_model.AddToTasks(task);
_model.SaveChanges();
}
}
Partial class to get tasks for a specific week (only dummy week at this time for testing):
public partial class TimeSegment
{
public DateTime DateTimeDate
{ get { return DateTime.Parse(Date); } }
}
Why is the model not updating, and what can I change to make it work?
Put a breakpoint on your first ActionResult Index(), is that getting called when you do the submit? you may need [HttpGet] on it, otherwise I think it gets both.
I'm working on making a modification to a site built with ASP/C#, and one of the tasks was to add in the display of 2 RSS feeds - one from the site's internal blog, and the other from the site's Twitter account. However, I seem to continually get empty feeds from both, even though I've confirmed that I'm pointing to the correct URL of the feeds. My code is shown below.
private void GetTwitterRSS()
{
IEnumerable items = Cache["TwitterFeed"] as List<SyndicationItem>;
if (items == null)
{
try
{
SyndicationFeed blogFeed = SyndicationFeed.Load(XmlReader.Create("http://twitter.com/statuses/user_timeline/84668697.rss"));
items = blogFeed.Items;
}
catch
{
items = new List<SyndicationItem>();
}
Cache.Insert("TwitterFeed", items, null, DateTime.Now.AddMinutes(5.0),TimeSpan.Zero);
twitterrssRepeater.DataSource = items;
twitterrssRepeater.DataBind();
}
}
private void GetBlogRSS()
{
IEnumerable items = Cache["BlogFeed"] as List<SyndicationItem>;
if (items == null)
{
try
{
SyndicationFeed blogFeed = SyndicationFeed.Load(XmlReader.Create("http://www.rentseeker.ca/blog/?feed=rss2"));
items = blogFeed.Items;
}
catch
{
items = new List<SyndicationItem>();
}
Cache.Insert("BlogFeed", items, null, DateTime.Now.AddHours(1.0),TimeSpan.Zero);
blogrssRepeater.DataSource = items;
blogrssRepeater.DataBind();
}
}
protected string DisplayBlogFeedItem(SyndicationItem item)
{
return string.Format(#"<p>{1}</p><p><strong>{2}</strong></p><p>{3}</p>",
FormatPublishDate(item.PublishDate.DateTime),
item.Title.Text,
item.Summary.Text);
}
protected string DisplayTwitterFeedItem(SyndicationItem item)
{
return string.Format(#"<li>{1}</li>",
item.Title.Text);
}
The code on the page is:
<ul>
<asp:ListView ID="twitterrssRepeater" runat="server">
<ItemTemplate>
<%# DisplayTwitterFeedItem((Container as ListViewDataItem).DataItem as System.ServiceModel.Syndication.SyndicationItem) %>
</ItemTemplate>
</asp:ListView>
</ul>
and
<asp:ListView ID="blogrssRepeater" runat="server">
<ItemTemplate>
<%# DisplayBlogFeedItem((Container as ListViewDataItem).DataItem as System.ServiceModel.Syndication.SyndicationItem) %>
</ItemTemplate>
</asp:ListView>
Clearly, I'm missing something. From what I've read, I understand that I'm supposed to authenticate myself in order to view a Twitter feed - I have the credentials, but am not sure how to pass them into SyndicationFeed when I load it.
Any tips, suggestions, or direction for further information is greatly appreciated.
Here's a simple example I would use to receive my Twitter feed (I'm only getting name, update title & id)
public class TwitterFeed
{
public string Name { get; set; }
public string Title { get; set; }
public string Id { get; set; }
}
Then the method to get the feed
public List<TwitterFeed> GetTwitterFeed(string name)
{
List<TwitterFeed> list = new List<TwitterFeed>();
XmlReader reader = XmlReader.Create(string.Format("http://search.twitter.com/search.atom?q=to:{0}", name));
SyndicationFeed feed = SyndicationFeed.Load(reader);
var tweetItems = from item in feed.Items
select new TwitterFeed()
{
Name = item.Authors.First().Name,
Title = item.Title.Text,
Id = item.Id
};
return tweetItems.ToList();
}
Hope that helps
SyndicationFeed.Items is not a List, but implements IEnumerable interface, so instead of
IEnumerable items = Cache["BlogFeed"] as List<SyndicationItem>;
use the following line:
IEnumerable items = Cache["BlogFeed"] as IEnumerable<SyndicationItem>;