I have a controller, which creates breadcrumbs as follows:
Software > Windows 7 > Outlook 2007
The code to create this is:
ViewBag.breadcrumbs = string.Join(" > ", cbh.Select(i => i.Title));
Is there a straightforward way of making the breadcrumbs hyperlinks, which would point to (i.ParentID) ie:
Software -> forum/index/12
Windows 7 -> forum/index/19
Outlook 2007 -> forum/index/23
Or should I just loop through cbh and manually build <a href=...> strings, and pass those to the view?
Thank you,
Mark
Your best bet is to put the required items into the model then loop through them.
Try something like this:
Model
public class Model
{
public struct BreadCrumb
{
public string Title;
public string Url;
}
public List<BreadCrumb> Breadcrumbs { get; set; }
}
View
#{ int index = 0; }
#foreach(var crumb in this.Model.Breadcrumbs)
{
#(crumb.Title)
if(index < this.Model.Breadcrumbs.Count - 1)
{
<span>></span>
}
index++;
}
Yes, you should build your breadcrumb links in the view. If it helps, you can create a BreadCrumbModel class (if you don't already have one).
ViewBag.breadcrumbs = cbh.Select(i => new BreadCrumbModel()
{
Id = i.Id,
Title = i.Title
});
#{
var printSeparator = false;
}
#foreach(BreadCrumbModel bc in ViewBag.breadcrumbs)
{
#if(printSeparator)
{
<span class="breadcrumb-separator"> > </span>
}
<span class="breadcrumb">
#Html.ActionLink(bc.Title, "index", "forum", new { id = bc.Id });
</span>
#{
printSeparator = true;
}
}
If you want to have breadcrumbs between different controllers and actions (not just forum / index), then add those as properties of your BreadCrumbModel.
Related
I developed site using ASP.net & c# it support English language, but now i want to convert my site to
Multi language supported site. Any one please explain, how to do it? and what are the problems i face?
<div id="google_translate_element"></div>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({pageLanguage: 'en'}, 'google_translate_element');
}
</script>
<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
For c# backend:
Create a new language project in your solution, give it a name like ProjectName.Language. Add a folder to it called Resources, and in that folder, create Resources files (.resx) for each language you want to support.
Reference to your project: Right click on References -> Add Reference -> Prjects\Solutions.
Use namespace in a file: using ProjectName.Language;
Use it like: string someText = Resources.productGeneralErrorMessage;
Generally, you can load the labels and text in your View pages dynamically, with the label data stored in a local database. Depending on the number of expected page request you could use a lite weight database like SQLite.
Place buttons for the user to select a language.
Basic Method:
Install SQLite by NUGET package manager
add a using statement
using System.Data.SQLite;
Create a database named mydb.db
SQLiteConnection.CreateFile("mydb.db");
Create a connection method
public const string connectionString = #"Data Source=.\mydb.db;Version=3";
public static SQLiteConnection ConnectionFactory()
{ return new SQLiteConnection(connectionString); }
Create a label Table method:
public void CreateTable()
{
string createTableQuery=
#"CREATE TABLE IF NOT EXISTS [LabelLanguage] (
[id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[labelName] Text NULL,
[englishValue] Text NULL,
[germanValue] Text Null
)";
using (IDbConnection connection = ConnectionFactory())
{ connection.Execute(createTableQuery);}
}
Populate database table with info
public void SaveQuery()
// a better approach would be to create a list of labels and values the use a for each to add them to database
//insert label 1 with values for house
{
string saveQuery = #""insert into LabelLanguage
(labelName, englishValue, germanValue) values
(label1, house, hous) "
using (IDbConnection connection = new SQLiteConnection(connectionString))
{
connection.Execute(saveQuery);
}
// insert label 2 for values of car
saveQuery = #""insert into LabelLanguage
(labelName, englishValue, germanValue) values
(label1, car, Wagen) "
using (IDbConnection connection = new SQLiteConnection(connectionString))
{
connection.Execute(saveQuery);
}
}
```
Create a class to hold label values
public class MyLabelValue
{
// should match database spellings
public string labelName{ get; set; }
public string englishValue{ get; set; }
public string germanValue{ get; set; }
}
add Dapper to the project [ maps classes ]
via nuget package manger
create a method to Get a list of labels and values from database
public List<MyLabelValue> GetLabelLanguageValues(string languageName )
{
using (IDbConnection connection = ConnectionFactory())
{
return connection.Query<MyLabelValue>($"SELECT labelName, "+ languageName +" FROM LabelLanguage").ToList();
}
}
Create a method to loop through each label on your form
'''
void LoopThroughLabels(string languageName )
{
Label mylabel;
list listOfLanguageValues = GetLabelLanguageValues(languageName );
foreach (Control con in this.Controls)
{
if (con.GetType() == typeof (Label)) //or any other logic
{
mylabel = (Label)con;
foreach (MyLabelValue languageValue in listOfLanguageValues )
{
if (mylabel.name.ToString() == languageValue.labelName.ToString()
{
```
//uses reflection so make sure the property GetProperty("languageName ") is how the column in database is spelt and the property of the class MyLabelValue
mylabel.Text= languageValue.GetType().GetProperty(languageName ).GetValue(languageValue, null); ;
}
}
}
}
}
Note: probably quite a few mistakes in there
and it relies on the exact naming of labels and also of language names
First of all if we are talking about Localization:
There is a way of doing that.
In your solution explorer, create a folder named: Resources
Go to your Startup.cs
In your ConfigureServices register your Localization Settings for Resources folder:
services.AddLocalization(options =>
{
// Store localization files under the Resources folder.
options.ResourcesPath = "Resources";
});
And also in ConfigureServices while you are registiring you MVC please add following:
services.AddMvc()
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2);
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-US"), // => English option
new CultureInfo("it-it") // => Italian option
};
options.DefaultRequestCulture = new RequestCulture("en-US");
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
And now in your Cofigure method please add following:
var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(options.Value);
Now please create a Controller named could be => LanguageController.cs
Please add the following:
//This will be your main Cookie Culture Info controller.
public class LanguageController : Controller
{
[HttpPost]
public IActionResult SetLanguage(string culture, string returnURL)
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture))
);
return LocalRedirect(returnURL);
}
}
Now, you have to create .resx files under your Resources File:
Lets say you have a HomeController.cs and a public IActionResult Index() method,
So, for this page you have to create a .resx file like following:
->English
Views.Home.Index.en-US.resx
->Italian
Views.Home.Index.it-it.resx
These .resx files are easy to use, like an excell view and a xml format you have to give same keys but different values for each .resx file.
Example:
-> en-US.resx -> Key: Hello and Value: Hello
-> it-it.resx -> Key: Hello and Value: Ciao
Now you can use this under your Views folder in Index.cshtml file:
Example: <text>#Localizer["Hello"]</text>
Now you can create a form in your website, whereever you'd like to create, lets assume you want to create it on your navbar
Go to your navbar.cshtml (In my example, this can be any different page in your site)
And add following top of your .cshtml
#{
var requestCulture = Context.Features.Get<IRequestCultureFeature>();
var cultureItems = LocOptions.Value.SupportedUICultures
.Select(c => new SelectListItem { Value = c.Name, Text = c.DisplayName })
.ToList();
}
also create a form for post data to LanguageController.cs -> SetLanguage(): (Below form's design could be the worse... sorry)
<form asp-controller="Language" asp-action="SetLanguage" id="selectLanguage"
asp-route-returnURL="#Context.Request.Path" method="post" class="form-horizontal" role="form">
<ul class="navbar mt-auto">
<li class="nav-item dropdown">
<button name="culture" value="#cultureItems[1].Value" type="submit">
<span class="flag-icon flag-icon-us mr-1"> </span>
#cultureItems[0].Value
</button>
<br />
<button name="culture" value="#cultureItems[2].Value" type="submit">
<span class="flag-icon flag-icon-it mr-1"> </span>
#cultureItems[1].Value</button>
</li>
</ul>
</form>
Note: Because of .resx files are in XML format you can use them everywhere and its so light to use.
I have an issue where between two pages I am sharing a modal, both pages are using the same angularJs version(v1.2.14), and both pages call the exact same directives (ui.select2). The select box inside of the modal works on one page, whilst on the other it simply stay as the default option.
As an fyi I have tried implementing the select box in different styles e.g. ng-repeat on the options, and not using the track by. This however results in the other pages selecting options to break. I can only ever get one page to work and the other to break.
The strange thing is that in the background the bound value is updating correctly:
<div class="col-md-10">
<select ui-select2="{width: '100%'}" class="form-control"
ng-model="Model.DocumentTypeId"
ng-options="documentType.DocumentTypeId as documentType.DocumentTypeDescription for documentType in Model.DocumentTypes track by documentType.DocumentTypeId">
<option value="">Select Document Type</option>
</select>
</div>
If you have any suggestions of why this is occurring it would be great.
Here is a heavily truncated view of the controller:
module Views.TMDocumentUpload {
export class DocumentUpload implements IDocumentUpload {
public static SetupDocumentUploadDialog = "onSetupDocumentUploadDialog";
public Init(model: DocumentUploadViewModel) {
var self = this;
if (self.$scope.Model.HideDocumentType || self.$scope.Model.DocumentTypeId == null) {
if (self.$scope.Model.DocumentTypes.length == 1) {
self.$scope.Model.DocumentTypeId = self.$scope.Model.DocumentTypes[0].DocumentTypeId;
}
}
}
constructor(public $scope: IDocumentUploadScope, $http: ng.IHttpService, $timeout: ng.ITimeoutService) {
$scope.isAllSelected = true;
$scope.ShareConfig = [];
$scope.Model.DisplayShareOptions = false;
$scope.Init = () => {
var self = this;
$scope.$on(DocumentUpload.SetupDocumentUploadDialog,
(e: ng.IAngularEvent, args?: Views.TMDocumentUpload.DocumentUploadViewModel) => {
self.$scope.Model = new DocumentUploadViewModel();
$http.get("/GetInitialModel")
.success(function (data: DocumentUploadViewModel) {
angular.extend(data, args);
self.Init(data);
});
});
};
}
}
DocumentUpload.$inject = ["$scope", "$http","$timeout"];
}
I have resolved the issue by removing ui-select2, it seems that this was causing some sort of conflict with another directive in my second page.
In my mvc solution I was originally using a viewModel to hold an IEnumerable of SelectListItems. These would be used to populate a dropdownfor element like below
#Html.DropDownListFor(model => model.Type, Model.PrimaryTypeList, new { data_acc_type = "account", data_old = Model.Type, #class = "js-primary-account-type" })
the problem being that whenever I had to return this view, the list would need re-populating with something pretty heavy like the following:
if(!ModelState.IsValid){
using (var typeRepo = new AccountTypeRepository())
{
var primTypes = typeRepo.GetAccountTypes();
var primtype = primTypes.SingleOrDefault(type => type.Text == model.Type);
model.PrimaryTypeList =
primTypes
.Select(type => new SelectListItem()
{
Value = type.Text,
Text = type.Text
}).ToList();
}
return View(model);
}
It seemed silly to me to have to rewrite - or even re-call (if put into a method) the same code every postback. - the same applies for the ViewBag as i have about 6 controllers that call this same view due to inheritance and the layout of my page.
At the moment i'm opting to put the call actually in my razor. but this feels wrong and more like old-school asp. like below
#{
ViewBag.Title = "Edit Account " + Model.Name;
List<SelectListItem> primaryTypes = null;
using (var typeRepo = new AccountTypeRepository())
{
primaryTypes =
typeRepo.GetAccountTypes()
.Select(t => new SelectListItem()
{
Value = t.Text,
Text = t.Text
}).ToList();
}
#Html.DropDownListFor(model => model.Type, primaryTypes, new { data_acc_type = "account", data_old = Model.Type, #class = "js-primary-account-type" })
Without using something completely bizarre. would there be a better way to go about this situation?
UPDATE: While semi-taking onboard the answer from #Dawood Awan below. my code is somewhat better, still in the view though and i'm 100% still open to other peoples ideas or answers.
Current code (Razor and Controller)
public static List<SelectListItem> GetPrimaryListItems(List<AccountType> types)
{
return types.Select(t => new SelectListItem() { Text = t.Text, Value = t.Text }).ToList();
}
public static List<SelectListItem> GetSecondaryListItems(AccountType type)
{
return type == null?new List<SelectListItem>(): type.AccountSubTypes.Select(t => new SelectListItem() { Text = t.Text, Value = t.Text }).ToList();
}
#{
ViewBag.Title = "Add New Account";
List<SelectListItem> secondaryTypes = null;
List<SelectListItem> primaryTypes = null;
using (var typeRepo = new AccountTypeRepository())
{
var primTypes = typeRepo.GetAccountTypes();
primaryTypes = AccountController.GetPrimaryListItems(primTypes);
secondaryTypes = AccountController.GetSecondaryListItems(primTypes.SingleOrDefault(t => t.Text == Model.Type));
}
}
In practice, you need to analyse where you app is running slow and speed up those parts first.
For starters, take any code like that out of the view and put it back in the controller. The overhead of using a ViewModel is negligible (speed-wise). Better to have all decision/data-fetching code in the controller and not pollute the view (Views should only know how to render a particular "shape" of data, not where it comes from).
Your "Something pretty heavy" comment is pretty arbitary. If that query was, for instance, running across the 1Gb connections on an Azure hosted website, you would not notice or care that much. Database caching would kick in too to give it a boost.
Having said that, this really is just a caching issue and deciding where to cache it. If the data is common to all users, a static property (e.g. in the controller, or stored globally) will provide fast in-memory reuse of that static list.
If the data changes frequently, you will need to provide for refreshing that in-memory cache.
If you used IOC/injection you can specific a single static instance shared across all requests.
Don't use per-session data to store static information. That will slow down the system and run you out of memory with loads of users (i.e. it will not scale well).
If the DropDown Values don't change it is better to save in Session[""], then you can access in you View, controller etc.
Create a class in a Helpers Folder:
public class CommonDropDown
{
public string key = "DropDown";
public List<SelectListItem> myDropDownItems
{
get { return HttpContext.Current.Session[key] == null ? GetDropDown() : (List<SelectListItem>)HttpContext.Current.Session[key]; }
set { HttpContext.Current.Session[key] = value; }
}
public List<SelectListItem> GetDropDown()
{
// Implement Dropdown Logic here
// And set like this:
this.myDropDownItems = DropdownValues;
}
}
Create a Partial View in Shared Folder ("_dropDown.cshtml"):
With something like this:
#{
// Add Reference to this Folder
var items = Helpers.CommonDropDown.myDropDownItems;
}
#Html.DropDownList("ITems", items, "Select")
And then at the top of each page:
#Html.Partial("_dropDown.cshtml")
I am developing a site in nopcommerce. i wish to change the image gallery depending on product name. for example if the product name contains Flowers display gallery 1 else display gallery 2
i have currently written the code
#Html.Widget("productdetails_top")
#using (Html.BeginRouteForm("Product", new { SeName = Model.SeName }, FormMethod.Post, new { id = "product-details-form" }))
{
#helper renderProductLine(ProductDetailsModel.ProductVariantModel product)
{
if (product.Name.Contains("Flowers"))
{
bool isFlowersGallery = true;
}
<div class="product-essential">
#Html.Widget("productdetails_before_pictures")
<!--product pictures-->
#if (isFlowersGallery)
{
I am now getting the error isFlowersGallery does not exist in the current context.
Have I done something wrong?
Try moving the declaration of isFlowersGallery out:
bool isFlowersGallery;
#helper renderProductLine(ProductDetailsModel.ProductVariantModel product)
{
if (product.Name.Contains("Flowers"))
{
isFlowersGallery = false;
}
I am making a Blog using MVC 3, Razor and Entity Framework. I am now working on the Comment section.
I am using the following table for comments.
Here I am using the 'CommentParent' column and setting it a value of an another 'CommentID' if a user is replying to a comment, else I have set a value of null.
Problem
I am using the following code to display comments,
#foreach (var comment in Model.Comments)
{
<div>
#comment.CommentContent
</div>
<br />
}
I am not sure how to display the "replyTo" comments as shown in the image below...
Please can anyone guide me as how this can be done...
First You will have to change your Model Class, Lets suppose your model class is :
public class CommentsModel
{
Public Int64 CommentId {get;set;}
....
....
//Introduce a new property in it as:
Public CommentsModel[] ChildComments {get;set;}
}
This new property will hold the child comments of a specific comment up to N Level. Than in your View you can do it like:
#foreach (var comment in Model.Comments)
{
<div>
#comment.CommentContent
</div>
<br />
#if(comment.ChildComments.Length > 0)
{
// Display Level 1 Comments and so on and so far
}
}
You can manage the lookout of comments by using Css Class on Divs.
try this:
private void CreateComments(int ? postId, int ? cid)
{
int? id = cid;
var replies = new List<Comment>();
if (postId.HasValue())
{
var BlogPost = context.Posts.Single(p=>p.Id == postId.Value);
replies = BlogPost.Comments.Where(c=>c.CommentParent == null);
}
else
{
replies = context.Comments.Where(c=>c.CommentParent == id);
}
int level = 0;
Comment tmp = new Comment();
foreach (Comment reply in replies)
{
tmp = reply;
while(tmp.CommentParent != null){
level++;
tmp = context.Comments.Single(c=>c.Id == tmp.CommentParent);
}
//logic for creating your html tag
//you can use "level" to leave appropriate indent back to your comment.
CreateComments(null,reply.id);
}
}
Edit:
you can even determine your current level like i did inside foreach loop.
i hope this could help.