Razor Pages: Set cookie from _Layout.cshtml - c#

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>

Related

How to create a link to switch language in ASP.NET Core 2.2?

A website supports several languages. The code to support multi-language including the routes are already in place and working.
The localization is set up using the RouteDataRequestCultureProvider as explaned at https://joonasw.net/view/aspnet-core-localization-deep-dive.
app.UseRouter(routes =>
{
routes.MapMiddlewareRoute("{culture=en-US}/{*mvcRoute}", subApp =>
{
subApp.UseRequestLocalization(localizationOptions);
subApp.UseMvc(mvcRoutes =>
{
// Routes are here
}
});
});
How to create a generic tag which will show the current page in a different language?
Ideally, I would just specify which language this link should point to and that it should keep all other route parameters (like the current controller, the current action, the current route model) so I can have this link in the _Layout.cshtml?
I managed to do this with the partial view for language dropdown list.
First get a list of supported cultures by injecting RequestLocalizationOptions to the partial view
Collect route data values and query string parameters as well into a dictionary, so if you have a link like below it will catch all parameters.
/en-US/Products/?page=5&keyword=bla-bla-bla
Loop in the supported cultures to create links and replace {culture} route value with the appropriate in the loop. The only thing to consider is to have {culture} defined in the global route.
here is my _Languages.cshtml partial view:
#using Microsoft.AspNetCore.Builder
#using Microsoft.Extensions.Options
#inject IOptions<RequestLocalizationOptions> LocOps
#{
var requestCulture = CultureInfo.CurrentCulture;
var supportedCultures = LocOps.Value.SupportedUICultures
.Select(c => new SelectListItem
{
Value = $"{c.Name}",
Text = $"{c.DisplayName}"
}).ToList();
var routeData = new Dictionary<string, string>();
foreach (var r in ViewContext.RouteData.Values)
{
routeData.Add(r.Key, r.Value.ToString());
}
foreach(var qs in Context.Request.Query)
{
routeData.Add(qs.Key, qs.Value);
}
}
<div class="dropdown">
<a class="btn-sm btn-default border border-secondary dropdown-toggle" href="#" role="button" id="dropdownLang" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
#($"{requestCulture.DisplayName}")
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownLang">
#foreach (var culture in supportedCultures)
{
if (culture.Value.ToLower() != requestCulture.Name.ToLower())
{
// replace {culture} value with the one from the list
routeData["culture"] = culture.Value;
<a class="dropdown-item small"
asp-all-route-data="#routeData">
#culture.Text
</a>
}
}
</div>
</div>
btw, I'm using bootstrap 4.
UPDATE
I created a nuget package that creates a language navigation menu with one line of code :)
install nuget package
PM > Install-Package LazZiya.RazorLibrary -Version 1.0.1
create a language navigaton dropdown:
<partial name="/Areas/LazZiya/Pages/_LanguageMenu.cshtml" />
compatible with .NetCote 2.1 or later and bootstrap 4
notice : Route key name must be culture
UPDATE 2 (14.04.2019)
I created a tag helper that supports all versions of current dotnet core frameworks to create a language navigation depending on supported cultures or manually selected list of cultures.
install nuget package (it contains another useful tag helpers as well):
Install-Package LazZiya.TagHelpers -Version 2.0.0
add tag helpers to _ViewImports.cshtml
#addTagHelper *, LazZiya.TagHelpers
Create the language naviation :
<language-nav view-context="ViewContext"></language-nav>
for more details visit project website, see live demos
This is not super elegant but it should do the trick. Here as an example for en-US and de-DE only:
var switchLang = new Dictionary<string, string>{{"de-DE", "en-US"}, {"en-US", "de-DE"}};
var controller = Html.ViewContext.RouteData.Values["controller"].ToString();
var action = Html.ViewContext.RouteData.Values["action"].ToString();
var lang = System.Threading.Thread.CurrentThread.CurrentUICulture.Name;
var newLang = lang;
switchLang.TryGetValue(lang, out newLang);
You can then use the variable controller, action and newLang to build an ActionLink to the other language. I just put a line into one of my _Layout.cshtml like to show the value of
var url = string.Format("{0}/{1}/{2}",newLang, controller, action);
And it looked OK.

Reload partial view using html razor

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/

ASP.NET MVC Razor get textbox value

How can I get the value of a textbox using razor?
<div>
<input type="text" id="somevalue" name="somevalue" class="form-control"/>
<input type="button" value="Search" class="btn btn-success"/>
</div>
<ul id="ReportsList" class="nav">
#foreach (var item in Model){
var roomName= document.getElementByID('somevalue').value
if (item.roomName == roomName) {
<li class="errorItem">
<a href="#" class="list-group-item">
<i class="fa fa-warning fa-fw"></i> #Html.DisplayFor(modelItem => item.roomName)
<span class="pull-right text-muted small">#Html.DisplayFor(modelItem => item.roomCapacity) pers.
</span>
..........
}
Is it possible to get the value of the textbox using MVC Razor? Cause using the getElementByID doesn't seem to work in razor...
Don't be brought down by the down-ticks.
You are obviously new to Razor and Mvc & Javascript. You problem is that you are mixing a server-side language with a client-side language. Razor is a server-side language so you will not be able to access client-side code (ie html or javascript) using Razor. Razor is used to render html to the client browser. Think of the code that you see in a cshtml file as a template for the code that will become an html file. The javascript on the other hand will only run when it gets to the users browser.
Now, lets try to make some sense of your code.
<div>
<input type="text" id="somevalue" name="somevalue" />
<input type="button" value="Search" />
</div>
<ul id="ReportsList" class="nav">
#foreach (var item in Model)
{
var roomName= document.getElementByID('somevalue').value; // This is javascript code.
if (item.roomName == roomName) {
<li>
#Html.DisplayFor(modelItem => item.roomName)
#Html.DisplayFor(modelItem => item.roomCapacity)
</li>
}
}
</ul>
I removed the classes to make it more legible. The problem above is that you are trying to find a value to use with your razor code. That code is running before it gets to the browser so that won't work.
You cannot solve this problem using Razor. That means your DisplayFor's are going to be useless for your scenario.
You need javascript to solve the problem, so you will need to do away with the Razor Code. Assuming your Model has as list of object with the properties you created in your example, you could do something like this.
<script type="text/javascript">
var data = #(Html.Raw(Json.Encode(Model));
for(var o in data) {
var item = data[o];
// You need to create an element here and add it to the ul here
// You could use jquery.
}
</script>
Unfortunately, you have the wrong tools here.
To actually accomplish what you are trying to do you are going to be better off investing in some javascript frameworks. I suggest that you learn AngularJs to do this.
Concerning Organization of Javascript
As stated in the comments you can use a script tag in your cshtml file. Unfortunately, this is not your problem. I added a little bit of a way to organize your javascript as well.
your.cshtml file.
<script type="text/javascript">
.. getElementById in here and do something.
</script>
Better Organization Might Look Like This
Put the code in a javascript file. In this example the name is person.js. I am using a person example because it is an easy way to look at creating an usable object in javascript. In this case person is the object.
person.js
function Person() {
}
Person.prototype = {
// Sets the element with id = "nameId" to "Jim Bob"
setName: function() {
var element = document.getElementById("nameId");
// Now do something with it.
element.innerHTML = "Jim Bob"; // get some user input.
}
};
// You could initialize this as a global reference.
// I don't recommend this but it will be the easiest way for now.
var person = new Person();
Next, you would have to use it somehow. The simplest way to use it is not the best way.
<button id="setNameButton" onclick="person.setName()">Set Name</button>
Improved example using JQuery
This example will bind the event in an unobtrusive way (ie. you won't be mixing javascript and html).
function Person() {
this.initialize();
this.name = "Jim Bob";
}
Person.prototype = {
initialize: function() {
// get reference to this object.
var self = this;
// Set up the click for button.
$(document).on('click', "#setNameButton", function() {
// Set the name
self.setName();
});
}
// Sets the element to this.name field.
setName: function() {
var element = document.getElementById("nameId");
// Now do something with it.
element.innerHTML = this.name;
}
};

Razor sharp html link differ from the executed link

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();

<$G+$> Strange characters appearing in ASP.NET MVC5 Helper

My helper in the view, which is intended to display the full name of a user who is registered in the application, and the username if logged in via 3rd party authentication.
#using MyApp.Models;
#helper GetUserName()
{
if (User.Identity.AuthenticationType == "Application")
{
using (var db = new MyAppDbContext())
{
#db.Users.Find(User.Identity.GetUserId()).FullName
}
}
else
{
#User.Identity.GetUserName()
}
}
Then I use this helper:
#if (Request.IsAuthenticated)
{
using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", #class = "navbar-form pull-right" }))
{
#Html.AntiForgeryToken()
<ul class="nav">
<li>
#Html.ActionLink("Hello " + GetUserName() + "!", "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li>Log off</li>
</ul>
}
}
The name of my user is Proba234, and it displays like this:
Why are those strange characters (<$G+$>) appear, and how to get rid of them?
It is probably some kind of feature or bug related with usage of helpers within Visual Studio Page Inspector, you probably won't see those tags under external browser. I reproduced it easily in VS 2013. A thread about it can be found for example on ASP.NET forum
It seems like those funny characters are generated by helper. The probleem seems to be that the output of the helper is not meant to be consumed as regular string. Apparently it generates some control characters, which are meant to be there for Razor, but if we try to use the output as regular string (as #Html.ActionLink() expects its argument) they become visible.
To get rid of them, I shouldn't use the GetUserName() helper inside the #Html.ActionLink(), but get the UserName in the controller, and pass it via a property of the viewmodel. I shouldn't have that kind of logic in my view anyway.

Categories