Brothers In Code

...a serious misallocation of .net resources

HTTP Requests With Oracle - Part 1

Making a web request from oracle is as easy as:


select utl_http.request('www.google.com')
from dual;

But if you're making a request from Oracle, chances are you need to do a little work and not just google something so here's a little pl/sql code...


declare
  l_url varchar2(4000) := 'www.google.com';
 
  l_httpRequest   utl_http.req;
  l_httpResponse  utl_http.resp;
  l_tempResponse clob;
  l_buffer varchar2(32767);
begin  
  utl_http.set_detailed_excp_support (true);

  --setup the http connection
  l_httpRequest := UTL_HTTP.begin_request(l_url, 'GET','HTTP/1.1');

  l_httpResponse := utl_http.get_response(l_httpRequest);
  
  --write the response (in blocks) to a temporary clob
  dbms_lob.createtemporary(l_tempResponse, FALSE);
  BEGIN
    LOOP
       UTL_HTTP.read_text(l_httpResponse, l_buffer, 32767);
       dbms_lob.writeappend(l_tempResponse, length(l_buffer), l_buffer);
    END LOOP;
  EXCEPTION
    WHEN utl_http.end_of_body THEN NULL;
  END;
  UTL_HTTP.end_response(l_httpResponse);

  DBMS_OUTPUT.PUT_LINE('Response: ' || l_tempResponse);

end;
/

It would be a simple job to add a couple of query string parameters on to the URL.  But it's very likely you'll be sending data to some sort of a web service and then expecting a response.

Here's an example of a POST request.


  ....
  l_httpRequest := UTL_HTTP.begin_request(l_serviceUrl, 'POST','HTTP/1.1');
  utl_http.set_header(l_httpRequest, 'Content-Type', 'application/x-www-form-urlencoded');
 
  --these are likely constant for the application
  l_postData :=
    'x_login='|| 'xxxxxx' 
    || '&x_tran_key='|| 'yyyyyyy'
    || '&x_delim_data='|| 'TRUE'

   utl_http.set_header(l_httpRequest, 'Content-Length', length(l_postData));


  utl_http.write_text(l_httpRequest, l_postData);
  --get a reference to the response
  l_httpResponse := utl_http.get_response(l_httpRequest);
  ....

That's it.  In Part 2 I'll go over some of the finer points of SSL requests and the Oracle Wallet.

Getting Active Directory Information From ASP.net The Easy Way

A couple years ago I wrote a short article on an old blog about connecting to Active Directory from an ASP.net project.  I thought the article was fairly complete until I tried to help somebody else do the same thing.  I had forgotten two major parts:

  • Manually sifting thru the different AD containers to find the right LDAP string. 
  • Ensuring the correct security context.

The security context can be an easy thing.  A console or winforms .net app runs in the context of the currently logged on user.  For simplicity the ASP.Net development server in Visual Studio does the same thing.  So as long as your user account as access to the AD (which is the norm unless your AD admin has locked down querying), then you should be fine while you are developing.

Running under IIS is a different story.  Depending on the version, the user context of the iis process could be all sorts of things - ASPNET, Network Service, Local Service, the new IIS app pool accounts, etc.  During testing the easiest thing to do is enable impersonation:


<configuration>
  <system.web>
    <identity impersonate="true" userName="contoso\Jane" password="********" />
  </system.web>
</configuration>

Optionally, you can remove the userName and password attributes and make sure that NTLM/Integrated security is still checked in the IIS configuration.  This will make IIS operate similar to the ASP.Net Development Server and use the current user as the execution context.

So with the security context out of the way, the next task is to actually query AD for a user object.  In my old example, I would specify the container to connect to whe creating a DirectoryEntry object.  For example:


System.DirectoryServices.DirectoryEntry directoryEntry
        = new System.DirectoryServices.DirectoryEntry(LDAP://CN=Users,DC=contoso,DC=com);

This worked fine for me but was a bit to limiting as an example to go by.  Many domains have an additional "corp" domain context.  "Users" might be named something different like "DomainUsers".  There are all sorts of variables with AD.  To combat this I added an extra query to "RootDSE" which is sort of the current context.  With that I'm able to get the address of an actuall domain controller and from there, can make the query on a higher level.  In the example below, I'm trying to get the AD User object for the currently authenticated user, but there's lots of diagnostic output that would let you find other things or narrow down the container that you're querying (for performance reasons).

 


<%@ Page Language="C#" %>
<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="System.Collections.Generic" %>

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

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
      OptLocalUser.Text = User.Identity.Name;
     
      Dictionary<String, String> directoryPropertyDictionary;
       
     
      //get the currently connected AD server info:
      DirectoryEntry de = new DirectoryEntry("LDAP://RootDSE");
     
      directoryPropertyDictionary = new Dictionary<string, string>(de.Properties.Count);
      foreach (string key in de.Properties.PropertyNames)
      {
        String allValues = "";
        foreach (object value in de.Properties[key])
          allValues += value;
        directoryPropertyDictionary.Add(key, allValues);
      }
      GridView1.DataSource = directoryPropertyDictionary;
      GridView1.DataBind();
     
      //connect to the current server
      de = new DirectoryEntry("LDAP://" + de.Properties["dnsHostName"][0].ToString());

      DirectorySearcher ds = new DirectorySearcher(de);
      String[] nameParts = User.Identity.Name.Split('\\');
      ds.Filter = "(&(objectClass=user)(sAMAccountName=" + nameParts[1] + "))";
      SearchResult result = ds.FindOne();


      OptUserPath.Text = result.Path;
      directoryPropertyDictionary.Clear();
      foreach (string key in result.Properties.PropertyNames)
      {
        String allValues = "";
        foreach (object value in de.Properties[key])
          allValues += value;
        directoryPropertyDictionary.Add(key, allValues);
      }
      GridView2.DataSource = directoryPropertyDictionary;
      GridView2.DataBind();

      OptLocalUser.Text = User.Identity.Name;
    }
  </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      Local User:<asp:Label ID="OptLocalUser" runat="server" /><br/>
     
      RootDSE:
      <asp:GridView ID="GridView1" runat="server">
      </asp:GridView>
     
      AD User Path:<asp:Label ID="OptUserPath" runat="server" /><br/>
      <asp:GridView ID="GridView2" runat="server">
      </asp:GridView>
    </div>
    </form>
</body>
</html>

AsyncPostBackTrigger On A Control External To An Update Panel Doesn't Display An Update Progress Control

Consider the following code:


    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>   
    <div>
      <asp:Label ID="Label2" runat="server" Text="Label"></asp:Label>
      <asp:Button ID="Button2" runat="server" Text="Button" />
      <asp:Button ID="Button3" runat="server" Text="Button" />
      <asp:UpdatePanel ID="UpdatePanel1" runat="server">
   <Triggers>
     <asp:AsyncPostBackTrigger ControlID="Button2" />
   </Triggers>
      <ContentTemplate>
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
      </ContentTemplate>
      </asp:UpdatePanel>
     <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
      <ProgressTemplate>
       Please Wait
      </ProgressTemplate>
     </asp:UpdateProgress>
    </div>


//code behind:
  protected void Page_Load(object sender, EventArgs e)
  {
    Label1.Text = DateTime.Now.ToString();
    Label2.Text = DateTime.Now.ToString();

    if (Page.IsPostBack)
    {
      System.Threading.Thread.Sleep(2000);
    }
  }

In the above example, Button1 will do a partial page update for UpdatePanel1.  Button3 will cause a full postback.  Button two will also do a partial page update for UpdatePanel1 since it is listed as a trigger.  However, Button2 will not cause UpdateProgress1 to be displayed.

There is some help for this on the buttom of an article on codeproject.com, but I needed something for multiple controls.  My solution ended up being two functions:


  private void RegisterExternalTriggerFixForUpdateProgress()
  {
    String updateProgressWTriggerFix = @"
      var triggerMappings = new Array();

      function TriggerMapping(controlId, updatePanelId)
        {
          this.TriggerControlId = controlId;
          this.UpdatePanelId = updatePanelId;
        }
        function RegisterTriggerMapping(controlId, updatePanelId)
        {
          triggerMappings.push(new TriggerMapping(controlId, updatePanelId));
        }
        function GetTriggerMapping(control)
        {
          for(var i=0; i<triggerMappings.length; i++)
          {
            if(triggerMappings[i].TriggerControlId == control.id)
            {
              return triggerMappings[i];
            }
          }
          return null;
        }

        var prm = Sys.WebForms.PageRequestManager.getInstance();
        function CancelAsyncPostBack() {
            if (prm.get_isInAsyncPostBack()) {
              prm.abortPostBack();
            }
        }


        prm.add_initializeRequest(InitializeRequest);
        prm.add_endRequest(EndRequest);
        var postBackElement;
        function InitializeRequest(sender, args) {
            if (prm.get_isInAsyncPostBack()) {
                args.set_cancel(true);
            }
            postBackElement = args.get_postBackElement();
       
            var triggerMapping = GetTriggerMapping(postBackElement);
            if (triggerMapping != null) {
                $get(triggerMapping.UpdatePanelId).style.display = 'block';
            }
        }
        function EndRequest(sender, args) {
            var triggerMapping = GetTriggerMapping(postBackElement);
            if (triggerMapping != null) {
                $get(triggerMapping.UpdatePanelId).style.display = 'none';
            }
        }";

    this.Page.ClientScript.RegisterStartupScript(typeof(Page), "UpdateProgressWTriggerFix", updateProgressWTriggerFix, true);
  }
  protected void RegisterExternalAsyncTrigger(Control triggerControl)
  {
    this.Page.ClientScript.RegisterStartupScript(
      this.GetType(),
      triggerControl.ClientID,
      String.Format(@"RegisterTriggerMapping('{0}','{1}');",
        triggerControl.ClientID,
        UpdateProgress1.ClientID),
      true);
  }

Then to fix a particular AsyncPostBackTrigger control, you just need the following call in your Page_Load:


    //fix for async update from a control outside of the update panel
    RegisterExternalTriggerFixForUpdateProgress();
    RegisterExternalAsyncTrigger(Button2);