If you’re anything like me the thought of recursion can be quite scary and the fact that you still decided to read this post is impressive. Also before I start I want to point out that this is by far the most difficult thing I have done since I started programming. The problem I faced was that we needed to make a page that allowed the user to group rules together just like an if statement using AND and ORs to join the rules. The page needed to be able to support as many levels deep of grouping the user wished to go although in reality I find it hard to believe a user going more than two or three levels deep.
There was already a service in place that evaluated the rules and a data type that supported the recursive nature of the rules. There was just never a UI that allowed the rules to be grouped like this so really most of the difficult stuff was already taken care of. To understand the solution I will have to explain the type a little bit. I probably won’t be displaying a lot of code so I will do my best to explain all relevant details. To start we had a base type and two derived types, one for the ANDs and ORs and one for the criteria, like first name equals John. The ANDs and ORs had a list that was of the base type which allowed it to have lists of other ANDs and ORs which allowed for the recursion.
I thought a lot about how to implement page. I decided to go with user controls and repeaters not sure if it would work but i thought it offered the approach with the least code. A colleague had already given me the code to fill the data type so that was nice. I modified the base type to have a guid property for an id and a boolean property to indicate if the type was editable, yes if it wasn’t hard enough only some of the types would be editable at a time. The users would add and delete rules until the hit a save button which would then cause existing rules to not be editable and only allowed to be removed.
I started by created the first user control with a table with two rows. This control would be used to show the AND and OR criteria. The first row had a label the prompted them for the operand they wanted to add or the current AND or OR. It also had a drop down populated with AND and OR along with the properties they would be able to compare such as first and last name. Finally there was a button to add the Type to the existing types. The second column simply contained a repeater with a placeholder control in it. Next I created another user control, this one would be used to display the criteria that would be the values we were comparing.
Repeater’s onItemCreated method:
protected void dlRules_ItemCreated(object sender, DataListItemEventArgs e)
{
PlaceHolder holder = (PlaceHolder)e.Item.FindControl("phControl");
if (e.Item.DataItem is AndCriteria || e.Item.DataItem is OrCriteria)
{
Private_PolicyManagement_Controls_TestControl ctl = (Private_PolicyManagement_Controls_TestControl)LoadControl("~/Private/PolicyManagement/Controls/TestControl.ascx");
ctl.ID = ((Criteria)e.Item.DataItem).ID.ToString();// "ctl1";
ctl.Init += new EventHandler(ctl_Init);
ctl.RemoveCriteria += new EventHandler<CustomEventArgs>(ctl_RemoveCriteria);
ctl.Criteria = (Criteria)e.Item.DataItem;
ctl.AddNewCriteria += new EventHandler<CustomEventArgs>(ctl_AddNewCriteria);
holder.Controls.Add(ctl);
}
else
{
Private_PolicyManagement_Controls_TestControl2 ctl = (Private_PolicyManagement_Controls_TestControl2)LoadControl("~/Private/PolicyManagement/Controls/TestControl2.ascx");
ctl.ID = ((Criteria)e.Item.DataItem).ID.ToString();
ctl.RemoveCriteria += new EventHandler<CustomEventArgs>(ctl_RemoveCriteria);
ctl.DisplayData = (OrderCharacteristicCriterion)e.Item.DataItem;
holder.Controls.Add(ctl);
}
}
code from control2’s set property
public OrderCharacteristicCriterion DisplayData
{
set
{
if (value != null)
{
lbRemove.CommandArgument = value.ID.ToString();
if (value.IsEdit)
{
lblOperand.Visible = lblValue.Visible = false;
ddlComparer.Visible = ddlValue.Visible = true;
lblCharacteristic.Text = value.FriendlyName;
BindValueDropDown(value.Name);
if (value.Comparison == PricingEngineLib.Logic.Comparer.Comparison.Eq)
ddlComparer.SelectedIndex = 0;
else
ddlComparer.SelectedIndex = 1;
if (!string.IsNullOrEmpty(value.Value) && !string.IsNullOrEmpty(value.FriendlyValue))
{
ddlValue.SelectedValue = string.Format("{0}||{1}", value.Value, value.FriendlyValue);
}
}
else
{
lblCharacteristic.Text = value.FriendlyName;
lblOperand.Text = value.Comparison.ToString();
lblValue.Text = value.FriendlyValue;
}
}
}
}
Still with me? Good cause here is where the fun starts. Since I needed to store the data until the user clicked save I decided to use ViewState. Since some of the drop downs were going to have a lot of values in them I only stored the recursive type in ViewState and not the user controls. The first load was easy, in the init method I got the type and bound the controls. I made a property on the first user control of the base type. I then set the data source of the repeater to this property. In the ItemCreated method of the repeater I used reflection to get the type of object that was being bound to it. If it was an AND or OR I loaded the same user control set the same property and stuck it in the placeholder. Otherwise I loaded the second user control and set its property to display the comparison values. I wasn’t sure you could use the same user control inside of itself which was why I was hesitant with the method I chose but it worked and that’s all that mattered to me. I added a left margin on the first control’s table to get an indent and show the nesting.
Normally that feat would be enough for me but we also needed to capture input when the user added or changed values. This gets pretty hairy so bear with me. I wasn’t sure how to do this since I created the control tree every post back and after ViewState loaded for that matter. I thought this might be a problem since I was raising events in the users controls and wasn’t sure the page would catch the events. This is where those extra properties I added came in handy. In the set method of the second controls property I changed the ID of the user control to include the guid of the data type I was setting it to. Also in the ItemCreated method of the repeater I needed to bind the controls to events on the controls to raise events when an item was added or deleted. If the user clicked the add or delete button the page caught the event and ran a function that recursively went thru the type and found the appropriate criteria to add under or to remove. To do this I created a custom EventArg that I used to pass in the guid of the type i needed to act on. For the events caused by the auto postback of the drop downs of the values being edited i had a similar function that went thru the type but this function also went thru all of the values in the Request.Form object and split the values based on the ids of the drop downs that I changed looking for the appropriate values to act on.
Unfortunately after all that work we decided to go with a coworkers implementation that instead of using user controls dynamically added controls to the page while loading the type. It worked the same and just goes to show that there is always more than one way to accomplish things. I apologize if this was hard to follow and for the lack of source. If something is unclear or you want to see more source let me know. Hope you enjoyed.
The other day at work I was binding a list of items to a DataList. Usually with the data bound controls such as a GridView you can either use the BoundField column or you can style your own columns using TemplateFields. In this case I had a user control inside of the item template of the DataList and needed to set a property on the control to the item being bound. Since I started out using Asp.Net 2.0 the syntax I use to bind to properties on objects is either the Eval or Bind tags. These tags are great when you need to bind to an individual property on the object but not so good when you need to bind to the object itself. Now I could have just overrode the RowDatabound event and did this is the code behind but I wanted to do it in the markup since it seemed cleaner.
I wasn’t really sure what to search for but after a little effort I found Container.DataItem. From what I understand this is what was used in 1.1 to bind to the properties of an object. I was able to bind the property of the user control in the markup and go from there.
I know what you’re thinking, why would anyone do that anyway. Believe me, I am one of them, but to understand how I got to this point you need to understand the project I am working on. For some reason our client has a few different web sites and right now we are making changes to one of them. What they wanted us to do was make this site look like one of their other ones. No problem, but when I asked what the other site looked like I was simply told to go to a url. This pretty much meant that I would be getting none of the style or pictures in an email or anything else for that matter which left me the option of “view source” on the browser. So I viewed a few pages to see what would be needed in the master page and to get the location of the styles and images and went from there. I know, this is probably the worst way to do this but the sad thing is if I waited for the client to send that stuff over .Net 5 might come out before I get it. Not to mention the site was done in PHP so I had a hard time knowing what was Html and what was generated on the server.
I got the style of the master page down and started on the content for the home page. I copied the source from the browser and went from there. I then started to focus on changing one thing at a time rather than focusing on the whole page at once. I added some login controls and changed a few links. I assumed there would be a logout link on this page so I added a LoginStatus control to the page. I only added it to test the logout functionality since I didn’t know if the page would have different items showing based on whether or not the user was logged in, I can tell you’re jealous of this style of development already. Logging in worked just as expected but when I clicked the logout link nothing happened. I noticed there was a javascript error so I investigated further. The error was “object does not support property or method”.
I didn’t know what to think. I thought the LoginStatus might be broke but that would be ridiculous. So I added it to another page and it all worked fine. I then thought I would just add a link button and log the user out in the code behind. Still didn’t work. I then thought about how the login button work just fine and confined the problem to just link buttons. Which brings me to the root of the problem.
As you know form generated by Asp.Net contains a few lines of javascript that look like this:
<script type="text/javascript">
//<![CDATA[
var theForm = document.forms['form1'];
if (!theForm) {
theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
theForm.__EVENTTARGET.value = eventTarget;
theForm.__EVENTARGUMENT.value = eventArgument;
theForm.submit();
}
}
//]]>
</script>
since LinkButtons are generated as “a” tags and not submit buttons they call this function to do a post back. The problem with my particular page was that I had not replaced an element that was named “submit” so when the form called submit it was trying to call the element like a method rather than trying to submit the form, crazy stuff. Obviously I was already not happy about having to develop like this so you can imagine how happy I was to spend some more time to dive into this error.
This is directly related to my previous post. I mentioned that I was using RenderTransform to turn some controls sideways. But when I did this is I had to readjust the margins to keep it on the edge of the screen. This works fine for one control I guess, well maybe, but I was rotating more than one control and there needed to be controls next to the controls I was rotating. If I turned a really wide control and a control that wasn’t so wide then there would be a gap between the adjacent controls since the original dimensions would remain.
I then remembered glancing over a transform that would rotate the controls and cause the adjacent controls to respect the boundaries of the rotated control. Instead of using RenderTransform I used LayoutTransform. To explain the difference, if I have two buttons on a page and the height of both is 30 and the width is 100 and I rotate the button on top, the button are in a vertically oriented stack panel by the way. If I used RenderTransform the top button 90 degrees the bottom button will still be where it normally would be but the top button will be under of the left side of the button. But if I used LayoutTransform the new width and height are respected by the other controls.
If you make and window like the one below you will see the difference.
<Window x:Class="RotateTransformTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<StackPanel>
<Button RenderTransformOrigin="0,0" Margin="20,0,0,0" Width="100">
<Button.RenderTransform>
<RotateTransform Angle="45" />
</Button.RenderTransform>
Button 1
</Button>
<Button Width="100">Button 2</Button>
</StackPanel>
<StackPanel>
<Button Width="100">
<Button.LayoutTransform>
<RotateTransform Angle="45" />
</Button.LayoutTransform>
Button 1
</Button>
<Button Width="100">Button 2</Button>
</StackPanel>
</StackPanel>
</Window>
I am only writing about this to justify how long this took me to figure out. Now that I know the relation, or lack thereof, between the two it is trivial. I have been working with some third party controls for WPF that allow panels to be moved around, docked and hidden just like the toolbox and solution windows in visual studio. While they make that easy styling the controls isn’t as straight forward. You need to create a resource dictionary and override templates with certain names. As far as docking goes I needed the text to be either normal if on the top or bottom or have the textblock be “sideways” if the panel was hidden on the side.
I decided that I would use the rotate transform to turn the textblock 90 degrees. I used a RenderTransformOrigin of “0,0” to turn it from the top left. When I viewed the app the text was no where to be found. I then decided to add some margin values. My mistake was that I thought once I rotated the object that the margins also rotated so that the left margin was in effect now the top margin. It turns out that is not the case. Had I would have just made a simple window with one control and not kept changing values in the resource file I would have noticed it in about 3 seconds. I know this because after I realized what was going on I made a simple window like the one below and my button was off the screen. Giving it a left margin of 20 put it the whole way down the side of the window. Code and frustration below.
<Window x:Class="RotateTransformTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Button RenderTransformOrigin="0,0" Margin="20,0,0,0">
<Button.RenderTransform>
<RotateTransform Angle="90" />
</Button.RenderTransform>
test
</Button>
</StackPanel>
</Window>
Over the last couple months I have been developing a site using Asp.Net MVC. For the most part I have enjoyed the experience but you definitely take some things for granted that are built into Asp.Net Web Forms. one of those things is the paging support that is built into the Gridview. MVC does have Html helpers for things like textboxes and drop downs but there is no built in support for paging.
Now that you have a little background on MVC, the problem I was facing was that we had several pages that were going to display results in lists and we needed a way for the user to be able to page through that data. While I probably could have found something written by someone else I decided to try and do this on my own. To accomplish this I decided to make an Html extension method. At the very least I needed to pass in the current page, the size of each page, and the total amount of records. I also chose to pass in the search criteria since i since i am passing the search parameters in the query string.
The first thing I did was find the total amount of pages by dividing the total by page size. if there was only one page I simply returned an empty string. Next I set an index based on the current page number and total pages. I needed to check to see if we were close to the end of the page count so that if there would be at most four links before the last page number.
After that I looped through creating the links that will be used for the paging extension. I hard coded a value of five so that the maximum amount of pages is never greater then five and i also try to keep every number but 1, 2 and the last two pages in the sequence in the middle. To create the links I created a Url helper by passing in the request context of the current helper. This is necessary in order to make the links that the pager will link to since we can’t create normal links in MVC because it does not go directly at the file system. I find this to be better than the paging in web forms because I can make the links contain the page number and search criteria in the query string. This way when you click on a pager link and then hit the refresh button it doesn’t give you a message asking if you want to re-post the page. Below is an example of the code used to create this extension. This is only a simple example so please don’t be too critical.
public static string Pager(this HtmlHelper helper, int currentPage, int pageSize, int total, string criteria)
{
UrlHelper u = new UrlHelper(helper.ViewContext.RequestContext);
double totalPages = total * 1.0 / pageSize;
// round total pages up to the nearest integer since we cant have something like 10.5 pages
totalPages = (int)Math.Ceiling(totalPages);
if (totalPages == 1)
return string.Empty;
int count = 0;
int curIndex = 1;
// needed for when we are toward the end of the page count
// the else ensures that the current page will usually showup in the middle
// with the exception of the first two pages
if (currentPage == totalPages)
curIndex = currentPage - 4;
else if (currentPage == totalPages - 1)
curIndex = currentPage - 3;
else
curIndex = currentPage - 2;
TagBuilder t = new TagBuilder("span");
StringBuilder sb = new StringBuilder();
while (count < 5 && curIndex <= totalPages)
{
if (curIndex < currentPage && curIndex > 0)
{
sb.Append(CreateLink(curIndex, criteria, u));
sb.Append("|");
count++;
}
else if (curIndex > currentPage && curIndex <= totalPages)
{
sb.Append(CreateLink(curIndex, criteria, u));
sb.Append("|");
count++;
}
else if (curIndex == currentPage)
{
sb.AppendFormat("<span>{0}</span>|", currentPage);
count++;
}
curIndex++;
}
if (sb.Length > 1)
sb = sb.Remove(sb.ToString().LastIndexOf('|'), 1);
t.InnerHtml = sb.ToString();
return t.ToString();
}
private static string CreateLink(int currentPage, string criteria, UrlHelper u)
{
TagBuilder t = new TagBuilder("a");
string address = u.Action("<Action>", "<Controller>", new { page = currentPage, startsWith = criteria });
t.MergeAttribute("href", address);
t.SetInnerText(currentPage.ToString());
return t.ToString();
}
Welcome to my blog. While I am not too sure about what type of stuff I will be blogging about, I hope that by visiting you get to know me, my work, and possibly the people closest to me a little better. Thanks for visiting.
I also want to send out a thank you to Nathan for setting this up for me. Thanks Nathan!!