Subscribe Now: Feed Icon

Sunday, May 29, 2011

Silverlight: String localization Problems

When starting the current version I decided to move the 5 fields we had localized in our web.config to a Resource file (I mostly used Tim Heuer blog post “Silverlight and localizing string data”). I thought I was doing the right thing but I didn’t consider our application and the limitation of Resx resources in Silverlight (I thought it might be used like ResourceDictionary by zipping/unzipping the XAP file). When the problem was discover it was already to late from 5 fields we have grown to use ~40 fields and had plans to expand on that.

So what is the problem? In our application we have a Silverlight web server per country, our deployment process is also a bit different, we always deploy to Israel first (give the application a month or two for the Israeli crowd to find all the bugs) and then deploy it in other countries. Even more difficult is the fact that countries are added on the fly and without any compilation to our application (and they always have little changes in the strings localization department). Whereas changes to Resx files in Silverlight cannot be done without compilation. Because of all of that using the String Resources of Silverlight is not a good solution for our string localization deal.

Problems with Silverlight current Resource Strings:

  • Add the cultures supported to the project file (changes here need compilation) 
  • Add resx per culture (changes here need compilation)
  • Change the internal constructor to public (that is just an annoying problem)
  • Have to be compiled
  • A different version for each deployed application (even if it is just the strings)

What do I need:

  • Only one culture
  • Silverlight Static typed – I want to know if I have a mistake and have auto complete (in Resharper 6 EAP resources that do not exist are marked red and auto complete)
    • Optional: Not writing the properties of the strings by hand
  • Changed without compiling
  • Better: can be changed without rebuilding the XAP
  • Better: resources can be saved in source control (if we have them in development time)

 

Options I thought of for Server Side:

  • Use the DB and get the resources at startup – I don’t really like this option, something about saving the resources in the DB just feels wrong
  • Use the web.config – this is how we done it until now. But at this version we started adding a lot of resources using resource files and adding all that “garbage” to the web.config feels wrong.
  • I entertained a thought about using a service to return a ResourceManager back from the server, but anything with “ComVisible(true)” attribute gets my red flag (if you get a chance open ResourceManager using dotPeek or any other decompiler the first line was just too funny:
  1. [ComVisible(true)]
  2. public class ResourceManager
  3. {
  4.   public static readonly int MagicNumber = -1091581234;

(not sure where it is used though…))

  • Service that returns a Dictionary of string,string – optional
  • File (xml or csv) in the Web project passed to the Silverlight Application on startup – optional
  • Chosen Solution: File (Resx) in the Web project with Custom Tool to generate the code – since resx is actually an XML it’s actually the last option disguised. The server sends the text in the resx file to the Silverlight client.

Client Side:

The client receives the text and reads it by parsing the resources xml and then puts it in the generated designer.cs file that we linked from the web project.

That was all good on paper but in actuality the setting of ResourceManager in the resources.designer.cs file is written without any way of changing this property (it’s not virtual, the property only has a get and it’s stored in a private member).

I had a thought about creating a tool of my own but in the end common sense won, and I found this post about using t4 templates to generate the resources code so I tweaked the original T4 a bit (too much)… You can find my T4 file here.

 

So lets look at some code!

Server side I have added the resources XML to the parameters for the config (see post), like this:

  1. private void SaveSilverlightResourcesSettings(StringBuilder parameters)
  2. {
  3.     var culture = ConfigurationManager.AppSettings["Culture"] ?? Thread.CurrentThread.CurrentCulture.Name;
  4.     var filename = Server.MapPath(String.Format("Resources\\StringsResource.{0}.resx", culture));
  5.     if (!File.Exists(filename))
  6.         filename = Server.MapPath("Resources\\StringsResource.resx");
  7.     TextReader tr = new StreamReader(filename);
  8.  
  9.     parameters.Append(StringsResourceName.Name);
  10.     parameters.Append("=");
  11.     parameters.Append(HttpUtility.UrlEncode(tr.ReadToEnd()));
  12.     parameters.Append(",");
  13.  
  14.     tr.Close();
  15. }

line 3: the current culture is taken from either the web.config or the server value (allowing multiple culture web sites in the same server)

Line 4: takes the file by the culture set in the server (that allows us to save the resources string in the Source control if we have them at development time). If it doesn’t exist we take the default file.

Link to code file

Server side resources look like this:

Server-Resources

The T4resx.Designer.cs is linked to the client Silverlight application:

Silverlight-Resources

 

In the Silverlight App.Xaml we load the text of the string resources:

  1. private void Application_Startup(object sender, StartupEventArgs e)
  2. {
  3.     MapApplicationConfigWrapper.Instance.Initialize(e);
  4.  
  5.     if (e.InitParams.ContainsKey(StringsResourceName.Name))
  6.     {
  7.         var resourceString = HttpUtility.UrlDecode(e.InitParams[StringsResourceName.Name]);
  8.         if (resourceString != null)
  9.             StringsResource.SetResourceDictionary(XDocument.Parse(resourceString).Element("root"));
  10.     }
  11.  
  12.     RootVisual = new MainPage();
  13. }

Link to code file

StringsResource is the class in StringsReource.Designer.cs, it is based on a regular Resource designer but has some changes:

  1. public class StringsResourceName { public const string Name = "StringsResource"; }
  1. private static bool _useResourceManager = true;
  2. private static Dictionary<string, string> _resourcesDictionary;
  1. public static void SetResourceDictionary(XElement xml)
  2. {
  3.     _useResourceManager = false;
  4.     _resourcesDictionary = new Dictionary<string, string>();
  5.     foreach (var data in xml.Descendants("data"))
  6.     {
  7.         _resourcesDictionary.Add(data.Attribute("name").Value, data.Element("value").Value);
  8.     }
  9. }
  1. private static string GetResourceString(string key, params string[] tokens)
  2. {
  3.     if(!_useResourceManager)
  4.     {
  5.         if (_resourcesDictionary.ContainsKey(key))
  6.             return _resourcesDictionary[key];
  7.         return string.Empty;
  8.     }

Link to code file

Using this was as easy as changing the namesapce for the resource in the xaml file:

  1. xmlns:res="clr-namespace:DllShepherd.SilverlightGoogleMaps.Web.Resources"
  1. <UserControl.Resources>
  2.     <res:StringsResource x:Key="StringsRes" />
  3. </UserControl.Resources>
  1. <Controls:MenuItem Header="{Binding Path=StreetView, Source={StaticResource StringsRes}}"

Link to code file

You can see a sample project using this at my CodePlex GoogleStreets project that changes the header of the context menu.

 

The only downside I found for this method is in changing the Resources file. The T4 template then needs to be included in the build process for this (you need to install Visual Studio Visualization and Modeling SDK (which uses VS2010 SDK)) and edit the web project file (see here). Though with this change the template causes problems with MsBuild because of how it is written (the baseT4MVC uses the VS IDE). I guess a better solution will be to rewrite the T4resx so that it will track changes in StringsResource.resx (possible using Directive Processor) but I don’t have time to implement it just yet.

Note: another solution is running the tool manually by right clicking on the T4resx.Designer.tt and:

run-custom-tool-tt

I think both solutions are still more elegant than changing the constructor to public.

 

Keywords: Resource, Silverlight

IceRocket Tags: ,

Thursday, May 26, 2011

Configuring Silverlight Applications

(Or how to configure service reference addresses easily)

Most developers will tell you that the XAP file is actually a ZIP file and all you have to do is edit the ServiceReferences.clientconfig file inside that XAP. Some will even tell you that they have developed an application that does it for you.

But is it easy? Is it keeping it simple (-stupid)?

I don’t think so, but you tell me?

What is more simple edit your web application web.config or edit your XAP file?

The first installation is a bit complex but afterwards all you have to do is edit the web.config.

In the Web Application Project:

On the aspx that host the XAP file add an ASP control for the config values. For example:

  1.     <div id="silverlightControlHost">
  2.         <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
  3.           <param name="source" value="ClientBin/SilverlightApplication.xap"/>
  4.           <param name="onError" value="onSilverlightError" />
  5.           <param name="background" value="white" />
  6.           <param name="minRuntimeVersion" value="3.0.40624.0" />
  7.           <param name="autoUpgrade" value="true" />
  8.                      <asp:Literal ID="ParamInitParams" runat= "server"></asp:Literal>
  9.           <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration:none">
  10.               <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style:none"/>
  11.           </a>
  12.         </object>
  13.         <iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe>
  14.     </div>

On the code behind add the code that puts the config values in the ASP control. For example:

  1. private void SaveSilverlightDeploymentSettings(Literal litSettings)
  2. {
  3.     NameValueCollection appSettings = ConfigurationManager.AppSettings;
  4.  
  5.     var parameters = new StringBuilder();
  6.     parameters.Append("<param name=\"InitParams\" value=\"");
  7.  
  8.     var settingCount = appSettings.Count;
  9.     for (int index = 0; index < settingCount; index++)
  10.     {
  11.         parameters.Append(appSettings.GetKey(index));
  12.         parameters.Append("=");
  13.         parameters.Append(HttpUtility.UrlEncode(appSettings[index]));
  14.         parameters.Append(",");
  15.     }
  16.     parameters.Remove(parameters.Length - 1, 1);
  17.     parameters.Append("\" />");
  18.  
  19.     litSettings.Text = parameters.ToString();
  20. }
  21.  
  22. protected void Page_Load(object sender, EventArgs e)
  23. {
  24.     Response.Cache.SetCacheability(HttpCacheability.NoCache);
  25.     SaveSilverlightDeploymentSettings(ParamInitParams);
  26. }

On the web.config add your config values as appSettings:

  1. <?xml version="1.0"?>
  2.  
  3. <configuration
  4.     <appSettings>
  5.     <add key="DataUrl" value="http://GIS_SERVER_NAME/ArcGIS/rest/services/Layers" />
  6.     <add key="RefreshTimerIntervalInMiliSeconds" value="15000"/>
  7.   </appSettings>
  8. </configuration>

As you can see you can even add Application wide settings here not just service URLs.

In the Silverlight application – app.xaml file:

  1. private void Application_Startup(object sender, StartupEventArgs e)
  2. {
  3.     MapApplicationConfigWrapper.Initialize(e);
  4. }

MapApplicationConfigWrapper is in a Common project (something all of your projects under the Silverlight application can access):

  1. public class MapApplicationConfigWrapper
  2. {
  3.     #region C'tor
  4.  
  5.     private readonly IDictionary<string, string> _config = new Dictionary<string, string>();
  6.  
  7.     private MapApplicationConfigWrapper(StartupEventArgs e)
  8.     {
  9.         foreach (string key in e.InitParams.Keys)
  10.         {
  11.             SetValue(key, HttpUtility.UrlDecode(e.InitParams[key]));
  12.         }
  13.     }
  14.  
  15.     private MapApplicationConfigWrapper()
  16.     {
  17.     }
  18.  
  19.     public static MapApplicationConfigWrapper Instance
  20.     {
  21.         get
  22.         {
  23.             if (!Application.Current.Resources.Contains(MapResources.WebConfig))
  24.             {
  25.                 Application.Current.Resources.Add(MapResources.WebConfig, new MapApplicationConfigWrapper());
  26.             }
  27.             return (MapApplicationConfigWrapper) Application.Current.Resources[MapResources.WebConfig];
  28.         }
  29.     }
  30.  
  31.     public static void Initialize(StartupEventArgs e)
  32.     {
  33.         var wrapper = new MapApplicationConfigWrapper(e);
  34.         Application.Current.Resources.Add(MapResources.WebConfig, wrapper);
  35.     }
  36.  
  37.     #endregion C'tor
  38.  
  39.     #region Config Properties
  40.  
  41.     public string DataUrl
  42.     {
  43.         get { return GetValue("DataUrl"); }
  44.         set { SetValue("DataUrl", value); }
  45.     }
  46.  
  47.     public int RefreshTimerIntervalInMiliSeconds
  48.     {
  49.         get
  50.         {
  51.             int result;
  52.             return (int.TryParse(GetValue("RefreshTimerIntervalInMiliSeconds"), out result)) ? result : 60000;
  53.         }
  54.         set { SetValue("RefreshTimerIntervalInMiliSeconds", value.ToString()); }
  55.     }
  56.  
  57.     #endregion Config Properties
  58.  
  59.     #region Private Helper Methods
  60.  
  61.     private string GetValue(string key)
  62.     {
  63.         if (!_config.ContainsKey(key))
  64.             return null;
  65.         return _config[key];
  66.     }
  67.  
  68.     private void SetValue(string key, string value)
  69.     {
  70.         _config[key] = value;
  71.     }
  72.  
  73.     #endregion Private Helper Methods
  74. }

So what is done here?

Well StartupEventArgs contains InitParams that is a dictionary with the config values we already entered in the aspx. We go through all the values and put them in our own dictionary.

Next we create properties with the specific types we need for the URL a string and for the RefreshTimerIntervalInMiliSeconds an int.

The code inside Instance (the if) is for when we just want to use the default parameters and we didn’t go through app.xaml.

 

Now using it is simply

MapApplicationConfigWrapper.Instance.DataUrl

MapApplicationConfigWrapper.Instance.RefreshTimerIntervalInMiliSeconds

and you have the value at your hand!

 

For everyday use it is much more simple and readable. But maybe it is just me…

What do you think?

 

Resources:

Managing service references and endpoint configurations for Silverlight applications

ConfigSwitcher: ServiceReferences.ClientConfig Switcher Utility

Configure Silverlight 3 Applications using the Web.config File from ASP.NET

Keywords: Silverlight, Config, configuration, web.config, Service Reference, ServiceReferences

Friday, May 20, 2011

GIS Silverlight Project

Yesterday my team was asked to present out system to two other companies working with ESRI technology. It started out with my boss giving a talk on the company and then I gave a presentation on the abilities of our Silverlight application. The talk was cut shorter since the manager of R&D in our organization summoned us for a surprise meeting (and beer, can’t forget the beer!), but we managed to cover all the issues even with the shortened meeting.

This meeting was doubly important to me personally since one of the companies that came around was my previous one, with a few of my friends mixed in…

 

So what does our Silverlight Application do?

First of we have two kinds of users: users of the company (with Active directory permissions) and our customers. Both need to access our application but with different controls turned on.

  • All users can see a map with Sites (our main components in the system)
  • The map layers update themselves according to changes in the system (for example when a Site is added, the users get the update automatically)
  • Sites grid with an arrow pointer for the Site location in the map (the Site the mouse is on top in the grid)
  • Address Search
  • Routing: getting directions from place to place (and in the near future what Sites are in the way)
  • Google Streets View: for those countries that support it (soon in Israel)
  • Real Time Traffic: a display on the map of the current traffic reports (and when ESRI will support this we will deliver Routing results that regard the current traffic)

Functionality for company users:

  • Display live (give or take 30 seconds) view of vehicles on the road with either lines or dots for their position
  • Querying old vehicle locations
  • Site creation
  • Inner site topology creation (all of which are geographic entities that can be moved on the map while in edit mode)
  • Various editing of field data

 

The after meetings talks were mostly on the type of Layers we used. We are actually using a custom FeatureLayer that handles the automatic updates (I will post about it later).

The meeting ended with Yossi (my previous company professional UI guy saying that our “application looks very nice”, a high complement from him…).

If you ever wonder what I yet have to post about, just check the lists above…

Until next time,

Roy

 

IceRocket Tags: ,

Saturday, May 14, 2011

ArcObjects: Introduction

This post is the first post in my ArcObjects series.

 

“I cried because I did not have an office with a door, until I met a man who had no cubicle.” Dilbert

(Introduction to the Joy of Work by Scott Adams)

 

Back when I started in my current place of employment I was given a month to learn Silverlight (I came from a WinForms background). After a week of reading a dry book I wanted to look into the team’s code – and discovered the Horror!

The usage of ArObjects was everywhere and not just that there was a file named SdeUtilities.cs that was copied around that contained functions for insert/update/delete data using ArcObjects.

Why is that such a Horror?

In my previous job I was in charge of a Framework that encapsulated all ArcObjects except for classes/interfaces that implemented IGeometry (such as IPoint, IPolyline etc.). The idea there was that some programmers do not have ArcObjects background but know the geometry of the entity they want to save/load and with that Framework in place they could just use it!

But using only helper methods (the SdeUtilities) insured that any programmer that needed to work for the team has to know ArcObjects, or fall in it’s pitfalls (such as not releasing a cursor, multithreading problems and much more).

Now the Framework I built is not up to the standards of NHibernate or Entity Framework, but it gets the job done. And it has both Unit Test and Coded Integration Tests (something that sometimes I wonder about ArcObjects in general).

For some unknown reason, I find the quote from Scott Adam’s book Joy of Work (a book I highly recommend) appropriate here: “I cried because I did not have an office with a door, until I met a man who had no cubicle.”

 

Real Example

Imagine the following scenario: you as a programmer need to write code for tracking down sheep in the following way: insert a point location to SHEEP_HISTORY_LOCATIONS, update the point in SHEEP_LOCATIONS and update the geometry in SHEEP_TRACK (a polyline layer). I actually had these actual functionality in both my previous and current jobs (just not with sheep…). What will you have to do using ArcObjects?

1. Initialize the ArcEngine license

2. Get the workspace from the connection string

3. Update the IFeature in SHEEP_TRACK (Get the IFeature from SHEEP_TRACK (and it’s shape IPolyline), and add a point)

4. Update the IFeature in SHEEP_LOCATIONS (Get the IFeature from SHEEP_LOCATIONS and change the point shape)

5. Insert an IFeature to SHEEP_HISTORY_LOCATIONS (insert a new feature with the correct fields)

The example code can be found here (in my CodePlex project). It is in the usual style of ESRI’s examples – in a word ‘messy’. As can be seen this code is neither readable nor testable. Using my Framework the code could look as easy as:

  1. public void AddSheepLocation(int code, IPoint location)
  2. {
  3.     var sheepTrack = SheepTrackDal.Instance.GetByCode(code);
  4.     var track = (IPolyline) sheepTrack.Geometry;
  5.     track.AddPoint(location);
  6.     SheepTrackDal.Instance.UpdateLocation(new KeyValuePair<String, object>("Code", code), track);
  7.     SheepLocationDal.Instance.UpdateLocation(new KeyValuePair<String, object>("Code", code), location);
  8.     SheepHistoryLocationDal.Instance.Insert(new SheepHistoryLocation { Code = code, ReportTime = DateTime.Now, Geometry = location});
  9. }

As opposed to ~100 lines of code this is actually readable… But lets go over it:

line 3: getting the SheepTrack entity from the DB

line 4-5: Getting the geometry and adding a point

lines 6-8: saving the data.

As you can see the only ArcObjects directly in use here are IPoint and IPolyline (which implement IGeometry).

Note: the red words are because I haven’t actually implemented the sheep classes and Dals (Dal = Data access layer) but that will come in the one of the next posts.

 

Are there no alternatives to ArcObjects?

At this point in time you must be thinking to yourselves “This is too much work! There must be an alternative.”, and you are right. You don’t have to use ArcObjects (even if you want to use ArcGIS Server). The solution is using an ESRI supported Spatial Database such as Oracle or Microsoft SQL Server 2008. Using those DBs you can register the tables in the SDE but handle all the insert/update/delete yourselves (like NHibernate Forge formerly NHibernate Spatial). You should be advised that Linq2SQL and Entity Framework have little to no support for Spatial types (though there is a workaround).

Using this Spatial Types is also a great workaround for people without ESRI license such as Engine, Editor or Server. Without these licenses the options of insert/update of Spatial Data in SDE is non existing (assuming you use an actual DB and not a File DB).

 

Next: ArcObjects: Getting Started

 

IceRocket Tags: ,,

Tuesday, May 3, 2011

Silverlight: Attach to Process Shortcut

Back when I was a WinForm programmer I used a keyboard shortcut to attach the process of that program to the Visual Studio Debugger, it was a fairly easy process involving recording a macro and attaching it to a keyboard shortcut. This morning I decided to do the same to my IE Silverlight window using this guide but run into a few difficulties:

  1. The way the guide got the computer name just didn’t work, I found out from here that Environment.MachineName works.
  2. It didn’t attach to the Silverlight process – thus didn’t work!

 

    1. Sub NotWorkingAttachToSilverlight()
    2.     Try
    3.         Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger
    4.         Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")
    5.         Dim dbgeng(2) As EnvDTE80.Engine
    6.         dbgeng(0) = trans.Engines.Item("Silverlight")
    7.         Dim proc2 As EnvDTE80.Process2 = dbg2.GetProcesses(trans, Environment.MachineName).Item("iexplore.exe")
    8.         proc2.Attach2(dbgeng)
    9.     Catch ex As System.Exception
    10.         MsgBox(ex.Message)
    11.     End Try
    12. End Sub

     

    Why didn’t it attach to the Silverlight process?

    Silverlight in IE works by using two processes: one for the window (red outline) and the other for Silverlight(selected):

    VS-attach-to-silverlight

    Now whenever I tested the Macro it just attached itself to the first process!

    The solution was to iterate through the processes until finding the one containing the Silverlight program and attaching to that process:

    1. Sub AttachToSilverlight()
    2.     Try
    3.         Dim dbg2 As EnvDTE80.Debugger2 = DTE.Debugger
    4.         Dim trans As EnvDTE80.Transport = dbg2.Transports.Item("Default")
    5.         Dim dbgeng(1) As EnvDTE80.Engine
    6.         dbgeng(0) = trans.Engines.Item("Silverlight")
    7.  
    8.         For Each process As EnvDTE80.Process2 In dbg2.GetProcesses(trans, Environment.MachineName)
    9.             If process.Name.Contains("iexplore.exe") Then
    10.                 For Each program As EnvDTE.Program In process.Programs
    11.                     If program.Name.Contains("Silverlight") Then
    12.                         process.Attach2(dbgeng)
    13.                     End If
    14.                 Next
    15.  
    16.             End If
    17.         Next
    18.  
    19.     Catch ex As System.Exception
    20.         MsgBox(ex.Message)
    21.     End Try
    22. End Sub

    Attach the Macro to a keyboard shortcut is as easy as:

    Keyboard-shortcut

    (Ctrl+Alt+S was for the Server Explorer but I don’t know many people who use this shortcut since the Server Explorer is by default always on).

     

    Keywords: Silverlight, Debugging

    IceRocket Tags: ,