Navigation

Search

Categories

On this page

Group Membership in .net
Netiquitte
Many to Many UI pattern using Access - Thinking outside of the (list) box
Playing With The Values Of Window.location
User Objects in AD
Javascript Closures

Archive

Blogroll

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 120
This Year: 1
This Month: 0
This Week: 0
Comments: 40

Sign In
Pick a theme:

# Thursday, May 24, 2007
Thursday, May 24, 2007 11:18:41 AM (Mountain Daylight Time, UTC-06:00) ( All things Microsoft | Code )

A colleage approached me and asked how he could convert some ASP Classic code to ASP.Net 2.

Here's the snippet

Dim user 'DOMAIN\USERNAME

Const DomainAuthority = "dc.hq.domain.com"

user = Request.ServerVariables("AUTH_USER") ' BASIC Authentication

user = Mid(user, InStr(user, "\") + 1)

Set user = GetObject("WINNT://" & DomainAuthority & "/" & user)

For Each grp in user.Groups

   If grp.Name = "GroupName1" Then GroupFlag1 = True

   If grp.Name = "GroupName2" Then GroupFlag2 = True

Next

This doesn't compile in a VB.Net web-app.

In order to fix this, lets first add a reference to the types we're using.

At the project level in the Solution Explorer, right click and select Add Reference

Switch to the COM tab, and Choose the "Active DS Type Library"

This will automagically add some things to your project.

These are the magical COM Interop DLLs that are generated for you, and allow you to use the objects inside that library.
Dim up a few objects, to use a bit later.

Dim usr As IADsUser
Dim grp As IADsGroup

Set a reference to your user the same as always, using the WinNT ADSI provider.

usr = GetObject("WINNT://" & TheDC & "/" & UsrName)

Now that the compiler knows what the grp object's type is, it won't puke on you

For Each grp In usr.Groups()
  If (grp.Name = "foo" Or grp.Name = "bar") Then
    ' Do something with it
  End If
Next

There are better ways to do this though, Stay tuned for how to totally pimp this out, the ASP.Net 2.0 way.

Comments [0] | | # 
# Monday, May 21, 2007
Monday, May 21, 2007 3:36:35 PM (Mountain Daylight Time, UTC-06:00) ( )

Thy email signature block must contain only one font-family, one font-size, and at most two font-styles (normal + bold, normal + italic)

Thy presence status description must not include anything conveying what you wish for, hope to be, feel like or other nonsense. If does not describe where you are, or why you are not availble, it does not belong.

Thou must never email to multiple recipients using the TO or CC recipient fields. Use the BCC to protect the sanctity of the recipient's message.

Thou must adhere to a painless stylesheet when creating email newsletters. Do not choose colors which are at opposing ends of the spectrum for emphasis (red, and blue). Do not use multiple font-families which are serif and sans-serif, choose one and stay with it throughout your message. Make appropriate use of Heading styles for emphasis, and paragraphs to layout your content appropriately.

 

Comments [0] | | # 
# Friday, May 18, 2007
Friday, May 18, 2007 11:57:08 AM (Mountain Daylight Time, UTC-06:00) ( )

I am a web developer by trade.

Ocassionaly, it's necessary to demonstrate a certain UI pattern for getting users access to data outside of your comfort zone. I've recently acquired a new mentoree, and he's very comfortable inside of the Access VBA environment. Not so much with VB.Net or C#, and certainly not on the web. Sigh, his time may yet come.

I'm trying to get him to learn some more generalized patterns for working with data in different formats. Trees, Many to Many, Quad-Trees, etc. Freeing the mind so to speak.

We're using this data-schema to represent a many to many relationship.

This is useful, because you can model things like Users and Roles. ala the ASP.Net Role Management system.

In this case, our "series" are actually Members. The same concept applies.

The reason we're discussing this, is because a database we work with has a new requirement. Only display certain series to users which are in a certain group. The "series" happens to be the domain object, and the roles are going to be fixed as NT Security groups. which the users are already members of. (Technically, some aren't they're just going to be anonymous, which is the security group BUILTIN\EVERYONE)

Killer, so how to do this inside Access?

Here's the form I came up with.

I've used some bogus data here, because we're working in a different database. It's pretty clear what's happening though.

The control on the left is an Access.istBox, bound to the Series Table. This allows us to get the SeriesID by examining the Value property of the SeriesListBox. This is going to be Null when the ListBox.ListIndex = -1.

The control on the right is a MSComctlLib.TreeCtrl.2. This control has an interesting feature called Checkboxes which "Returns/sets a value which determines if the control displays a checkbox next to each item in the tree."

This is horribly useful, because it allows us to treat the single control as a List of Checkbox Nodes... handy.

Essentially this is what's going to happen.

1. The form will load, and the Series Binding will happen automagically, due to the control's design-time definition.

2. We want to clear the TreeView of all nodes it might have.

    Dim tree As TreeView
    Set tree = uxTreeViewRoles.Object
    tree.Nodes.Clear

3. We want to populate the TreeView with a list of possible Roles from the appropriate table, keeping track of which node is which ID.

    Dim tree As TreeView
    Dim n As Node
    Set tree = uxTreeViewRoles.Object
   
    Dim rstRole As DAO.Recordset
    Set rstRole = CurrentDb.OpenRecordset("Select RoleID, Name FROM Roles;")
    rstRole.MoveFirst
   
    Do While Not rstRole.EOF
   
        Set n = tree.Nodes.Add(, , , rstRole!Name) ' short for rstRole.Fields("Name").Value
        n.Tag = rstRole!RoleID
        n.Checked = False
        rstRole.MoveNext
   
    Loop

4. Every time a Series is selected from the ListBox, we'll do two things.

4.1. Clear the checkboxes in the TreeView.

    Dim tree As TreeView
    Set tree = uxTreeViewRoles.Object
    Dim n As Node
    For Each n In tree.Nodes
        n.Checked = False
    Next

4.2 Update the checkboxes to represent the Roles which are available for the series

    Dim intSeriesID As Integer
    intSeriesID = uxListSeries.Value
   
    ' Tools, References, Microsoft Scripting Runtime
    Dim dRoles ' This will store the Roles we find when we query the SeriesRoles table
    Set dRoles = CreateObject("Scripting.Dictionary")
    Dim tree As TreeView 
    Set tree = uxTreeViewRoles.Object
   
    ' Add RoleIDs from the SeriesRoles table
    Dim rstSeriesRoles As DAO.Recordset
    Set rstSeriesRoles = CurrentDb.OpenRecordset("SELECT SeriesID, RoleID FROM SeriesRoles WHERE SeriesID=" & intSeriesID)
   
    ' No Records
    If (rstSeriesRoles.BOF = True Or rstSeriesRoles.EOF = True) Then Exit Sub ' Easy, no Roles exist for this seriesID
    
    rstSeriesRoles.MoveFirst
    Do While Not rstSeriesRoles.EOF
        dRoles.Add CStr(rstSeriesRoles!RoleID), CStr(rstSeriesRoles!SeriesID) ' Add this role to the Roles dictionary, we'll use it in a minute.
        rstSeriesRoles.MoveNext
    Loop
   
    Dim n As Node
    For Each n In tree.Nodes
        If (dRoles.Exists(CStr(n.Tag))) Then ' The role was found when we looked at the SeriesRole table.
            n.Checked = True
        Else
            n.Checked = False
        End If
    Next 'n

5. All that's left now, is to handle what happens when one of those checkboxes is clicked on.

   Dim nRoleID, nSeriesID As Integer

   Dim n As Node
   Set n = Node
   nRoleID = n.Tag
   nSeriesID = uxListSeries.Value

   Select Case n.Checked

   Case True
     ' Add a row to the join table
  
     Access.CurrentDb.Execute _
      "INSERT INTO SeriesRoles (SeriesID,RoleID) " & _
      "VALUES (" & nSeriesID & "," & nRoleID & ")"
  
    Case False

     Access.CurrentDb.Execute _
      "DELETE FROM SeriesRoles " & _
      "WHERE (" & _
      "SeriesID=" & nSeriesID & _
      " AND RoleID=" & nRoleID & ")"

End Select


And that's a wrap.

Still, I wonder what I could have done with VSTO, instead of VBA...

Comments [0] | | # 
# Tuesday, May 15, 2007
Tuesday, May 15, 2007 10:10:29 AM (Mountain Daylight Time, UTC-06:00) ( )

Playing with the values of window.location

<html>
<head>
<script type="text/javascript">
function displayURLs()
{
  setBox('location', window.location);
  setBox('hash', window.location.hash);
  setBox('host', window.location.host);
  setBox('hostname', window.location.hostname);
  setBox('href', window.location.href);
  setBox('pathname', window.location.pathname);
  setBox('port', window.location.port);
  setBox('protocol', window.location.protocol);
  setBox('search', window.location.search);
}
function setBox(name, value)
{
  var el = document.getElementById(name);
  el.innerText = value;
  if(value != null)
  {
    properSize = value.toString().length;
    if(properSize == 0)
    el.size = 1;
    else
    el.size = properSize;
  } 
}
</script>
<style type="text/css">
label {
display: block;
width: 100px;
margin-right: .5em;
float: left;
text-align: right;
}
</style>
</head>
<body onload="displayURLs()">
<form >
<label for="url">url</label><input type="text" name="location" /><br/>
<label for="hash">hash</label><input type="text" name="hash" /><br/>
<label for="host">host</label><input type="text" name="host" /><br/>
<label for="hostname">hostname</label><input type="text" name="hostname" /><br/>
<label for="href">href</label><input type="text" name="href" /><br/>
<label for="pathname">pathname</label><input type="text" name="pathname" /><br/>
<label for="port">port</label><input type="text" name="port" /><br/>
<label for="protocol">protocol</label><input type="text" name="protocol" /><br/>
<label for="search">search</label><input type="text" name="search" /><br/>
<p><input type="button" value="Refresh Page" name="B3"></p>
</form>
</body>
</html>
Comments [1] | | # 
# Monday, May 14, 2007
Monday, May 14, 2007 1:33:55 PM (Mountain Daylight Time, UTC-06:00) ( Code )

I've had to do this way too many times, and keep forgetting to document how here.

Referencing an article http://www.activedir.org/article.aspx?aid=110. It occurred to me that you can impliment the snippet very easily in VBS.

  1. Connect to the of the current workstation rootDSE
  2. query for all User objects using an indexed attribute to get results as quickly as possible, also with little load on the server
  3. get a full IADsUser object using the LDAP ADSI provider
  4. do something with each user.

Here's what I wound up with.

Option Explicit ' Enough said. Don't write production code without it.

Dim objConnection, objCommand, objRecordSet, objRootDSE, objUser

Dim start, strDomain
start = Now

' AD (NTDS) is, afterall, just a service that sits on top of a
' JET Blue-based Extensible Storage Engine (WikiPedia Active_Directory)
' So why not just treat it as such and use a Database-esque provider to access the data?

Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open "Provider=ADsDSOObject;"

Set objCommand = CreateObject("ADODB.Command")
objCommand.ActiveConnection = objConnection

' Get Domain name from RootDSE object.
Set objRootDSE = GetObject("
LDAP://RootDSE")
strDomain = objRootDSE.Get("DefaultNamingContext")

' One could just as easily do (&(objectClass=User)(objectCategory=Person))

' I found this approach using indexed attributes to produce a speed gain of several magnitudes.

objCommand.CommandText = _
"<LDAP://" & strDomain & ">;" & _
 "(&(sAMAccountType=805306368)(!(objectClass=inetOrgPerson)));" & _
 "adspath;subtree"

Set objRecordSet = objCommand.Execute

While Not objRecordset.EOF

  ' Here is an IADsUser object using the ADSI LDAP provider.

 Set objUser = GetObject(objRecordset.Fields("adspath"))
 
 WScript.Echo objUser.name ' Do your work here! woohoo!
 
 objRecordset.MoveNext
 
Wend

WScript.Echo "Retrieved (" & objRecordSet.RecordCount & ") records in (" & DateDiff("s", start, Now) & ") seconds"

objConnection.Close

Comments [0] | | # 
Monday, May 14, 2007 8:52:20 AM (Mountain Daylight Time, UTC-06:00) ( Code )

Javascript closures

<SCRIPT type="text/javascript">
function CreateRandomNumbers(count, max)
{
 var a = new Array();
 for(var i = 0; i < count; i++)
 {
   a.push(Math.floor(Math.random()*max)+1);
 }
 return a;
}

var smallRandomNumbers = CreateRandomNumbers(20, 20);
var largeRandomNumbers = CreateRandomNumbers(20, 500);

function makeGreaterThanPredicate(lowerBound) {
    return function(numberToCheck) {
    return (numberToCheck > lowerBound) ? true : false;
  };
}

function filter(sortby, arr)
{
  var l = arr.length;
  var a = new Array();
 
  for(var i = 0; i < l; i++)
  {
    var currentElement = arr[i];
    if(sortby(currentElement)) a.push(currentElement);
  }
  return a;
}

function dumpArray(obj)
{
  var l = obj.length;
  document.write('[');
  for(var i = 0; i < l; i++)
  {
    document.write(obj[i]);
    if(i != l-1)
    {
    document.write(',');
    }
  }
 document.write(']');
}
function dump(obj)
{
  var switchToken = typeof(obj);

  switch (switchToken)
  {
    case 'object':
      dumpArray(obj);
      break;
    default:
      document.writeln("Unknown type:");
      document.writeln(obj.toString());
  }
  document.writeln("<br/>");
}
var greaterThan10 = makeGreaterThanPredicate(10);
var greaterThan100 = makeGreaterThanPredicate(100);
dump(smallRandomNumbers);
dump(filter(greaterThan10, smallRandomNumbers));
dump(largeRandomNumbers);
dump(filter(greaterThan100, largeRandomNumbers));
dump(filter(makeGreaterThanPredicate(250), largeRandomNumbers));
</SCRIPT>

Comments [0] | | #