I have a database driven menu Helper that gets called from within my master page:
<div class="topBar">
<%= Html.MenuTree(39, false, "first", "last") %>
<div class="clear"></div>
</div>
Below is the code that outputs my HTML unordered list. The problem is that sometimes the output of the menu is completely wrong and all over the place ie. sub menu items appear as equal as top menu items.
I cannot find any pattern to why it does it so thought I'd post the code to see if anyone can spot the problem. My only other thought is that somehow its half cached half called and mixes the output.
This is what it should look like Correct http://img718.imageshack.us/img718/9317/screenshot20100328at120.png
Sometimes it comes out like this:alt text http://img413.imageshack.us/img413/9317/screenshot20100328at120.png
Here's the code (the boolean IsAdmin is false in this scenario):
public static string MenuTree(this HtmlHelper helper, int MenuCategoryID, bool Admin, string firstCssClass, string lastCssClass)
{
//TODO: Check for Subsonic fix for UNION bug
IOrderedQueryable<Menu> menuItems;
if (Admin)
{
menuItems = (from menu2 in Menu.All()
join pages in WebPage.All() on menu2.PageID equals pages.ID
join pagesRoles in PageRole.All() on pages.ID equals pagesRoles.PageID
join roles in aspnet_Role.All() on pagesRoles.RoleId equals roles.RoleId
where Roles.GetRolesForUser().Contains(roles.RoleName) && menu2.CategoryID == MenuCategoryID && menu2.Visible
select menu2).Distinct().OrderBy(f => f.OrderID);
}
else
{
menuItems = (from menu2 in Menu.All()
join pages in WebPage.All() on menu2.PageID equals pages.ID
where menu2.CategoryID == MenuCategoryID && menu2.Visible
select menu2).Distinct().OrderBy(f => f.OrderID);
}
var nonlinkedmenuItems = (from menu in Menu.All().Where(x => x.PageID == null && x.CategoryID == MenuCategoryID && x.Visible).OrderBy(f => f.OrderID) select menu);
var allCategories = menuItems.ToList().Concat<Menu>(nonlinkedmenuItems.ToList()).OrderBy(p => p.OrderID).ToList();
allCategories.ForEach(x => x.Children = allCategories.Where(y => y.ParentID == x.ID).OrderBy(f => f.OrderID));
Menu home = null;
if (Admin)
{
home = (from menu in Menu.All()
join pages in WebPage.All() on menu.PageID equals pages.ID
where pages.MenuName == "Home" && pages.IsAdmin
select menu).SingleOrDefault();
}
IEnumerable<Menu> topLevelItems;
if (Admin)
topLevelItems = allCategories.Where(f => f.ParentID == 0 && (f.Children.Count() > 0 || f.ID == home.ID));
else
topLevelItems = allCategories.Where(f => f.ParentID == 0);
var topLevelItemList = topLevelItems.ToList();
sbMenu.Length = 0;
sbMenu.AppendLine("<ul>");
LoopChildren(helper, Admin, topLevelItemList, 0, firstCssClass, lastCssClass);
sbMenu.AppendLine("</ul>");
string menuString = sbMenu.ToString();
//if ((menuString.IndexOf("<li>")) > 0)
// menuString = menuString.Insert((menuString.IndexOf("<li>") + 3), " class='first'");
//if (menuString.LastIndexOf("<li>\r\n") > 0)
// menuString = menuString.Insert((menuString.LastIndexOf("<li>\r\n") + 3), " class='last'");
return sbMenu.ToString();
}
private static void LoopChildren(this HtmlHelper helper, bool Admin, List<Menu> CurrentNode, int TabIndents, string firstCssClass, string lastCssClass)
{
for (int i = 0; i < CurrentNode.Count; i++)
{
sbMenu.Append(Tabs(TabIndents + 1));
string linkUrl = "";
string urlTitle = "";
if (CurrentNode[i].PageID != null)
{
WebPage item = WebPage.SingleOrDefault(x => x.ID == CurrentNode[i].PageID);
linkUrl = item.URL;
urlTitle = item.MenuName;
}
else
{
linkUrl = CurrentNode[i].URL;
urlTitle = CurrentNode[i].Title;
}
//Specify a RouteLink so that when in Error 404 page for example the links don't become /error/homepage
//If in admin we can manually write the <a> tag as it has the controller and action in it
bool selected = false;
if (helper.ViewContext.RouteData.Values["pageName"] != null && helper.ViewContext.RouteData.Values["pageName"].ToString() == linkUrl)
selected = true;
string anchorTag = Admin ? "<a href='" + linkUrl + "'>" + urlTitle + "</a>" : helper.RouteLink(urlTitle, new { controller = "WebPage", action = "Details", pageName = linkUrl });
if (TabIndents == 0 && i == 0 && firstCssClass != null)
sbMenu.AppendLine("<li class='" + firstCssClass + "'>" + anchorTag);
else if (TabIndents == 0 && i == (CurrentNode.Count - 1) && lastCssClass != null)
sbMenu.AppendLine("<li class='" + lastCssClass + "'>" + anchorTag);
else if (selected)
sbMenu.AppendLine("<li class='selected'>" + anchorTag);
else
sbMenu.AppendLine("<li>" + anchorTag);
if (CurrentNode[i].Children != null && CurrentNode[i].Children.Count() > 0)
{
sbMenu.Append(Tabs(TabIndents + 2));
sbMenu.AppendLine("<ul>");
LoopChildren(helper, Admin, CurrentNode[i].Children.ToList(), TabIndents + 2, "", "");
sbMenu.Append(Tabs(TabIndents + 2));
sbMenu.AppendLine("</ul>");
}
sbMenu.Append(Tabs(TabIndents + 1));
sbMenu.AppendLine("</li>");
}
}
private static string Tabs(int n)
{
return new String('\t', n);
}
I agree with the comments that string concatenation for this is painful. TagBuilder is a lot less painful for you.
I didn't check your code for problems, but I imagine that what I would do is basically to take the text output from your helper in a good case and a bad case and run them through a diff tool. Leave some markers before and after the point where you call Html.MenuTree() for debugging purposes - this way you will know exactly where the output starts and stops.
The diff tool will tell you what the differences in the two outputs are. Then you can go looking for the cause of these differences.
Another way I would seriously consider approaching this is through unit testing. Start with a simple unit test giving the MenuTree() method a very simple structure to work with. Verify that the output is sane. Then test more complex scenarios. If you during testing, debugging or in production discover a certain combination of input that causes the problem, write a unit test that tests for the correct output. Then fix it. When the test passes, you'll know that you are finished. Also, if you run your tests whenever you change something, you will know that this particular bug will never creep back in.
New bug? New unit test. And so on. Unit tests never solve the problem for you, but they give you the confidence to know that what used to work still works, even when you refactor and come up with cool new stuff.
Related
I want to put the list of deleted comments in the admin section. IsDelete capability also has a comments section. The code works fine, but when I put the where condition, the output value is zero and no comments are displayed.
public ShowCommentUsersForAdminViewModel GetRemovedCommentByAdminSearch(int pageId = 1, string filter = "")
{
//lazy load enable with IQueryable
IQueryable<BlogComment> result = _context.BlogComments
.Include(c => c.Blog)
.Include(c => c.User);
if (!string.IsNullOrEmpty(filter))
{
result = result.Where(u => u.Comment.Contains(filter) || u.User.UserName.Contains(filter) || u.Blog.BlogTitle.Contains(filter));
}
//show Item In Page
int take = 10;
int skip = (pageId - 1) * take;
ShowCommentUsersForAdminViewModel list = new ShowCommentUsersForAdminViewModel();
list.CurrentPage = pageId;
list.PageCount = (result.Count() / take) + 1;
list.BlogComments = result.Where(c => c.IsDelete).OrderBy(u => u.CreateDateComment).Skip(skip).Take(take).ToList();
return list;
}
yes I used this code
modelBuilder.Entity<ProductComment>().HasQueryFilter(b => !b.IsDelete);
Reviewed the code again, and used IgnoreQueryFilters() problem solved.
thanks Ivan Stoev
I'm new to DOORS and DXL scripting (not sure if it'll be needed here or not). What I'm looking to do is create a new column in two separate modules that will copy the Absolute Numbers. I know this sounds simple enough, but what I'm running into an issue with is that I use a tool to convert my modules into a PDF and it combines them into one module before doing so. Doing this messes up the Absolute Numbers and I can't afford to have that happen.
More descriptive:
I have a column that contains the following:
'REQ01: 4' where 'REQ01:' represents the module and '4' represents the Absolute Number. Similarly, I have 'REQ02: 4'. I need to copy those in their respective modules and make sure they don't change after the modules have been combined.
I've tried my hand at some DXL scripting and this is what I came up with:
displayRich("REQ01: " obj."Absolute Number" "")
This appropriately shows the column, but again will not work as the Absolute Number ends up changing when I merge the modules.
Thanks in advanced for your help and I apologize if I left any crucial information out.
Here is the code that ultimately worked for me.
// DXL generated on 11 June 2014 by Attribute DXL wizard, Version 1.0
// The wizard will use the following comments on subsequent runs
// to determine the selected options and attributes. It will not
// take account of any manual changes in the body of the DXL code.
// begin parameters (do not edit)
// One attribute/property per line: true
// Show attribute/property names: false
// Include OLE objects in text: true
// Attribute: Object Text
// end parameters (do not edit)
Module m
AttrDef ad
AttrType at
m = module obj
ad = find(m,attrDXLName)
if(!null ad && ad.object)
{
at = ad.type
if(at.type == attrText)
{
string s
Buffer val = create
Buffer temp = create
ad = find(m,"Object Text")
if(!null ad)
{
probeRichAttr_(obj,"Object Identifier", temp, true)
val += tempStringOf temp
}
obj.attrDXLName = richText (tempStringOf val)
delete val
delete temp
}
}
There's a builtin "Copy Attributes" script which does this. At least in the version installed at my company, it's in the PSToolbox, and also available in the menu PSToolbox/attributes/copy.../betweenattributes... Here you go:
// Copy values from one attribute to another
/*
*/
/*
PSToolbox Tools for customizing DOORS with DXL V7.1
-------------------------------------------------
DISCLAIMER:
This programming tool has been thoroughly checked
and tested at all stages of its production.
Telelogic cannot accept any responsibility for
any loss, disruption or damage to your data or
your computer system that may occur while using
this script.
If you do not accept these conditions, do not use
this customised script.
-------------------------------------------------
*/
if ( !confirm "This script copies values from one attribute to another in the same module.\n" //-
"Use this to assist in changing the types of attributes.\n\n" //-
"It asks you to select the source and destination attributes.\n\n" //-
"It works by ... well, copying attribute values!\n\n" //-
"Continue?"
)
{
halt
}
#include <addins/PSToolbox/utensils/dbs.inc>
const int MAXATTRS = 1000
DB copyAttrValsDB = null
DBE copyAttrValsFrom = null
DBE copyAttrValsTo = null
DBE copyAttrValsConfirm = null
DBE copyAttrValsButt = null
string attrListFrom[MAXATTRS]
string attrListTo[MAXATTRS]
string confirmOpts[] = { "Yes", "Yes to all", "No", "No to all", "Cancel" }
bool confirmDataLoss = true
bool noToDataLoss = false
///////////////////////////////////////////////////////////
// Call-backs
void doAttrValsCopy(DB db) {
// check attributes
string fromAn = attrListFrom[get copyAttrValsFrom]
string toAn = attrListTo [get copyAttrValsTo ]
if ( fromAn == toAn ) {
ack "Cannot copy attribute to itself."
return
}
// get confirmation
if ( !confirm "Confirm copy of attribute '" fromAn "' to attribute '" toAn "'." ) {
return
}
confirmDataLoss = get copyAttrValsConfirm
// do copy
Object o
for o in current Module do
{
Buffer oldVal = create()
Buffer newVal = create()
if ( fromAn == "Object Identifier" ) newVal = identifier(o)
else if ( fromAn == "Object Level" ) newVal = level(o) ""
else if ( fromAn == "Object Number" ) newVal = number(o)
else newVal = richText o.fromAn
oldVal = richText o.toAn
if ( confirmDataLoss && !noToDataLoss && length(oldVal) > 0 && oldVal != newVal )
{
current = o
refresh current
int opt = query("About to lose attribute '" toAn "' = '" stringOf(oldVal) "' on current object.", confirmOpts)
if ( opt == 1 ) confirmDataLoss = false
if ( opt == 2 ) continue
if ( opt == 3 ) noToDataLoss = true
if ( opt == 4 ) return
}
if ( !confirmDataLoss || !noToDataLoss || length(oldVal) == 0 ) o.toAn = richText stringOf(newVal)
delete(oldVal)
delete(newVal)
}
hide copyAttrValsDB
}
///////////////////////////////////////////////////////////
// Main program
int numAttrsFrom = 0
int numAttrsTo = 0
AttrDef ad
for ad in current Module do {
if ( ad.module ) continue
string an = ad.name
if ( canRead (current Module, an) ) attrListFrom[numAttrsFrom++] = an
if ( canWrite(current Module, an) ) attrListTo [numAttrsTo++ ] = an
}
attrListFrom[numAttrsFrom++] = "Object Identifier"
attrListFrom[numAttrsFrom++] = "Object Level"
attrListFrom[numAttrsFrom++] = "Object Number"
copyAttrValsDB = create "Copy attribute values"
copyAttrValsFrom = choice(copyAttrValsDB, "From:", attrListFrom, numAttrsFrom, 0)
copyAttrValsTo = choice(copyAttrValsDB, "To:", attrListTo, numAttrsTo, 0)
copyAttrValsButt = apply (copyAttrValsDB, "Copy", doAttrValsCopy)
copyAttrValsConfirm = toggle(copyAttrValsDB, "Confirm on loss of data", true)
show copyAttrValsDB
I'm developing an application with ASP.NET MVC 4 and I wanna my URLs look like below :
http://www.mysite.com/Product/Category/2/دربهای-چوبی
to do that I'm using this kind of URL pattern in Global.asax :
routes.MapRoute(
name:"ViewProduct",
url:"Product/Category/{Id}/{productName}",
defaults: new { controller = "Product", action = "Category", id = UrlParameter.Optional, productName = "" }
);
and in my view I'm using generating my URLs with this way :
#item.Name
as you can see I'm create extension method called ToFriendlyUrl :
public static string ToFriendlyUrl(this UrlHelper helper,
string urlToEncode)
{
urlToEncode = (urlToEncode ?? "").Trim().ToLower();
StringBuilder url = new StringBuilder();
foreach (char ch in urlToEncode)
{
switch (ch)
{
case ' ':
url.Append('-');
break;
case '&':
url.Append("and");
break;
case '\'':
break;
default:
if ((ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'z'))
{
url.Append(ch);
}
else
{
url.Append('-');
}
break;
}
}
return url.ToString();
}
above way works correctly for English URLs(productName's segment) :
http://www.mysite.com/Product/Category/3/category-type-one
but for Persian URLs I'm getting this URL for example :
http://www.mysite.com/Product/Category/1/--------------
for example second one should have be:
http://www.mysite.com/Product/Category/2/دربهای-چوبی
How can I change the code to work with Persian URLs?
PS : I know in Persian language we don't have lower and upper cases and I omitted this line of code :
urlToEncode = (urlToEncode ?? "").Trim().ToLower();
But still I've same problem.
Thanks
This if
if ((ch >= '0' && ch <= '9') ||
(ch >= 'a' && ch <= 'z'))
means only English numbers and characters are allowed. But Persian characters have a different range:
public static bool ContainsFarsi(string txt)
{
return !string.IsNullOrEmpty(txt) &&
Regex.IsMatch(txt, #"[\u0600-\u06FF]");
}
+
Don't use that ToFriendlyUrl method. Create an extension method to apply your filtering and then use the standard Html.ActionLink method and pass your parameters as new route values or it's better to use the T4MVC HTML helper methods.
By constructing your links manulally, you will lose a lot of features like adjusting the root path based on the current domain or sub domain and also encoding the special characters and many outer built-in features.
I'm getting confused with this and I know there will be a more slick way of starting it off. The 'result' variable has many records and I want to check if IN_SiteId is > 0 and filter on it, same after that for LandownerId and PaymentCategoryId etc. If I can get the right approach for the first 2 I will be ok from there. This should be easier but having a brick wall day. Any comments appreciated
public IQueryable rptRentPaidMonthly(int IN_SiteId, int IN_LandownerId, int IN_PaymentCategoryId, int IN_PaymentTypeId, string IN_ShowRelevantProportion)
{
var result = this._lmsDb.rptRentPaidMonthly(IN_daysFrom, IN_daysTo, IN_SiteId, IN_LandownerId, IN_PaymentCategoryId, IN_PaymentTypeId, IN_ShowRelevantProportion);
if (IN_SiteId > 0)
{
var searchResults = (from s in result
where (s.SiteId == #IN_SiteId)
select s);
return searchResults.AsQueryable();
}
return result.AsQueryable();
}
I'm not a LINQ expert but I think you can do something like this:
public IQueryable rptRentPaidMonthly(int IN_SiteId, int IN_LandownerId, int IN_PaymentCategoryId, int IN_PaymentTypeId, string IN_ShowRelevantProportion)
{
var result = this._lmsDb.rptRentPaidMonthly(IN_daysFrom, IN_daysTo, IN_SiteId, IN_LandownerId, IN_PaymentCategoryId, IN_PaymentTypeId, IN_ShowRelevantProportion);
var searchResults = (from s in result
where (IN_SiteId <= 0 || s.SiteId == IN_SiteId)
&& (IN_LandownerId <= 0 || s.LandownerId == IN_LandownerId)
&& (IN_PaymentCategoryId <= 0 || s.PaymentCategoryId == IN_PaymentCategoryId)
&& (IN_PaymentTypeId <= 0 || s.PaymentTypeId == In_PaymentTypeId)
select s);
return searchResults.AsQueryable();
}
The where clause checks if each filter value is less than or equal to 0, if so then it will return true and will not evaluate the next bit which attempts to filter the actual field on the value provided.
I have some problems with LINQ and maby someone got answers
string[] roleNames = Roles.GetRolesForUser(currentUserName);
result = context.MenuRoles.Select(mr => new MenuGenerateViewModel
{
MenuID = mr.MenuID,
MenuNazwa = mr.Menu.MenuNazwa,
MenuKolejnosc = mr.Menu.MenuKolejnosc,
MenuStyl = mr.Menu.MenuStyl,
MenuParentID = mr.Menu.MenuParentID,
MenuActive = mr.Menu.MenuActive,
MenuActionName = mr.Menu.MenuAction.MenuActionName,
MenuControlName = mr.Menu.MenuControl.MenuControlName,
RoleName = mr.Role.RoleName,
RoleID = mr.RoleID,
MenuID = mr.MenuID
})
.Where(mr => mr.MenuActive == true)
.ToList();
How to take only compare string[] roleNames and return only if match. Problem alwais is when user is in the 2 or more roles.
Tx for answers
If I understand what you are asking for, add a second condition to your Where clause:
.Where(mr => mr.MenuActive && roleNames.Contains(mr.Role.RoleName))
You would be better off switching round your Where clause and Select for the simple reason that then you will not be retrieving from the database records which are not required.
result = context.MenuRoles.Where(mr => mr.MenuActive
&& roleNames.Contains(mr.Role.RoleName))
.Select(mr => ... )
.ToList();
This will generate a sql which only selects the necessary records, instead of selecting the whole lot and then filtering it. Try it and watch SQL profiler to see the difference (useful skill in any case when using EF)
With the help of brilliant people here, reached the target.
string[] roleNames = Roles.GetRolesForUser(currentUserName);
result = context.MenuRoles
.Where(mr => mr.Menu.MenuActive && roleNames.Contains(mr.Role.RoleName))
.Select(mr => new MenuGenerateViewModel
{
MenuID = mr.MenuID,
MenuNazwa = mr.Menu.MenuNazwa,
MenuKolejnosc = mr.Menu.MenuKolejnosc,
MenuStyl = mr.Menu.MenuStyl,
MenuParentID = mr.Menu.MenuParentID,
MenuActive = mr.Menu.MenuActive,
MenuActionName = mr.Menu.MenuAction.MenuActionName,
MenuControlName = mr.Menu.MenuControl.MenuControlName,
RoleName = mr.Role.RoleName
})
.ToList();
var userresult = context.MenuUsers
.Where(mr => mr.Menu.MenuActive && mr.User.Username == currentUserName)
.Select(mr => new MenuGenerateViewModel
{
MenuID = mr.MenuID,
MenuNazwa = mr.Menu.MenuNazwa,
MenuKolejnosc = mr.Menu.MenuKolejnosc,
MenuStyl = mr.Menu.MenuStyl,
MenuParentID = mr.Menu.MenuParentID,
MenuActive = mr.Menu.MenuActive,
MenuActionName = mr.Menu.MenuAction.MenuActionName,
MenuControlName = mr.Menu.MenuControl.MenuControlName,
Username = mr.User.Username
})
.ToList();
Here, gets all the menu to which you have the right, both through group membership and assigned directly to the menu itself.
// Kick all duplicates
var noduplicates = result.Concat(userresult)
.Distinct(new RoleMenuGenerateComparer());
Because usually we do not want duplicates in the menu so we remove them. For this to work properly we need to implement IEqualityComparer (U can read about this little bit up)
public class RoleMenuGenerateComparer : IEqualityComparer<MenuGenerateViewModel>
{
public bool Equals(MenuGenerateViewModel x, MenuGenerateViewModel y)
{
//Check whether the compared objects reference the same data.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether the products' properties are equal.
return x.MenuNazwa == y.MenuNazwa && x.MenuID == y.MenuID;
}
public int GetHashCode(MenuGenerateViewModel menuGenerateViewModel)
{
if (Object.ReferenceEquals(menuGenerateViewModel, null)) return 0;
int hashMenuName = menuGenerateViewModel.MenuNazwa == null ? 0 : menuGenerateViewModel.MenuNazwa.GetHashCode();
int hashMenuID = menuGenerateViewModel.MenuID == null ? 0 : menuGenerateViewModel.MenuID.GetHashCode();
return hashMenuName ^ hashMenuID;
}
}
Of course I believe that you can optimize this code, but for the moment I have something like this.
Tx all for help.