Thursday, July 22, 2021

An error occurred while parsing EntityName

 PROBLEM: Received the below error at runtime:


Server Error in '/' Application.
Configuration Error
Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: An error occurred while parsing EntityName. Line 86, position 346.

Source Error:
Source File: C:\Dev\mywebsite\web.config    Line: 86


SOLUTION: Pretty straight forward case of an XML element that needed replacing in my web.config file https://stackoverflow.com/a/23542105/1450351 - in my case it was ampersand in a URL, so replaced with &  ... BUT what wasn't simple was that this was that to handle local secrets and prevent them going into source control we use:

<appSettings file="Web.secrets.config">

(This approach is taken from https://www.hanselman.com/blog/best-practices-for-private-config-data-and-connection-strings-in-configuration-in-aspnet-and-azure and https://stackoverflow.com/a/60961168/1450351 (we did consider using SlowCheetah but it is only appropriate for app.config files e.g. with console apps))

...the complication this causes is that the error message refers to web.config but the problem was actually in web.secrets.config ... so when I looked at line 86 in the web.config I couldn't see an issue - and even when I deleted line 86 the issue still remained - because it was referring to line 86 of the web.secrets.config file (the two files kind of merge at run time, with any app settings in the secrets file overriding anything that exists in web.config).

Anyway, luckily I checked the secrets file and saw the issue.

INTERESTING SIDE NOTE: Because I like being able to quickly copy and paste passwords (yes ideally we should be using Azure Key Vault or similar), when I'm creating new passwords I'll explicitly avoid certain characters which needs replacing or escaping in web.config files or other places.  So I opt for a really long password that uses only a subset of special characters e.g. $ # % -

Monday, June 27, 2016

Could not load file or assembly or one of its dependencies. The located assembly's manifest definition does not match the assembly reference.

PROBLEM: Received the below error at runtime:

{"Could not load file or assembly 'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)":"Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed"}

SOLUTION: Lots of potential answers here: http://stackoverflow.com/questions/22507189/could-not-load-file-or-assembly-newtonsoft-json-version-4-5-0-0-culture-neutr

But for me the problem was I had a solution with multiple projects, each with different versions of the nuget package. Obviously only one version can be deployed and if it an old version that gets deployed a binding redirect isn't going to help - so in my case I had to upgrade each project to use the same version of the nuget package - note there is a new feature (which I didn't use) that can help manage one nuget package across multiple projects in a solution (https://artczernecki.wordpress.com/2015/09/08/consolidating-package-versions-with-visual-studio-2015-nuget-package-manager/ and http://stackoverflow.com/questions/34022454/nuget-consolidate-vs-update).

Note, this problem reared its head again briefly after I pulled a different branch via github... I think in that case it was an issue with Visual Studio cache files (*ResolveAssemblyReference.cache) found in the obj folder of the projects... closing the solution and re-starting Visual Studio was enough to refresh these and fix the issue.

INTERESTING SIDE NOTE: Nuget package versions are not necessarily the same as the dll AssemblyVersion (the important version - this is what the compiler cares about and is the version number that is important for binding redirects etc - can view in Visual Studio under properties where it is referenced or can use ILSpy or similar) or AssemblyFileVersion (what you see when you right click and view a DLLs properties in explorer - compiler never refers to this).  As an example... Newtonsoft.json nuget package 5.0.2 has a AssemblyVersion 4.5.0.0 and AssemblyFileVersion 5.0.2.16008 ... some more reading is here: http://stackoverflow.com/questions/64602/what-are-differences-between-assemblyversion-assemblyfileversion-and-assemblyin and http://stackoverflow.com/questions/11965924/nuspec-version-attribute-vs-assembly-version

Friday, January 29, 2016

HTTP Error 404.17 when running WCF service in Visual Studio 2015 (VS2015)

PROBLEM: On Windows 7 with Visual Studio 2012 I could run an asp.net .NET 3.5 project with WCF services.  After installing Windows 10 and Visual Studio 2015, I could no longer debug a WCF service which previously ran fine on Windows 7 and Visual Studio 2012.  I get the following error: "

HTTP Error 404.17 - Not Found - The requested content appears to be script and will not be served by the static file handler. - Most likely causes: The request matched a wildcard mime map. The request is mapped to the static file handler. If there were different pre-conditions, the request will map to a different handler. Things you can try: If you want to serve this content as a static file, add an explicit MIME map."

SOLUTION: I had to turn on a number of Windows features that aren't on by default:

  • .NET Framework 3.5 (includes .NET 2.0 and 3.0) > Windows Communication Foundation HTTP Activation
  • .NET Framework 3.5 (includes .NET 2.0 and 3.0) > Windows Communication Foundation Non-HTTP Activation
  • .NET Framework 4.6 Advanced Services > WCF Services > HTTP Activation (probably not required for my project but useful for the future)


I also ran this but I don't think I needed to / should have (http://ngeor.net/2011/07/iis-7-gives-404-17-error-with-svc-wcf-services/):

"%WINDIR%\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\ServiceModelReg.exe" –r

And then despite all this things didn't work, I had to manually update the IIS Express settings used by Visual Studio... WHICH are hidden away in a hidden folder in your solution e.g.

[PROJECT FOLDER]\.vs\config\applicationhost.config

I had to add the following to the section starting with <handlers accessPolicy="Read, Script">

<add name="xoml-64-ISAPI-2.0" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
<add name="rules-64-ISAPI-2.0" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
<add name="svc-ISAPI-2.0-64" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" />
<add name="xoml-ISAPI-2.0" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
<add name="rules-ISAPI-2.0" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
<add name="svc-ISAPI-2.0" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" />
<add name="xoml-Integrated" path="*.xoml" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
<add name="rules-Integrated" path="*.rules" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
<add name="svc-Integrated" path="*.svc" verb="*" type="System.ServiceModel.Activation.HttpHandler, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />

There are lots of other similar applicationhost.config files but this is the one you need to update.

I thought that because of our source control branching approach that I'd need to manually update this anytime I create a new branch that needs WCF working BUT apparently there is a project file (not web.config) setting: UseGlobalApplicationHostFile, which you can set to true to just use the global setting (which is stored in Documents\IISExpress\config) http://stackoverflow.com/questions/32940080/visual-studio-2015-debugger-uses-local-applicationhost-config-instead-of-global and http://stackoverflow.com/questions/12946476/where-is-the-iis-express-configuration-metabase-file-found (that being said, I created a new branch the other day and hadn't yet set that setting or updated the Visual Studio applicationhost.config file and I was still able to debug the WCF services... go figure!

Wednesday, June 10, 2015

ELMAH filtering of exceptions by type using the is-type assertion

PROBLEM: In one of our projects, validation errors are thrown as exceptions (ValidationException) - we obviously don't want to be notified about a user forgetting a mandatory field etc, and luckily ELMAH has support to filter out certain exceptions from being logged and/or emailed.  Have a read here: https://code.google.com/p/elmah/wiki/ErrorFiltering and here https://code.google.com/p/elmah/wiki/ErrorFilterExamples and here http://www.diaryofaninja.com/blog/2011/09/20/adding-filters-to-your-elmah-installation

I tried the following in the web.config and it resulted in a runtime error:

<errorFilter>
  <test>
    <is-type binding="BaseException" type="MyProject.Models.ValidationException" />
  </test>
</errorFilter>

SOLUTION:
You need to include the assembly name e.g.

<errorFilter>
  <test>
    <is-type binding="BaseException" type="MyProject.Models.ValidationException, MyProject" />
  </test>
</errorFilter>

(this isn't in the limited doco of ELMAH but the creator of ELMAH does mention it in a random forum post i.e. "In the type attribute of the is-type element, you forgot to specify the assembly where the CustomLibrary.CustomException type can be found. In absence of the assembly specification, ELMAH looks for the type in its own assembly. If your assembly name is the same as the namespace (i.e. CustomLibrary) then try changing the is-type element to read as follows: <is-type binding="Exception" type="CustomLibrary.CustomException, CustomLibrary" />"

Monday, May 25, 2015

Azure Scheduler - Job Schedule Starting On value explained

PROBLEM: For me it wasn't clear what value the Azure Scheduler - Job Schedule "Starting On" value was meant to represent (there is a list of times AM and PM and list of UTC offsets). I wasn't sure if I was meant to be entering the GMT (UTC) value for when I wanted my job to run or the local time I wanted the job to run (it's obvious after you do it). (if you just want general info on Azure Scheduler hit this link: http://azure.microsoft.com/en-us/documentation/articles/scheduler-get-started-portal/#create-a-job-collection-and-a-job)

SOLUTION: Short answer, it is the local time. When creating a new scheduled job, the job schedule "Starting On" value is the local date/time you want the job to run, to which you apply the relevant UTC offset based on the time zone of the local area e.g. If I want a job to execute at 8.30pm AEST (Australian Eastern Standard Time i.e. not daylight savings), I'd set the date to the date I want and the time to 8.30 PM and the UTC drop down list to UTC 10:00 (i.e. the time is not a GMT/UTC time, it is the local time zone you're interested in, reflected by the UTC offset value).

When you click Save, Azure will then convert this to a GMT (UTC - go here if you want the technical difference between the two http://www.timeanddate.com/time/gmt-utc-time.html) value e.g. 2015-05-25 8:30 PM UTC 10:00 will display as Mon, 25 May 2015 10:30:00 GMT (you'll no longer see the local time and UTC offset that you originally entered, this confused me a bit when looking at other schedules that others had set up).

NOTE: The big gotcha is that all that is saved is a GMT time, not a time zone - this means you'll potentially need to update your schedule for daylight savings.
http://codeofmatt.com/2013/11/04/windows-azure-scheduler/
https://blogs.endjin.com/2015/04/azure-automation-scheduler-and-daylight-saving-time/

Vote on this improvement/fix here:
http://feedback.azure.com/forums/246290-azure-automation/suggestions/6621981-fix-the-scheduler-to-be-aware-of-daylight-saving

Friday, March 6, 2015

“GatherAllFilesToPublish” Error in VS2010 Project Upgraded to VS2012

PROBLEM: After upgrading a VS2010 web forms project to VS2012, when I went to publish the project I received this error "The target "GatherAllFilesToPublish" does not exist in the project.".

SOLUTION: For me I simply unloaded the project, edited the project file to change this line:

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />

to this:

<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />

After saving and reloading the project file the upgrade wizard automagically kicked in and did some magic and the publish started working.  

Aftewards I went in and had a look at the project file again and it seemed the upgrade wizard added a PropertyGroup element with VisualStudioVersion and VSToolsParth sub-elements (similar to what this guy recommended manually adding but for me manually adding them didn't work - <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />)

You'll also find lots of links about renaming Microsoft.WebApplication.targets and running a repair (http://stackoverflow.com/questions/10989051/why-do-i-get-the-error-the-target-gatherallfilestopublish-does-not-exist) - which I tried but this didn't fix the issue.

Wednesday, March 4, 2015

Cannot merge branches due to "incompatible pending change" error

PROBLEM: Merging two branches in TFS2012 and it fails saying there are 0 errors but X warnings - refer to Output window for details. When you check the Output window you see:

"TF203015: The item $/XXX/YYY has an incompatible pending change"

SOLUTION: In my case, it was because the file in the changeset I wanted to merge (web.config) had pending changes in the target branch which had been added to "Excluded Changes".  In my case I moved the file to "Included Changes" and Checked In the changeset (so the server knew). Then I was able to merge my two branches.  I think if I had discarded my changes on the target branch for that file it would have potentially resulted in the same effect.  Some other googling showed that sometimes people had this issue due to file permissions but that wasn't the issue in my case. That's a few hours of my life I'll never get back!

Friday, May 24, 2013

Publishing encrypted connection strings in web.config


PROBLEM: You publish your website to a server (e.g. Stage or Production) and then have to manually run a command to encrypt the connectionStrings within the web.config (would be nice for the publish process to just take care of everything) - this guy on stackoverflow had the same issue: http://stackoverflow.com/questions/14838156/encrypting-webconfig/16728000

SOLUTION: Take the encrypted portion of the web.config (remember you'll need to be remoted on to your server to do the encryption) and add to your Web.[CONFIGURATION].config transformation file e.g. Web.Stage.config or Web.Release.config... since the encryption is based on machine keys you'll need to have a different transformation file for each server that you are deploying to.  This is all quite simple, I just never thought of doing it before - the hardest bit was finding the syntax for the web.config transformation file - the syntax I've got below works but the compiler will flag a warning about invalid syntax i.e.


Warning 15 The element 'connectionStrings' has invalid child element 'EncryptedData' in namespace 'http://www.w3.org/2001/04/xmlenc#'. List of possible elements expected: 'add, remove, clear'. C:\DevTFS\YourProject\Web.Stage.config 14 6 YourProject

 (I'm open to suggestions on how to make it compliant and still work).


Step 1.
Encrypt connectionStrings in the web.config as per: http://msdn.microsoft.com/library/dtkwfdky.aspx or http://stackoverflow.com/questions/8230864/how-can-i-safely-store-and-access-connection-string-details

I keep a batch file in the root of my website for this:


@ECHO OFF
echo "This will Encrypt or Decrypt the Connections section of web.config - it should be run after deploying to any publicly accessible version of the site e.g. production."

CHOICE /C:ed /M "Encrypt or Decrypt"

IF %errorlevel%==1 goto enc
IF %errorlevel%==2 goto dec

:enc
echo "Encrypting section connectionStrings"
c:\WINDOWS\Microsoft.net\Framework\v4.0.30319\aspnet_regiis -pef "connectionStrings" %CD%
goto EOF

:dec
echo "Decrypting section connectionStrings"
c:\WINDOWS\Microsoft.net\Framework\v4.0.30319\aspnet_regiis -pdf "connectionStrings" %CD%
GOTO EOF

:EOF
EXIT



Step 2.

Add the following to your Web.[Config].config file...


  <connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider"
  </connectionStrings>

And then copy and paste the <EncryptedData> elements from your encrypted web.config file on your server between these tags e.g.


  <connectionStrings configProtectionProvider="RsaProtectedConfigurationProvider" xdt:Transform="Replace">
    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
      xmlns="http://www.w3.org/2001/04/xmlenc#">
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <KeyName>Rsa Key</KeyName>
          </KeyInfo>
          <CipherData>
           <CipherValue>t8p7aOZTjMon8B1qC4L4gmKasdfsdafasdfJHckY0fl9hfaasdffQWrpdX1jqF6vD3/X4Ejg+UeiCWujkx+dfvDOif3sodfsdfsd6kHAtah2o59UmzsfdasdfdfdKzUliSgMe01fRbjA/bxA6Bbq+sjzE6FAAI=</CipherValue>
          </CipherData>
        </EncryptedKey>
      </KeyInfo>
      <CipherData>
        <CipherValue>Vy1TZWY8ojpf343XQCQwK/r4lmp+vbJPS5sdfdfbv0YMTGEdGuCwdLND5ezMe9iLkuI5/fmvU1TSDzPgvKcAwNc1rXU5jiU0234234JtviOMe6vjU8FSkHilwLITGS9/XUDiacqccfuXsBcdBtcwAfBxIAwwCQxOQIFi6hN/cG2emFj1oSIU468O8ezOG+UMSd4HzaDS2jzZyrfsdfsdfyi0bg8OV5QVOlSUjjuh54Bt4t2pd0O2vsUbwsdfsdfVxB0KgIlL6Kqe53z2Ns6GHlRwJuMFRHQnQT234234SSVLLGkWdI1IGyl12JdlTrd5JItDHGgPNat+fe5FR5GNasdfsdfivft4YZV3iXgbPtZyiHm6aI7ccDuCTHJ+V78AwZAVlIGRKzVbqsic+Qg6T7U</CipherValue>
      </CipherData>
    </EncryptedData>
  </connectionStrings>


Credit goes to this guy for actually adding a useful comment to forums.asp.net (unfortunately they are few and far between) - http://forums.asp.net/post/5390287.aspx

Thursday, January 24, 2013

String Truncate

PROBLEM: The .NET framework doesn't have a truncate method for strings.  Substring will fail if you're not careful (i.e. if you don't check that the string has at least as many characters at the truncation length)

SOLUTION: Add a Truncate extension method for the string class that does the boring checks (length, null - some other examples don't include the null check, etc) for you each time.


        /// <summary>
        /// Ensure that a string is no longer than a specified maximum number of characters.
        /// This string extension method has been written because there is no string truncate method
        /// in the .NET framework and Substring will throw an exception if the length you enter is
        /// longer than the length of the string.
        /// </summary>
        /// <param name="originalString"></param>
        /// <param name="maximumLength"></param>
        /// <returns></returns>
        public static string Truncate(this String originalString, int maximumLength)
        {
            return (originalString == null || originalString.Length <= maximumLength) ? originalString : originalString.Substring(0, maximumLength);
        }

Example use:

          string newString = oldString.Truncate(10);

Credit goes to this guy and one of the people in his comments: http://jamesfricker.blogspot.com.au/2007/08/truncating-string-in-c-easy-huh.html

Friday, January 11, 2013

OnClientClick breaks validation


PROBLEM: Your validators (RequiredFieldValidator, RangeValidator, RegularExpressionValidator etc) are not being called/fired when you have some client side javascript in OnClientClick that asks a yes/no question e.g. Are you sure you want to delete this?


<asp:Button ID="btnConfirmDelete"
                    runat="server"
                    Text="Delete this Session"
                    ValidationGroup="vgDelete"
                    onclick="btnConfirmDelete_Click"
                    OnClientClick='return confirm("Are you sure you want to delete this session?")' />


SOLUTION:
When the page is rendered, the javascript for the onclick will look something like:


onclick="return confirm('Are you sure?');WebForm_DoPostBackWithOptions(...)"

As you can see, the validation doesn't even have a chance to fire (which happens when WebForm_DoPostBackWithOptions is called).

(this guy figured that out: http://vaultofthoughts.net/OnClientClickBreaksValidation.aspx)

Changing it to the below fixes that:

'if (!confirm("Are you sure you want to delete this session?")) return false;'

BUT then you realise that the validators are being called AFTER the prompt (ideally you don't want to prompt the user that they're sure until the last minute after everything is completed). So the this version takes care of that:

'if(Page_ClientValidate()) return confirm("Are you sure you want to delete this session?"); return false;'

Some more info here: http://alvinzc.blogspot.com.au/2006/10/aspnet-requiredfieldvalidator.html

BUT if you're using validation groups, remember to specify it when you call Page_ClientValidate to prevent fields being validated that you don't want validated. Therefore the final version in our case is:

OnClientClick='if(Page_ClientValidate("vgDelete")) return confirm("Are you sure you want to delete this session?"); return false;'

http://techbrij.com/client-side-validation-using-asp-net-validator-controls-from-javascript
http://programmer.webhostingdevelopment.com/index.php/2010/03/specifying-validationgroup-in-page_clientvalidate-function/


Monday, October 1, 2012

Getting rid of "Only secure content is displayed" messages

PROBLEM: You have a secure page(s) on your site (e.g. online payment page) and you are getting "Only secure content is displayed" type warnings in your browser, but you're already using relative URLs for all your assets on your page.

SOLUTION: Have a look at any external assets e.g. Twitter javascript libraries etc. and see how you are linking to them.  You probably have hard coded the protocol - I'd suggest you use protocol-relative hyperlinks (this doesn’t apply to simply linking to another page e.g http://www.twitter.com, only for actual content that is being used on the page e.g. http://twitter.com/javascripts/blogger.js). So instead of having:


            <script type="text/javascript" src="http://twitter.com/javascripts/blogger.js"></script>
            <script type="text/javascript" src="http://twitter.com/statuses/user_timeline/UserXXX.json?callback=twitterCallback2&count=2"></script>

Use this instead:

            <script type="text/javascript" src="//twitter.com/javascripts/blogger.js"></script>
            <script type="text/javascript" src="//twitter.com/statuses/user_timeline/ UserXXX .json?callback=twitterCallback2&count=2"></script>

And this will help you avoid those ugly ‘mixed content’ security warnings which you normally brush under the carpet when browsing the web (obviously make sure that the external sites offer a HTTPS version of their resource).

Some more info is here:

http://weblogs.asp.net/jgalloway/archive/2009/10/15/did-you-know-about-protocol-relative-hyperlinks.aspx

An alternative approach is to use javascript like Google does

   ...
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
   ....

Friday, September 14, 2012

Multiple HTTP Redirects in IIS 7

PROBLEM: On one of our websites we recently moved from having a separate mobile site vs full site to just having a full site with "responsive design".  We initially just put in place a HTTP Redirect (301) in place via IIS 7 for the mobile site to redirect all mobile pages to the home page of the full site (hoping that people would update their bookmarks after getting redirected) - feedback said people didn't want to update their bookmarks so we needed a way to put in place multiple redirects.

SOLUTION: I couldn't see a way to put multiple HTTP Redirect rules in place easily via IIS 7 for a single site so I decided to use "URL Rewrite 2.0" for IIS 7, a module/extension for IIS provided by Microsoft that has to be downloaded and installed separately (watch out for the first gotcha!)
  1. To install download it from  http://www.iis.net/downloads/microsoft/url-rewrite which will install it via the Web Platform Installer... BUT... be aware, that despite a lack of warning it will:
    1. Stop all the websites on your server.
    2. Require a server reboot before you can use it
  2. After you've scrambled to reboot your server after seeing all your sites down then it's simply a matter of opening the "URL Rewrite" module you'll now see in IIS for your site and creating a number of redirect rules - here is some info: http://www.iis.net/learn/extensions/url-rewrite-module/using-the-url-rewrite-module but this is what I did
    • Requested URL = Matches Pattern
    • Using = Regular Expressions (man, these always suck if you haven't learnt them! A cheat sheet might help you: http://regexlib.com/CheatSheet.aspx ... I find trying to search for regular expressions in regexlib is pointless if it's a general concept you're looking for e.g. "find me all matches with a word but not having this word" vs "email validator")
    • Pattern: e.g. (?!.*timetable.*)^clubs/(.*)/(.*)$
      • Matches any URL that starts with clubs/ and doesn't contain the word timetable
    • Another example: ^clubs/(.*)/(.*)/timetable.*$
      • Matches any URL that starts with clubs/ and then has timetable at the end
    • Ignore case = ticked
    • Action Type: Redirect
    • Redirect URL: e.g. http://yoursite.com/clubs/{R:1}/{R:2}/ (see how you can grab anything from the regular expression that was in brackets (known as a back reference apparently) and use it in the redirect URL.
    • Another example: http://yoursite.com/clubs/{R:1}/{R:2}/timetable/
    • Append Query String = false (not required since we were using friendly urls without query strings anyway).
    • Redirect Type: Permanent (301).
  3. To ensure all other pages are redirected to the root of your homepage simply update your HTTP Redirect settings in IIS
    1. Redirect requests to this destination: Ticked
    2. http://yoursite.com/
    3. Redirect all requests to exact destination (instead of relative to destination).
    4. Status code: Permanent (301).
I'm sure there are better ways out there and would love to hear them.  Otherwise hopefully this helps someone else - at the very least hopefully someone will read the warning about the installation of URL Rewrite causing your sites to stop and the server requiring a reboot!

Wednesday, June 6, 2012

Winforms ListControls (ListBox or ComboBox) binding performance gotcha

PROBLEM: Excessive rebinding can occur with ListControls in Winforms.

SOLUTION: Specifically when the DataSource is changed, or when DisplayMember or ValueMember is changed after DataSource has been set, the binding infrastructure forces the control to rebind. Logically, this makes sense. But it poses a problem if you handle certain control events, in particular SelectedIndexChanged and SelectedValueChanged. In the worst case, the following simple sequence will raise SelectedIndexChanged three times in a row:
listbox.DataSource = dataTable
listbox.ValueMember = "id"
listbox.DisplayMember = "name"

Not a big deal if nothing data intensive is happening in SelectedIndexChanged but in my case it often calls the code to call a webservice. So simply changing the order of these to be as below results in just one SelectedIndexChanged call instead of 3, and hence one webservice call instead of 3.

listbox.DisplayMember = "name"
listbox.ValueMember = "id"
listbox.DataSource = dataTable

This very smart fellow discusses it in full on codeproject: http://www.codeproject.com/KB/database/scomlistcontrolbinding.aspx

Wednesday, April 11, 2012

Checking Server Disk Space

PROBLEM: You have a large network of computers, you want to know when they are getting low on space since that can cause many issues such as websites running slowly or not at all, logs might not get written, defragging can't occur, windows updates can't be applied etc. So you want to monitor them constantly but don't want to waste time doing it.
SOLUTION: I'm sure there are products out there (free and paid for) to do this but my solution was to go the DIY path and use PowerShell and two Windows Scheduled Tasks (one daily that will email any warnings and one weekly that will email regardless). I have zero PowerShell experience but have borrowed liberally from a few fellows (attributions in the script). I'll give you the code and then raise a few points about it:


##############  Script starts Here ##########

# This script is designed to loop through a list of servers in an external file and check the
# space on each drive associated with each server, report on their free disk space percentage
# and output the results to the screen (if run in a console), and an email (which will only get
# sent if the parameter of WarningsOnly is passed when executing the script
# e.g. C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -command "C:\ServerDiskSpaceChecker\ServerDiskSpaceChecker.ps1 WarningsOnly").
#
# Acknowledgements:
# DISK SPACE PORTION OF THE SCRIPT TAKEN AND MODIFIED FROM: http://www.youdidwhatwithtsql.com/check-disk-space-with-powershell-2/195
# EMAIL PORTION OF THE SCRIPT TAKEN AND MODIFIED FROM: http://www.techrepublic.com/blog/window-on-windows/send-an-email-with-an-attachment-using-powershell/4969
# EMAIL HTML FORMATTING PORTION OF THE SCRIPT TAKEN AND MODIFIED FROM: http://exchangeserverpro.com/powershell-send-html-email
# SCHEDULING PROCESS (no code required but good to know) TAKEN FROM: http://dmitrysotnikov.wordpress.com/2011/02/03/how-to-schedule-a-powershell-script/



# MODIFY THE BELOW VARIABLES AS REQUIRED

# Issue warning if % free disk space is less
$percentWarning = 15;

# Get server list (text file containing server names)
$servers = Get-Content "C:\ServerDiskSpaceChecker\computers.txt";

# IP address of email server
$smtpServer = "10.10.0.999"

# Email address to send notifications to
$emailTo = "ITTeam@yourcompanyhere.com.au"



# LET'S CHECK SOME DISKS!

$warningsExist = $false;
$scriptParameter = $args[0];
$msgSubject = "Server Disk Space Checker Results";
$datetime = Get-Date -Format "yyyyMMddHHmmss";

# variable for storing body of email (formatted in html so that we can use pretty colours)
$emailBody = "<p>The following are the results of an automated check of remaining server disk space (Refer to the <a href='http://yourwikiurlhere'>wiki</a> for more information). Entries will be marked in red if they fall below the current threshold - please investigate these ASAP.<p>";

# Add headers to log file
Add-Content "$Env:USERPROFILE\server disks $datetime.txt" "server,deviceID,size,freespace,percentFree";

foreach($server in $servers)
{
# Get fixed drive info
$disks = Get-WmiObject -ComputerName $server -Class Win32_LogicalDisk -Filter "DriveType = 3";

foreach($disk in $disks)
{
$deviceID = $disk.DeviceID;
[float]$size = $disk.Size;
[float]$freespace = $disk.FreeSpace;

$percentFree = [Math]::Round(($freespace / $size) * 100, 2);
$sizeGB = [Math]::Round($size / 1073741824, 2);
$freeSpaceGB = [Math]::Round($freespace / 1073741824, 2);

$colour = "Green";
if($percentFree -lt $percentWarning)
{
$colour = "Red";
$warningsExist = $true;
}

# Get results
$results = "$server $deviceID percentage free space = $percentFree% (Total Size=$sizeGB GB, Total Free=$freeSpaceGB GB)"

# Write results to email
$emailBody = $emailBody + "<span style='color:$colour'>$results</span><br />"

# Write results to screen
Write-Host -ForegroundColor $colour $results;
}
}



# Send an email with the details

if(($warningsExist -eq $true) -or ($scriptParameter -ne "WarningsOnly"))
{
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Admin -erroraction silentlyContinue;

$msg = new-object Net.Mail.MailMessage;
$smtp = new-object Net.Mail.SmtpClient($smtpServer);
$msg.From = "system@yourcompanynamehere.com.au";
$msg.To.Add($emailTo);
if($warningsExist -eq $true)
{
$msgSubject = "Warnings Exist - " + $msgSubject;
}
$msg.Subject = $msgSubject;
$msg.IsBodyHTML = $true;
$msg.Body = $emailBody;
$smtp.Send($msg);
}


############## End of Script ##########

To enable the PowerShell script to run permissions needed to be changed on the server to allow local scripts to run ok (but external scripts require signing) - run this command in PowerShell:

  • Set-ExecutionPolicy RemoteSigned
There are a number of variables within the script that can be modified:
  • $percentWarning (Issue warning if % free disk space is less)
  • $servers (text file containing server names))
  • $smtpServer (IP address of email server)
  • $emailTo (Email address to send notifications to)
To specify which servers to check, create a file (e.g. computers.txt) and simply have a list of server names, one on each row, no empty rows at the end, and ensure the $servers variable uses that file.

When executing the script, if you want it to only email you when there is a warning then pass the parameter "WarningsOnly" after the script name (provide any other value if you want to always be notified) e.g. C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -command "C:\Server Disk Space Checker\ServerDiskSpaceChecker.ps1 WarningsOnly"

To troubleshoot run the script/scheduled task manually - if you include the -NoExit flag when running the script it will not close the PowerShell window when it's done so you can see any errors etc that may have occurred.
    Common problems include:
    • Incorrect server name in computers.txt
    • Blank lines in computers.txt
    • Incorrect script permissions (refer above)
    • NaN% in the email (this means the script has had a problem reading the disk space - NaN stands for Not a Number - add the -NoExit flag and see what's happening).
    This script is basically a Frankenstein compliation of a few different scripts (refer to the URL references within the script itself) - I'm definitely no PowerShell expert, but it seems to do a good job when combined with the schedule task and parameters. (apologies for the blog formatting - I'm really not a big fan of the Blogger formatting!).

    Thursday, February 23, 2012

    CustomValidator not firing with TextBox

    PROBLEM: You've added a CustomValidator control to your page and for some reason the validation isn't firing.
    SOLUTION: In my case it was ValidateEmptyText="true" needed to be added. Also if you're using multiple validation groups on the page make sure you set your ValidationGroup property as well.

    Also remember when you use a CustomValidator to ensure you add a Page.IsValid check on your submission button to prevent anything progressing further if the custom validation is false.

    Friday, January 20, 2012

    Windows Services

    PROBLEM: Recently I created a simple windows service with .net 4 to perform a check periodically on the status of our company's websites (are they up or not) and send an email notification if they aren't.

    SOLUTION: While not particularly challenging there definitely some lessons learnt...
    • Firstly, people (http://weblogs.asp.net/jgalloway/archive/2005/10/24/428303.aspx) will point out that probably creating a console program which can be scheduled with the windows scheduler is the easier and better approach to this problem, but I mainly just wanted the experience in creating a windows service.
    • Some good web service examples:
    • Add a try/catch around your code in Main which logs any exceptions - useful to determine problems if your service won't start correctly etc.
    • To debug a windows service in Visual Studio you need to open the solution and then choose Debug > Attach to Process... > Tick both "Show processes from all users" and "Show processes in all sessions" and then click the exe and choose Attach. Set a breakpoint and away you go (remember to stop the timer while debugging if it is set to raise an event frequently otherwise you won't be able to step through the code easily).
    • When using windows logging, logs are identified by their first 8 characters - if you try and create two different logs, with the same first 8 characters, it will cause an exception.
    • The HttpWebRequest.GetResponse method returns various HttpStatusCode enums BUT quite often will raise an exception (e.g. if the website is down), so make sure you use a try/catch and it is from the exception (cast to WebException) that you can get the HttpStatusCode i.e. WebException.Response.StatusCode).
    • When you create a new log (not a new entry within a log), the windows Event Viewer doesn't do a great job at refreshing and showing it (even when you choose refresh) - just close the Event Viewer and open it again.
    • If you are testing a service and constantly installing and uninstalling it, you may get the system into a state where the install starts failing or complaining that the service has been marked for deletion but can't be removed. Try shutting down the Services window and see if that makes a difference, or try manually uninstalling it view Add/Remove Programs or via installutil /u but usually you'll just resolve it quicker by restarting your computer.
    • If you want to use an app.config with your windows service, you'll need to manually add a reference to the System.Configuration DLL so that you can use ConfigurationManager.AppSettings["BLAH"] (it's not included by default in a windows service project).

    Friday, October 28, 2011

    Microsoft Lync 2010 No Audio without Microphone Fail

    PROBLEM: In Microsoft Lync 2010, you cannot listen in a web meeting if you don't have a microphone plugged in. It would make sense that you could at least listen in a web meeting (e.g. someone is presenting and you just want to listen) and if you need to provide feedback you can just type a question. But alas that isn't supported (fail) - it's a bug, and Microsoft has said they won't be changing their product (fail fail).

    Microsoft has already documented this as a bug with no comments on if it will ever be repaired.

    This means that audio cannot be received by a participant in a Lync Server Online Meeting unless the participant has a microphone plugged in.

    I was asked by the MS support tech (who was as helpful and polite as he could be) to submit a BIS (Business Impact Statement). Hopefully the BIS will be viewed as worthy of getting the developers to repair this problem.

    I'm surprised that I had to go this far to find an answer if not a solution. Is it really that odd for a typical training to be held using Lync/OCS or do most companies install a microphone or webcam on every PC?
    Unfortunately the response that fellow got was:
    Microsoft just sent me an update:

    "During our review we noticed that the Microphone parameter is intertwined to the nerve center of the MCUs.

    Making a change would involve too many components and the high cost associated for making these changes would not justified to qualify it as a Cumulative Update within this product release lifecycle.

    Unfortunately, the bug that has been filed for this case has been rejected."

    He goes on to recommend buying a few hundred microphones from Amazon.

    Insane.
    The only workaround talked about is:
    As far as I can tell, the software is satisfied as long as you plug anything into the microphone jack (even a disconnected plug from an old headset worked for me), though you can't unplug it afterwards without the call getting interrupted (so multiple people can't share the same plug).
    Someone else tried this:
    I found that out and ended up buying 500 audio adapters for less than a quarter each. Explaining over and over to people why they have to be plugged in was pretty humiliating. I tested about 20 machines and found 2 that had sound cards/sound card drivers smart enough to recognize that the adapters weren't microphones. I'll have to get them real mics.

    I haven't yet tried to find out what happens with outside callers using web app. I have a good guess as to what the results will be...

    MS recognizes this is a bug, and decided to fix it it CU4, but then decided not to. The only thing I can come up with for that is the recent purchase of Skype. Maybe the more difficult repairs will be delayed/cancelled while Skype is being integrated into the next version of Lync? I really don't have any idea if that's the plan or not - I'm just speculating.
    SOLUTION: So the alternatives are:

    1. Get headphones with a microphone built in; or
    2. Plug anything into the microphone plug; or
    3. Watch a recorded webinar afterwards (rather than participate live); or
    4. Use a different product (e.g. Skype).

    If you can figure out where to log this as a problem with MS and can be bothered that would be great. I think we'll just go with option 1 for those who require it. Overall, sorry MSFT but this is a fail.

    Thursday, October 27, 2011

    Facebook Graph API and redirect_uri and QueryString parameters

    PROBLEM: If you are using the Facebook API e.g. to do the login for your website etc (http://developers.facebook.com/docs/authentication/), you may find yourself wanting to get the user to login to Facebook but then return to the page they came from. My first reaction was to add the URL they came from to the end of the redirect_uri querystring parameter e.g.

    https://www.facebook.com/dialog/oauth?client_id=YOUR_APP_ID&redirect_uri=YOUR_URL?orig_url=http://example.com/default.aspx

    Of course you would url encode the orig_url parameter and then decode it when you wanted to use it... it actually does work for the above type of call... but when calling the app authentication api it doesn't like it. Base64 encoding it doesn't work either. And remember, a little known fact is that the redirect_uri needs to be the same for when you are doing the app authorization as the app authentication (see below url)

    https://graph.facebook.com/oauth/access_token?client_id=YOUR_APP_ID&redirect_uri
    =YOUR_URL?orig_url=http://example.com/default.aspx&client_secret=YOUR_APP_SECRET&code=THE_CODE_FROM_ABOVE

    Bottom line is that I tried the above approaches and kept getting a HTTP 400 error "Error validating verification code.".

    SOLUTION: Taking a step back, rather than try and get what should work working, try a different approach. In my case, simply put the original URL into a session variable for use later on (http://stackoverflow.com/questions/5747320/how-to-encode-the-redirect-uri-for-facebook-login).

    e.g.

    Session["UrlToReturnToAfterLogin"] = HttpContext.Current.Request.Url.OriginalString;

    Too easy.

    Tuesday, October 18, 2011

    Add the optgroup tag to your DropDownList

    Problem: The asp.net DropDownList control does not support rendering of the optgroup tag (it will only generate option tags).

    Solution: Once again, a bit like trying to get the validation controls to use the display:block style when its display is set to dynamic, sometimes it can be tough to bend asp.net to do what you want when it isn't supported out of the box.


    Your options seem to be:

    1. Use an adapter to change the html that the asp.net DropDownList control renders (affects all DropDownLists in your solution).
    2. Inherit from the DropDownList and change the html that your new control renders.
    3. Use javascript to do some magic once the control has rendered.
    4. Don't use optgroup - depending on your scenario, it may not be the best UI solution.

    I went with option 1. and a lot of trial and error based on various examples on the net. My main problem was that there weren't enough complete code solutions showing all the bits of code and how to use it.

    The examples I tried from the stackoverflow page ended up giving me the following error when I tried submitting the page:

    Invalid postback or callback argument. Event validation is enabled using ...
    I didn't have time to investigate so I went with a different example. In the end the best example I found came from Christoff Truter: http://www.cstruter.com/blog/259. The only clarifications: Step 3 should all be within:
    public class DropDownListAdapter : WebControlAdapter
    {
    ...
    }
    In the overridden version of OnLoad in Step 3, you need to add a null check before setting the Group attribute value (in case you have provided a listelement in your dropdownlist without the "Group" attribute e.g. a blank 'please select a value' item). i.e.
    if (groups[i] != null)
    {
    dropDownList.Items[i].Attributes["Group"] = groups[i].ToString();
    }
    The final piece of the puzzle was how to create your DropDownList. The big gotcha was that I couldn't use DataSource/DataBind because when I did I lost the attribute that I had set on the listitems (perhaps it was because of the issue described here: http://www.4guysfromrolla.com/articles/091405-1.aspx). This was my code (if you have a way to get DataSource/DataBind working let me know).

    protected void Page_Load(object sender, EventArgs e)
    {
    if (!Page.IsPostBack)
    {
    //Find all currently open clubs which have indicated that they provide X-Service
    List currentXServiceClubs = DBHelper.GetAllCurrentClubsOfferingXService();

    //Create list with the custom "Group" attribute
    List listItemsForCurrentXServiceClubs = new List();

    lstXServiceClubs.Items.Add("");
    foreach (var club in currentXServiceClubs)
    {
    ListItem newListItem = new ListItem();
    newListItem.Attributes.Add("Group", club.State);
    newListItem.Text = club.CentreName;
    newListItem.Value = club.CentreID.ToString();
    lstXServiceClubs.Items.Add(newListItem);
    //NOTE: If you use the DataSource / DataBind notation then
    //the custom attribute will not be passed through to the DropDownListAdapter
    //and hence you will lose the OptGroup functionality. As a future project
    //review if there is a way around this.
    }
    }
    }



    ASP.NET Validation Controls and Styles

    PROBLEM: RequiredFieldValidator with Display="Dynamic" places display:inline on the error text when displayed (and I want it to be display:block)

    SOLUTION: Well there is no elegant solution! Sometimes the rapid development tools that are normally so helpful in .net just aren't flexible enough to allow what you want to do. The issue is discussed here: http://www.stackoverflow.com/questions/2243498.

    Essentially when the validator is first displayed it sets (via .net generated javascript) style="display:none" on the span tag. This element then gets switched from "none" to "inline" when there is an error to display.

    Your options?

    1. Modify the automatic asp.net javascript - not nice, and could possibly break after future upgrades or browser changes etc. - but probably your best bet if you are desperate - here is how: http://caliberwebgroup.blogspot.com/2009/02/overriding-javascript-in-webresourceaxd.html

    2. Roll your own validation controls - possible but then you lose all the default out of the box goodness that come with the asp.net validator controls.

    3. Live with it. I went with this option. Sometimes 95% is good enough.