Posts
  • CarlosAg Blog

    Using Nested Azure Logic Apps … or Invoking Flows from another Logic App

    • 0 Comments

    Logic Apps has built-in functionality to allow organizing and splitting your workflows in small manageable units that can then be invoked from another workflow. At this moment that functionality is not exposed through the User Interface, but it is still possible to do that.

    The high level steps to enable that is to:

    1. Create the Child Logic App that will be invoked by the parent logic app and any logic to it.
    2. Create an Access Key in the Child Logic App so that the Parent Logic App can use that as the user name and password to invoke it.
    3. Create the Parent Logic App that uses an action of type “Workflow” and specify the Basic Credentials with the access keys created above.
    4. That is all…

     

    Now the biggest challenge right now is that the User Interface does not currently support managing Access Keys for flows as well as adding an Action for it, so we will be using the great ARMClient that David Ebbo built to do that easily through command line.

     

    Create a Child Logic App

    Here you can build any logic app the way you would like, in my case I called the Resource Group “logicNewRG”. The most important thing here is to Copy the full URL of the Endpoint since you will be using that to invoke this workflow from the Parent Logic App. You can do that in the Blade that shows the Endpoint, in my case since I deployed it to North Central US it looks something like: https://northcentralus.logic.azure.com/subscriptions/049c02d1-000-0000-0000-0000000/resourceGroups/logicNewRG/providers/Microsoft.Logic/workflows/ChildLogicApp

    image 

     

    Create an Access Key for the Logic App

    This is the most complicated part today since there is currently no support for the UI to manage the access keys, however it is simple enough to do that through command line.

    Install ARM Client: Using Chocolatey it is very easy to install ARM Client, you can just run: choco install armclient

    Once you do that you can run the following:

    List the Subscriptions
    armclient GET /subscriptions?api-version=2014-04-01
    set SUB=/subscriptions/049c02d1....e734c


    Create the Access Key sending a PUT and use a POST to list the generated keys
    armclient PUT %SUB%/resourceGroups/logicNewRG/providers/Microsoft.Logic/workflows/ChildLogicApp/accessKeys/mykey?api-version=2015-02-01-preview "{}"

    armclient POST %SUB%/resourceGroups/logicNewRG/providers/Microsoft.Logic/workflows/ChildLogicApp/accessKeys/mykey/list?api-version=2015-02-01-preview

    And that will return the actual keys: (note that there are two so you can rollover your keys and have no downtime easily):

    {
      "primarySecretKey": "uzdaaa18wbccccccc….FpdQ",
      "secondarySecretKey": "4Kpdddp7Rccc….DPyUVX6HeKw"
    }

     

     

    Create the Parent Logic App

    To create the parent logic app you will need to edit manually the Code in the designer for the actual invocation of the Workflow by entering an action of type “Workflow”.

        "actions": {
            "flow"
    : {
                "type": "Workflow",
                "inputs": {
                    "apiVersion": "2015-02-01-preview",
                    "method": "GET",

                    "uri": "https://northcentralus.logic.azure.com/subscriptions/049c02d1...734c/resourceGroups/logicNewRG/providers/Microsoft.Logic/workflows/ChildLogicApp",


                    "trigger": {
                        "Name": "Parentflow"

                        "outputs": {
                            "somedata": {
                                "type": "string",
                                "value": "@{guid()}"
                            }
                        } 
                    },


                    "authentication"
    : {
                        "Type"
    : "Basic",
                        "UserName": "mykey",
                        "Password": "uzdEC...PfUFpdQ"
                    }

                }
            }
        }
    ,

     

    Passing data to the child logic app

    Notice that in this case I’m also passing some data, like a generated GUID to the flow that then can be referenced in the child logic app with something like: @triggers().outputs.somedata.

    Returning data back to the Parent Logic App

    You can send data back to the parent workflow by using the Outputs in the child logic app, such as:

        "outputs": {
            "endTime"
    : {
                "type": "String",
                "value": "Child Flow completed at: @{utcnow()}"
            }
        }
  • CarlosAg Blog

    Install IIS SEO Toolkit in Windows 8.1

    • 0 Comments

    Today I was trying to install the SEO Toolkit in IIS 8.5 running in my Windows 8.1 desktop machine. It appears that the Web Pl has not been updated to allow the installation of it, but you can easily install it if you use the MSI directly so feel free to install them from:

    IIS SEO Toolkit x64 / IIS SEO Toolkit x86

  • CarlosAg Blog

    How to display a DateTime in WinDbg using SOS

    • 1 Comments

    One of the things that I’ve always struggled in the past while looking at some Managed Code and analyzing a crash dump in WinDbg using SOS was dumping a System.DateTime. Sure enough you can use DumpVC and get the actual “data” inside it, but that is not very readable. In the past I used the trick of using .formats for displaying it, but that only works when you are looking at one DateTime at a time, but it is incredible painful when you need to look to many. So finally decided to write a quick “script” to dump the DateTime with a single command.

    So just for fun and to contrast it, lets look at the “hard way I used to do this before”, if you don’t care and want the better way just ignore “The Hard Way”section, so for this in my crash dump the DateTime lives at: 0000003692816498:

    The New Way

    As you probably will see below in “The Hard Way” and after doing that a few times, you get motivated to find a better way, and that is why I decided to write a quick little WinDbg scripts using the “Debugger Command Programs” that make this process very nice. Now I can run one line and get that super easy:

    0:000> $$>a<e:\Scripts\DumpDate.txt 0000003692816498
    2014-10-21 21:50:27 (local)

    Notice that in this case my script will actually dump the right date, including the fact that it is in local time. See the bottom for the actual script, or download the script from my web site at: http://www.carlosag.net/downloads/DumpDate.txt

    The Hard Way

    First you need to find the MT of DateTime, and like “everyone knows” that is in mscorlib.dll, then you call DumpVC, then you remove the flags from DateTime data to get the ticks (remember the magic number 0x3FFFFFFFFFFFFFFF)… and then finally you call the nice and handy “.formats”… easy, right?

    0:000> !Name2EE mscorlib.dll System.DateTime
    Module:      00007ffab1741000
    Assembly:    mscorlib.dll
    Token:       000000000200005b
    MethodTable: 00007ffab1dc7af8
    EEClass:     00007ffab17439d8
    Name:        System.DateTime

    0:000> !DumpVC 00007ffab1dc7af8 0000003692816498
    Name:        System.DateTime
    MethodTable: 00007ffab1dc7af8
    EEClass:     00007ffab17439d8
    Size:        24(0x18) bytes
    File:        D:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
    Fields:
                  MT    Field   Offset                 Type VT     Attr            Value Name
    00007ffab1dcac18  40000dc        0        System.UInt64  1 instance 5247181268704488069 dateData
    00007ffab1db3768  40000d8       50       System.Int32[]  0   shared           static DaysToMonth365
                                     >> Domain:Value  00000..0 <<
    00007ffab1db3768  40000d9       58       System.Int32[]  0   shared           static DaysToMonth366
                                     >> Domain:Value  00000..0 <<
    00007ffab1dc7af8  40000da       40      System.DateTime  1   shared           static MinValue
                                     >> Domain:Value  00000..0 <<
    00007ffab1dc7af8  40000db       48      System.DateTime  1   shared           static MaxValue
                                     >> Domain:Value  000000..768 <<


    0:000> ? 0n5247181268704488069 & 0x3FFFFFFFFFFFFFFF
    Evaluate expression: 635495250277100165 = 08d1bb90`2936ea85

    0:000> .formats 08d1bb90`2936ea85
    Evaluate expression:
      Hex:     08d1bb90`2936ea85
      Decimal: 635495250277100165
      Octal:   0043215671005115565205
      Binary:  00001000 11010001 10111011 10010000 00101001 00110110 11101010 10000101
      Chars:   ....)6..
      Time:    Tue Oct 21 14:50:27.710 3614 (UTC - 7:00)
      Float:   low 4.06155e-014 high 1.26228e-033
      Double:  3.43715e-266

    And also always remember to decrease 1,600 years from the date since the managed dates are not based on the same “starting year”, and they will always be off by 1,600 years. Now imagine what you are trying to do is look at 10 or 20 DateTime objects, it can become cumbersome.

     

    Summary

    It was quite fun getting to do this script, I learned more than I ever thought I wanted to know about DateTime and its internal representation and dealing with leap years and more. I’m sure the script could be written in a much better way (maybe using named variables/addresses as opposed to pseudo registers, etc) but I needed to get it done quickly. The actual task I was trying to achieve was to dump all HttpContext in ASP.NET and see what was the time when they were created (_utcTimestamp), will post a different blog at some point showing a handy script to dump HTTP Requests, System.Net.Connection, and other common objects that I get to have the pleasure to debug issues on the server side.

    The Script

    $$ WinDbg Program to Dump a System.Date...

    $$  requires specifying the DateTime Address

    $$

    $$ Written by: CarlosAg (http://www.carlosag.net/)

    $$

    $$ Run as: $$>a<DumpDate.txt <DateTimeAddress>

    $$

    $$ Example : $$>a<e:\shares\public\AzureUX\scripts\DumpDate.txt 000007fef7411c48       

    $$ Uses Pseudo Registers t11-t19

     

    ad/q *

     

    $$ Read the argument

    r$t11=poi(${$arg1});

     

    $$   Now for the Date Time Magic... and fun...

    $$ DateTime stores in a QuadWord Binary format using the last two digits to signal UTC and other flags

    $$ Is UTC? dq & 0x4000000000000000

    $$  so lets get rid of those to get the ticks  dq ... time ... Mask with 0x3FFFFFFFFFFFFFFF

    $$  ticks in one millisecond = 10,0000, that means...

    $$ seconds== ((0n10000) * (0n1000))                            == 0n10000000 

    $$ minutes== ((0n10000) * (0n1000) * (0n60))                   == 0n600000000

    $$ hours  == ((0n10000) * (0n1000) * (0n60) * (0n60))          == 0n36000000000

    $$ days   == ((0n10000) * (0n1000) * (0n60) * (0n60) * (0n24)) == 0n864000000000

    $$

     

    $$ First lets get rid of the UTC/Flags stuff to get just the ticks

    r$t11=@$t11&0x3FFFFFFFFFFFFFFF;

     

    $$ Now get the # of Days == day count...

    r$t12=(@$t11/0n864000000000);

     

    $$ Easy... we can get the Hours

    r$t13=(@$t11 % 0n864000000000)/0n36000000000;

    $$ and Minutes

    r$t14=((@$t11 % 0n864000000000) % 0n36000000000)/0n600000000;

    $$ and Seconds

    r$t15=(((@$t11 % 0n864000000000) % 0n36000000000) % 0n600000000)/0n10000000;

     

    $$ Now for the heavy duty of figuring out days...  leap years and more fun

    $$ DaysPerYear = 365

    $$ DaysPer4Years = DaysPerYear * 4 + 1   == 1,461

    $$ DaysPer100Years = DaysPer4Years * 25 - 1 == 36,524

    $$ DaysPer400Years = DaysPer100Years * 4 + 1 == 146,097

     

    $$ t16 will have the Years...

    $$  the following lines will try to get the right number of years

    $$  which is tricky due to leap years and so the math...

    $$  first figure out the # of "400 years" + the "100 years" + "4 years" + "1 years" + 1

    $$ Days... == y400

    r$t16=(@$t12/0n146097);

    $$ n... - diff 400years

    r$t12=@$t12-(@$t16*0n146097);

     

    $$ Days... == y100

    r$t17=(@$t12/0n36524);

    $$ n... - diff 100years

    r$t12=@$t12-(@$t17*0n36524);

     

    $$ Days... == y4

    r$t18=(@$t12/0n1461);

    $$ n... - diff 4years

    r$t12=@$t12-(@$t18*0n1461);

     

    $$ Days... == y1

    r$t19=(@$t12/0n365);

     

    $$ Last year has an extra day, so decrement result if 4

    .if(@$t19==0n4){

          r$t19=0n3;

    }

     

    $$ Here now we have in t12 the day number in the current year...

    $$ n... - diff 1years

    r$t12=@$t12-(@$t19*0n365);

     

    $$ Finally!!! we know the right year

    r$t16=(@$t16*0n400)+(@$t17*0n100)+(@$t18*0n4)+(@$t19)+1;

     

    $$ Now determine if this is a Leap Year... we'll need that to get the right month/day

    r$t18=0n0;

    .if(@$t19==0n3){

          .if(@$t18!=0n24){

                r$t18=0n1;

          }

          .if(@$t17==0n3){

                r$t18=0n1;

          }

    }

     

    $$ Month and Day ...

    r$t19=0n0;

    .if(@$t18==0n0){

          $$ .printf "non-leap year";

          r$t19=12;

          .if(@$t12<=0n31){

                r$t19=0n1;

                r$t12=@$t12-0n0+1;

          }

          .elsif(@$t12<=0n59){

                r$t19=0n2;

                r$t12=@$t12-0n31+1;

          }

          .elsif(@$t12<=0n90){

                r$t19=0n3;

                r$t12=@$t12-0n59+1;

          }

          .elsif(@$t12<=0n120){

                r$t19=0n4;

                r$t12=@$t12-0n90+1;

          }

          .elsif(@$t12<=0n151){

                r$t19=0n5;

                r$t12=@$t12-0n120+1;

          }

          .elsif(@$t12<=0n181){

                r$t19=0n6;

                r$t12=@$t12-0n151+1;

          }

          .elsif(@$t12<=0n212){

                r$t19=0n7;

                r$t12=@$t12-0n181+1;

          }

          .elsif(@$t12<=0n243){

                r$t19=0n8;

                r$t12=@$t12-0n212+1;

          }

          .elsif(@$t12<=0n273){

                r$t19=0n9;

                r$t12=@$t12-0n243+1;

          }

          .elsif(@$t12<=0n304){

                r$t19=0n10;

                r$t12=@$t12-0n273+1;

          }

          .elsif(@$t12<=0n334){

                r$t19=0n11;

                r$t12=@$t12-0n304+1;

          }

    }

    .else{

          $$ .printf "leap year";

          r$t19=12;

          .if(@$t12<=0n31){

                r$t19=0n1;

                r$t12=@$t12-0n0+1;

          }

          .elsif(@$t12<=0n60){

                r$t19=0n2;

                r$t12=@$t12-0n31+1;

          }

          .elsif(@$t12<=0n91){

                r$t19=0n3;

                r$t12=@$t12-0n60+1;

          }

          .elsif(@$t12<=0n121){

                r$t19=0n4;

                r$t12=@$t12-0n91+1;

          }

          .elsif(@$t12<=0n152){

                r$t19=0n5;

                r$t12=@$t12-0n121+1;

          }

          .elsif(@$t12<=0n182){

                r$t19=0n6;

                r$t12=@$t12-0n152+1;

          }

          .elsif(@$t12<=0n213){

                r$t19=0n7;

                r$t12=@$t12-0n182+1;

          }

          .elsif(@$t12<=0n244){

                r$t19=0n8;

                r$t12=@$t12-0n213+1;

          }

          .elsif(@$t12<=0n274){

                r$t19=0n9;

                r$t12=@$t12-0n244+1;

          }

          .elsif(@$t12<=0n305){

                r$t19=0n10;

                r$t12=@$t12-0n274+1;

          }

          .elsif(@$t12<=0n335){

                r$t19=0n11;

                r$t12=@$t12-0n305+1;

          }

    }

     

    $$ Finally we have all the pieces!!! just print them...

    $$ yy-MM-dd Hour:Minute:Second

    .printf"%d-%d-%d %d:%d:%d",@$t16,@$t19,@$t12,@$t13,@$t14,@$t15;

     

    $$ FlagsMask             = 0xC000000000000000

    $$ KindUtc               = 0x4000000000000000;

    $$ KindLocal             = 0x8000000000000000;

    r$t17=poi(${$arg1})&0xC000000000000000;

    .if(@$t17==0x4000000000000000){

          .printf" (local)";

    }

    .elsif(@$t17==0x8000000000000000){

          .printf" (UTC);

    }

  • CarlosAg Blog

    Winsxs is huge… Free up a few Gigabytes with dism

    • 9 Comments

    I was running out of disk space in C: and was unable to install a small software that I needed, so I decided to clean up a bit. For that I like using WinDirStat http://windirstat.info/ which very quickly allows you to find where the big files/folders are. In this case I found that my c:\Windows\winsxs folder was over 12 GB of size. One way to reclaim some of that disk space is to cleanup all files that have been backed up when a Service Pack has been installed. To do that in Windows 7 you can run the following DISM command:

    dism /online /cleanup-image /spsuperseded /hidesp


    That freed up 4 GB in my machine and now I can move on.

    Disclaimer: I only ran this in my Windows 7 machine and it worked great, have not tried it in Server SKUs so run at your own risk.

  • CarlosAg Blog

    It has been a long time since last post

    • 1 Comments

    Wow, just realized that in the last 6 months I’ve only had a chance to post 2 items and I think it is about time to start this going again.

    So why this much silence? Well, About 8 months ago a couple of big changes happened at my division as described in this link. As part of that transition my responsibilities changed and I transitioned from being the Development Manager for the Web Platform (IIS, WebMatrix, WebDeploy, etc…) to take a new role and start a new team that we called Azure UX team. Our team is in charge of reimagining the Windows Azure User Experience and we stated on a mission to really make it even better. As part of that we’ve been super busy delivering a set of projects, some of which we released in December such as our brand new Windows Azure web site at http://www.windowsazure.com/ and the new Windows Azure Billing Web site at https://account.windowsazure.com/, several updates to the Windows Azure Management Portal at http://windows.azure.com/ and some that have not yet been released.

    These last months have been quite a ride, I feel privileged of having been part of the IIS family and community and its multiple releases since even before Windows Vista shipped, 7, 7.5, ARR, URL Rewrite, Web Deploy, WebMatrix, WebPI, IIS Media Services, FTP, WebDAV, SEO Toolkit, WFF, FastCGI, WinCache, AdminPack, PowerShell, Database Manager, Dynamic IP Restrictions, and many many more releases my team delivered, and obviously, the awesome new version in Windows 8. I will always remain close to all of them, both the people, the products, and the community.

    At the same time, I’m extremely happy and excited to also have now a chance to be part of such an amazing group of people the AUX team, a very fast pace, dedicated and professional group, working on a new mission that will influence so deeply the shape of the cloud.

  • CarlosAg Blog

    Using Windows Authentication with Web Deploy and WMSVC

    • 0 Comments

    By default in Windows Server 2008 when you are using the Web Management Service (WMSVC) and Web Deploy (also known as MSDeploy) it will use Basic authentication to perform your deployments. If you want to enable Windows Authentication you will need to set a registry key so that the Web Management Service also supports using NTLM. To do this, update the registry on the server by adding a DWORD key named "WindowsAuthenticationEnabled" under HKEY_LOCAL_MACHINE\Software\Microsoft\WebManagement\Server, and set it to 1. If the Web Management Service is already started, the setting will take effect after the service is restarted.

    For more details on other configuration options see:

    http://technet.microsoft.com/en-us/library/dd722796(WS.10).aspx

  • CarlosAg Blog

    Razor Migration Notes 3: Use app_offline.htm to deploy the new version

    • 1 Comments

    This is the third post on the series:

    1: Moving a SitemapPath Control to ASP.NET Web Pages

    2: Use URL Rewrite to maintain your Page rankings (SEO)

     

    ASP.NET has a nice feature to help for deployment processes where you can drop an HTML file named app_offline.htm and it will unload all assemblies and code that it has loaded letting you easily delete binaries and deploy the new version while still serving back to customers the friendly message that you provide telling them that your site is under maintenance.

    One caveat though, is that Internet Explorer users might still see the “friendly” error that they display and not your nice message. This happens because of a page size validation that IE performs. See Scott’s blog on how to workaround that problem: App_Offline.htm and working around the IE Friendly Errors

    Note: The live site is now running in .NET 4.0 and all using Razor.

  • CarlosAg Blog

    Razor Migration Notes 2: Use URL Rewrite to maintain your Page rankings (SEO)

    • 0 Comments

    This is the second note of the series:

    1: Moving a SitemapPath Control to ASP.NET Web Pages

    My current Web Site was built using ASP.NET 2.0 and WebForms, that means that all of my pages have the extension .aspx. While moving each page to use ASP.NET Web Pages their extension is being changed to .cshtml, and while I’m sure I could configure it in a way to get them to keep their aspx extensions it is a good opportunity to “start clean”. Furthermore, in ASP.NET WebPages you can also access them without the extension at all, so if you have /my-page.cshtml, you can also get to it using just /my-page. Given I will go through this migration I decided to use the clean URL format (no extension) and in the process get better URLs for SEO purposes, for example, today one of the URLs look like http://www.carlosag.net/Articles/configureComPlus.aspx but this would be a good time to enforce lower-case semantics and also get rid of those ugly camel casing and get a much more standard a friendly format for Search Engines using “-“, like: http://www.carlosag.net/articles/configure-com-plus.aspx.

    Use URL Rewrite to make sure to keep your Page Ranking and no broken links

    The risk of course is that if you just change the URLs of your site you will end up not only with lots of 404’s (Not Found), but your page ranking will be reset and you will loose all the “juice” that external links and history have provided to it. The right way to do this is to make sure that you perform a permanent redirect (301) from the old URL to the new URL, this way Search Engines (and browsers) will know that the content has permanently moved to a new location so they should “pass all the page ranking” to the new page.

    There are many ways to achieve this, but I happen to like URL Rewrite a lot, so I decided to use it. To do that I basically created one rule that uses a Rewrite Map (think of it as a Dictionary) to match the URL and if it matches it will perform a permanent redirect to the new one. So for example, if /aboutme.aspx is requested, then it will 301 to /about-me:

    <?xml version="1.0"?>
    <configuration>
     
    <system.webServer>
       
    <rewrite>
         
    <rules>
           
    <rule name="Redirect for OldUrls" stopProcessing="true">
             
    <match url=".*"/>
              <
    conditions>
               
    <add input="{OldUrls:{REQUEST_URI}}" pattern="(.+)"/>
              </
    conditions>
             
    <action type="Redirect" url="{C:1}" appendQueryString="true" redirectType="Permanent" />
            </
    rule>
         
    </rules>
         
    <rewriteMaps>
           
    <rewriteMap name="OldUrls">
             
    <add key="/aboutme.aspx" value="/about-me"/>
              <
    add key="/soon.aspx?id=1" value="/coming-soon"/>
              <
    add key="/Articles/configureComPlus.aspx" value="/articles/configure-com-plus"/>
              <
    add key="/Articles/createChartHandler.aspx" value="/articles/create-aspnet-chart-handler"/>
              <
    add key="/Articles/createVsTemplate.aspx" value="/articles/create-vs-template"/>
          ...
            </
    rewriteMap>
         
    </rewriteMaps>
       
    </rewrite>
     
    </system.webServer>
    </configuration>

     

    Note that I could have also created a simple rule that would change the extension to cshtml, however I decided that I also wanted to change the page names. The best thing is that you can do it incrementally and only rewrite them once your new page is ready or even switch back to the old one later if any problems occur.

    Summary

    Using URL Rewrite you can easily keep your SEO and pages without broken links. You can also achieve lots more, check out: SEO made easy with IIS URL Rewrite 2.0 SEO templates – CarlosAg

  • CarlosAg Blog

    Razor Migration Notes 1: Moving a SitemapPath Control to ASP.NET Web Pages

    • 2 Comments

    After many years I decided that it is time to rewrite my Web site using Razor. A bit of history, I started it around 2003 using ASP.NET 1.1. When .NET 2.0 came around in 2005 I migrated to it and it was great being able to leverage features like MasterPages, Themes, Sitemaps, and many other features. Honestly it is a pretty simple Web site, with mostly content, so very few controls, Sitemap, my own custom Menu control, and a couple more. Last week it was moved to use .NET 4.0 and it feels its about time to go back and update it a bit, both in look and features. So this (if time permits) will be the first of a series of migration notes that I discover as I move it to use ASP.NET Razor (aka WebPages). Do note that this is not meant to be a best practice in anyway, I would never claim I can make such a thing, these will be only my personal notes as I discover more details in ASP.NET WebPages features and as I move my own implementation to use them.

    So with that, one of the first things I faced during this migration, was the use of a Sitemap control (asp:SiteMapPath) in my MasterPage (future post about moving from MasterPages coming). I knew about Sitemap API, so I just decided to write a simple Sitemap helper that I can now use anywhere in Razor. The code is pretty simple, it basically generates an unordered list of links using <ul> and <li> with <a> inside, and used CSS to layout them in a way that I liked.

    SitemapPath Control in WebForms

    The original code I was using in my MasterPage looked like the following:

    <asp:SiteMapPath CssClass="HeaderText" runat="server" ID="siteMap" ShowToolTips="true" NodeStyle-ForeColor="White" CurrentNodeStyle-Font-Bold="true" />

    And generated the following markup:

    <span id="siteMap" class="HeaderText"><a href="#siteMap_SkipLink"><img alt="Skip Navigation Links" height="0" width="0" src="http://blogs.msdn.com/WebResource.axd?d=S2jbW9E-HYlS0UQoRCcsm94KUJelFI6yS-CQIkFvzT6fyMF-zCI4oIF9bSrGjIv4IvVLF9liJbz7Om3voRpNZ8yQbW3z1KfqYr4e-0YYpXE1&amp;t=634219272564138624" style='border-width:0px;' /></a><span><a title='Home' href='/' style='color:White;'>Home</a></span><span> &gt; </span><span><a title='Free tools for download' href='/Tools/' style='color:White;'>Tools</a></span><span> &gt; </span><span style='color:White;font-weight:bold;'>Code Translator</span><a id='siteMap_SkipLink'></a></span>

    Which looks like the following in the browser:

    image

    I used some CSS to set the color, and background and other stuff, but still to set the last item to bold required me to use a property in the Sitemap to get it to look the way I wanted.

    My Sitemap Helper in Razor

    Since I was familiar with the Sitemap API and my goal was to change as “little” as possible as part of this first migration, I decided to write a Sitemap helper that I can use in my Layout pages. The code in the Page is as simple as it gets, you just call @Helpers.Sitemap() and that’s it (added the Div below to get some context in the markup, but that was already there with the SitemapPath control anyway):

    <div class="bannerPath">
    @Helpers.Sitemap()
    </div>

    This new helper version generates the markup below. I don’t know about you, but I can sure make more sense of what it says, and I imagine Search Engines will as well, I decided to use more semantically correct markup using a <nav> to signal navigational section and use a list of links.

    <nav>
        <ul class="siteMap">
            <li><a href="http://blogs.msdn.com/" title="Home">Home</a>&nbsp;&gt;&nbsp;</li>
            <li><a href="http://blogs.msdn.com/Tools/" title="Free tools for download">Tools</a>&nbsp;&gt;&nbsp;</li>
            <li><span>Code Translator</span></li>
        </ul>
    </nav>

    And it looks like the following in the browser (I decided to remove the underlining, and have more padding, and a new font, but all of that is CSS):

    image

    The Sitemap helper code

    The code to do the Sitemap was pretty simple, just use the SiteMap API to get the current node. Since I’m picky and I wanted to generate the markup in the “right” order (note you could use CSS to float them to the right instead), I used a Stack to push the nodes while traversing them up. Finally just generate the <li>.

    @helper Sitemap()
    {
        SiteMapNode currentNode = SiteMap.CurrentNode;
        <nav>
        <ul class="siteMap">
        @if (currentNode != null)
        {
            // Push into a stack to reverse them
            var node = currentNode;
            var nodes = new Stack<SiteMapNode>();
            while (node.ParentNode != null)
            {
                nodes.Push(node.ParentNode);
                node = node.ParentNode;
            }
           
            while(nodes.Count != 0)
            {
                SiteMapNode n = nodes.Pop();
                <li><a href="@n.Url" title="@n.Description">@n.Title</a>&nbsp;&gt;&nbsp;</li>
            }
            <li><span>@currentNode.Title</span></li>
        }
        else
        {
            <li><span>@Page.Title</span></li>
        }
        </ul>
        </nav>
    }

     

    To make it look the way I wanted I used the following CSS:

    .siteMap

      { float:right; font-size:11px; color:White; display:inline; margin-top:3px; margin-bottom:3px; margin-left:0px; margin-right:10px; } .siteMap li,span { float:left; list-style-type:none; padding-left:5px; border-width:0px;} .siteMap span { font-weight:bold; } .siteMap a,a.Visited { color:White; text-decoration:none; }

     

    Conclusion

    SitemapPath control gives you a really easy way to put together a navigation control based on the Sitemap APIs (and the Web.Sitemap file in my case). Creating a simple ASP.NET Razor helper is actually pretty easy since all the functionality needed is there in the base API’s and although it required some code (20 lines of code) now I feel like I have more control over my markup, can style it in anyway I want using CSS and have cleaner markup rendered.

    I’m sure there are better ways to do this, but as I said, the goal of this first pass is to push my site soon with as little changes possible while keeping the same functionality first.

  • CarlosAg Blog

    Get IIS bindings at runtime without being an Administrator

    • 3 Comments

    Today there was a question in StackOverflow asking whether it was possible to read the IIS binding information such as Port and Protocols from the ASP.NET application itself to try to handle redirects from HTTP to HTTPS in a way that was reliable without worrying about using different ports than 80/443.

    Turns out this is possible in the context of the IIS worker process by using Microsoft.Web.Administration.

    The following function will take care of that by reading the Worker Process isolated configuration file and find the HTTP based bindings.

        private static IEnumerable<KeyValuePair<string, string>> GetBindings(HttpContext context) {
           
    // Get the Site name 
            string siteName = System.Web.Hosting.HostingEnvironment.SiteName;

           
    // Get the sites section from the AppPool.config
            Microsoft.Web.Administration.ConfigurationSection sitesSection =
               
    Microsoft.Web.Administration.WebConfigurationManager.GetSection(null, null, "system.applicationHost/sites");
            
           
    foreach (Microsoft.Web.Administration.ConfigurationElement site in sitesSection.GetCollection()) {
               
    // Find the right Site
                if (String.Equals((string)site["name"], siteName, StringComparison.OrdinalIgnoreCase)) {

                   
    // For each binding see if they are http based and return the port and protocol
                    foreach (Microsoft.Web.Administration.ConfigurationElement binding in site.GetCollection("bindings")) {
                       
    string protocol = (string)binding["protocol"];
                       
    string bindingInfo = (string)binding["bindingInformation"];

                       
    if (protocol.StartsWith("http", StringComparison.OrdinalIgnoreCase)) {
                           
    string[] parts = bindingInfo.Split(':');
                           
    if (parts.Length == 3) {
                               
    string port = parts[1];
                               
    yield return new KeyValuePair<string, string>(protocol, port);
                           
    }
                       
    }
                   
    }
               
    }
           
    }
       
    }

     

    If you want to try it, you could use the following page, just save it as test.aspx and add the function above, the result is a simple table that shows the protocol and port to be used:

    <%@ Page Language="C#" %>
    <%@ Import Namespace="System.Collections.Generic" %>
    <script runat="server">
       
    protected void Page_Load(object sender, EventArgs e) {
           
    Response.Write("<table border='1'>");
           
    foreach (KeyValuePair<string, string> binding in GetBindings(this.Context)) {
               
    Response.Write("<tr><td>");
               
    Response.Write(binding.Key);
               
    Response.Write("</td><td>");
               
    Response.Write(binding.Value);
               
    Response.Write("</td></tr>");
           
    }
           
    Response.Write("</table>");
       
    }
    </script>



    Also, you will need to add Microsoft.Web.Administration to your compilation assemblies inside the web.config for it to work:

    <?xml version="1.0"?>
    <configuration>
      
    <system.web>
        
    <compilation debug="true">
          
    <assemblies>
            
    <add assembly="Microsoft.Web.Administration, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
          </
    assemblies>
        
    </compilation>
      
    </system.web>
    </configuration>
Page 1 of 10 (94 items) 12345»