I have ASP.NET MVC Project and I have some module. Some modules have pagination. For test and understand MvcSiteMapProvider I working with one module Forum and created ForumDynamicNodeProvider class
public class ForumDynamicNodeProvider : DynamicNodeProviderBase
{
private readonly IForumsService _forumsService;
public ForumDynamicNodeProvider(IForumsService forumsService)
{
this._forumsService = forumsService;
}
public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node)
{
string rootTitle = ManagerLocalization.Get("Forums", "FORUMS");
var nodes = new List<DynamicNode>
{
new DynamicNode
{
Title = rootTitle,
Controller = "Forums",
Action = "Index",
Key = "forum_home"
}
};
var forums = this._forumsService.GetForums<ForumNode>().ToList();
var topics = this._forumsService.GetTopics<TopicNode>().ToList();
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue
});
}
foreach (var topic in topics)
{
var forum = forums.FirstOrDefault(item => item.Id == topic.ForumId);
var forumRouteValue = new Dictionary<string, object> { { "forum", forum.NameTranslit }, { "topicName", topic.TitleTranslite }, {"page", 0 } };
nodes.Add(new DynamicNode
{
Key = $"topic_{topic.Id}",
ParentKey = $"forum_{topic.ForumId}",
Title = topic.Title,
Controller = "Forums",
Action = "ShowTopic",
RouteValues = forumRouteValue
});
}
return nodes;
}
private ForumNode GetParentForum(List<ForumNode> forums, ForumNode forum)
{
if (forum.ForumId > 0)
{
return forums.FirstOrDefault(item => item.Id == forum.ForumId);
}
return null;
}
}
But I can't found a good decision for pagination. For easy I can use page prefix for key and make duplicate DynamicNode. But it's bad idea, because when I have example 1000 topics and each topic have 20 page I must create 20000 DynamicNode. Maybe have other decision?
For ambient context (such as page number) you can use PreservedRouteParameters to force a match on any value for the specified keys. These keys match either route values or query string parameters from the request (route values take precedence if they are the same).
foreach (var forum in forums)
{
var parentForum = this.GetParentForum(forums, forum);
string parentKey = parentForum?.Id.ToString() ?? "home";
var forumRouteValue = new Dictionary<string, object> { { "forumName", forum.NameTranslit } };
// Always match the "page" route value regardless of its value
var forumPreservedRouteParameters = new List<string>() { "page" };
nodes.Add(new DynamicNode
{
Key = $"forum_{forum.Id}",
ParentKey = $"forum_{parentKey}",
Title = forum.Name,
Controller = "Forums",
Action = "ShowForum",
RouteValues = forumRouteValue,
PreservedRouteParameters = forumPreservedRouteParameters
});
}
NOTE: When you use PreservedRouteParameters, they are included in the generated URL from the current request if provided and not included in the URL if not provided in the request. Therefore, if you have more than one page number in the same ancestry you need to have a separate route key name for each one or the current page number will be passed to the ancestor nodes from the current request.
Related
Using ASP.NET MVC 5 and Bootstrap 3.2, I am trying to create an Html ActionLink for the current URL like in the Bootstrap example here:
https://www.quackit.com/bootstrap/bootstrap_3/tutorial/bootstrap_breadcrumbs.cfm
<ul class="breadcrumb">
<li>Home</li>
<li>Fruit</li>
<li class="active">Pears</li>
</ul>
Microsoft has a lot of information on their page for ActionLink
https://learn.microsoft.com/en-us/dotnet/api/system.web.mvc.html.linkextensions.actionlink?view=aspnet-mvc-5.2
There doesn't seem to be a version for creating the active list item unless I am missing something in those overloads.
So, how would I create an ActionLink for "Pears" in the unordered list above (no hyperlink and class="active")?
The solution was:
If you don't want a hyperlink, don't use the ActionLink
I still used an ActionLink for Home and for Fruit, but Pears just got the standard <li> treatment like below:
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
var result = string.Empty;
var controllerName = helper.ViewContext.RouteData.Values["controller"].ToString();
// optional condition: I didn't wanted it to show on home and account controller
if ((controllerName != "Home") && (controllerName != "Account"))
{
var homeLink = helper.ActionLink(
linkText: "Home",
actionName: "Index",
controllerName: "Home").ToHtmlString();
var sb = new StringBuilder($"<ol class='breadcrumb'><li>{homeLink}</li>");
var url = HttpContext.Current.Request.Url.ToString();
var urlParts = url.Split(new char[] { '/' });
if (!urlParts.Contains("Console"))
{
var controllerLink = helper.ActionLink(
linkText: controllerName.SplitTitleWords(),
actionName: "Index",
controllerName: controllerName);
sb.Append($"<li>{controllerLink}</li>");
} else
{
var a = $"Console";
sb.Append($"<li>{a}</li>");
}
var actionName = helper.ViewContext.RouteData.Values["action"].ToString();
sb.Append($"<li class=\"active\">{actionName.SplitTitleWords()}</li>");
result = sb.Append("</ol>").ToString();
}
return result;
}
SplitTitleWords is a custom extension that I wrote to break-up words such as AddPDFResource into Add PDF Resource.
public static string SplitTitleWords(this string value)
{
var cList = new List<char>();
if (!string.IsNullOrEmpty(value))
{
cList.Add(value[0]); // just add the first letter, whether caps, no caps, or number
for (var i = 1; i < value.Length; i++)
{
var c = value[i];
if (char.IsUpper(c))
{ // 01234567891234 0123456789012345
// check special cases like class AddPDFResource => Add PDF Resource
var c0 = value[i - 1];
if (char.IsUpper(c0))
{
if (i + 1 < value.Length)
{
var c1 = value[i + 1];
if (!char.IsUpper(c1))
{
cList.Add(' ');
}
}
} else
{
cList.Add(' ');
}
}
cList.Add(c);
}
}
var result = new String(cList.ToArray());
return result;
}
I am learning Nopcommerce from the tutorial provided by Pluralsight.
When it comes to adding menu for the plugin in the admin panel it is different with the version 3.5 and 3.8. There is no public SiteMapNode BuildMenuItem()
instead we have to use public void ManageSiteMap(SiteMapNode rootNode).
I have used ManageSiteMap according to the documentation provided by NopCommerce How to add a menu item into the administration area from a plugin, but by using that code i was only able to show the parent menu not the sub menu.
This is my code:
public void ManageSiteMap(SiteMapNode rootNode)
{
var menuItem = new SiteMapNode()
{
Title = "Promo Slider",
ControllerName = "PromoSlider",
ActionName = "CreateUpdatePromoSlider",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", "admin" } }
};
var createUpdate = new SiteMapNode()
{
SystemName = "Widgets.PromoSlider",
Title = "New Sliders",
ControllerName = "PromoSlider",
ActionName = "CreateUpdatePromoSlider",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } }
};
var manageSlider = new SiteMapNode()
{
SystemName = "Widgets.PromoSlider",
Title = "Manage Sliders",
ControllerName = "PromoSlider",
ActionName = "ManagePromoSliders",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null} }
};
menuItem.ChildNodes.Add(createUpdate);
menuItem.ChildNodes.Add(manageSlider);
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Third party plugins");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
}
But all it shows is the parent menu only
I want to show like this
Plugins
|---->Promo Slider
|-----------> New Slider
|-----------> Manage Sliders
Can anyone please help me with my code.
Your code need some fixes:
menuItem is a parent node, does not required RouteValues.
Basically, parent node needs SystemName
After doing upper changes, the parent node should be look like:
var menuItem = new SiteMapNode
{
Title = "Promo Slider",
Visible = true,
SystemName = "Widgets.PromoSlider",
};
Okay, now coming to the child nodes, you're creating new node each time..instead of add to the parent one!
var createUpdate = new SiteMapNode()
var manageSlider = new SiteMapNode()
So, change it to:
menuItem.ChildNodes.Add(new SiteMapNode
{
SystemName = "Widgets.PromoSlider",
Title = "New Sliders",
ControllerName = "PromoSlider",
ActionName = "CreateUpdatePromoSlider",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } }
});
menuItem.ChildNodes.Add(new SiteMapNode
{
SystemName = "Widgets.PromoSlider",
Title = "Manage Sliders",
ControllerName = "PromoSlider",
ActionName = "ManagePromoSliders",
Visible = true,
RouteValues = new RouteValueDictionary() { { "area", null } }
});
At the end, add parent node to the Plugins node:
var pluginNode = rootNode.ChildNodes.FirstOrDefault(x => x.SystemName == "Third party plugins");
if (pluginNode != null)
pluginNode.ChildNodes.Add(menuItem);
else
rootNode.ChildNodes.Add(menuItem);
All done! Run it and it will display as you want.
{Sorry new to JSON}
I need to build up an array of resources (Users) and pass it in to my view, might be a better way than what ive done below? (Demo)
My model is simply
public class ScheduleUsers
{
public string Resource{ get; set; }
}
On my controller
var users = new JsonArray(
new JsonObject(
new KeyValuePair<string,JsonValue>("id","1"),
new KeyValuePair<string,JsonValue>("name","User1")),
new JsonObject(
new KeyValuePair<string, JsonValue>("id", "2"),
new KeyValuePair<string, JsonValue>("name", "User2"))
);
model.Resources = users.ToString();
Why don't you just return a list of entities as a JSON result, like:
public class CarsController : Controller
{
public JsonResult GetCars()
{
List<Car> cars = new List<Car>();
// add cars to the cars collection
return this.Json(cars, JsonRequestBehavior.AllowGet);
}
}
It will be converted to JSON automatically.
I did this and this works
JavaScriptSerializer js = new JavaScriptSerializer();
StringBuilder sb = new StringBuilder();
//Serialize
js.Serialize(GetResources(), sb);
public List<ScheduledResource> GetResources()
{
var res = new List<ScheduledResource>()
{
new ScheduledResource()
{
id = "1",
color = "blue",
name = "User 1"
},
new ScheduledResource()
{
id = "2",
color = "black",
name = "User 2"
},
};
return res;
}
I am trying to add filter by ID for the following:
public ActionResult Index()
{
var model = from o in new MainDBContext().OffLinePayments
select new EditOffLinePayment
{
ID = o.ID,
Amount = o.Amount
};
return View(model);
}
What I would like to do is the following:
public ActionResult Index(long? id)
{
if (id != null)
{
var model = from o in new MainDBContext().OffLinePayments
**Where Assigned_ID == id**
select new EditOffLinePayment
{
ID = o.ID,
Amount = o.Amount
};
return View(model);
}
else
{
var model = from o in new MainDBContext().OffLinePayments
select new EditOffLinePayment
{
ID = o.ID,
Amount = o.Amount
};
return View(model);
}
}
try
var model = from o in new MainDBContext().OffLinePayments
where o.Assigned_ID == id
select new EditOffLinePayment
{
ID = o.ID,
Amount = o.Amount
};
If I understand correctly, your problem is that the compiler doesn't let you write where o.Assigned_ID == id in the query.
That's because id is a Nullable<long>, which is not implicitly convertible to a long (which OffLinePayment.Assigned_ID presumably is).
You need to write where o.Assigned_ID == id.Value instead. Take a look at what the Value property does so that you don't get any surprises.
A cleaner, shorter and much more readable syntax would look like this:
public ActionResult Index(long? id){
using (var ctx = new MainDBContext())
{
var entities = ctx.OfflinePayments.Where(e => !e.HasValue || e.Assigned_ID == id.Value);
var model = entities.Select(e => new EditOfflinePayment { ID = e.ID, Amount = e.Amount }).ToList();
return View(model);
}
}
I am using this code in my view:
#(Html.Telerik().TreeView()
.Name("AjaxTreeView")
.BindTo(Model, (item, category) =>
{
// bind initial data - can be omitted if there is none
item.Text = category.Name;
item.Action("Details", "Categories", new { Id = category.Id });
item.Value = category.Id.ToString();
item.LoadOnDemand = category.NOChildren > 0;
})
.DataBinding(dataBinding => dataBinding
.Ajax().Select("_TreeViewAjaxLoading", "Categories")
)
)
It works fine (ajaxified expand and collapse). The action links work fine but only for the root nodes. My current controller that spews out JSON for the ajax load:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0
};
return new JsonResult { Data = nodes };
}
does not set the action link. How can I set the action link here? Thanks.
Christian
This seems to do the trick:
[Transaction]
[HttpPost]
public ActionResult _TreeViewAjaxLoading(TreeViewItem node)
{
int? ParentId = !string.IsNullOrEmpty(node.Value) ? (int?)Convert.ToInt32(node.Value) : null;
UrlHelper u = new UrlHelper(this.ControllerContext.RequestContext);
var nodes = from item in CategoryRepository.GetChildren(ParentId)
select new TreeViewItem
{
Text = item.Name,
Value = item.Id.ToString(),
LoadOnDemand = item.NOChildren > 0,
Url = u.Action("Details", "Categories", new { Id = item.Id} )
};
return new JsonResult { Data = nodes };
}