How to localize structures HTML views in ASP.NET? - c#

I have a multi-language website and views content (including the markup) can differ from language to language. That's why I cannot just localize strings and put them into resources.
For example, my view in Russian:
<div class="card">
<div class="card-image">
<img src="~/Images/CardHeaders/Business_CreditsSME.jpg" />
</div>
<div class="card-content">
Text
</div>
</div>
View in English:
<p>Text</p>
I see two ways to localize this:
Create Page.cshtml and Page.en.cshtml, and create a custom LocaleView() method in controller which will use the localized view files.
The problem is that Views do not always differ. It means that sometimes I will duplicate the markup.
Copy all the HTML markup in my resources as it is, and use Html.Raw(Resources.PageView).
It is not convenient to create, modify or read HTML which is located in a resource flies
Moreover, I do not like Html.Raw method at all and prefer not to use it anywhere
How do you localize large views of different structures?

I have combined two solutions.
I have overriden View method in my base controller so that it looks for the existent localized view. Now, if my views differ too much, I simply create View.en.cshtml file and it is automatically being processed by a controller:
public abstract class BaseController : Controller
{
protected override ViewResult View(string viewName, string masterName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = (string)RouteData.Values["action"];
string localizedViewName = $"{viewName}.{CultureInfo.CurrentCulture.Name}";
bool hasLocalizedView = ViewEngines.Engines
.FindPartialView(ControllerContext, localizedViewName)
.View != null;
return base.View(hasLocalizedView ? localizedViewName : viewName, masterName, model);
}
}
If my views have the same structure, then I don't create separate files, but simply use the same view with localized strings within it like this:
<div class="card">
<div class="card-image">
<img src="~/Images/CardHeaders/Business_CreditsSME.jpg" />
</div>
<div class="card-content">
#GlobalResources.LocalizedText
</div>
</div>
The structure now looks like:
- Views
-- About
--- History.cshtml
--- History.kk.cshtml
--- History.en.cshtml
--- Vacancies.cshtml
where Vacancies.cshtml file uses resources to localize a page, and History* files are localized like a plain text without resources.

Awesome question!
Between these two option the first one look a little bit better.
But one thing that I usually do. It's created an HTML helper to locate the localised view.
So, you will be able to use it on the view like this:
#Html.RenderLocale("YourView", ...);
Then, in your RenderLocale, you change the file path to the view that the user wants.
public static string RenderLocale(this HtmlHelper helper, string target, ...)
{
var filePath = this.getLocaleFilePath(target); //it returns yourView.cshtml or yourView.ru.cshtml
return this.Action(filePath); // it renders or performs the action you want
}

Related

How do you add data from the database to the shared _Layout.cshtml in ASP.NET Core MVC (.NET 6) application?

NET gurus, I am going crazy over here. I have been trying for 3 days already and nothing seems to work. I have created a partial view, a base controller, tried out different functions. Every solution I have found is outdated.
Does anyone know how to do it now in ASP.NET Core .NET 6, MVC with Razor pages? I can't find the right documentation.
I have a menu in my _Layout.cshtml and want to display categories from database there, but the model is always NULL. I have no problem displaying the data in a different view, I just can't seem to add the model to the _Layout.cshtml.
You can try to use a View Component to render the category.
For example, we could refer to the following steps to display category in the MVC application layout page:
Create new folder named ViewComponents. In this folder, create new class named CategoryViewComponent.cs as below:
using Microsoft.AspNetCore.Mvc;
using WebApplication1.Data;
namespace WebApplication1.ViewComponents
{
[ViewComponent(Name ="Category")]
public class CategoryViewComponent:ViewComponent
{
private readonly ApplicationDbContext _context;
public CategoryViewComponent(ApplicationDbContext applicationDbContext)
{
_context = applicationDbContext;
}
public async Task<IViewComponentResult> InvokeAsync()
{
return View("Index", _context.Categories.ToList());
}
}
}
In Views folder, create new folders with path Views\Shared\Components\Category. In Category folder, create new folder named Index.cshtml:
#model List<WebApplication1.Data.Category>
<h2>Category</h2>
<ul>
#foreach(var item in Model)
{
<li>#item.CategoryName</li>
}
</ul>
Invoke the view component in the _Layout.cshtml page:
<div class="container">
<main role="main" class="pb-3">
#await Component.InvokeAsync("Category")
#RenderBody()
</main>
</div>
The file structure as below:
Then, the result is like this:
If using the Razor page application, use the same code and the file structure as below:
Then, the result is like this:
More detail information, you can also refer View Components in Razor Pages.
A layout view can have a typed model. With razor pages I would introduce a base page model;
interface IViewTemplate{
IEnumerable<string> GetCategories();
}
public abstract class BasePageModel : PageModel, IViewTemplate{
...
}
public class MyPageModel : BasePageModel
...
}
Then your view can;
#model IViewTemplate
#foreach(var category in Model.GetCategories()){
...
}

Why does Umbraco treat IPublishedContent differently when called via AJAX

The short story is I have a list of blog articles which I'm casting to a model which contains .wordCount. This is calculated by striping the HTML from the output of the RTE and then calculating the lengh. The output of the RTE can contain Macros.
On first load, i.e from a view, the output of the RTE renders my Macros as HTML. However, when I refresh my list articles using a clientside AJAX the output of the RTE is rendered differently. My Macros now look like this: <?UMBRACO_MACRO macroAlias="ArticleAudio" audioPicker="6068" audioPosition="left" audioTitle="Interview Audio" />
I also get the error System.InvalidOperationException: 'Cannot render a macro when there is no current PublishedContentRequest.
public static Article ToArticle(this IPublishedContent item)
{
string rawText = HelperFunctions.StripHTML(item.GetPropertyValue<string>("richText"));
...
}
public static string StripHTML(string htmlString)
{
string pattern = #"<(.|\n)*?>";
return Regex.Replace(htmlString, pattern, string.Empty);
}
Expected
<div class="audio-player">
<audio style="display: none;">
<source src="https://www.address.com/media/77390/interview.mp3" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</div>
My Output
<?UMBRACO_MACRO macroAlias="ArticleAudio" audioPicker="6068" audioPosition="left" audioTitle="Interview Audio" />
Thank you in advance for your help
When you get the property value, it will have the placeholder string for the macro in it. There is some code that runs as part of rendering the page that turns those into the actual Macros. I can't remember off the top of my head where that lives though.
Can you post the full code of the controller you are calling to update via AJAX? Is it a surface controller, an API controller, or something different?

Need to pass parameter to multiple Partial Views on single view page

I am trying very hard to rewrite this question better than my previous effort which received no responses. Even though I’m nearly done with this application, I am still a relative newbie at programming and it seems like one challenge just leads to another. I have looked at many posts related to the problem of passing a parameter to several Partial Views in a single view page. So let’s take this in order from the AlertPick.cshtml page where the user chooses one of three Alert_Identifier/SelectedAlertIndex parameters from the application database. I’m only showing the #model and Select Tag Form.
#model edxl_cap_v1_2.Models.ContentViewModels.EdxlCapMessageViewModel
#{
<h4>#Model.Alerts.Count Alerts</h4>
<form asp-controller="Alerts" asp-action="PickAlert" method="post">
<select class="cap_select" id="cap_select" style="width:100%;max-width:95%;"
asp-for="SelectedAlertIndex" asp-items="Model.Alert_Identifiers">
<option>Select one</option>
</select>
<br />
<input type="submit" name="PickAlert" value="Pick Alert to Assemble EDXL-Cap Message" />
</form>
}
This takes the user to thePickAlert.cshtml page, a table of five rows where the first four rows are the Data Categories of the application: Alert, Info, Area and Resource each with the Alert_Identifier repeated as a reminder in a text box followed by its own submit button named Check Alert, Check Info, Check Area, and Check Resource, respectively. These submit buttons take the user to a _DetailsAlert.cshtml, _DetailsInfo.cshtml, _DetailsArea.cshtml, and _DetailsResource.cshtml pages and they work correctly, with the data item names and values from the record that matches the Alert_Identifier. The fifth row repeats the Identifier and its button reads Add All, to assemble the whole set together for review and takes the user to the_Assemble.cshtml page below, where the individual data categories are correctly assembled with the data item names, but lack the correct data values that match the record that corresponds to the Alert_Identifier. I’m thinking that I need to add a third parameter for the SelectedAlertIndex or Alert_Identifier to each of the #Html.Partial(...) Views, but I haven’t found the correct form/syntax for that, and If someone could supply that or point me to an example similar enough to this, I would deeply appreciate it.
#model edxl_cap_v1_2.Models.ContentViewModels.EdxlCapMessageViewModel
<!DOCTYPE html>
<head>
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet" href="~/css/capv1_2_refimp.css" />
<title>Assembled EDXL-CAP Message</title>
</head>
<h4>Assemble EDXL-CAP Message</h4>
<!-- DetailsAlert -->
<div class="content-wrapper">
#Html.Partial("_DetailsAlert", Model.Alert)
</div>
<!-- End of DetailsAlert -->
<!-- DetailsInfo -->
<div class="content-wrapper">
#Html.Partial("_DetailsInfo", Model.Info)
</div>
<!-- End of DetailsInfo -->
<!-- DetailsArea -->
<div class="content-wrapper">
#Html.Partial("_DetailsArea", Model.Area)
</div>
<!-- End of DetailsArea -->
<!-- DetailsResource -->
<div class="content-wrapper">
#Html.Partial("_DetailsResource", Model.Resource)
</div>
<!-- End of DetailsResource -->
Responding to first comment below, I'm showing the InfosController.cs code for _DetailsInfo(int? id) the controller action for the Info Data Category. It is virtually identical for each of the data categories except that the line ... .SingleOrDefaultAsync(m => m.InfoIndex == id); becomes ....SingleOrDefaultAsync(m => m.AlertIndex == id); and the method itself becomes_DetailsAlert(int? id).
// GET: Infos/Details/5
public async Task<IActionResult> _DetailsInfo(int? id)
{
if (id == null)
{
return NotFound();
}
var info = await _context.Info
//.Include(e => e.Elements)
// .ThenInclude(d => d.DataCategory)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.InfoIndex == id);
if (info == null)
{
return NotFound();
}
return View(info);
}
PickAlert method from AlertsController follows:
public IActionResult PickAlert(Alert obj, int? SelectedAlertIndex)
{
if (SelectedAlertIndex.HasValue)
{
ViewBag.Message = "Alert loaded successfully";
}
return View(_context.Alert.Where(x => x.AlertIndex == SelectedAlertIndex));
}
I am not sure if I got the requirement correctly, but I think you have to create another model for all 4 partial views, e.g. for Alert, create a new model
class AlertModel:EdxlCapMessageViewModel
{
int SelectedAlertIndex {get;set;}
}
And then your view would look like:
<!-- DetailsAlert -->
<div class="content-wrapper">
#Html.Partial("_DetailsAlert",new AlertModel { Alert = Model.Alert,
SelectedAlertIndex = <ID SOMEHOW>
});
</div>
In .net core when I need to pass around a lot of data across the views, I usually find it cleanest to use services and DI. First, you can create a class that could store a set of data:
class MyDataForViews {
// the following is an example. You can have any properties
public string Info { get; set; }
}
You now have to add this class as a service. To do so go to your startup class and add the following within the services function:
services.AddScoped<MyDataForViews>();
Scoped means that the framework will create a new object of MyDataForViews for each HTTP request. No matter how many places you "inject" an object of MyDataForViews, it would use the same object across the current HTTP request. You can also replace the function with AddSingleton if you want to use the same object throughout the web app. The following is how you inject an object into your controller:
public class MyController : Controller
{
MyDataForViews myData;
// in controllers injection is done using the constructor
public MyController(MyDataForViews MyData) => myData = MyData;
public IActionResult Index()
{
myData = .... // assign all required data here
View();
}
}
Once this is done, instead of passing models to each view, you can inject the data into views using the following:
#inject MyDataForViews MyData;
Once you use this line on the top of any view, you can use the MyData object and there is no need to pass models to each partial view.
Here's a bit more detailed answer, since you've said at softwareengineering.stackexchange.com site that you still need help with this.
Let's first make sure you understand the basics correctly.
When it comes to passing data to the view, each controller in ASP.NET MVC has a property named ViewData, which is essentially a dictionary of key-value pairs. The ViewData itself has a property called Model, and this is what you access in the page using the Razor syntax #Model. You can use this property to pass a model that is strongly-typed, to avoid using magic strings for the keys of ViewData.
Note: ViewBag is a dynamic wrapper around the ViewData, so it's essentially the same thing (ViewBag.SomeProperty is the same as ViewData['SomeProperty']); the use of ViewBag is discouraged, though.
In a controller action when you do something like return View(), ASP.NET uses the cshtml page as a template to create actual HTML, and return it as the response to the client (this is all server-side).
There are a few ways to pass data to the view which are equivalent, for example:
ViewData.Model = someObject;
return View();
is the same as:
return View(someObject); // the View method can accept a model object
When it comes to partial views, by default, they get passed a copy of the parent page ViewData (this includes the reference to the Model), so you don't have to do anything special to pass this data to a partial view (but you can pass data of your choice if you want to).
The select tag helper renders (generates HTML) for the select element with the options specified. This is then sent as HTML to the client. On the client side, when the user clicks the submit button, a POST request is sent to the server, which is ultimately handled by the method PickAlert method on the AlertsController. If everything is setup correctly, you should get the SelectedAlertIndex as the parameter. Note that this is happening back at the server side, and that you now need to again return a page as the response.
You can pick the corresponding Alert object from your _context. Use the FirstOrDefault method for this instead of Where, as you only need a single item (convert types for comparison if necessary - e.g., if you have a string, but you are comparing to an int, or something along those lines).
var selectedAlert = _context.Alert.FirstOrDefault(x => x.AlertIndex == SelectedAlertIndex);
Now, all you need to do is set this selectedAlert and any other data that you need as a property on your model object (or under some key in ViewData), and render the correct view.
Note that if you just return View(model) without specifying the name of the view, the system will look for a view with the same name as your action method (here, PickAlert.cshtml), so use return View("ViewName", model) to change that if necessary.
For example, based on the code you've posted in your question, you could do something like this:
[HttpPost]
public IActionResult PickAlert(int? SelectedAlertIndex)
{
var model = new EdxlCapMessageViewModel(/* ... params, if any */);
if (SelectedAlertIndex.HasValue)
{
ViewBag.Message = "Alert loaded successfully";
var selectedAlert = _context.Alert.FirstOrDefault(x => x.AlertIndex == SelectedAlertIndex);
// I added a property to your model to store the alert;
// if you already have one, just use that one instead.
model.SelectedAlert = selectedAlert;
}
return View("YourViewName", model);
}
The YourViewName should be the parent view that has the partial views in it (the "Assembled EDXL-CAP Message" view, I presume).
BTW, I know that the way the system is passing the parameters to the action methods in a controller may seem a bit like magic, but it's convention-based. In the example above, it works because the parameter is named SelectedAlertIndex, and the model object has a property with the same name (and because you've specified that property in the select tag helper using asp-for="SelectedAlertIndex"). You can also modify the method signature so that it receives the entire model object (assuming that the model class is not too complicated - you can read more about how parameter binding works here):
[HttpPost]
public IActionResult PickAlert(EdxlCapMessageViewModel model)
{
// extract the index from model.SelectedAlertIndex
// you can also pass this same model object to the view
// (set some properties first if necessary)
// ...
}
Now for the partial views. Assuming that you are relying on the default mechanism which passes the parent ViewData to each partial view, you need to modify each partial view so that the code is written under the assumption that you can access the selected alert using #Model.SelectedAlert (the property you've set in the PickAlert action).
For example, a here's a simple partial view:
<div style="border: solid 1px #000000; padding: 30px; margin: 2px 2px 10px 2px;">
<p>The selected index is: #Model.SelectedAlert.AlertIndex</p>
</div>
Note that I'm just using the same model as in the parent view to access the SelectedAlert object: #Model.SelectedAlert.AlertIndex.
Again, when rendering the partial views, if you pass no additional parameters, they'll get a copy of the ViewData dictionary, and the same Model:
#Html.Partial("_DetailsAlert");
If you pass something else as the model, e.g., only the selected alert, then you need to change the partial view code accordingly:
#Html.Partial("_DetailsAlert", Model.SelectedAlert);
<div style="border: solid 1px #000000; padding: 30px; margin: 2px 2px 10px 2px;">
<p>The selected index is: #Model.AlertIndex</p>
</div>
Note that now, in the partial view, the local #Model refers to what was #Model.SelectedAlert in the parent view. (In other words, here #Model is of type Alert.) This only affects the ViewData.Model property; the key-value pairs stored in ViewData are still the same as those in the parent view.

How to pass data from asp.NET MVC to Angular2

What is the best way to pass data from an ASP.NET MVC controller to an Angular 2.0 component? For example, we use the ASP.NET MVC Model and would like to send a JSON version of it to Angular to use it in Angular.
When the controller is serving the view, we can already push some data to Angular2 (the model). So additional AJAX call to fetch that data is not required.
However, I am struggling to "inject" it into the Angular component. How do you do this? Any good references for this? As you may have noticed, I'm quite new to Angular2.
My index.cshtml looks like this.
<div class="container">
<div>
<h1>Welcome to the Angular 2 components!</h1>
</div>
<div class="row">
<MyAngular2Component>
<div class="alert alert-info" role="alert">
<h3>Loading...</h3>
</div>
</MyAngular2Component>
</div>
</div>
Kind regards,
Rob
The best way that I have found to pass data in from MVC (or any hosting/startup page) is to specify the data as an attribute on the bootstrapped component, and use ElementRef to retrieve the value directly.
Below is an example for MVC derived from the this answer, which states that it is not possible to use #Input for root-level components.
Example:
//index.cshtml
<my-app username="#ViewBag.UserName">
<i class="fa fa-circle-o-notch fa-spin"></i>Loading...
</my-app>
//app.component.ts
import {Component, Input, ElementRef} from '#angular/core';
#Component({
selector: 'my-app',
template: '<div> username: <span>{{username}}</span> </div>'
})
export class AppComponent {
constructor(private elementRef: ElementRef) {}
username: string = this.elementRef.nativeElement.getAttribute('username')
}
If you want to retrieve more complex data that is not suitable for an attribute, you can use the same technique, just put the data in a HTML <input type='hidden'/> element or a hidden <code> element, and use plain JS to retrieve the value.
myJson: string = document.getElementById("myJson").value
Warning: Access the DOM directly from a data-bound application such as Angular, breaks the data-binding pattern, and should be used with caution.
You might want to look for similar questions related to AngularJS, not Angular 2 specific, as the main gist of the thing remains the same:
you want your server-side Razor engine to render some kind of view (i.e. HTML or JS directly)
this view contains a JS template where part of the content is filled from a server model instance or anyway server data (e.g. a resource, dictionary, etc.)
in order to properly fill a JS variable from Razor, C# server-side data has to be properly serialized into a JSON format
In this post by Marius Schulz you can see as he serializes the data and uses that to fill a template AngularJS value component:
<script>
angular.module("hobbitModule").value("companionship", #Html.Raw(Model));
</script>
Something similar could be made to inject some data e.g. into window.myNamespace.myServerData, and then have Angular2 bootstrap that value among other providers.
In this post by Roel van Lisdonk, a similar approach is used, again, to fill an AngularJS-based template, with that ng-init directive:
<div ng-controller="main"
ng-init="resources={ textFromResource: '#WebApplication1.Properties.Resources.TextFromResource'}">
<h1>{{ title }}</h1>
{{ resources.textFromResource }}
</div>
As the first post points out, there's something to think about (pasting here):
A word of caution: The method I'm about to use is probably not a good fit for large amounts of data. Since the JavaScript data is inlined into the HTML response, it's sent over the wire every single time you request that page.
Also, if the data is specific to the authenticated user, the response can't be cached and delivered to different users anymore. Please keep that in mind when considering to bootstrap your Angular Apps with .NET data this way.
The second part may be less of an issue if your served page is already dynamic server-side, i.e. if it already has bits filled in out of server-side data.
HTH
You need to first bundle your services and controllers in separate module files and load services before controllers.
For example:
dist
|--services.js
|--controllers.js
Then you need to load the JavaScript code of the Services via ASP.NET MVC JavaScript result, here you need to inject your startup data.
public class ScriptController: Controller
{
public ActionResult GetServices(){
string file= File.ReadAllText(Server.MapPath("~dist/services.js"));
//modify the file to inject data or
var result = new JavaScriptResult();
result.Script = file;
return result;
}
Then in the index.html load the scripts as follows
<script src="/script/getservices"></script>
<script src="/dist/controller.js"></script>
This way you can inject data into angular code while loading.
However, even this has a performance impact due to time spent on fetching the view, compiling the view, and binding data to the view. For an initial load performance can still be improved if you use Server Side Rendering.
You can use the Input function exposed by #angular/core, I have for example an Angular 2 component to display information messages to the user of the application
My HTML template take an Angular 2 Message property
<div class="alert alert-info col-lg-10 col-lg-offset-1 col-md-10 col-md-offset-1 col-sm-10 col-sm-offset-1 col-xs-10 col-xs-offset-1">
<i class="fa fa-info-circle"></i> {{ Message }}
</div>
The Message property is passed as an input to my Angular 2 component named informationmessage.component.ts, for example
import { Component, Input } from '#angular/core';
#Component({
selector: 'informationmessage',
templateUrl: '../Templates/informationmessage.component.html'
})
export class InformationMessageComponent {
#Input() Message: string;
}
I then pass the data to my InformationMessageComponent using property binding in the HTML page, for example.
<informationmessage [Message]="InformationMessage"></informationmessage>
You can replace InformationMessage in the above example with the data that you get from your MVC controller for example
<informationmessage [Message]="#Model.InformationMessage"></informationmessage>
Please note: I did not test this scenario, but there is no technical reason for it not working, at the end of the day you are just binding a value to an Angular 2 property.
src/ApplicationConfiguration.ts
export class ApplicationConfiguration {
public setting1: string;
public setting2: string;
}
src/main.ts
declare var config : ApplicationConfiguration;
var providers = [{ provide: ApplicationConfiguration, useValue: config }];
platformBrowserDynamic(providers).bootstrapModule(AppModule)
.catch(err => console.log(err));
src/index.html
<script type="text/javascript">
var config = {setting1: "value1", setting2: "value2"};
</script>
src/app/app.component.ts
export class AppComponent {
private _config : ApplicationConfiguration;
constructor(config: ApplicationConfiguration) {
this._config = config;
}
}
I found a much simpler solution. Don't attempt to get the attribute from in the constructor! Use the ngOnInit() hook instead. The property will be accessible as long as it has been decorated with #Input(). It just appears that it is not available by the time the constructor is called.
Component HTML:
<MyComponent [CustomAttribute]="hello"></MyComponent>
Component TS:
export class MyComponentComponent {
#Input()
public CustomAttribute: string;
constructor() {}
ngOnInit() {
console.log("Got it! " + this.CustomAttribute);
}
}
Further to #Joseph Gabriel's approach, if you want a complex object to be passed via an attribute, in MVC you should serialize it to a string, and then in the angular side de-serialize it using JSON.Parse.

How to send server side CSS and JS to client side in .cshtml file

I'm new at MVC application development.I'm working on Visual studio ASP.NET MVC4 project. I want to send my server-side CSS and JS to the client side in a .cshtml file. How the work will be done. Is it right way of doing this?
Controller method:
public ActionResult GetCss()
{
Response.ContentType = "text/css";
return View("Css1", new FontSpec {FontName = "Arial"});
}
View:
#model MvcApplication2.Models.FontSpec
.foo
{
font-family: #Model.FontName;
}
Simplified Model:
public class FontSpec
{
public string FontName {get; set;}
}
The important part is to set the ContentType in the controller method. The model can be used to pass dynamically generated data to your view, making your CSS truly dynamic.
In your page view (the view that actually generates the page) you can use the following link tag:
<link rel="stylesheet" href="#(Url.Action("GetCss", "Home"))"/>
And finally, of course, you can use your CSS classes in your tags:
<span class="foo">This will be in your specified font.</span>
You can, of course, generate your CSS by any means you like, as long as it is legal CSS at the end of the processing. This will also work for generating dynamic Javascript, but of course you have to change the content type you use.

Categories