Reputation: 519
VERY new to C#, using Visual Studio 2010. In my old PHP, to build this menu, what I would do is have 2 arrays of hyper-links. Level1 array would be displayed if a 'requester' logs in, Level1 AND Level2 array would be merged and sorted and displayed if an 'administrator' logs in. In C#, on my default page (which has a Site.Master page), I find myself doing this:
case NewCourseRequestView.Administrator:
if (access.Administrator)
{
UserTypeLabel.Text = "Administrator Details:";
AdministratorMenuList.Items.Add("View Un-Approved Requests");
adminContent.Visible = true;
}
break;
case NewCourseRequestView.Requestor:
if (access.Requestor)
{
UserTypeLabel.Text = "Requester Details:";
RequestorMenuList.Items.Add("Request A New Course");
RequestorMenuList.Items.Add("View My New Course Requests");
requestContent.Visible = true;
}
break;
How can I
I need a fairly specific explanation, thanks!
Upvotes: 1
Views: 370
Reputation: 898
I suggest you to start from the design first, no matter what language you're using. Imagine you will need to add one more User Type (for example, "Guest") which has different set of menu items. What needs to be done then? Adding one more "if" statement and, basically copy-pasting. This is not good at all. And now imagine what happens when you have 10 user types. Your switch-case statement will become giant monster and everyone in your team (even you) will be afraid to add a new Item there or change an existing one.
Ok, that was sad, let's have some fun now :)
Ideally, your "client" code, which I suppose you're going to place into the Page_Load method will have only one line:
protected void Page_Load(object sender, EventArgs e){
BuildMenu();
}
How cool it is to have only one line in your method and have everything "magically" sorted out.
Let's say that we have some kind of a policy which regulates available items for our user types. The basic interface is pretty simple:
public abstract class MenuItemsPolicy{
public abstract List<MenuItem> GetMenuItems();
protected virtual MenuItem CreateMenuItem(string text, string url){
//add parameter checks, etc.
MenuItem item = new MenuItem();
item.Text = text;
item.NavigateUrl = url;
return item;
}
}
At the moment, we have two user types, so we will have exactly two implementations. The Requestor:
public class RequestorMenuItems : MenuItemsPolicy{
public override List<MenuItem> GetMenuItems(){
return new List<MenuItem>(){
CreateMenuItem("Request A New Course", "~/Courses/RequestNewCourse.aspx"),
CreateMenuItem("View My New Course Requests", "~/Courses/ViewMyCourseRquests.aspx")
};
}
}
And an Administrator (pay attention where the "merge" is)
public class AdministratorMenuItems : MenuItemsPolicy{
public override List<MenuItem> GetMenuItems(){
List<MenuItem> resultingMenuItems = (new RequestorMenuItems()).GetMenuItems();
resultingMenuItems.Add(CreateMenuItem("View Un-Approved Requests", "~/Administration/ViewUnAprroved.aspx"));
return resultingMenuItems;
}
}
As you can see, we retrieve Requestor's items and then adding one more item there. If this needs to be changed in future, your client/calling code will know nothing about the implementation details because you depend on abstract entity. That means you'll need no changes in your client/calling code the rules will be changed; or if available links for a particular user type will need to be changed in future.
To simplify the example, I have used enum for User Types:
public enum UserAccessType{
Requestor = 0,
Administrator = 1
}
And now, let's have a look on the implementation. I have placed a simple and standard asp.net menu control onto the page:
<form id="form1" runat="server">
<asp:menu runat="server" Id="mainMenu"></asp:menu>
<div>
And Here's the "codebehind":
public partial class _Default : System.Web.UI.Page{
//We will initialize this variable a bit later
//for example from URL parameter ?userType=1 or something like that
private UserAccessType _currentUserAccess;
//Ideally, dictionary should be a member of a class
//I left a simple dictionary just to avoid over-complicaation
private readonly Dictionary<UserAccessType, MenuItemsPolicy> _userMenuItems = new Dictionary<UserAccessType, MenuItemsPolicy>();
protected override void OnInit(EventArgs e){
//Associating our user types with Menu Items
_userMenuItems.Add(UserAccessType.Administrator, new AdministratorMenuItems());
_userMenuItems.Add(UserAccessType.Requestor, new RequestorMenuItems());
//Init the Current User / Access Type. For example, you can read the value from Request["UserType"]
//Here I've just assigned a value
_currentUserAccess = UserAccessType.Administrator;
base.OnInit(e);
}
protected void Page_Load(object sender, EventArgs e){
BuildMenu();
}
private void BuildMenu(){
mainMenu.Items.Clear();
var menuItems = GetMenuItems();
foreach(var item in menuItems){
mainMenu.Items.Add(item);
}
}
private List<MenuItem> GetMenuItems(){
MenuItemsPolicy currentPolicy = _userMenuItems[_currentUserAccess];
return currentPolicy.GetMenuItems();
}
}
I hope, the example above answers your questions. The example, of course, is not ideal. However, it helps to keep your code relatively clean and maintainable.
P.S. I do recommend you to go through the article: http://objectmentor.com/resources/articles/Principles_and_Patterns.pdf
refer to the OCP example - you'll see, how close it is to your situation.
Thanks for reading :)
Upvotes: 1
Reputation: 408
The first thing i would suggest is that you need an object as the type for your collections, these objects will have a URL and a Text properties.
class UrlText : IEquatable<UrlText>
{
public string Url { get; set; }
public string Text { get; set; }
public UrlText(string url, string text)
{
this.Url = url;
this.Text = text;
}
#region IEquatable<UrlText> Members
public bool Equals(UrlText other)
{
return this.Url.Equals(other.Url);
}
#endregion
}
now you continue with your code as you have put it, with slight modifications:
string baseUrl = "http://localhost/myapp";
case NewCourseRequestView.Administrator:
if (access.Administrator)
{
UserTypeLabel.Text = "Administrator Details:";
AdministratorMenuList.Items.Add(new UrlText(baseUrl + "/viewunapproved.aspx", "View Un-Approved Requests"));
adminContent.Visible = true;
}
break;
case NewCourseRequestView.Requestor:
if (access.Requestor)
{
UserTypeLabel.Text = "Requester Details:";
RequestorMenuList.Items.Add(new UrlText(baseUrl + "/RequestCourse.aspx","Request A New Course"));
RequestorMenuList.Items.Add(new UrlText(baseUrl + "/viewNewRequest.aspx","View My New Course Requests"));
requestContent.Visible = true;
}
break;
Finally, whenever you will want to merge the collections, do something simple like this:
public static List<UrlText> MergeLists(List<UrlText> listAdmin, List<UrlText> listUser)
{
List<UrlText> result = new List<UrlText>();
foreach (UrlText myMenuItem in listAdmin)
{
result.Add(myMenuItem);
}
foreach (UrlText myMenuItem in listUser)
{
if (!result.Contains(myMenuItem))
result.Add(myMenuItem);
}
return result;
}
and use it with (the equals method implemented from the interface allows us to use correctly the Contains method for the list):
mergedList = MergeLists(AdministratorMenuList, RequestorMenuList);
Upvotes: 1