Request a topic or
contact an Arke consultant
404-812-3123
September 2010

Arke Systems Blog

Useful technical and business information straight from Arke.

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Archive

Authors

Disclaimer

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

© Copyright 2024

Silverlight Issues

Silverlight often feels a bit much like a work in progress, other times just like parts weren’t thought through all the way.  I thought I’d post a few examples of the sorts of things we’ve run into while developing with it.  Main lesson is, somehow you will need to plan for the unplanned.

For example, in the global application error handler:

Exception tmp = e.ExceptionObject;
if (tmp != null)
{
    while (tmp.InnerException != null)
    {
        tmp = tmp.InnerException;
    }

    // Silverlight defect where File stream can error out if file open failed due 
    // to an open file error, then later the garbage collector tries to clean up
    // the stream.  Can't be reliably handled at regular code level because we don't
    // get back a stream to operate on due to the file method erroring out.
    if (tmp is InvalidOperationException)
    {
        // string parse is bad and fragile, but I can't find any way to get the error code
        // this should be error code -2146233079 per Reflector, but, well.
        if (tmp.Message.Contains("UI Thread") && tmp.Message.Contains("System.Windows.SaveFileStream.Dispose"))
        {
            // ignore
            return;
        }
    }
. . .

Forums suggest this one may have been fixed in 4.

My favorite issue is a layout issue: If you want a label next to a textbox, you put it in a horizontal stackpanel.  But horizontal stackpanels don’t constrain themselves horizontally.  Bottom line?  Your labels won’t wrap and will clip instead.   Solution?  Specify fixed widths on your labels.  I wonder if nobody did a form with labels next to textboxes and a dynamic layout all during the testing and design phase.

We also have these gems:

                // NOTE: A "Dialogs must be user initiated" error can crop up if running under the debugger.
                // It is a spurious error and will not happen when not using the debugger.
                // See http://forums.silverlight.net/forums/t/82454.aspx

 

// firefox doesn't render our silverlight right in an iframe.
function isfirefox() {
    if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
        return true;
    } else {
        return false;
    }
}

 

/*
    This service has to be activated using basicHttp because Silverlight cannot 
    function against wsHttp.  
*/

 

        //sizechanged doesn't fire properly when column widths are changed, tabs need to
        // be hidden and refreshed for it to update... also, this can be fired
        // too early, before the column width has really changed. Layoutupdated seems to mostly work, though.

 

// assigning width directly is sub-optimal because
// it won't handle a situation where this column should be bigger
// than the main column... but the grid doesn't redraw properly
// if we don't.  Tried forcing just minwidth to work 
// with invalidatemeasure, invalidatearrange,
// measure, arrange, and updatelayout, all of which redraw the column
// header right but not the column data.

Categories: ASP.NET | Silverlight
Posted by David Eison on Sunday, September 26, 2010 5:59 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Sitecore:Custom Error Pages

One of the important steps of any website is setting it up to fail Nicely. Sitecore has done a lot of this for us with their error pages, but that may not be the look you want your visitors to see! And what if Sitecore isn’t able to help at all? Not so pretty with ASP.NET error pages…

But do not fear! With the configuration files Sitecore makes available, this is a snap to set up and fix.***

***You Will need to change the web.config file, so please back it up before making any changes suggested here!

The first and hardest step – make your error/not found pages that you want your visitors to see – these can be html pages, aspx pages, or even an existing page within Sitecore.

Usually Sitecore will handle your errors nicely, but if .NET explodes you will need to display something to the user that will Not fail, so I’d VERY STRONGLY recommend an html page for errors and not aspx pages: if there’s an error in the error page, you’re back to square one.

Upload these files to your Website directory or a subfolder within it – for our example we have saved the files in Website/ErrorPages/.

Now for the magic: In the website folder is a folder called App_Config and inside that is the Include folder. This folder automagically updates the web.config with new settings – it doesn’t require anything to be restarted and immediately takes effect. Also – using these files instead of modifying the web.config directly means you can make web.config type changes in packages without completely destroying the site.

There should be a file in that directory called: SitecoreSettings.config.example

This is what we will be changing into SitecoreSettings.config and adding our custom ‘not found’ page details.

Currently the file looks like this, along with some comments describing the use of the file at the top:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<
sitecore>
<
settings>
<!--
REQUIRE LOCK BEFORE EDITING
If true, the user must have a lock on a document before
he can edit it, otherwise it is always ready for editing
-->
<
setting name="RequireLockBeforeEditing" value="false"/>

</
settings>
</
sitecore>
</
configuration>

We are going to erase this, and put in the following:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<
sitecore>
<
settings>
<
setting name="ItemNotFoundUrl">
<
patch:attribute name="value">/ErrorPages/404.html</patch:attribute>
</
setting>
<
setting name="LinkItemNotFoundUrl">
<
patch:attribute name="value">/ErrorPages/404.html</patch:attribute>
</
setting>
<
setting name="LayoutNotFoundUrl">
<
patch:attribute name="value">/ErrorPages/404.html</patch:attribute>
</
setting>
<
setting name="ErrorPage">
<
patch:attribute name="value">/ErrorPages/Error.html</patch:attribute>
</
setting>
</
settings>
</
sitecore>
</
configuration>

The above basically tells the web.config file to update the specified settings with our new and improved values.

You can choose other files to use or have different files for each – and these special pages do not stop any logging from happening on the server – they simply give a better experience for your users when your site is having some trouble.

ItemNotFoundUrl and LinkItemNotFoundUrl will come up when a visitor tries to access an item that doesn’t exist or hasn’t been published (it doesn’t exist yet on the web database). This replaces the default value of: /sitecore/service/notfound.aspx with our own /ErrorPages/404.html

You’ll notice that the default is pulling from /sitecore/service ß if you have blocked this directory or don’t have it available, you will need to make the changes above so that users see Some kind of page instead of a browser default.

LayoutNotFoundUrl will come up when the user tries to visit an item that does exist, but no layouts have been assigned to the item so Sitecore doesn’t know what to display for the user. This replaces /sitecore/service/nolayout.aspx in the default web.config file.

ErrorPage should come up whenever there is a generic error within Sitecore – the default setting we are overwriting is: /sitecore/service/error.aspx

Save your new file without the .example extension and you should now see your custom error pages whenever you try to access missing items!

The next step is the change to the web.config for handling errors when sitecore can’t handle them for us – save a backup of your web.config and then open it up in an editor.

Here is what we are looking for:

<!--  CUSTOM ERROR MESSAGES
Set customError mode values to control the display of user-friendly
error messages to users instead of error details (including a stack trace):

"On" Always display custom (friendly) messages
"Off" Always display detailed ASP.NET error information.
"RemoteOnly" Display custom (friendly) messages only to users not running
on the local Web server. This setting is recommended for security purposes, so
that you do not display application detail information to remote clients.
-->
<
customErrors mode="On" />

Search for customErrors to find it quickly. It may also have one of the above settings, RemoteOnly or Off. I would recommend On or RemoteOnly if you won’t have visitors accessing the site locally.

We want to change the setting to also include a redirect to our new Error page:

<customErrors mode="RemoteOnly" defaultRedirect="/ErrorPage.htm"/>

Now, if anything serious should happen, your users will not end up seeing an ASP.NET error page telling them it is broken and will instead get to see a page of your choice! This should Not point to a page you have in Sitecore since it will not work in the case of ASP.NET failing.

Crossposted from Sitecore Adventures!


Posted by Amy Winburn on Thursday, September 16, 2010 9:11 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Internet Explorer 9 Beta and Dynamics CRM

With the release of the Internet Explorer 9 Beta today, I just wanted to make sure that everyone knows not to install IE9 on any machine using CRM Online, CRM 4.0 or testing out the CRM 2011 Beta.  IE9 Beta is currently unsupported for all three CRM environments, and if the IE8 Beta is any indication, it will break things in a big way.

So enjoy testing the Internet Explorer 9 Beta, just don't do it on a machine that uses Dynamics CRM. 


Posted by Wayne Walton on Wednesday, September 15, 2010 12:24 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Undoing Sql Server Database Engine Tuning Advisor

Trying out a set of recommendations from database engine tuning advisor on a test CRM system, I saw one particular query go from 1 second to 25 seconds.  Obviously I need to remove the recommendations and see if it’s a fluke and see if any other queries changed.  But there is no ‘unapply’ command. I can’t find anything better than rewriting the sql file or restoring from a backup.  Rewriting the sql file is easier since there are only two types of statements in it – create index and create statistics.  Regular expressions to the rescue…

First off, in tuning advisor, always always always pick ‘Save Recommendations’ before (or instead of) apply recommendations. This gives you a SQL file with all the intended changes.

Since it’s a CRM system I was only tuning by adding indexes and statistics.  Index creates are very easy to replace – change CREATE to DROP, get rid of all the extra info after the first line.  The hard part is finding a regular expression tool that can handle multi line replace – sql server and visual studio and notepad++ don’t appear to be up to the task.  I ended up using this visual studio add-in which adds a proper regex parser to visual studio, but it has some bugs so I wouldn’t rely on it.  Make one find/replace, save, reload the file, make the second find/replace, save the file, then put the tool away. 

I just want to find:

^CREATE .*?(INDEX.*)$[^$]*?$?(go)

and replace:

DROP $1

go

regex[7]

 

Statistic drop syntax is slightly different than create syntax.  But not multi-line, so a bit easier, just needs groups.

Find:

CREATE STATISTICS (.*) ON (.*)\(.*\)

Replace With:

DROP STATISTICS $2.$1

And now I have a SQL file that undoes the changes my tuning advisor made.


Categories: SQL Server
Posted by David Eison on Tuesday, September 14, 2010 6:55 PM
Permalink | Comments (0) | Post RSSRSS comment feed

ASP.NET click tracking

In order to integrate some classic ASP pages with an ASP.NET site, I need the user to go through an intermediate session copy page every time they click on a link to an asp page. 

This requirement is also very common in ‘click tracking’, where you want to keep track of what outgoing links users have clicked on from your site.  You do this by sending them through an intermediate page before redirecting them out of the site.

This leads to a lot of ugly links like:

http://demo/util/sessionupdate.aspx?url=%2targetdir%2findex.asp

When previously they were clear links like:

http://demo/targetdir/index.asp

How can we get back our clear links while still going through the intermediate page every time?

Before clicking:

image

After clicking:

image

One of the earliest approaches was to use javascript to change the status bar.  This led to a lot of fake urls so it was widely disabled by the browser makers – IE6 is the last major browser where this approach still works. 

Another approach is to use javascript ‘onclick’ to redirect the user; onclick runs before the href is followed, and if you return false from it the href won’t be followed, and you can use a javascript redirect like window.location.  This tends to work, but only for users with javascript enabled, and I believe there are some instances where it doesn’t really work.  In my specific case, I need to always send the user through the intermediate page.  In general, it’s cleaner to send the user only where the href actually links them.

Search engines like Google, Yahoo and Bing are all currently using a javascript ‘onmousedown’ event to respond to the users clicks.  Yahoo and Google both serve up the regular href, so that your status bar has the nice clear link, but when you mouse down they swap it out with their click tracking page.  (Bing instead kicks off an AJAX xmlhttprequest as soon as you mousedown.) 

Before clicking:

image

After mouse down:

image

These approaches have the advantage of serving links up in a fail-safe works-fine-with-javascript-off manner, and also only tracking real clicks.  However, in my specific case, my fail-safe is the opposite: I need to go through the intermediate page if javascript is off.

So, my solution is to serve up the links with the intermediate page.  Then if the user has javascript enabled I use javascript to replace the intermediate link with the clear direct link, and in onmousedown I swap it back again replacing the clear direct link again with the intermediate link.  End result is everybody should get gets session links, and users with javascript see nice clear links in their status bars.

Below is an asp.net user control that makes these click tracking links.

Register controls in web.config:

    <pages buffer="true" validateRequest="true">
      <controls>
        <add tagPrefix="uc" namespace="arkelibrary.controls" assembly="arkelibrary"/>
      </controls>
    </pages>

Using it on a page:

    <!-- specialchars are: ?&<z>![test]+ -->
    <fieldset>
    <legend>Click Tracking with JavaScript</legend>
    <uc:asplink runat="server" href="/webtest/asppage.asp?param1=foo&param2=specialchars%3f%26%3cz%3e!%5btest%5d%2b">ASP Page A - manually encoded url</uc:asplink>
    <br />
    <uc:asplink ID="Asplink1" runat="server" >ASP Page B - URL encoded in codebehind</uc:asplink>
    <br />
    </fieldset>
    <br />
    <fieldset>
    <legend>For comparison, same links as static HTML</legend>
    <a href="/webtest/asppage.asp?param1=foo&param2=specialchars%3f%26%3cz%3e!%5btest%5d%2b">Plain HTML Href - Direct</a>
    <br />
    <a href="http://demo/webtest/sessionupdate.aspx?url=/webtest/asppage.asp?param1=foo&param2=specialchars%3f%26%3cz%3e!%5btest%5d%2b">Plain HTML Href - Through intermediate session page</a>
    <br />
    </fieldset>

 

Manually setting a property in code behind for ASP Page B:

protected void Page_Init(object sender, EventArgs e)
{
    Asplink1.href = "/webtest/asppage.asp?param1=foo&param2=" + Server.UrlEncode("specialchars?&<z>![test]+");
}

 

asplink server control:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace arkelibrary.controls
{
    /// <summary>
    /// This control takes a href parameter and renders the page
    /// with a link to 'localclicktracker' and that href appended,
    /// plus javascript to swap the localclicktracker href out to the direct
    /// link and more javascript to swap it back in mouse down.  The purpose
    /// is to have clear direct links in the user's status bar, while
    /// always sending users through the clicktracker intermediate page regardless
    /// of whether they have javascript enabled or not.
    /// e.g.: 
    /// <uc:asplink runat="server" href="/dir/target.asp">ASP Page</uc:asplink>
    /// </summary>
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:asplink runat=server Target='{1}'></{0}:asplink>")]
    [ParseChildren(true, "Text")]
    public class asplink : ArkeControlBase
    {
        private const string localclicktracker = "/webtest/sessionupdate.aspx?url=";

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string href
        {
            get
            {
                String s = (String)ViewState["href"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["href"] = value;
            }
        }

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text
        {
            get
            {
                String s = (String)ViewState["Text"];
                return ((s == null) ? String.Empty : s);
            }

            set
            {
                ViewState["Text"] = value;
            }
        }

        protected override HtmlTextWriterTag TagKey
        {
            get
            {
                return HtmlTextWriterTag.A;
            }
        }

        protected override void AddControlAttributes()
        {
            AddUnencodedAttribute(HtmlTextWriterAttribute.Href, localclicktracker + href);
            base.Attributes.Add(HtmlTextWriterAttribute.Id.ToString(), this.ClientID);
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            Type type = GetType();
            if (!this.Page.ClientScript.IsClientScriptBlockRegistered(type, "friendlyhref"))
            {
                string script = @"<script type=""text/javascript"">
window.rewrite=function(t){
  try {
    var orig=t.href;
    var esc=encodeURIComponent||escape;
    if (t.href.indexOf(""" + localclicktracker + @""") < 0) {
      t.href=""" + localclicktracker + @""" + esc(t.href)
    }
    t.onmousedown="""";
  } catch (err) {try {t.href=orig;} catch (e2) {}}
  return true;
}
window.friendlyhref=function(id,target){ 
  try {
    var t=document.getElementById(id);
    if (t) {
      var orig=t.href;
      t.href=target;
      t.onmousedown=function() {return rewrite(t);};
    }
  } catch (err) {try {t.href=orig;t.onmousedown="""";} catch (e2) {}}
}
</script>";
                this.Page.ClientScript.RegisterClientScriptBlock(type, "friendlyhref", script);
            }
        }

        protected override void Render(HtmlTextWriter writer)
        {
            AddAttributesToRender(writer);
            writer.RenderBeginTag(HtmlTextWriterTag.A);
            if (Text == String.Empty)
            {
                Text = href;
            }
            writer.WriteEncodedText(Text);
            writer.RenderEndTag();

            // Render is too late in the page processing cycle for Page.ClientScript.RegisterClientScriptBlock
            // to work.  I could instead use a startup script registered in onprerender, but that 
            // would wait until the whole page was loaded before fixing up the links.  So
            // instead I just write javascript here directly so that it will run as soon as possible.
            string scriptForThis = String.Format(@"<script type=""text/javascript"">friendlyhref('{0}','{1}');</script>",
                this.ClientID, href);
            writer.WriteLine(scriptForThis);
        }
    }
}

 

Base class:

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace arkelibrary.controls
{
    /// <summary>
    /// WebControl by default will encode any attributes
    /// added to it.  We want to add two features:
    /// 1) Support already-encoded attributes
    /// 2) Support asserting that our attributes don't include any <%= %> blocks, which
    ///    show up during conversion from ASP and are easy to miss.
    /// </summary>
    public abstract class ArkeControlBase : WebControl
    {
        // we have to support two separate attribute collections, because
        // the base class collection encodes everything
        private Dictionary<HtmlTextWriterAttribute, string> unencodedAttrs = new Dictionary<HtmlTextWriterAttribute, string>();

        // code blocks can show up when translating from asp.
        // they are dangerous because they will work fine if in a plain html control,
        // but will fail if in a server control.  So, error out if we hit one.
        protected void AssertNoCodeBlocks()
        {
            foreach (var key in this.Attributes.Keys)
            {
                AssertNoCodeBlock(key, this.Attributes[key.ToString()]);
            }
            foreach (var pair in unencodedAttrs)
            {
                AssertNoCodeBlock(pair.Key, pair.Value);
            }
        }

        protected void AssertNoCodeBlock(object key, string value)
        {
            if (value.Contains("<%"))
            {
                throw new ArgumentException("Can not have asp.net block tag in control attribute: " + key + "=" + value);
            }
        }

        // override this method in subclasses and add attributes -
        // add to base.Attributes.Add(key,value) anything that should be encoded,
        // and unencodedAttrs[key]=value anything that should not be encoded.
        protected abstract void AddControlAttributes();

        protected void AddUnencodedAttribute(HtmlTextWriterAttribute key, string value)
        {
            unencodedAttrs[key]=value;
        }

        // calls our internal addcontrolattributes method, asserts we have no code blocks,
        // adds base attributes to writer, then adds our unencoded attributes to writer.
        protected override void AddAttributesToRender(
            HtmlTextWriter writer)
        {
            // add attributes to control
            AddControlAttributes();

            // validate attributes
            AssertNoCodeBlocks();

            // copy control attributes to writer
            base.AddAttributesToRender(writer);
            foreach (var pair in unencodedAttrs)
            {
                writer.AddAttribute(pair.Key, pair.Value);
            }
        }
    }
}

Posted by David Eison on Friday, September 3, 2010 7:35 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and external .js

As near as I can tell, if document.write is added to a CRM onload method the entire method is disabled and not served to the client.

document.write is usually a bad idea, but it is quite useful when including an external javascript page, because it blocks while waiting for the page to load. 

In CRM you have two choices for including external files:

  • Directly manipulate the DOM, which does an async load, which means your scripts might not load in order.  If you load jquery first and then something that depends on jquery, the something that depends on jquery might fail.
  • Or, you can use window.execScript, which is IE specific but synchronous.  See http://danielcai.blogspot.com/2010/02/another-talk-about-referencing-external.html – but add a ?version=whatever or ?version=Math.random parameter to give you some control over caching as versions change.

(Disclaimer: including external pages in CRM can lead to problems with the outlook client working offline – you need to include them in your /isv folder and distribute them via a separate installer to your users for them to continue to have offline functionality.  Javascript inside the entity is automatically synced by CRM to offline clients, but files in /isv aren’t.)

You could also take a look at CrmExt, which is a fully featured toolkit for handling these sorts of issues: http://blogs.msdn.com/b/kbowling/archive/2010/04/30/crmext-js-an-external-javascript-loader-for-dynamics-crm.aspx


Posted by David Eison on Wednesday, September 1, 2010 4:06 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and prepended org names

Customers with on-premise or installations of CRM will be familiar with seeing /ORGNAME/ as the root directory in their CRM URLs. 

Microsoft added this feature so that one CRM installation could support multiple different organizations (Internet Facing Deployments and CRM online have something similar but use different server names instead of the virtual directory so that cookies can be kept separate).

However, it can lead to some confusion when wanting to link to CRM files.  Say you put a .html file in an iframe – do you use orgname or not?

The /ORGNAME/ handling is done via an ASP.NET virtual path provider – which means that it only kicks in if the ASP.NET engine is processing the request.  By default, IIS is normally configured to process requests for URLs ending in .aspx via a “Handler Mapping” .aspx to the ASP.NET engine, but resources like .html or .js or .gif or .jpg usually get processed by the default Static File handler.

So a .html file under http://[crm server]/ORGNAME/isv/myapp/myapp.html will 404, but the same file as a .aspx would happily show up.  To get to the static file, you need to leave out the /ORGNAME/: http://[crm server]/isv/myapp/myapp.html

Note that if you do want to link to a .aspx, the global JavaScript function prependOrgName is the best way to add the org name:

prependOrgName(‘/isv/myapp/myapp.aspx’)


Posted by David Eison on Wednesday, September 1, 2010 3:32 PM
Permalink | Comments (0) | Post RSSRSS comment feed