While creating a custom pager I came across the problem when I send an string to my view and I encode it as #raw(strHTML) it will automatically add a controller name in front of all my links. On initial load the pager is loaded correctly and no extra controllername was added. When I hit the next button a get request is done to the action and the next page has to be loaded and this will also create a new pager. The outputted html is exactly the same as the first time this was executed. The html that is created by my customPager while debugging:
<ul>
<li class='previousPage'><span>Previous</span></li>
<li class='currentPage'><span>1</span></li>
<li><a title='Next page' rel='next nofollow' href='Invoices/Index?page=2'>2 </a></li>
<li><a title='Next page' rel='next nofollow' href='Invoices/Index?page=3'>3 </a></li>
<li><a title='Next page' rel='next nofollow' href='Invoices/Index?page=4'>4 </a></li>
<li><a title='Next page' rel='next nofollow' href='Invoices/Index?page=5'>5 </a></li>
<li class='nextPage'><a title='Volgende pagina' rel='next nofollow' href='Invoices/Index?page=2'>Volgende</a></li>
</ul>
The html is correct, but when the page is rendered and I hover over the link it reproduces the following link:
localhost:xxxx/company/Invoices/Invoices/Index?page=1
company is the area, Invoices the controller , second Invoices (NOT necessary, this breaks the link), index the action name.
I was wondering how the html and the reproduced link while clicking in the browser can be different.
Thanks in advance
Do not hardcode the href property value, Use the Url.Action helper method instead. It will fix your problem.
Replace
href='Invoices/Index?page=2'
with
href='#Url.Action("Index","Invoices",new { page=2 })'
EDIT:(As per the comment) :
If you want to use Url.Action method in your custom class
Pass the RequestContext to your custom class from the controller. I would add a Constructor to your custom class to handle this.
using System.Web.Mvc;
public class PaginationCustom
{
private UrlHelper _urlHelper;
public PaginationCustom(UrlHelper urlHelper)
{
_urlHelper = urlHelper;
}
public string GetPagingMarkup()
{
//add your relevant html markup here
string html = "<div>";
string url = _urlHelper.Action("Index", "Invoices", new { id = 3 });
html= html+"<a href='"+url + "'>3</a></div>";
return html;
}
}
You need to import System.Web.Mvc namespace to this class to use the UrlHelper class.
Now in your controller, create an object of this class and pass the controller context,
UrlHelper uHelp = new UrlHelper(this.ControllerContext.RequestContext);
var paging = new PaginationCustom(uHelp );
//Now call the method to get the Paging markup.
string pagingMarkup = paging.GetPagingMarkup();
Related
In _Layout.cshtml I have menu to change language of the application, like:
<nav id="nav-lang">
<ul>
<li>
EN
</li>
<li>
PL
</li>
</ul>
</nav>
What it does is reloading the page and sets new culture - works well. The thing is, that if the user changes culture and then go to other page within my app, default culture is loaded. I checked my options and the best choice seems to be setting a cookie "UserCulture" to e.g. "c=pl-PL|uic=pl-PL". The thing is I don't really know how to do it from in razor pages. I think that I should have a with asp-page-handler set to some method (e.g. "SetCulture") and have setting cookie in that method, but this causes some problems:
where to put "SetCulture" if the form would be in _Layout.cshtml?
_Layout.cshtml doesn't have code behind file
how to submit the form from anchor? If I put input type="submit" it
ruins the look of the menu completely.. I know I could do it from js
but I try to avoid js where it's not absolutely required, especially
for such basic stuff..
I might be missing something very basic here, I'm quite new to Razor Pages still. From the hindsight I should have probably sticked to MVC but Razor Pages was said to be easier..
Thanks, Brad. The solution you proposed works well. In the meantime I've got also other suggestion elsewhere and I'll post it too for anyone searching the answer in future.
In _Layout.cshtml:
<nav id="nav-lang">
<ul>
<li><a asp-page="/Index" asp-page-handler="SetCulture" asp-route-culture="en-EN">EN</a></li>
<li><a asp-page="/Index" asp-page-handler="SetCulture" asp-route-culture="pl-PL">PL</a></li>
</ul>
</nav>
In code-behind of Index (or any other page having code-behind):
public async Task<IActionResult> OnGetSetCultureAsync(string culture)
{
HttpContext.Response.Cookies.Append("Culture", "c=" + culture + "|uic=" + culture);
var returnUrl = Request.Headers["Referer"].ToString();
if (returnUrl.Contains("?culture="))
{
var url = returnUrl.Substring(0, returnUrl.IndexOf("?culture="));
return Redirect(url + "?culture=" + culture);
}
else
{
return Redirect(returnUrl + "?culture=" + culture);
}
}
And of course, for both solutions to work, there must be info in Startup.cs >> Configure:
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("pl-PL")
};
var lo = new RequestLocalizationOptions // Localization Options
{
DefaultRequestCulture = new RequestCulture("en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
var cp = lo.RequestCultureProviders.OfType<CookieRequestCultureProvider>().First(); // Culture provider
cp.CookieName = "Culture";
I haven't tested this out but what about setting the cookie using JavaScript and then reloading the page. The server side razor page code should check the code instead of a query parameter.
Something like the following on the _Layout page. Modify the menu to call a JS function instead of a link with a query parameter. In the JS set the cookie and reload the page.
<nav id="nav-lang">
<ul>
<li class="nav-item" onClick="setCulture('en-EN')">EN</li>
<li class="nav-item" onClick="setCulture('pl-PL')">PL</li>
</ul>
</nav>
...
<script>
function setCulture(culture) {
document.cookie = "culture="+culture;
location.reload();
}
</script>
I have this simple list of users in my model.
If we click one user, I would like to set that user as the chosen one and refresh the partial view.
My code looks like:
<div id="myPartialView">
#if (#Model.ChosenUser != null)
{
#Model.ChosenUser.UserName
}
<ul>
#foreach (var u in Model.Users)
{
<li>
<a href='#Url.Action("ChooseUser", "Controller", new { userId = u.UserId })'>#u.UserName</a>
</li>
}
</ul>
The controller method returns an Ok();
My current code redirects me to an empty page and I have to go back and refresh the page in order to see the model changes.
My question is, how can I refresh only this partial view after the razor action?
You will need to use Ajax.ActionLink here :
#foreach (var u in Model.Users)
{
<li>
#Ajax.ActionLink(u.UserName, // <-- Text to display
"Choose User", // <-- Action Name
"Controller", // <-- Controller Name
new AjaxOptions
{
UpdateTargetId="myPartialView", // <-- DOM element ID to update
InsertionMode = InsertionMode.Replace, // <-- Replace the content of DOM element
HttpMethod = "GET" // <-- HTTP method
})
</li>
}
The helper method would render the html needed to create the anchor tag element with the specified values, and you need to make sure that you have scripts added in the master layout for unobtrusive ajax.
For that you can look here, what scripts are needed to be pre-requisite:
How to use Ajax.ActionLink?
and your action method should be returning the model of the same type that your partial view expects with data populated in the model.
Please refer to the following post to learn in detail about Ajax Action Link:
http://www.c-sharpcorner.com/UploadFile/abhikumarvatsa/ajax-actionlink-and-html-actionlink-in-mvc/
I've setup a partialview to handle navigation throughout multiple views. Some of these views use a different model so I'm passing that model in like this
#Html.Partial("~/Views/Navigation/_PartialTabs.cshtml", new xxx.OpenAccess.OBProfiles())
It load up my partialview just fine
#using xxx.Helpers;
#model xxx.OpenAccess.OBProfiles
<ul class="nav nav-tabs">
<li role="presentation" class="#Html.IsActive("Edit", "OBProfile")">#Html.ActionLink("Edit", "Edit", "OBProfile", new { id = Model.ProfileID }, null)</li>
<li role="presentation" class="#Html.IsActive("Index", "OBProfileTasks")">#Html.ActionLink("Tasks", "Index", "OBProfileTasks", new { id = Model.ProfileID }, null)</li>
<li role="presentation">Messages</li>
However when I mouse over the links, the parameters (Model.ProfileID) return 0 regardless of what screen i'm on. So the tab URLs look like this http://localhost:55129/OBProfileTasks/Index/0
What am I missing that it isn't returning the /Number of whatever profileid ive selected?
You need to prepopulate OBProfiles. Here are two ways:
Constructor:
In the OBProfiles class add:
public OBProfiles(){
ProfileID = *some value*;
}
Static method:
public static OBProfiles GetProfiles(){
return new OBProfiles{ProfileId = *Some value*};
}
The for static method call:
#Html.Partial("~/Views/Navigation/_PartialTabs.cshtml", OBProfiles.GetProfiles())
I believe TheDizzle hit the nail on the head. When you pass the model in as new, you're passing in a clean version of that model. Try just passing the model in without using the new keyword.
In my MVC2 app that uses the Service - Repository pattern, how can I call a service method from the master page?
+--------------------------------------+
| Logo Welcome xyz|
+--------------------------------------+
| Home | Sales | Import | Admin (menu) |
+--------------------------------------+
In my menu I now have some pages that have restricted access by user role. I have an existing service method that can check if the current user can view a certain page or not:
IPageAccessService.CanAccess(int pageId, int roleId);
On the controller methods I can call this to check if the user can see the page or not:
public ActionResult Update(int id?)
{
if (!_pageAccessService.CanAccess(pageId, roleId))
{
return RedirectToAction("Index", "Home");
}
}
But I don't know how to call this method from my Site.Master so that when it creates the menu it does not show the menu item if the user does not have access (the menu is a simple unordered list):
<li>Admin
<ul>
<li>User Roles</li>
<li>Admin Reports</li>
</ul>
</li>
I'm guessing that it would need look something like this (have to check each page before adding to the list):
if (_pageAccessService.CanAccess(pageId, roleId)) <li>Admin
<ul>
if (_pageAccessService.CanAccess(pageId, roleId)) <li>User Roles</li>
if (_pageAccessService.CanAccess(pageId, roleId)) <li>Admin Reports</li>
</ul>
</li>
But before I can do that I need to know how to actually call a service method from the master.
EDIT:
I've adapted Darin's answer and have this:
public static class LinkExtensions
{
private static readonly IPageAccessRepository _repo = new PageAccessRepository();
private static readonly IPageAccessService _pageAccess = new PageAccessService(_repo);
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper, string linkText,
string url, string pageName
)
{
if (!_pageAccess.CanAccess(pageName))
{
return MvcHtmlString.Empty;
}
// The user can access the page => show the menu
var a = new TagBuilder("a");
a.Attributes["href"] = url;
a.SetInnerText(linkText);
return MvcHtmlString.Create(string.Format("<li>{0}</li>",a));
}
The problem is that I still need to call the service, so I need to be able to instantiate it. Because its a static class, my IoC container won't help here. So I still have to manually do create the service and repository. And it's still got the same problem as my original ugly workaround - manually creating a repository in a view.
You could write a custom HTML helper rendering the different items of this menu. Inside the helper based on the user roles you will decide whether to generate or not the given item. For example something among the lines:
public static class LinkExtensions
{
public static MvcHtmlString MenuItem(
this HtmlHelper htmlHelper,
string linkText,
string url,
string requiredRole
)
{
var a = new TagBuilder("a");
a.Attributes["href"] = url;
a.SetInnerText(linkText);
if (string.IsNullOrEmpty(requiredRole))
{
// No role required => show the menu item
return MvcHtmlString.Create(a.ToString());
}
var user = htmlHelper.ViewContext.HttpContext.User;
if (!user.IsInRole(requiredRole))
{
// A role is required but no user authenticated or user is not in role
// => show empty
return MvcHtmlString.Empty;
}
// The user is in role => show the menu
return MvcHtmlString.Create(a.ToString());
}
}
and inside the view:
<li>
<%= Html.MenuItem("Admin", Url.Content("~/Admin"), "admin") %>
<ul>
<li>
<%= Html.MenuItem("User Roles", Url.Content("~/Admin/Roles"), "userroles") %>
</li>
<li>
<%= Html.MenuItem("Admin Reports", Url.Content("~/Admin/AdminReports"), "admin") %>
</li>
</ul>
</li>
Another possibility is to use child actions and the Html.Action helper inside the master.
Anything that requires logic should not be in Views, including their helpers of course.
I'd suggest for this you add a new Controller Action, add your logic to the action, and return the username directly from the controller or if you want to display more stuff like login controls whatever you can use a view.
So, create LoginWelcomeMessage class with Username and IsLoggedIn properties for example. In the action set those based on the whatever checks you want, and send them to the view which shows/hides based on them.
In your masterpage, you execute #Html.RenderAction() to execute the action that brings the username and so on.
The same idea can be done for the entire menu. IT's up to you whether to have the whole header in one Action / View or have two different ones. If you have the same view then the model can have extra properties like CanViewSalesMenuItems, CanViewAdminMenuItems and so on, that are set from your action method, and used from its view to display/hide items.
The beauty is that the masterpage won't care, it just delegates the whole thing to the controller action and its view using RenderAction()
I'm trying to implement a Widget control that exists on every page in the system, which will allow the user to have basic Search & Directory functionality available on each page. This is a tab control defined below, where in the <ul> the currently selected tab is determined by the the value in Model.CurrentTab and the corresponding content that I want to display (basically, make visible) is also determined by that value.
<div class="WidgetControl">
<ul class="WidgetTab tabs">
<li <%= (Model.CurrentTab == "Search") ? "class='active'" : "" %>>
<span href='<%= Url.Action("SearchBox", "Search") %>'>Search</span>
</li>
<li <%= (Model.CurrentTab == "Directory") ? "class='active'" : "" %>>
<span href='<%= Url.Action("DirectoryList", "Group") %>'>Directory</span>
</li>
</ul>
<div id="Search" class="tab_container">
<% Html.RenderAction("SearchBox", "Search"
, (Model.CurrentTab == "Search") ? Model.Search : ""); %>
</div>
<div id="Directory" class="tab_container">
<% Html.RenderAction("DirectoryList", "Group"
, (Model.CurrentTab == "Directory") ? Model.Search : ""); %>
</div>
</div>
The reason I want to load both Search and Directory is so the page doesn't have to request the content depending on which tab is clicked. I want it all to be available immediately.
The problem I'm having is that if CurrentTab contains the value "Directory" this means (I assumed) that Html.RenderAction("SearchBox"... should pass in an empty string. But when it gets to the action method, the View Model passed into SearchBox contains a value and not ""
I don't understand why this is happening. Even when I pass an empty string into SearchBox, the View Model still contains a value. Can somebody please explain what's going on? Should I be doing this differently?
update:
public PartialViewResult DirectoryList(DirectoryViewModel vm)
{
return PartialView(vm.Search); // this is expecting a string
}
public PartialViewResult SearchBox(SearchViewModel vm)
{
return PartialView(vm); // the among other things, the Search string is used
}
Both DirectoryViewModel and SearchViewModel contain a property called Search
The ModelBinder will new() up any object in a ActionMethod's parameters. I don't think this behavior can be turned off without implementing your own modelbidner. You need to create an overload that has no parameters and route it accordingly.
Should you be doing something like this
<% Html.RenderAction("SearchBox", "Search",
new { vm = ((Model.CurrentTab == "Search") ? Model.Search : "") }); %>
Since the third parameter of the Html.RenderAction is object routeValues which is a dictionary with a parameter of the Action you are calling as a Key. If you don't specify the parameter that you are passing in your routeValues parameter of your Html.RenderAction it will always pass an object value to the vm parameter of your Action.