Monday 2 June 2014

Serving up Silverlight Apps

Building on the previous post covering a simple web-service/server one of the first uses I wanted for this was to serve up some Silverlight apps with the associated services. This is pretty easy on the Silverlight side as you just need to bundle up the bin directory and point the server to those folders and allow them to be served up... like this:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.IO;

namespace SimpleService
{
    [ServiceContract]
    public interface IApplicationRetriever
    {
        [OperationContract, WebGet(UriTemplate = "/")]
        Stream GetBasePage();

        [OperationContract, WebGet(UriTemplate = "/{page}")]
        Stream GetSilverlightPage(string page);
    }


    public class ApplicationRetrieverService : PolicyRetrieverService, IApplicationRetriever
    {
        public static string style = @"<style type=""text/css"">
                                        html, body { height: 100%; overflow: auto; }
                                        body { padding: 0; margin: 0; }
                                        #silverlightControlHost { height: 100%; text-align:center; }
                                      </style>";



        public static string errorscript = @"<script type=""text/javascript"">
                                            function onSilverlightError(sender, args) {
                                                var appSource = """";
                                                if (sender != null && sender != 0) {
                                                    appSource = sender.getHost().Source;
                                                }
            
                                                var errorType = args.ErrorType;
                                                var iErrorCode = args.ErrorCode;

                                                if (errorType == ""ImageError"" || errorType == ""MediaError"") {
                                                    return;
                                                }

                                                var errMsg = ""Unhandled Error in Silverlight Application "" +  appSource + ""\n"" ;

                                                errMsg += ""Code: ""+ iErrorCode + ""    \n"";
                                                errMsg += ""Category: "" + errorType + ""       \n"";
                                                errMsg += ""Message: "" + args.ErrorMessage + ""     \n"";

                                                if (errorType == ""ParserError"") {
                                                    errMsg += ""File: "" + args.xamlFile + ""     \n"";
                                                    errMsg += ""Line: "" + args.lineNumber + ""     \n"";
                                                    errMsg += ""Position: "" + args.charPosition + ""     \n"";
                                                }
                                                else if (errorType == ""RuntimeError"") {           
                                                    if (args.lineNumber != 0) {
                                                        errMsg += ""Line: "" + args.lineNumber + ""     \n"";
                                                        errMsg += ""Position: "" +  args.charPosition + ""     \n"";
                                                    }
                                                    errMsg += ""MethodName: "" + args.methodName + ""     \n"";
                                                }

                                                throw new Error(errMsg);
                                            }
                                            </script>";




        public string applicationpath = "";

        public Stream GetBasePage()
        {
            return GetApplication(null);
        }

        public Stream GetApplication(string docid)
        {
            Console.WriteLine("Get Index");
            WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";

            //string path = Directory.GetCurrentDirectory() + "\\application\\index.html";
            //return new FileStream(path, FileMode.Open);

            string page = "";

            page += @"<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Transitional//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">";
            page += @"<html xmlns=""http://www.w3.org/1999/xhtml"" >";

            page += "<head>";
            page += "<title>AgApplication</title>";
            page += style;

            page += @"<script type=""text/javascript"" src=""Silverlight.js""></script>";


            page += errorscript;
            page += "</head>";
            page += "<body>";

            page += @"<form id=""form1"" runat=""server"" style=""height:100%"">";
            page += @"<div id=""silverlightControlHost"">";
            page += @"<object data=""data:application/x-silverlight-2,"" type=""application/x-silverlight-2"" width=""100%"" height=""100%"">";

            page += String.Format(@"<param name=""source"" value=""{0}""/>", "AgApplication.xap");
            
            page += @"<param name=""onError"" value=""onSilverlightError"" />";
      page += @"<param name=""background"" value=""white"" />";

            if (docid != null)
            {
                page += String.Format(@"<param name=""initparams"" value=""doc={0}"" />",docid);
            }

      page += @"<param name=""minRuntimeVersion"" value=""4.0.50826.0"" />";
      page += @"<param name=""autoUpgrade"" value=""true"" />";
      page += @"<a href=""http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0"" style=""text-decoration:none"">";
    page += @"<img src=""http://go.microsoft.com/fwlink/?LinkId=161376"" alt=""Get Microsoft Silverlight"" style=""border-style:none""/>";
      page += @"</a>";
         page += @"</object>";
            page += @"<iframe id=""_sl_historyFrame"" style=""visibility:hidden;height:0px;width:0px;border:0px""></iframe></div>";
            page += @"</form>";

            page += "</body>";
            page += "</html>";

            return new MemoryStream(UTF8Encoding.Default.GetBytes(page));
        }

        public Stream GetSilverlightPage(string page)
        {
            Console.WriteLine("Get Page {0}", page);
            Stream stream = null;

            try
            {
                string[] pageitems = page.Split('.');

                if (pageitems.Length > 1)
                {
                    switch (pageitems[pageitems.Length - 1])
                    {
                        case "xml":
                        case "html":
                            WebOperationContext.Current.OutgoingResponse.ContentType = "text/html";
                            break;
                        case "js":
                            WebOperationContext.Current.OutgoingResponse.ContentType = "text/javascript";
                            break;
                    }

                }
                string path = String.Format("{0}\\{1}", applicationpath, page);
                stream =  new FileStream(path, FileMode.Open);
            }
            catch (Exception)
            {
                string path = Directory.GetCurrentDirectory() + "\\application\\error.html";
            }

            return stream;
        }
    }

} 
 


So, what this does is add a set of service calls to respond to request for pages. The root URL is used to get the 'base page' which returns the Silverlight App, which refers to the app 'Application.Xap'. The second function returns any requested pages (files) in the bundle with the appropriate content types.

All you need to do now is to have the SimpleService class inherit this and you can serve up Silverlight Apps.

So, what's next? Well, if you're serving up the app, it's pretty usual to think you want to provide some service back-end. All fine if you're running the service and the app from the same server, but when you're testing this and running the Silverlight app from the IDE it kicks off its own temporary server and so any of the services on this web server are seen as cross-process access and are refused in the Silverlight app, so you need to be able to serve up a Silverlight policy file as well. For sake of completeness I've added in the Flash policy file as well, but commented out as this was in my original source and it's not worth losing.


using System.ServiceModel.Web;
using System.ServiceModel.Description;
using System.IO;

namespace SimpleService
{
    [ServiceContract]
    public interface IPolicyRetriever
    {
        [OperationContract, WebGet(UriTemplate = "/clientaccesspolicy.xml")]
        Stream GetSilverlightPolicy();
        //[OperationContract, WebGet(UriTemplate = "/crossdomain.xml")]
        //Stream GetFlashPolicy();
    }

    public class PolicyRetrieverService : IPolicyRetriever
    {
        private Stream StringToStream(string result)
        {
            WebOperationContext.Current.OutgoingResponse.ContentType = "application/xml";
            return new MemoryStream(Encoding.UTF8.GetBytes(result));
        }

        /*
        public Stream GetFlashPolicy()
        {
            Console.WriteLine("Get Flash Policy");
            string result = @"<?xml version=""1.0"" encoding=""utf-8""?>";
            return StringToStream(result);
        }
        */

        public Stream GetSilverlightPolicy()
        {
            Console.WriteLine("Get Silverlight Policy");
            string result = @"<?xml version=""1.0"" encoding=""utf-8""?>
            <access-policy>
            <cross-domain-access>
                <policy>
                <allow-from http-request-headers=""*"">
                <domain uri=""*""/>
            </allow-from>
            <grant-to>
                <resource path=""/"" include-subpaths=""true""/>
            </grant-to>
        </policy>
        </cross-domain-access>
        </access-policy>";
            return StringToStream(result);
        }
    }
}

All that needs to be done now is to inherit from this class as well and start to put some web-service calls into the original SimpleService class.

Happy coding!

No comments:

Post a Comment