# Monday, July 27, 2009

Add-ADPermission with Exchange 2007 databases

http://technet.microsoft.com/en-us/library/aa996343.aspx discusses means to grant access to mailboxes. The Console can grant it to individual mailboxes, but what if you want the whole kit and kaboodle? They mention using the Add-ADPermission like this from the Shell:

Add-ADPermission -Identity "Mailbox Store" -User "Trusted User" -ExtendedRights Receive-As

This seem to be fairly straightforward. For example:

Add-ADPermission -Identity "myServer\mySG\myDB" -User "myDomain\my.name" -ExtendedRights - Receive-As

But if you do that you get yelled at:

Add-ADPermission : myServer\mySG\myDB was not found. Please make sure you have typed it correctly.
At line:1 char:17
+ ADD-ADPermission  <<<< -Identity "myServer\mySG\myDB " -User "myDomain\my.name" -ExtendedRights Receive-As

The trick here is that in this case the "Mailbox Store" means something different than every other time I have run across that phrase. In this case it is looking for the AD Distinguished Name:

[PS] C:\Windows\System32>add-adpermission -identity "CN=InformationStore,CN=EX07ServerName,CN=Servers,CN=Exchange Administrative Group,CN=Administrative Groups,CN=Our Company,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=myDomain,DC=com" -User "myDomain\my.name" -ExtendedRights Receive-As

That works. Kind of intuitive, no? No? Well here is a way to find that beast:

1 - Install ADSI Edit (if you have not already) http://technet.microsoft.com/en-us/library/cc773354%28WS.10%29.aspx

2 - Open up "Configuration (NOT Domain) by selecting it in the "Select a well known Naming Context

3 - Drill down to (ready, take a breath)

  • Configuration
  • Your domain
  • CN=Services
  • CN=Microsoft Exchange
  • CN=%Organization Name as stored in Exchange%
  • CN=Servers
  • CN=%Server Name that has the database%
  • CN=%Mailbox Storage Name%
  • CN="Database% (optional)
  • Right Click and select 'Properties'

4. What you need to know is stored in distinguishedName. You can double-click and it will popup a textbox (as shown below). You can copy that, just DO NOT DELETE IT!!! This will give you the information you need to supply in the -Identity entry. You can also select a particular database if you so choose.

If you have been observant you will note that the DistinguishedName (which is what is passed into the -Identity variable) matches the path you drilled down. So theoretically, you do not need to go through this. Your entry should be something like:

CN=InformationStore,CN=%Exchange Server That Has Databases%,CN=Servers,CN=Exchange Administrative Group,CN=Administrative Groups,CN=%Your Exchange Organizational Name,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=%Your Domain%,DC=%your DomainExtension"

Good luck!

# Thursday, July 16, 2009

Windows 2008 and the User Account Control and Clustered drives...

NOTE: This is NOT an issue with clustering, but appears to be an issue with w08 (and w08r2) regardless of whether the drive is clustered or local. For more info look here -> http://www.myfriedmind.com/techBlog/2009/10/14/UACAndDomainAdminsPermissionsIssueOnWindows2008.aspx

============ The information below is misleading - see the above link for correction

 

Another addition to w08 that might trip you up is the use of the User Account Control (or UAC) to prevent Administrator accounts (other than the default created one) from doing anything useful (unless prompted). Connect that with the fact that you can only sign onto a machine once per account (see this) and you have a case where you have to log on as the non-default Administrator but are hampered in doing your work.

Put aside the annoying popups (are you SURE you want to see the security permissions? Really? Really?) there is are more serious issues. Case in point - we have a Cluster server with the Role of File Services. Logged on as a lowly Domain Admin I can not get to the actual drive that it is sharing. Let me state that again clearly

  1. I am working on a Clustered w08 server with the Role of File Services
  2. I am logged on with a Domain Admin account (but not with the default Administrator account)
  3. UAC is turned on
  4. I can NOT access the drive(s) (much less the shares) that the Cluster uses


I don't even get a chance to say that "YES, I WANT TO ACCESS THAT FOLDER" which you normally get with UAC, just a big red X.

What are the possible choices? It seems that there are two:
  1. Always use the default Administrator account when logging on to a Clustered w08 account. This always gives you access.
  2. Turn off UAC ON ALL CLUSTERED SERVERS (since if it is not turned off on the host server, whichever one that is, you are going to run into the same problem).
I prefer #2 since (hopefully) the only people who will EVER be logging directly onto your server are Administrators anyway. Once the UAC is turned off you will be able to access all the appropriate folders, etc. Note that changing the UAC setting requires a reboot (one of the few things that still does in Windows - yeah!) so I would suggest you do it on the non-active nodes first so you are not constantly moving your active node from one node to the next.

I am not sure firstly, why this happens; and secondly, why there is no prompt to override it (I am, after all, a Domain Admin and therefore in the Administrators group of the servers) but it does happen. There is no way that I am aware of to set UAC to allow groups, or even to add more people. It is on (and only the default Administrator account can do the work) or it is off.

Hope this helps...

Note: MSoft reports that this is unique (or at least they have never heard of it). One interesting note - I can run the Cluster Configuration Validator even logged in as a non-default Admin with UAC turned on. Go figure...


# Wednesday, July 15, 2009

HP Laserjet 8060, Digital Sending, and Network Folder setup resolution (in Windows 2008)

I am moving our old windows 2003 clustered file share over to a brand, spanking new w08 clustered file share. There are things to note about the differences (another name, another IP???) but the thing I want to note about today had to do with one of our Multi-Function Printers, specifically the HP 8060.

We have that beast setup so that staff can scan documents and it gets sent to our public folder where they can get it. I guess when it was setup they were having trouble with specifying the network share and so my cohorts were advised to use the IP address of the share ->
\\10.10.10.10\public\scanner\folder.

This worked.

HOWEVER............

There always seems to be something that throws a wrench in the works and the wrench in this case is that in w08 you CAN NOT get to a share via IP address, but only by the name of the server. Let me state that again:

\\myserver\myshare\myfolder appears
\\10.10.10.10\myshare\myfolder does not

It seems that in w08 clustered file shares do not share on IP addresses. I have not mucked with this to see if there is a way around it, but out of the box there is no way to get to a shared folder via IP address. This, of course, means that the HP 8060 is throwing a hissy fit.

I was poking around in the Networking settings for the printer when I noticed that under the TCP/IP settings, in the Network Identification tab there was no entry for the DNS Suffixes. So I added our domain extension (ourdomain.com) and it worked!

Just to note, I had previously tried mapping \\myserver.ourdomain.com\myshare\myfolder on the printer and that did not work, but this did.

Hope that helps...
m

# Tuesday, July 14, 2009

Windows 2008 Administrator Remote Access vs Windows 2003 Administrator Remote Access

I funny thing happened to me this morning. I was remotely connected to one of our w08 servers with our standard Administrator account when suddently my session came to a sudden end. I knew what must have happened, and sure enough one of my cohorts had signed on remotely to that server.

At first I thought it might be a limitation in w08 that you could now only have a single Remote Access connection, but I quickly realized that that was not the case. Instead the new tweak in w08 is that you can only sign on ONCE per account. So, when my cohort signed on with the same username it booted me off and handed my session over to him. This is new to w08, in w03 you could have sign on more than once with the same account and run different sessions.

The takeaway seems to be that you are going to have to have multiple Domain Admin accounts, probably assigning one per administrator. This will mean that you can have better security auditing (hopefully) but it also means that you will have more accounts that can do more damage.

Note that with the addition of User Account Control turned on by default this may restrict some critical tasks (see here).

Not a bad thing to have added to w08, just something to be aware of...

# Thursday, July 09, 2009

Ajax History - a how to - Part 4 - final notes

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern

There is some clean up I need to do in regards to my posts for Ajax History.

Where can I see an example?

You can download the zipped files here -> AjaxExamples.zip or go to http://www.myfriedmind.com/AjaxExamples and poke around

What version of .net do I need?

I want to reiterate that you must be using .net 3.5 (or higher) for this to function. The methods and properties that are used are packaged into .net 3.5 (as is Ajax itself).

How long does my history last (ie going back/forward)?

Your history lasts only as long as you are on that particular page. You can go forward and backward over your Ajax history all you want, but once you got to a different page, either preceding or following, you lose your pathway. This does not mean that if you get one of the intermediate URIs either through bookmarks, links, manually typing, etc that it will not return that particular page. It will. But the tracking of the entries in your browser history will be lost.

This also means that if the user modifies the URI manuallyto change what is in the has, it will lose that history since it considers you having gone to a new page.

What if I need to keep a perfect snapshot of the page itself?

If that is the case then you are going to need to look at a different way to store/retrieve the data. You will probably need to store each page, as it appears, into the database and then recall it from there. Do NOT store it in the History Points Remember, what is stored in the History Points must be tiny!

Why does Opera NOT WORK???

http://www.myfriedmind.com/techBlog/2009/09/21/Opera9x10xFailingOnAjaxHistoryAndTheHackToFixIt.aspx (thanks to Tomi for discovering this issue)

Ajax History - a how to - Part 3 - complex example (c#)

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern
Extra Bonus - Issues with Opera

This is the third in a series looking at using Ajax History in .net 3.5. I would recommend you look at Part 1 and Part 2 if you have not already done so.

Ajax out of the box is wonderful in that it reduces page reloads, but it is not wonderful in that it does not treat each Ajax call as a unique URI. As a result all Ajax modified changes are lost when the Back or refresh button are clicked. In addition there is no way to store the URL of the page in such a way that it will display the results of Ajax changes. I explain much of this in Part 1. In Part 2 I provide (very) basic example of storing/retrieving Ajax changes. However in the real world we do not live with simple pages. Most of what we want to reconstruct are complex systems. This post will attempt to give a real-life example of such a functionality.

Many people use mapping pages as an example of how to do Ajax history functionality. That is very nice because, especially for retrieving the pages, because the world generally changes very little geographically. Longitude and Latitude have been fairly constant since the early explorers. GPS does not change from year to year. So it is the perfect example of how you can use a small portion of data (GPS coords for example) to return a large complex data (a map). This is similar to the Memento pattern (take a look at http://www.myfriedmind.com/techBlog/2009/05/20/AjaxHistoryAndTheMementoPattern.aspx for more on my thoughts on that).

Using a Memento pattern is critical because if you were to try to store all that complex data in the Ajax history it would quickly fall to pieces.

So, rule #1 - do not store complex data, store simple data that can be used to reconstruct complex data

While racking my brain for what to use as a non-map example, I came across a post by Senthilkumar Moorthy on forums.asp.net in which he lays out an interaction using Ajax that I thought would be a useful example since it uses multiple history points

The page layout

For simplicity sake I am keeping their names fairly generic and not adding a lot of extra (fun) code...

  • There are two UpdatePanels (upPanel1 and upPanel2).
  • upPanel1 contains a TextBox (textBox1), a Button (button1) and a DropDownList (ddl1)
  • when the user enters in phrase in textBox1 and clicks button1, ddl1 is populated with subchoices
  • when the user clicks on ddl1 to select a choice, upPanel2 displays the details of the selected item in a handful of labels

It should be noted that I am using the Adventure Works (light) database as my source of data.

  1. I will use textbox1 to search through ProductDescription and return a list of possible matching items into ddl1.
  2. Selecting an item from ddl1 will then display information about that particular item in the details pane.

I have also included a class in the page called ProductInformation.

public class ProductInformation
{
      
public int ProductId { get; set; }
      
public string Name { get; set; }
      
public string Model { get; set; }
      
public string Description { get; set; }
      
public DateTime LastModified { get; set; }
}

The history point events

The first thing to consider is where we want to put our history points. Ie, where do we want, if the user clicks 'Back' to roll them back to. Also, what do we want, if they post the URI, to display. It seems to me that there are clearly two logical places and these all take place when events occur (which is a good way to start)

  1. when the user clicks button1 to populate ddl1
  2. when the user selects an item from ddl1 and the detail is displayed

First off, note that we are storing three connected, but different points of data, as opposed to the single point in the simple example of Part 2. In addition we are having to store the Page Title (more on that later) because of an apparent issue with the way that .net handles that.

The caveats

The biggest issue here is that data changes. Here are a few possibilities

  1. the entry of the textbox1 is no longer valid and returns no matching items
  2. the selected item of ddl1 is no longer valid and returns no matching product
  3. the details in upPanel2 have changed since the last update

Again, if this were simply mapping, #1 and #2 (changes in basic data) would not be an issue. And since most people want the most up-to-date maps, #3 does not matter. However, we are going, for the sake of study, assume that we care about all 3 of these potential issues. This means we are going to have to handle that in a graceful way when we unpack it to recreate the data.

I have the sample code included below for you to download and I am using the AdventureWorksLT for my data. You can snag it from CodePlex at http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004 . The code itself is fairly rough in patches since I really wanted to hit the main points about Ajax History but let me know if I need to clarify any section of it just so you can understand what I am attempting to show.

Creating the basic methods to create the page display

My methods for populating the DDL are as follows:

    private void PopulateDDL(string text)
    {
        ddl1.Enabled = true;
        StringDictionary _results = GetMatchingDescriptions(text);
        if (_results != null)
        {
            ddl1.DataSource = _results;
            ddl1.DataTextField = "Value";
            ddl1.DataValueField = "Key";
            ddl1.DataBind();
        }
    }
    private void PrefixDDL(string text, string value)
    {
        ddl1.Items.Insert(0, new ListItem(text, value));
        if (ddl1.SelectedIndex != -1)
        {
            ddl1.SelectedItem.Selected = false;
        }
        ddl1.Items[0].Selected = true;
    }

My method for the Display Info (upPanel2) are as follows:

    private void DisplayInfo(string selectedValue, DateTime requestedOn)
    {
        if (String.IsNullOrEmpty(selectedValue))
        {
            DisplayError("That selection is no longer valid. Please retry");
        }
        else
        {
            int _productId;
            if (Int32.TryParse(selectedValue, out _productId))
            {
                ProductInformation _product = GetProduct(_productId);
                if (_product == null)
                {
                    DisplayError("That selection could not be found. Please retry");
                }
                else
                {
                    lblLastModified.Text = _product.LastModified.ToString("F");
                    lblName.Text = Server.HtmlEncode(_product.Name);
                    lblModel.Text = Server.HtmlEncode(_product.Model);
                    lblDescription.Text = Server.HtmlEncode(_product.Description);
                // what if the data is old? notify the user
                    if (requestedOn < _product.LastModified)
                    {
                        lblRequestedOn.Text = String.Format("This item has been modifed since request at {0}", requestedOn.ToString("F"));
                    }
                    else
                    {
                        lblRequestedOn.Text = requestedOn.ToString("F");
                    }
                }
            }
            else
            {
                DisplayError("That was an invalid selection");
            }
        }
    }
    private void ResetDisplayInfo()
    {
        lblRequestedOn.Text = "";
        lblLastModified.Text = "";
        lblName.Text = "";
        lblModel.Text = "";
        lblDescription.Text = "";
    }
    private void DisplayError(string errorText)
    {
        ResetDisplayInfo();
        lblName.Text = errorText;
    }

I will call these methods not merely when the page is initially created but also when it is REcreated during the ScriptManager's Navigate command.

Creating the HistoryPoint methods/event handler

Apart from the modifications that are made similar to what was done on Part 2, the main changes deal with the methods to store the History Points and to recreate the page after the History Points have been reloaded. In other words what happens when a History Point is set and what happens when a History Point is retrieved. Because we do not want to store the ENTIRE page in the browser URI the fundamental question is "what can I get away with?" This is a good time to practice your YAGNI (You ain't gonna need it) to eliminate all but the most essential and simplest pieces of information. In this case I have three pieces that can not be reduced any more

  1. the text in the textbox
  2. the selected value of the dropdown list
  3. the DateTime that the page was loaded (to see if the data has been changed)
  4. (bonus) In addition, due to a 'feature' of Ajax History in .net 3.5, I am also including the Page Title. I will discuss more about this later, but just know that it is included.

My method to store the History Point looks like this:

    protected void StoreHistoryPoints(string textBoxText, string ddlValue,
        DateTime requestedOn, string pageTitle)
    {
        ScriptManager _mySM = ScriptManager.GetCurrent(this.Page);
        NameValueCollection _historyPointEntries = new NameValueCollection();
        _historyPointEntries.Add("whatTextBoxContains", textBoxText);
        _historyPointEntries.Add("whatWasSelectedInDDL", ddlValue);
        _historyPointEntries.Add("requestedOn", requestedOn.Ticks.ToString()); // ticks are shorter in length
        _historyPointEntries.Add("pageTitle", String.Format("AV - {0}", pageTitle));
        _mySM.AddHistoryPoint(_historyPointEntries, String.Format("AV - {0}", pageTitle));
    }

It is very straightforward. I create a NameValueCollection to hold my minimal data and then call the AddHistoryPoint.

Before I continue I should mention that there are three overloaded methods for AddHistoryPoint

  • ScriptManager.AddHistoryPoint(string, string)
  • ScriptManager.AddHistoryPoint(string, string, string)
  • ScriptManager.AddHistoryPoint(NameValueCollection, string)

The second two have an additional string parameter which is used to set the Page Title when the History Point is stored. In this example I use it to set the Page Title to whatever bike model that they are looking at. Thus if they save it in their favorites it gives a user friendly name.

HOWEVER!!! While this works going forward that information is lost when the Navigate method is called and the HistoryPoint information is unpackaged. Ie - going forward the ScriptManager changes the Page Title, but when the page is loaded using the "Back" button or from a manually typed URI, it does not set the Page Title, nor is it accessible through the HistoryEventArgs. As a workaround I have included the Page Title as one of the KeyValue pairs in my history point so that I can recreate along with the rest of the page. Of course, since this is an issue I could simply have set the Page Title manually (for example in the portion of the code that determines the bike to display) but I wanted to show you this overload.

Also note that I could as easily have done this in my method:

    protected void StoreHistoryPoints(string textBoxText, string ddlValue,
        DateTime requestedOn, string pageTitle)
    {
        ScriptManager _mySM = ScriptManager.GetCurrent(this.Page);
        _mySM.AddHistoryPoint("whatTextBoxContains", textBoxText);
        _mySM.AddHistoryPoint("whatWasSelectedInDDL", ddlValue);
        _mySM.AddHistoryPoint("requestedOn", requestedOn.Ticks.ToString()); // ticks are shorter in length
        _mySM.AddHistoryPoint("pageTitle", String.Format("AV - {0}", pageTitle));
    }

There is actually no discernable difference except for the Page Title being handled by the Script Manager going forward. The NameValueCollection that the HistoryEventArgs of the ScriptManager's Navigate method returns are the same in both methods. The code that I display below for the Navigate event handles them exactly the same because both return the same NameValueCollection.

Quick note also that all these entries must be string values (they are stored in the URI after all). For this reason I have changed the DateTime to use ticks instead of writing it out, but you could have easily have chosen a format which would return a simpler string. The key is to approach this with a minimalist attitude. Not being too obsessive, but the shorting the string, the shorter the URI, the better for the system.

Recreating the page

Now I have the process in place for the storing of History Points. I can call this method as my final command from both the button1_Click event handler and the ddl1_SelectedIndexChanged event handler and it will store the information I pass it into the history. The Page does not need to know what this data represents. It is the Caretaker (see Ajax and Memento). All it knows is that it needs to store this information and return it when requested.

The second portion is the unpackaging when the History Points have been returned. This is handled through the ScriptManager's Navigate event. One quick thing to note - this event is called when the "Back" or "Refresh" commands are given to the browser, but it also is called when the page is loaded with the specific History Point URI, for example from a "Bookmark" or from a link.

My event handler looks like this:

   protected void sm1_Navigate(object sender, HistoryEventArgs e)
    {
        long _requestedOnAsTicks;
        bool _hasHistory = long.TryParse(e.State["requestedOn"], out _requestedOnAsTicks);
        if (_hasHistory)
        {
            DateTime _requestedOn = new DateTime(_requestedOnAsTicks);

            string _textStored = e.State["whatTextBoxContains"];
            if (!String.IsNullOrEmpty(_textStored))
            {
                // restore entries on TextBox and DDL
                textbox1.Text = _textStored;
                PopulateDDL(_textStored);
            }
            string _ddlValueStored = e.State["whatWasSelectedInDDL"];
            if (!String.IsNullOrEmpty(_ddlValueStored))
            {
                ListItem _matchingItem = ddl1.Items.FindByValue(_ddlValueStored);
                if (_matchingItem != null)
                {
                   // we have victory, display the item...
                    _matchingItem.Selected = true;
                    DisplayInfo(_ddlValueStored, _requestedOn);
                }
                else
                {
                    // something is wrong, the item no longer shows. Display error...
                    ListItem _errorItem = new ListItem(_ddlValueStored, "");
                    DisplayInfo("", _requestedOn);
                }
            }
            else
            {
                // since no DDL value was selected, add prompt at beginning of DDL
                PrefixDDL("Select One", "");
            }

            // this is a required hack because even though page title is specified in the AjaxHistory, it
            // does not work when you navigate backwards..
            string _pageTitle = e.State["pageTitle"];
            Page.Title = _pageTitle;
        }
        else
        {
            // if there is no DateTime stored than this is the original (ie no history) -> reset form
            textbox1.Text = "";
            ddl1.Items.Clear();
            ResetDisplayInfo();
            Page.Title = "AV";
        }
    }

The Navigate event uses the HistoryEventArgs. This really only has one thing in there - the State, which is the NameValueCollection that you passed the ScriptManager during the AddHistoryPoints calls.

The first thing I do is see if there is anything in there. Since I am wanted to get the date and the TryParse is a convenient way for me to verify that it is a valid long, I check it there. If there is no history than this is a non-modified Ajax Page (probably the very beginning) and I simply reset everything.

Once I know there is a history I have to go through the process of recreating the page. I extract the appropriate information and repopulate the Textbox, the DropDownList (if appropriate) and the Display (if appropriate). By doing this I am creating a complex page from simplistic data.

Because my data may have changed (say the Description has been updated) I have also included a notification to the user if this takes place in the DisplayInfo method. However, I no longer have access to the original data. That data is lost to me because all I am passing are the minimal pieces to recreate the page. What is displayed is the CURRENT information on that particular product.

Also, note the setting of the Page.Title. Remember that even though I did pass that as a parameter during my AddHistoryPoint method call, it is no longer available to me during the Navigate event. If you want to use a unique Page Title when an Ajax modification takes place, make sure to pass the Page Title as its own KeyValue pair and then unpack and recreate it.

Final Notes

If you have stuck in this long, congrats!!! I know this may still seem murky, probably more due to my explanation than to anything else, but there are a few more things I want to touch on. I will actually them in my (potentially) last blog on this - part 4 - wrap up.

Zipped Example

AjaxExamples.zip (1.37 MB)

# Wednesday, July 01, 2009

Ajax History - a how to - Part 2 - a basic example

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern
Extra Bonus - Issues with Opera

Handling Ajax history has been a critical issue since Ajax first started. With the advent of .net 3.5 the ability to easily deal with that is rolled into the code. This is part 2, here we will look at a simple example of handling Ajax history to give a basic understanding of the process. If you have not read Part 1, I suggest you do that now. Part 3 will expand on handling Ajax history when the data is more complex.
For simplicity sake we are only going to look at an example of very, very basic history. We are going to look at a page that displays changes made to the time.

A basic assumption is that you are fairly familiar with Ajax (http://ajax.asp.net). We are going to be using the ScriptManager (of course) and an UpdatePanel.
Our basic page looks like this

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AjaxHistory.aspx.cs" Inherits="AjaxHistory" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="sm1" EnablePartialRendering="true" />
    <div>
        <asp:UpdatePanel runat="server" ID="upME">
            <ContentTemplate>
                <asp:Button runat="server" ID="uxChangeTime"
                    OnClick="uxChangeTime_Click" Text="-- change:" />
                <asp:Label runat="server" ID="lblTime" />
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="uxChangeTime"
                     EventName="Click" />
            </Triggers>
        </asp:UpdatePanel>
    </div>
    </form>
</body>
</html>

The codebehind sets the label text to be the current date/time on every click:

(c# code)
protected void uxChangeTime_Click(object sender, EventArgs e)
{
    string _currentDateTime = DateTime.Now.ToString("F");
    lblTime.Text = _currentDateTime;
}

(vb code)
Protected Sub uxChangeTime_Click(ByVal sender As Object, &_
  ByVal e As System.EventArgs) Handles uxChangeTime.Click
    Dim _currentDateTime As String = DateTime.Now.ToString("F")
    lblTime.Text = _currentDateTime
End Sub

As you can see this is fairly straightforward. Clicking on the button displays the current DateTime in the label. However, there is no history. Clicking back will not display the last DateTime displayed but either be grayed out (if this was the first page loaded) or take you back to the page before this one.

To enable history is very, very simple in 3.5

  1. modify the ScriptManager, setting a property or two and registering an event (Navigate) for the ScriptManager
  2. modify the Click handler to notify the ScriptManager to record a History Point
  3. handle the event called by the ScriptManager to rollback, when required, the page

Step 1: Modify the ScriptManager

The modifications are very simple - set EnableHistory equal to true and specify what should handle the Navigate event. You can also specify if you want the HistoryState to be 'secure' by setting EnableSecurityHistoryState equal to true, but this is optional.

Your ScriptManager should now look like this:

<asp:ScriptManager runat="server" ID="sm1"
     EnableHistory="true" OnNavigate="sm1_Navigate"
     EnableSecureHistoryState="true"
/>

Step 2: Add an AsyncPostBackTrigger for the Navigate event of the ScriptManager

<asp:UpdatePanel ...>
    ...
    <Triggers>
         <asp:AsyncPostBackTrigger ControlID="uxChangeTime"
                     EventName="Click" />
         <asp:AsyncPostBackTrigger ControlID="sm1" EventName="Navigate" />
    </Triggers>
</asp:UpdatePanel>

Step 3: Modify the Click handler (or whatever event it is that would trigger a HistoryPoint to be recorded

What you are doing here is notifying the ScriptManager what information to store as a HistoryPoint by calling its AddHistoryPoint method. There are three possible overloads and we will look at the other two next blog, but for now we will use one of the simplest, a single key/value string pair to store the current DateTime with the key "myTime":

(c# code)
protected void uxChangeTime_Click(object sender, EventArgs e)
{
    string _currentDateTime = DateTime.Now.ToString("F");
    lblTime.Text = _currentDateTime;
    ScriptManager.GetCurrent(this).AddHistoryPoint("myTime", _currentDateTime);
}

(vb code)
Protected Sub uxChangeTime_Click(ByVal sender As Object, &_
  ByVal e As System.EventArgs) Handles uxChangeTime.Click
    Dim _currentDateTime As String = DateTime.Now.ToString("F")
    lblTime.Text = _currentDateTime
    ScriptManager.GetCurrent(Me).AddHistoryPoint("myTime", _currentDateTime)
End Sub

Step 4: handle the ScriptManager Navigate event

In this event we retrive the State property of the HistoryEventArgs and use that to set the page to be what we want it to be. In this very simple example we will use it to set the value of the label to be the DateTime that that particular page displayed:

(c# code)
protected void sm1_Navigate(object sender, HistoryEventArgs e)
{
    string _historyValue = e.State["myTime"];
    if (_historyValue != null)
    {
        lblTime.Text = _historyValue;
    }
}

(vb code)
Protected Sub sm1_Navigate(ByVal sender As Object, &_
  ByVal e As System.HistoryEventArgs) Handles sm1.Navigate
    Dim _historyValue As String = e.State["myTime"]
    if(_historyValue <> Nothing)
    {
       lblTime.Text = _historyValue
    }
End Sub

Voila! We now have a functional Ajax page with history. Try it!

What has happened is that we are using History Points. When we click the button it determines the current DateTime, displays it, and stores it as a history point. When we click the "Back" button it retrieves the previous history point (which was the current DateTime in string format) and displays that (see chart below).

Next blog: a look at more intensive use of History Points...
btw, for prep, take a looksee at my blog on the Memento pattern and Ajax history to understand my basic approach: http://www.myfriedmind.com/techBlog/2009/05/20/AjaxHistoryAndTheMementoPattern.aspx

# Tuesday, June 30, 2009

Ajax History - a how to - Part 1

Part 1 - Introduction
Part 2 - Basic Example
Part 3 - Complex Example
Part 4 - Final Notes
Bonus - Ajax History and the Memento Pattern
Extra Bonus - Issues with Opera

One of the most exciting prospects with .net 3.5 is the ability to handle Ajax History.

Ajax, for those of you who may be only vaguely familiar, is a way to do partial postbacks of web pages. This allows you to modify only a portion of the screen rather than updating the entire transaction. This increases the potential speed exponentially. One form are CallBacks, another used by the UpdatePanel is a full PostBack but only a redrawing of the appropriate sections of the page.

However, there are three main problems with Ajax when using controls like the UpdatePanel:

  1. Hitting the “Back” button loads the page that was loaded BEFORE the ajax page
  2. Refreshing the page has the effect of losing whatever Ajax calls have been made (ie the page itself is reloaded from the start).
  3. There is no effective method to ‘link’ to the page resulting from ajax calls. All links lose any Ajax calls because the URL does not display the necessary information to recreate the page at the appropriate place.

All of these can be resolved (as we shall see) by using Ajax History.

This first blog entry will lay out the problems in greater detail and the general means by which Ajax History resolves them. Later entries will go more into detail on how to do Ajax History (yes, there is some additional work, but not very much).

Hitting the “Back” button loads the page that was loaded BEFORE the ajax page

The issue here is caused by two factors:

  1. The "Back" button loads the last entry in url history
  2. Ajax does not automatically change the url history

An example of what I am talking about can be seen in my (non-artistic) image below.

Hitting the "Back" button after doing regular Postbacks will take you to the most recent page of information. However, hitting the "Back" button after doing CallBacks takes you all the way back to the page that was loaded BEFORE you even got to the Ajax page.

The reason for this lies in the way that the href property of the Html Document Object Model is handled. By default whenever a full PostBack occurs or a modification to the hash in the URI (such as an internal link), the URI of the new entry is stored in the location history. This is handled by the browser behind the scenes and has been since the early days. However, since Ajax calls are not full PostBacks the browser mechanism does not, by default, record their entry.

To get around this, a rather ingenious method is used. Because modifications to the URI’s hash portion is included in the history but DOES NOT require full PostBack (think internal anchor links) .net 3.5 writes to the hash portion of the page to trigger the entry being recorded, by the browser, in the href history. Pretty slick, hunh?

For those of you unfamiliar with the hash concept of the URI, consider the following quick example:

<html>
<head></head>
<body>
<a name=’Up’></a>
<a href=’upsidedown.html#Down’>Go Down</a>
<p>&nbsp;</p>
<a name=’Down’></a>
<a href=’upsidedown.html#Up’>Go Up</a>
</body>
</html>

I have posted this basic page to my site. Note that there are anchor tags (<a…) that have names and others that have a hash sign (#) in them that refer to that name. These are commonly used to enable users to just to a particular portion of the page without having to scroll and are known as Internal Links (because they link to an internal portion of the page and not to an external page).

When the page first loads you can see that the URI is simply http://www.myfriedmind.com/updown.html. There is one link visible (the other would be visible if you scrolled but I shrunk the browser window to give an idea of what internal links are generally used for.

Once the link is clicked it moves the page down to where the “named” anchor is. In other words, clicking on the anchor tag that references #Down (<a href=’upsidedown.html#Down’>Go Down</a>) moves the page to the anchor that contains the name that matches the one after the hash sign – so you are moved WITHIN the page (to <a name=’Down’></a>). This is very important – note that the URI has changed, it is now showing the hash. Also critical to note is that THE PAGE DID NOT POSTBACK!

Clicking on the link that has the hash entry for Up (<a href=’upsidedown.html#Up’>Go Up</a>) results in moving to the anchor tag that has the name (Up) that matches what was after the hash mark. Again, the URI is updated to a new hash display and THE PAGE DID NOT POSTBACK!!!

It is critical that you understand that when this takes place the browser records the change in its href history. In other words, modifications to the hash are RECORDED in the browser’s href history. Thus hitting the “Back” button means that we move back to the previous entry, which is NOT http://www.myfriedmind.com/updown.html, but http://www.myfriedmind.com/updown.html#Down (see below)

Thus the elegant solution – to store Ajax postbacks, record the entries in the hash of the URI (like you are doing an internal link) and the updated URI will be recorded in the browser history for you. Any clicks on the “Back” button will load the previous entry. See the basic example below. Note that the change after CallBack to the URL (adding a hash entry).

I must note here that the adding to the hash is not automatic. You do need to do code but very, very little. Your code mostly deals with how to effectively package the info and unpackage when required. If you are curious take a looksee at my (fascinating) blog on how this is similar to the Memento pattern ->  http://www.myfriedmind.com/techBlog/2009/05/20/AjaxHistoryAndTheMementoPattern.aspx.

Once you grasp this concept – that the Ajax History is stored in the URI’s hash, the solutions to both of problems #2 and #3 (page refresh and linking) are resolved.

  • Refreshing  the page refreshes the URI, INCLUDING the hash. Thus the page that gets returned is the page that has had the Ajax modifications done
  • Sending a link to this page, INCLUDES the URI’s hash, and thus the page will be able to return the point in its Ajax modifications that a person wants

There are a number of very useful examples of how this is used today (Ajax History is not new with 3.5 but it DOES make it a whole lot simpler). The most commonly used are mapping websites. One can hunt around (using Ajax) until one gets to the exact display one desires and then save/send/whatever that display. Without the hash entry one would always end up at the very beginning and the desired display would be lost. I can use bing to zoom into the street view of Dunn Bros (the BEST coffee) by my old alma mater in Saint Paul and shoot that url to a friend.( shortened for display - http://www.bing.com/maps/#JnE9eX...zMTI1)

But this is not limited to mapping sites. Imagine that you have a Ajax based site that enables users to filter/sort/etc on a GridView display customer accounts. “Ah ha,” says the manager (they say things like that when no one is listening) “this is precisely the display I am looking for. I will just save it into my favorites. And I will also email it to the big boss.” Without the hash they would be exceedingly frustrated as would the big boss. With proper Ajax history coding the sun shines, the wind blows, and gas prices drop. Everyone is happy.

;)

Next blog: What exactly is involved in doing Ajax History, the concepts….

# Monday, June 22, 2009

CascadingDropDownLists, GridViews, and Dynamically selecting the appropriate ListItem during Edit...

okay, I apologize. I promised to do this weeks ago and am only finally getting it up. Hopefully it will be worth the wait...

If you are planning on simply using a DDL (and not Cascading), look here -> http://www.myfriedmind.com/techBlog/2009/06/04/DropDownListGridViewAndDynamicallySelectingTheAppropriateListItemDuringEdit.aspx

Note that this example uses a remote webservice (rather than a PageMethod) for the CascadingDropDownList calls and I have not included them to simplify the amount of code on this page, but they are fairly straightforward -> http://www.asp.net/AJAX/AjaxControlToolkit/Samples/CascadingDropDown/CascadingDropDown.aspx

Sample DataObject (in c#)

class MyObject
{
   public int ObjId { get; set; }
   public string StateAbbreviation { get; set; }
   public string ZipCode { get; set; }
}

Sample DataObject (in vb.net)

Class MyObject
   Public ObjId As Integer
   Public StateAbbreviation As String
   Public ZipCode As String
End Class

Entry on SampleGridView.aspx page (as simple as you can get it)

<asp:GridView runat="server" ID="lstMyData" DataKeyNames="ObjId"
    AutoGenerateColumns="false" EnableViewState="true"
    AutoGenerateEditButton="true"
    OnRowDataBound="lstMyData_RowDataBound" >
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:Label runat="server" ID="lblState"    
                    Text='<%# Eval("StateAbbreviation") %>' />
            </ItemTemplate>
            <EditItemTemplate>
                <asp:DropDownList runat="server" ID="uxState" />
                <aKit:CascadingDropDown ID="ajxState" runat="server"
                    TargetControlID="uxState" Category="State" 
                    ServiceMethod="GetStates" />
            </EditItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:Label runat="server" ID="lblZipCode"  
                   Text='<%# Eval("ZipCode") %>' />
            </ItemTemplate>
            <EditItemTemplate>
                <asp:DropDownList runat="server" ID="uxZipCode" />
                <aKit:CascadingDropDown ID="ajxZipCode" runat="server"
                    TargetControlID="uxZipCode" Category="ZipCode"
                    LoadingText="[Loading Zipcode...]"
                    ServicePath="WebServiceToReturnData.asmx" 
                    ServiceMethod="GetZipCode"
                  ParentControlID="uxState" />
            </EditItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Codebehind (in c#) SampleGridView.apx.cs (note this does NOT include the code to return the values)

protected void lstMyData_RowDataBound(object sender,
        GridViewRowEventArgs e)
{
    // verify this is a DataRow, not a Header, Footer, etc...
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // in case you want to do something else here....
       
        // see if this is the Row used for Editing
        if ((e.Row.RowState & DataControlRowState.Edit) != 0)
        {
            // get the DataItem that is bound
            // and cast it accordingly
            MyObject _myObject = (MyObject)e.Row.DataItem;

            // get the CascadingDropDowns
            CascadingDropDown _ajxState =
                (CascadingDropDown) e.Row.FindControl("ajxState");
            CascadingDropDown _ajxZipCode =
                (CascadingDropDown) e.Row.FindControl("ajxZipCode");

            // the data is automatically updated via the Ajax,
            // so we merely need to select the item

            _ajxState.SelectedValue = _myObject.StateAbbreviation
            // _ajxState.ContextKey = this can be set here if you are using it;

            _ajxZipCode.SelectedValue = myObject.ZipCode;
        }
    }
}

Codebehind (in vb.net) SampleGridView.apx.cs (note this does NOT include the code to return the values)

Protected Sub lstMyData_RowDataBound(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) _
    Handles lstMyData.RowDataBound
    If e.Row.RowType = DataControlRowType.DataRow Then

            ' in case you want to do something else here....

            ' see if this is the Row used for Editing
            If (e.Row.RowState And DataControlRowState.Edit) <> 0 Then


                ' get the DataItem that is bound
                ' and cast it accordingly
                Dim _myObject As MyObject = _
                    DirectCast(e.Row.DataItem, MyObject)

                ' get the CascadingDropDowns
                Dim _ajxState As CascadingDropDown = _
                    DirectCast(e.Row.FindControl("ajxState"), CascadingDropDown)
                Dim _ajxZipcode As CascadingDropDown = _
                    DirectCast(e.Row.FindControl("ajxZipCode"), CascadingDropDown)

                ' ddl population should be handled by the Ajax
                ' ServiceMethod of the CascadingDropDown

                ' select the appropriate item
                _ajxState.SelectedValue = _myObject.StateAbbreviation
                _ajxZipCode.SelectedValue = _myObject.ZipCode

            End If
     End If
End Sub

# Thursday, June 11, 2009

Installation Error if Diagnostics Log location moved...

I was installing a new Sharepoint Server in the farm, mostly because the currently had was too small.

I ran into errors with the Office Server Web Services (it kept saying it could not find the path, although the path to that website was fine (I checked it three times). The Root virtual directory could not be found...

I tried many things. Finally I ran the Office wizard to remove the new server from the farm. I then ran it again to reinstall it (after making sure that various dbs had the Sharepoint Server as a user - see previous log). And I got the error below:

The error in the log informed me that I it was missing the location of the Diagnostics Logs that I had moved to the E: drive on the old, slow server. So I moved the Diagnostics logs to the D: drive before running the wizard to try to join the farm again. No dice. Same error. I moved the logs back to there original locale in the 12-spot. No dice.
 
I try renaming the D: drive to E: - it refuses.
 
On the point of uninstalling/reinstalling MOSS on the new server I hesitate...
 
Searching through Regedit in the NEW server (not currently on the Farm) I find an entry in HKLM\Software\Microsoft\Shared Tools\Web Server Extensions\12.0\WSS called LogDir. And guess what is in it? You got it - a reference to the entry on the E: drive...
 
Manually changed the regedit entry to match to the new d: location (on both servers) and VOILA!!!
 
So be warned - if you move the Diagnostics Logs location in a farm, a folder must be available on ALL servers (makes sense).
 
And the Office Server IIS site is gone. Probably a carryover from before...