ASP.NET MVC 3 WebGrid, preserve the paging and define the route - asp.net-mvc-3

I am start to learn the MVC3, But i have some problem on WebGrid,
The Controller name is TestController, so i navigate to :
http://localhost:53503/Test/
And the Webgrid shown as below:
two column : ID and Name.
the 3rd & 4th column : Actionlink and item.GetSelectLink.
The first problem is :
Assume i viewing page 3 of webgrid, and press the ActionLink,after go-thought the TestContoller's Action, the webgrid will return to page 1 after PostBack.
However,if i press the [View(GetSelectLink)] on right end:
(e.g. http://localhost:53503/Test/?gridItems_page=3&gridItems_selectedRow=3
It works.
So, i should use ActionLink or GetSelectLink for general Add/Update/Delete operation?
MVC3 hasn't viewstate / control state, so how can i preserve the current page selection after PostBack?
The second problem is :
The (href) of ActionLink is :
http://localhost:53503/Test/GetSelection/7?Name=PSP
I would make it to
http://localhost:53503/Test/GetSelection/7/PSP
I add a new route to global.asax, but it is no luck.
Thanks you very much for help.
It is my code:
Views/Test/Index.cshtml
#model List<MvcContract.Controllers.Products>
#{
System.Web.Helpers.WebGrid grid = new System.Web.Helpers.WebGrid(
source: Model,
canPage: true,
rowsPerPage: 3,
fieldNamePrefix: "gridItems_",
pageFieldName: "page",
selectionFieldName: "selectedRow"
);
}
#{
if (Model != null)
{
#grid.GetHtml(
columns: grid.Columns(
grid.Column("ID"),
grid.Column("Name"),
grid.Column(format: (item) => Html.ActionLink("View(ActionLink)", "GetSelection", new { ID = item.ID, Name = item.Name })),
grid.Column(format: (item) => item.GetSelectLink("View(GetSelectLink)"))
)
);
}
}
Controllers/TestController.cs
namespace MvcContract.Controllers
{
public class Products
{
public string ID { get; set; }
public string Name { get; set; }
public List<Products> GetItems()
{
List<Products> items = new List<Products>();
items.Add(new Products() { ID = "1", Name = "PS3" });
items.Add(new Products() { ID = "2", Name = "XBox360" });
items.Add(new Products() { ID = "3", Name = "Wii" });
items.Add(new Products() { ID = "4", Name = "Saturn" });
items.Add(new Products() { ID = "5", Name = "Dreamcast" });
items.Add(new Products() { ID = "6", Name = "NDS" });
items.Add(new Products() { ID = "7", Name = "PSP" });
items.Add(new Products() { ID = "8", Name = "NeoGeo" });
items.Add(new Products() { ID = "9", Name = "3DO" });
items.Add(new Products() { ID = "10", Name = "Playdia" });
return items;
}
}
public class TestController : Controller
{
//Bind data to WebGrid
public ActionResult Index()
{
Products products = new Products();
return View(products.GetItems());
}
//Some Logic
public ActionResult GetSelection(string ID, string Name)
{
string SelectedID = ID;
return RedirectToAction("Index");
}
}
}
RegisterRoutes() in Global.asax.cs
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Test_GetSelection", // Route name
"{controller}/{action}/{id}/{name}", // URL with parameters
new { controller = "Test", action = "GetSelection", id = UrlParameter.Optional, name = UrlParameter.Optional } // Parameter defaults
);
}

Check out this discussion. I think one of several solutions there may solve your issue.
Since its been a whole year since you posted this, please post an answer if you figured it out another way.

Related

Recommended way to implement array of Ajax cascading dropdown lists in MVC4/Razor?

I am writing an ASP.NET MVC4 / Razor / C# based application that needs to render a grid of records. Each row has several columns, and there may be 100 or so rows.
Each row has a checkbox field, text field and then three cascading dropdown lists. The first dropdown is prepopulated on page load. The second needs to be populated using Ajax on change of the first dropdown list. The third from a change on the second. Each row is separate and does not influence each other.
What is the recommended way to implement something like this? The various solutions for cascading dropdown lists are only for single cascading lists - they do not work (for me) when placed inside a foreach loop.
The skeleton of what I have is shown below:
#model IList<MyModel>
#using (Html.BeginForm("MyAction", "Home")) {
<table><tr><th>Use?</th><th>Name</th><th>List A</th><th>List B</th><th>List C</th></tr>
#Html.EditorForModel()
</table>
}
The model looks something like this:
public class MyModel
{
public bool Use { get; set; }
public string Name { get; set; }
public int? ListAId { get; set; }
public int? ListBId { get; set; }
public int? ListCId { get; set; }
public IList<ListAList> ListA { get; set; }
}
The shared EditorTemplates file MyModel.cshtml follows this structure:
#model MyNamespace.MyModel
<tr>
<td>#Html.CheckBoxFor(model => model.Use)</td>
<td>#Html.DisplayFor(model => model.Name)</td>
<td>#Html.DropDownListFor(model => model.ListAId, new SelectList(Model.ListA, "Id", "Name", Model.ListAId), "")</td>
<td>??</td>
<td>??</td>
</tr>
The ?? indicates I am unsure what to put in here.
How do I proceed to render the ListB select box using Ajax on change of the ListA select box, then on change of ListB render the ListC select box?
check this:
on select change event - Html.DropDownListFor
http://addinit.com/node/19
or
http://www.codeproject.com/Articles/258172/Simple-Implementation-of-MVC-Cascading-Ajax-Drop-D
Update1: Suppose that there is Name ROWID, (and list all the same data source).
Update2: the example available on github
Based on these responses:
How to populate a #html.dropdownlist mvc helper using JSon
Populate a <select></select> box with another
Model:
using System.Collections.Generic;
namespace MyNamespace
{
public class MyModel
{
public MyModel() { ListA = new List<ListAList>(); }
public bool Use { get; set; }
public string Name { get; set; }
public int? ListAId { get; set; }
public int? ListBId { get; set; }
public int? ListCId { get; set; }
public IList<ListAList> ListA { get; set; }
}
public class ListAList
{
public int Id { get; set; }
public string Name { get; set; }
}
}
Main action in the Home Controller:
public ViewResult MyAction()
{
var model = new List<MyModel>();
for (int i = 0; i < 10; i++)
{
var item = new MyModel()
{
Name = string.Format("Name{0}", i),
Use = (i % 2 == 0),
ListAId = null,
ListBId = null,
ListCId = null
};
for (int j = 0; j < 10; j++)
{
item.ListA.Add( new ListAList()
{
Id=j,
Name = string.Format("Name {0}-{1}",i,j)
});
}
model.Add(item);
}
return View(model);
}
Data source provider in the Home controller:
public JsonResult PopulateOption(int? listid, string name)
{
//todo: preparing the data source filter
var sites = new[]
{
new { id = "1", name = "Name 1" },
new { id = "2", name = "Name 2" },
new { id = "3", name = "Name 3" },
};
return Json(sites, JsonRequestBehavior.AllowGet);
}
EditorTemplate:
#model MyNamespace.MyModel
<tr>
<td>#Html.CheckBoxFor(model => model.Use)</td>
<td>#Html.DisplayFor(model => model.Name)</td>
<td>#Html.DropDownListFor(model => model.ListAId, new SelectList(Model.ListA, "Id", "Name", Model.ListAId), "", new { #id = string.Format("ListA{0}", Model.Name), #class="ajaxlistA" })</td>
<td><select class="ajaxlistB" id="ListB#(Model.Name)"></select></td>
<td><select class="ajaxlistC" id="ListC#(Model.Name)"></select></td>
</tr>
And the main view with Ajax functions:
#using MyNamespace
#model IList<MyModel>
#using (Html.BeginForm("MyAction", "Home")) {
<table><tr><th>Use?</th><th>Name</th><th>List A</th><th>List B</th><th>List C</th></tr>
#Html.EditorForModel()
</table>
}
<script src="#Url.Content("~/Scripts/jquery-1.7.1.js")" type="text/javascript"></script>
<script>
$(document).ready( $(function () {
$('.ajaxlistA').change(function () {
// when the value of the first select changes trigger an ajax request
list = $(this);
var listvalue = list.val();
var listname = list.attr('id');
$.getJSON('#Url.Action("PopulateOption", "Home")', { listid: listvalue, name: listname }, function (result) {
// assuming the server returned json update the contents of the
// second selectbox
var listB = $('#' + listname).parent().parent().find('.ajaxlistB');
listB.empty();
$.each(result, function (index, item) {
listB.append(
$('<option/>', {
value: item.id,
text: item.name
})
);
});
});
});
$('.ajaxlistB').change(function () {
// when the value of the first select changes trigger an ajax request
list = $(this);
var listvalue = list.val();
var listname = list.attr('id');
$.getJSON('#Url.Action("PopulateOption", "Home")', { listid: listvalue, name: listname }, function (result) {
// assuming the server returned json update the contents of the
// second selectbox
var listB = $('#' + listname).parent().parent().find('.ajaxlistC');
listB.empty();
$.each(result, function (index, item) {
listB.append(
$('<option/>', {
value: item.id,
text: item.name
})
);
});
});
});
}));
</script>
And the result:

MVC3 Areas routing conflict

Question: i want my route to be like that
/admin/main/category/1 -> 1 == ?page=1
i don't want page=1 to be seen
My Controller
public class MainController : BaseController
{
private const int PageSize = 5; //pager view size
[Inject]
public ICategoryRepository CategoryRepository { get; set; }
public ActionResult Index()
{
return View();
}
public ActionResult Category(int page)
{
//int pageIndex = page.HasValue ? page.Value : 1;
int pageIndex = page != 0 ? page : 1;
return View("Category", CategoryViewModelFactory(pageIndex));
}
/*
*Helper: private instance/static methods
======================================================================*/
private CategoryViewModel CategoryViewModelFactory(int pageIndex) //generate viewmodel category result on pager request
{
return new CategoryViewModel
{
Categories = CategoryRepository.GetActiveCategoriesListDescending().ToPagedList(pageIndex, PageSize)
};
}
}
public class AdminAreaRegistration : AreaRegistration
{
public override string AreaName
{
get
{
return "admin";
}
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRouteLowercase(
"AdminCategoryListView",
"admin/{controller}/{action}/{page}",
new { controller = "Category", action = "Category", page = "1" },
new { id = #"\d+" },
new[] { "WebUI.Areas.Admin.Controllers" }
);
}
}
My Exception:
The parameters dictionary contains a null entry for parameter 'page'
of non-nullable type 'System.Int32' for method
'System.Web.Mvc.ActionResult Category(Int32)' in
'WebUI.Areas.Admin.Controllers.MainController'. An optional parameter
must be a reference type, a nullable type, or be declared as an
optional parameter. Parameter name: parameters
Thank you all in advance.
Make sure that in your Admin area route registration you have defined the {page} route token instead of {id} which is generated by default:
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Admin_default",
"Admin/{controller}/{action}/{page}",
new { action = "Index", page = UrlParameter.Optional }
);
}
Now when you are generating links make sure you specify this parameter:
#Html.ActionLink(
"go to page 5", // linkText
"category", // actionName
"main", // controllerName
new { area = "admin", page = "5" }, // routeValues
null // htmlAttributes
)
will emit:
go to page 5
and when this url is requested the Category action will be invoked and passed page=5 parameter.

hypen in MVC 3 routes

Here is my desired url format: /product-24-hid-35wh4-cx-dsgtx
How can I map this URL to my action method:
public ActionResult Product(int id)
Here is my routing code:
routes.MapRoute(
"ProductDetail",
"product-{id}-{name}",
new { controller = "product", action = "detail", name = UrlParameter.Optional },
new string[] { "SphereLight.Controllers" }
);
However, it does not work; I used phil haack's routedebugger to test this route, and below is the result:
Key Value
name dsgtx
id 24-hid-35wh4-cx
controller product
action detail
Only id = 24 is correct.
In one word, I need a route to match:
/product-24
/product-24-
/product-24-hid-35wh4-cx-dsgtx
Try to add constraints in your MapRoute:
routes.MapRoute(
"ProductDetail",
"product-{id}-{name}",
new { controller = "product", action = "detail", name = UrlParameter.Optional },
new { id = #"\d+" }, // <-- change it for #"[^-]+", if it can be non-digit
new string[] { "SphereLight.Controllers" }
);
UPDATE:
Finally got it.
The main problem is that you can't use parameters which contains the same separator.
For example, the example above will work with /product-24-nm, but not with product-24-nm-smth.
So, let's try this solution:
I've made it on the default routing, you can make it your way
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new MyRouteHandler()
).RouteHandler = new MyRouteHandler();
Implementation of MyRouteHandler:
public class MyRouteHandler : MvcRouteHandler
{
private static readonly Regex ProductPattern = new Regex(#"product\-(\d+)\-?(.*)");
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var match = ProductPattern.Match(requestContext.RouteData.Values["controller"].ToString());
if (match.Length > 0)
{
requestContext.RouteData.Values["controller"] = "Home";
requestContext.RouteData.Values["action"] = "Detail";
requestContext.RouteData.Values["id"] = match.Groups[1].Value;
requestContext.RouteData.Values["name"] = match.Groups[2].Value;
}
return base.GetHttpHandler(requestContext);
}
}
So, the main idea is to check if the values matches our pattern product-id-name in the handler, and not trying to make it in MapRoute.
Hope this helps.

Problems with MVC 3 DropDownList() in WebGrid()

I'm trying to place a DropDownList inside a WebGrid but I can't figure out how to do it :(
Things I've tried:
grid.Column("Id", "Value", format: ((item) =>
Html.DropDownListFor(item.SelectedValue, item.Colors)))
and
grid.Column(header: "", format: (item => Html.DropDownList("Colors", item.Colors)))
and
grid.Column(header: "", format: Html.DropDownList("Colors"))
and various others but I couldn't get it to work.
Any help is much appreciated.
Model
public class PersonViewModel
{
public string Name { get; set; }
public int Age { get; set; }
public SelectList Colors { get; set; }
public int SelectedValue { get; set; }
}
public class ColorViewModel
{
public int ColorID { get; set; }
public string ColorName { get; set; }
}
Controller
public ActionResult Index()
{
var colorList = new List<ColorViewModel>() {
new ColorViewModel() { ColorID = 1, ColorName = "Green" },
new ColorViewModel() { ColorID = 2, ColorName = "Red" },
new ColorViewModel() { ColorID = 3, ColorName = "Yellow" }
};
var people = new List<PersonViewModel>()
{
new PersonViewModel() {
Name = "Foo",
Age = 42,
Colors = new SelectList(colorList)
},
new PersonViewModel() {
Name = "Bar",
Age = 1337,
Colors = new SelectList(colorList)
}
};
return View(people);
}
View
#model IEnumerable<PersonViewModel>
#{
var grid = new WebGrid(Model);
}
<h2>DropDownList in WebGrid</h2>
#using (Html.BeginForm())
{
#grid.GetHtml(
columns: grid.Columns(
grid.Column("Name"),
grid.Column("Age"),
grid.Column() // HELP - INSERT DROPDOWNLIST
)
)
<p>
<button>Submit</button>
</p>
}
grid.Column(
"dropdown",
format: #<span>#Html.DropDownList("Color", (SelectList)item.Colors)</span>
)
Also in your controller make sure you set the value and text properties of your SelectList and instead of:
Colors = new SelectList(colorList)
you should use:
Colors = new SelectList(colorList, "ColorID", "ColorName")
Also it seems a bit wasteful to me to define the same SelectList for all row items especially if they contain the same values. I would refactor your view models a bit:
public class MyViewModel
{
public IEnumerable<SelectListItem> Colors { get; set; }
public IEnumerable<PersonViewModel> People { get; set; }
}
public class PersonViewModel
{
public string Name { get; set; }
public int Age { get; set; }
public int SelectedValue { get; set; }
}
and then:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// define the values used to render the dropdown lists
// for each row
Colors = new[]
{
new SelectListItem { Value = "1", Text = "Green" },
new SelectListItem { Value = "2", Text = "Red" },
new SelectListItem { Value = "3", Text = "Yellow" },
},
// this is the collection we will be binding the WebGrid to
People = new[]
{
new PersonViewModel { Name = "Foo", Age = 42 },
new PersonViewModel { Name = "Bar", Age = 1337 },
}
};
return View(model);
}
}
and in the view:
#model MyViewModel
#{
var grid = new WebGrid(Model.People);
}
<h2>DropDownList in WebGrid</h2>
#using (Html.BeginForm())
{
#grid.GetHtml(
columns: grid.Columns(
grid.Column("Name"),
grid.Column("Age"),
grid.Column(
"dropdown",
format: #<span>#Html.DropDownList("Color", Model.Colors)</span>
)
)
)
<p>
<button>Submit</button>
</p>
}
UPDATE:
And since I suspect that you are not putting those dropdownlists for painting and fun in your views but you expect the user to select values inside them and when he submits the form you might wish to fetch the selected values, you will need to generate proper names of those dropdown lists so that the default model binder can automatically retrieve the selected values in your POST action. Unfortunately since the WebGrid helper kinda sucks and doesn't allow you to retrieve the current row index, you could use a hack as the Haacked showed in his blog post:
grid.Column(
"dropdown",
format:
#<span>
#{ var index = Guid.NewGuid().ToString(); }
#Html.Hidden("People.Index", index)
#Html.DropDownList("People[" + index + "].SelectedValue", Model.Colors)
</span>
)
Now you could have a POST controller action in which you will fetch the selected value for each row when the form is submitted:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
// the model variable will contain the People collection
// automatically bound for each row containing the selected value
...
}
..make my dropdown list to select a value from the list when it loads.The following works for sure. I have tried it out myself. Any questions keep me posted.
#Html.DropDownList("RejectReason", Model.SalesReasonList.Select(u => new SelectListItem
{
Text = u.Text,
Value = u.Value,
Selected = u.Value ==#item.RejectReason
}))

Orchard CMS rendering parts outside of admin section

I am working on a module by following the instructions here http://orchardproject.net/docs/Creating-a-module-with-a-simple-text-editor.ashx
The one change I want to do is, rendering the product creation outside of admin module. So I created homecontroller like this
public class HomeController : Controller
{
public HomeController(IContentManager cm) {
ContentManager = cm;
}
private IContentManager ContentManager { get; set; }
public ActionResult Index() {
return Content("This is index");
} [Themed]
public ActionResult Create()
{
var product = ContentManager.New("Product");
var model = ContentManager.BuildEditor(product);
return View((object) model);
}
and a file routes.cs in the root folder
public class Routes : IRouteProvider
{
public void GetRoutes(ICollection<RouteDescriptor> routes)
{
foreach (var routeDescriptor in GetRoutes())
routes.Add(routeDescriptor);
}
public IEnumerable<RouteDescriptor> GetRoutes()
{
return new[] {
new RouteDescriptor {
Priority = 5,
Route = new Route(
"commerce",
new RouteValueDictionary {
{"area", "SimpleCommerce"},
{"controller", "Home"},
{"action", "Index"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "SimpleCommerce"}
},
new MvcRouteHandler())
},
new RouteDescriptor {
Priority = 6,
Route = new Route(
"commerce/Create",
new RouteValueDictionary {
{"area", "SimpleCommerce"},
{"controller", "Home"},
{"action", "Create"}
},
new RouteValueDictionary(),
new RouteValueDictionary {
{"area", "SimpleCommerce"}
},
new MvcRouteHandler())
}
};
}
}
So how should I move from here onwards to render this whole thing together when I navigate to url http://localhost:35713/commerce/create
But it throws an error saying create view didnt find. Then I created a view (create.cshtml) in Views/Home folder
#model SimpleCommerce.Models.ProductPart
<fieldset>
<label class="sub" for="Sku">#T("Sku")</label><br />
#Html.TextBoxFor(m => m.Sku, new { #class = "text" })<br />
<label class="sub" for="Price">#T("Price")</label><br />
#Html.TextBoxFor(m => m.Price, new { #class = "text" })
</fieldset>
Now it throws an error saying
The model item passed into the dictionary is of type 'IShapeProxyedcfa08c61cf49898dc2e77bde025039', but this dictionary requires a model item of type 'SimpleCommerce.Models.ProductPart'.
Oh, that's cross-posted. BuildEditor is creating a shape, and your template is expecting a strongly-typed model. Dropping the #Model directive should substitute that problem with another (which is that you won't be able to use the Lambda-based helpers with shapes.

Resources