Hosting HTML in Silverlight (not Out of Browser)

Some Silverlight projects may require that you render HTML content – perhaps a non-Silverlight control or web page. Unfortunately unless the project runs Out of Browser (OOB) the WebBrowser control isn’t an option. What can you do?

While there is no easy solution to truly embed HTML inside of the Silverlight rendering engine, it is possible to render the HTML using the browser into a DIV element that is overlaid on your Silverlight application. In fact, because the rendering engine is powerful enough to provide you with all of the sizing events and triggers you need, you can create a very convincing experience displaying HTML in your application – even from the browser.

Note: are you looking to modernize your Silverlight application? Check out our free whitepaper about Silverlight modernization.

To illustrate this in action, we’ll build a sort of “Silverlight browser” that allows you to navigate to any web page. In reality, we’ll load the URL into an IFRAME that lives inside a DIV. What will be important to note is that the DIV automatically moves and resizes when you resize the application, as if it were a part the application itself!

Silverlight Browser

In fact, before we jump into the code, why not click here to check it out yourself? Note that FireFox and IE handle this fine. Chrome creates an infinite loop by placing a scrollbar in the iframe, which resizes it, which then triggers an app resize and … you get the point. Working with HTML this way in Chrome is fine if you are just injecting the HTML, but using iframes requires some tweaks to avoid scrollbars and set margins for Chrome to behave, and quite frankly I didn’t have time to do all of the tweaking for his example.

First, we’ll set up the DIV. In order for this to work, we also need to set windowless to true for our Silverlight application, so that we can safely overlay the HTML content. The host page now looks like this:

<form id="form1" runat="server" style="height:100%">

 

Get Microsoft Silverlight </div> </form>

The DIV we’ll use is called htmlHost. Notice that we position it absolutely and leave it hidden by default.

Next, we’ll create a control for the HTML content. The control will do a few things:

  • It will accept a URL to render
  • It will interact with the DIV to inject an IFRAME that points to the URL
  • It will automatically synchronize the size and position of the DIV to a Grid inside of the Silverlight application

We’ll create a control called HtmlHost. In the Xaml, we only define one thing: the Grid we’ll use to synchronize with the DIV. Take a look:

<Grid x_Name="LayoutRoot" HorizontalAlignment="Stretch" MinWidth="100" MinHeight="100" VerticalAlignment="Stretch" Background="Aquamarine">
    </Grid>

I gave it a background that contrasts with the rest of the application so you can visually see how the DIV moves as you resize and move the application.

In the code behind, I defined some constants for the attributes to manipulate on the DIV and the markup for the IFRAME. The control has two dependency properties: a HostDiv that points to the id of the DIV it will synchronize with, and a Url to load into the DIV.

namespace HtmlExample
{
    public partial class HtmlHost
    {
        private const string IFRAME =
            @"";

        private const string ATTR_INNER_HTML = "innerHTML";
        private const string ATTR_LEFT = "left";
        private const string ATTR_TOP = "top";
        private const string ATTR_WIDTH = "width";
        private const string ATTR_HEIGHT = "height";
        private const string ATTR_VISIBILITY = "visibility";
        private const string VISIBLE = "visible";
        private const string HIDDEN = "hidden";
        private const string PX = "{0}px";
        private HtmlElement _div;

        private double _width, _height;

        public static DependencyProperty HostDivProperty = DependencyProperty.Register(
            "HostDiv",
            typeof (string),
            typeof (HtmlHost),
            null);

        public string HostDiv
        {
            get { return GetValue(HostDivProperty).ToString(); }

            set
            {                
                SetValue(HostDivProperty, value);
                if (!DesignerProperties.IsInDesignTool)
                {
                    _div = HtmlPage.Document.GetElementById(value);
                }
            }
        }

        public static DependencyProperty UrlProperty = DependencyProperty.Register(
            "Url",
            typeof (string),
            typeof (HtmlHost),
            null);

        public string Url
        {
            get { return GetValue(UrlProperty).ToString(); }

            set
            {                
                SetValue(UrlProperty, value);
                if (!DesignerProperties.IsInDesignTool)
                {
                    _div.SetProperty(ATTR_INNER_HTML, string.Format(IFRAME, value));
                }
            }
        }

Whenever the host DIV is set, the control grabs an HtmlElement reference to the DIV. Whenever the URL property is set, the DIV is injected with the IFRAME code to point to the URL.

When the control is constructed, we’ll wait for it to load, record the current width and height, and then set up the synchronization:

public HtmlHost()
{
    InitializeComponent();
    if (DesignerProperties.IsInDesignTool)
    {
        return;
    }
    Loaded += (o, e) =>
                    {
                        _width = Width;
                        _height = Height;
                        if (_div != null)
                        {
                            Show();
                        }
                    };
}

The synchronization is setup up in the Show method. Here, we set the DIV to be visible. We hook into two events. Whenever the entire application is resized, we’ll need to compute a new offset for the DIV. Whenever the grid is resized, we’ll need to compute a new height and width. The set up for these hooks looks like this:

public void Show()
{
    _div.RemoveStyleAttribute(ATTR_VISIBILITY);
    _div.SetStyleAttribute(ATTR_VISIBILITY, VISIBLE);
    Application.Current.Host.Content.Resized += Content_Resized;
    LayoutRoot.SizeChanged += LayoutRoot_SizeChanged;
    _Resize();
}

private void LayoutRoot_SizeChanged(object sender, SizeChangedEventArgs e)
{
    _width = e.NewSize.Width;
    _height = e.NewSize.Height;
}

private void Content_Resized(object sender, System.EventArgs e)
{
    _Resize();
}

Notice we’re banking on the fact that the only way the grid can be resized is when the application is, therefore we just record the height and width for the _Resize method to use.

The resize is where the magic happens that allows us to place the DIV so it covers our Grid exactly. Take a look:

private void _Resize()
{
    var gt = LayoutRoot.TransformToVisual(Application.Current.RootVisual);
    var offset = gt.Transform(new Point(0, 0));
    _div.RemoveStyleAttribute(ATTR_LEFT);
    _div.RemoveStyleAttribute(ATTR_TOP);
    _div.RemoveStyleAttribute(ATTR_WIDTH);
    _div.RemoveStyleAttribute(ATTR_HEIGHT);

    _div.SetStyleAttribute(ATTR_LEFT, string.Format(PX, offset.X));
    _div.SetStyleAttribute(ATTR_TOP, string.Format(PX, offset.Y));
    _div.SetStyleAttribute(ATTR_WIDTH, string.Format(PX, _width));
    _div.SetStyleAttribute(ATTR_HEIGHT,
                            string.Format(PX, _height));
}

The TransformToVisual gives us a reference to know where our Grid is relative to the entire Silverlight application, which was configured to take up the entire browser window. Doing this, we can compute the offset of the grid from the left and top edge of the application. Then, we can simply take this same offset in pixels and apply it to the top and left style attributes of the DIV. We then apply the height and width, and we’re done!

To use the control, I created a simple form that accepts a Url and on the button click will update the control. I used gradients in the background to show how it overlays the Silverlight application. Dropping in the control with a default Url looks like this:

<HtmlExample:HtmlHost Grid.Row="1" x_Name="HtmlHostCtrl" 
Margin="40" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HostDiv="htmlHost" Url="http://www.jeremylikness.com/"/>

Notice I passed the id of the DIV and a starting Url.

You can see the control in action here (and again, if you are in Chrome, you’ll see a LOT of action): Click here to open in a new tab or window.

And finally, you can grab the source code by clicking here. Don’t forget to check out our free whitepaper about Silverlight modernization.

Enjoy!

Jeremy Likness

We put security at the center of everything we do.

We understand that technology is a critical part of your business and that the applications you rely can never go down.
We offer an extensive suite of capabilities to improve your Information Security (InfoSec) including meeting specific compliance requirements.

Trusted advisor with shared responsibility and liability.

We take on our customer’s infrastructure security and compliance concerns by becoming an extension of their team or their application development vendor. We share the responsibility and liability associated with maintaining a secure or compliant environment, and stand by that commitment with a guarantee based on the defined lines of responsibility.

Secure and able to meet specific compliance requirements.

Our methodology encompasses design through deployment and focuses on delivering InfoSec and Compliance solutions which are realistically implementable. Our services span the entire computing stack, from connectivity to applications, with stringent physical and logical security controls. Whether you are looking for a higher level of security and protection or need to comply with specific regualtory mandates, we have the expertise to deliver the right solution.

Get a thorough assessment.

Build the right plan.

Rely on 24x7x365 proactive services.

Stay in good standing.

We can be counted on to actively help you prepare and pass required industry compliance audits.

We always implement networks which deliver better security and usability.

All our deployments take into consideration the end-to-end solution and we secure all aspects of the network and connectivity.

  • Defense in Depth – Our approach offers a flexible and customizable set of capabilities based on enterprise-grade standards for performance, availability, and response.
  • WANs, MANs, LANs, and VPNs – We provide comprehensive networking and high-speed connectivity options from Wide Area Networks (WAN), Metropolitan Area Networks (MAN) and Local Area Networks (LAN), as well as managed Virtual Private Networks (VPNs) and firewalls.
  • Stay current – We can be counted on to deliver proactive testing and the most secure network protocols.
  • Reduce vulnerabilities – We help your team leverage advanced security features coupled with relentless vigilance from trained experts.
Stay Informed

Sign up for the latest blogs, events, and insights.

We deliver solutions that accelerate the value of Azure.
Ready to experience the full power of Microsoft Azure?

Atmosera is thrilled to announce that we have been named GitHub AI Partner of the Year.

X