Monthly Archives: May 2009

Adding Drag/Drop Reorder to ArcGIS Flex Viewer TOC

Wow, that is a long, clumsy title.  Google will like it, though.

Anyway, I’ve been neck-deep in the world of Flex, focusing on the ArcGIS Server Flex API the last couple of months.  We delivered a map viewer based on Flex to a client recently, and it was a great learning experience.  ESRI released a Sample Flex Viewer component that includes the Table of Contents (TOC) sample from the code gallery.  The TOC component was originally built by Tom Hill at ESRI.  He and I have traded a couple of e-mails about adding drag and drop reorder functionality, so I thought I’d run through the basics of what it takes to get it working.

Download the Flex Viewer

If you haven’t already, download the flex viewer from here.  Extract the sample SOURCE (there are two zips, binary and source…we are using the source archive)  to your development area.  I am using FlexBuilder, so my life is easy, as I can just import the existing project into the IDE.  You don’t have to have FlexBuilder, but I am going to presume you know how to edit source files and compile the project.

Modify the LiveMapsWidget

The TOC component is used by the com.esri.solutions.flexviewer.widgets.LiveMapsWidget MXML component.  Open the source for that bad boy (the namespace==the directory).   Find the TOC component, which looks like

<toccomp:TOC id="toc" width="100%" height="100%"/>

We want to enable dragging on the component.  The TOC component extends the core Flex Tree component, so enabled drag drop is as simple as adding

dragEnabled=true

to the element.  If you do that and the run the viewer, you’ll notice you can open the Live Maps widget (in the Globe menu)  and drag the “Lousiana Landbase” layer around.  It doesn’t do much, but you can drag it.  In fact, you get a rex “X” while you drag, which means you can’t drop it anywhere.  So, how do we get rid of that pesky red “X”?   We have to tell the DragManager that the TOC will accept a drag/drop.  The DragManager is a Flex class that maanged drag and drop operations.  Google it for more info (sorry, but I have to constrain the post ot it will go War and Peace on me)   The time to tell the DragManager about the openness of the TOC is when something is dragged (?  drug?) over it.  We do this with the dragEnter event.  So, adddragEnter

dragEnter="onDragEnter(event)"

to the toc element.  Now we have to write the onDragEnter  method.  In the mx:Script area of LiveMapsWidget.mxml, copy this code:

			private function onDragEnter(event:DragEvent):void{
				DragManager.acceptDragDrop(event.currentTarget as TOC);
				if ((event.currentTarget as TOC)==null)
				{
					DragManager.showFeedback(DragManager.NONE);
					event.preventDefault();
				}

			}

This function fires when we drag something over the TOC and tells it that we are open for business. Business is, of course, things that can be dragged and dropped. You could put more logic in here to filter out non TOCItems, etc, but that is left as a lesson for the reader.

OK,  so our red “X” is gone over the TOC (but still there if you drag a layer over the map, cool!)  but now we want it to do something when we drop the layer.  Specifically, we want it to reorder the layers.  But we only have 1 layer, so let’s quickly add another.  Open up the config.xml in the src directory and add the following tag to the <livemaps> element:

<mapservice label="Louisville Public Records" type="dynamic" visible="false" alpha="0.4">http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Louisville/LOJIC_PublicSafety_Louisville/MapServer</mapservice>

Now we have two layers, making a drag/drop scenario much more compelling.  If you run the viewer you will see both our layers in the LiveMapsWidget.   Now, just like we had to tell Flex to enabled dragging on the TOC, we have to tell it to enabled dropping.  You guessed it, add

dropEnabled="true"

to the toc element.  Now, when you drag/drop a layer, they will reorder in the TOC, which is nice.  The map layers don’t do anything, but we’ll get there.  We have a problem now, though.  The Tree component makes no distinctions between services (roots, in this case) and the layers that make up those services.  This results in the ability of the user to be able to drop a map service under another map service, which is bad.  You can do a lot of checking when the drop occurs to make sure that the user hasn’t dropped one service as a child of another.  I am gonna go an easier route and collapse all the roots (map services) on drag start so those crafty users can’t do it.  They may not like it, but life is sometimes hard.  So, let’s add a dragStart function:

(on the toc)

dragStart = "onDragStart(event)"

(the function)

			private function onDragStart(event:DragEvent):void{
				//Close the dragged item
				_draggedLayer=toc.selectedItem  as TocMapLayerItem;
				var openItems:Array = toc.openItems as Array;
				for each(var o:Object in openItems){
					toc.expandItem(o,false);
				}
				//setInfoText(resourceManager.getString("resource","toc-layer-reorder"));
			}

K…we’re getting there.  Now to the meat of what needs to happen.  When we drop the service, we need to calculate it’s new index in the map, then tell the map to reorder the services.  It took me awhile to find an algorithm that worked for this, and here is why:

  • The basemaps.  The TOC doesn’t have the basemaps, so you have to take that into account when calculating the new index.
  • If the layer is dragged down, then you have to account for that.
  • The TOC indices are reversed form the map services.  So index 0 in the map is the lowest, but it’s the topmost node in the TOC.

So, nothing overwhelming, but details.    Let’s add the event to the TOC element:

dragComplete = "onDragComplete(event)"

and the code:


private function onDragComplete(event:DragEvent):void{
 if (event.action!="move")
 return;
 var _roots:ArrayCollection = toc.dataProvider as ArrayCollection;
 //Unclear why I have to do this....but I need the selectedIndex later
 toc.selectedItem = _draggedLayer;
 var dropIndex:uint=toc.calculateDropIndex(event);
 // I've seen thie calculated drop index be > than the number of 
 // services.  This usually happens when a service node is expanded,
 // but let's just make sure.  We'll put it at the bottom of the list 
 // in this cse.
 if (dropIndex>_roots.length)
 dropIndex=_roots.length;

 var ind:int=0;
 // Set in onDragStart....if it's null, get outta dogdge
 if (_draggedLayer==null)
 return;
 var delta:int = _roots.length-dropIndex;

 ind = delta + 1;//We have two base layers....HACK...THIS IS BAD, MAKE IT BETTER

 // If the selected item is dragged down, then the index needs to account for that
 if (toc.selectedIndex>dropIndex)
 ind=ind-1;

 toc.map.reorderLayer(_draggedLayer.layer.id,ind);

 }

The comments in that function go through what I am trying to do.  I’ll be the first to admit that it isn’t the prettiest code at the ball, but it’s the one I brought so I am dancing with it (Note to self:  work on better analogies)

So, you’d expect it to work now, woudln’t you?  Well, it doesn’t.  We have to make a minor change to the TOC to handle the layer reorder.   In the TOC.as (in src\com\esri\solutions\flexviewer\components\toc) the onLayerReorder function looks like:

private function onLayerReorder( event:MapEvent ):void
{
var layer:Layer = event.layer;
var index:int = event.index;

for (var i:int = 0; i < _tocRoots.length; i++) { var item:Object = _tocRoots[i]; if (item is TocMapLayerItem && TocMapLayerItem(item).layer === layer) { _tocRoots.removeItemAt(i); _tocRoots.addItemAt(item, _tocRoots.length - index - 1); break; } } } [/sourcecode]

Modify the TOC Code (Just a little…)

When you run the viewer now, you’ll get RangeErrors on the addItemAt line above.   So, my approach was to calculate the new TOC index by figuring out the difference between the number of layers and the new index.  Then, make sure somethign didn’t go haywire and we are out of range.  See below:

private function onLayerReorder( event:MapEvent ):void
{
var layer:Layer = event.layer;
var index:int = event.index;
//How far did we move?
var addbackind:int=(map.layerIds.length-1) – event.index;
for (var i:int = 0; i < _tocRoots.length; i++) { var item:Object = _tocRoots[i]; if (item is TocMapLayerItem && TocMapLayerItem(item).layer === layer) { _tocRoots.removeItemAt(i); // If we are out of range on the high end, rein it in if (addbackind>_tocRoots.length)
addbackind=_tocRoots.length-1;
// If we are out of range on the low end, rein it in
if (addbackind<0) addbackind=0; _tocRoots.addItemAt(item,addbackind); break; } } } [/sourcecode] May not be the prettiest...etc, etc.  But it works.  You should now have drag/drop reoder working like a champ.  Obviously, this code could still be improved.  The biggest example is accounting for the basemaps in a cleaner fashion.  I will tell you that we used the Specification Pattern to determine if a service was a basemap, allowing the TOC to ask the specification.  I liked that, but didn't include it here to try and keep this post focused. Anyway, try it out and see how it goes.  If you have improvments or suggestions, hit me in the comments.  Here is a link to the final LiveMapWidgets.mxml I used for this post.

Advertisements

Creating a REST Service with WCF and Windsor

Following on from my previous post, here is a short post showing how quickly you can create a REST based service using the WCF Facility for Castle Windsor.   We will use the same service contract and implementation as the last post, with some very minor changes (only one, actually).    The list of steps to expose our service from the previous post with a REST endpoint consists of:

  1. Create a new .svc file for the second endpoint.
  2. Add an attribute to the operation contract to specify what HTTP verbs are allowed.

That’s it.  Please note that we are not replacing our SOAP endpoint from before, nor are we changing the configuration of the WCF Facility, nor are we touching the web.config.  The changes below will highlight how little we need to do.  Here is the new service endpoint (.svc file):

<%@ ServiceHost Service="my service"
Factory="Castle.Facilities.WcfIntegration.WindsorServiceHostFactory`1[[Castle.Facilities.WcfIntegration.Rest.RestServiceModel,
Castle.Facilities.WcfIntegration]], Castle.Facilities.WcfIntegration" %>

All we have done here is replace our factory with the REST aware service model from the WCF Facility.  We are using the Service attribute to point to the same component as the SOAP endpoing.

And the new service contract:

[ServiceContract]

public interface IMyService

{

[OperationContract]

[WebGet]

string MyOperation1(string myValue);

}

We’ve added the System.ServiceModel.Web attribute [WebGet] to allow HTTP GET to this endpoint.  Now, our operation is availalbe as a REST endpoint, using a URL like so:

http://server/site/rest.svc/MyOperation1?myValue=I%20am%20RESTful

which returns

<string>A WCF Facility Service says I am RESTful</string>

It’s likely that you wouldn’t want applicaiton/xml to be the default return content-type, but the point of this post is not to show a best practice with REST, but to show how bleeding easy it is to expose a REST endpoint.  We are also now exposing TWO endpoints (SOAP and REST) with a singular service implementation, a detail worthy of note.  In the real world, you would likely have a service manager that registered service implementations and formatters, allowing the clients to specify a return type (XML, JSON, etc) and amending the content-type accordingly.

This, however, is my blog and could not be farther from the real world. 😉


WCF and Castle Windsor Update

My old post about the Castle Windsor WCF facility is a bit long in the tooth, as Mr. Craig Neuwirt and others have been hard at work improving the facility.  The release of Windsor 2.0 inspired me to attempt to go over the facility and highlight some of the new items.  In the process, I’ll cover some basics, as even those have changed a bit.   I have written most of the new documentation for the WCF facility, which needs to be reviewed by Craig since some of the new stuff is above my pay grade.  The good news is that, when released, the facility will have some good documentation as well.

The best place to see the new facility in action is to grab Castle’s trunk and go thorugh the WCF Facility unit tests.  They are comprehensive and give a great example of the new features of the facility.  This post, and the documentation I wrote, are almost entirely based on those tests.  Now, on to some examples.

Basic Server Quick Start

The basic steps to using the WCF Facility are the same, and consist of:

  1. Create your WCF service and data contracts.
  2. Create a service type, implementing your contract.
  3. Create your .svc file for your service.
  4. Configure Windsor to use the WCF Facility for your service.

Create WCF Service and Data Contracts

In this very simple example, I am only going to use a service contract.  Here is my contract:

namespace MyService.Core.Interfaces

{

[ServiceContract]

public interface IMyService

{

[OperationContract]

string MyOperation1(string myValue);

}

}

Reblog this post [with Zemanta]So, all my operation contract does is take in a string and return a string. No big whoop.

Implement the Contract

namespace MyService.Core

{

public class TheService : IMyService

{

public string Prefix { get; set; }

public TheService(string prefix)

{

Prefix = prefix;

}

 

#region IMyService Members

public string MyOperation1(string myValue)

{

return String.Format(“{0} {1}”,Prefix, myValue);

}

#endregion

}

}

Create your SVC file

Presuming you have a web application to host your service, all raring to go, we need to tell the framework how to creaate your service.  That’s what the .svc file does:

<%@ ServiceHost Service="my service"
Factory="Castle.Facilities.WcfIntegration.DefaultServiceHostFactory, Castle.Facilities.WcfIntegration" %>

As with the previous version of the facility, you have to use a custom ServiceHostFactory, which has changed to Castle.Facilities.WcfIntegration.DefaultServiceHostFactory.  Also, the Service attribute in the .svc file needs to point to the component name in your Windsor configuration, which brings us to…

Configure Windsor

In my last post, I used Boo to configure the container.  This time around, I am going to use the fluent API that Craig has created because it is kick ass.  The container needs to be configured on application start, so we put the following in the Global.asax file:

#region IContainerAccessor Members

public IWindsorContainer Container { get; private set; }

#endregion

 

protected void Application_Start(object sender, EventArgs e)

{

ServiceMetadataBehavior metadata = new ServiceMetadataBehavior();

metadata.HttpGetEnabled = true;

 

Container = new WindsorContainer()

.AddFacility<WcfFacility>()

.Register(

Component.For<IServiceBehavior>().Instance(metadata),

Component.For<IMyService>()

.Named(“my service”)

.ImplementedBy<TheService>()

.DependsOn(new {prefix = “A WCF Facility Service says”})

);

}

Here, lemme ‘splain.  As with any web application that uses Windsor, the container lives on the Global Application object.   Here, I first add the WcfFacility followed by adding a service behavior (ServiceMetadataBehavior).  This behavior will be added to all services registered with the container, however, new to the facility is the ability to explicitly scope a behavior.  You can scope a behavior to:  all services, all clients, or to explicit clients/services.  I know that feature was oft-requested by the community and Craig is the man for getting it done.  Back to our example, the service is registered is a IMyService named “my service” (remember our .svc file) and implemented by the TheService type.  Finally, I pass in my constructor argument, which is a string.  Oh, and in order to use the facility, you’ll need a reference to the following assemblies:

  • Castle.Core
  • Castle.Windsor
  • Castle.MicroKernel
  • Castle.Facilities.WcfIntegration
  • Castle.DynamicProxy2

That’s it!   The service is ready to run.  You’ll notice (or maybe you didn’t), that I did not even open the web.config.  You can use the web.config if you like, but I didn’t here because I am lazy.  You also may have noticed that I did not specify an endpoint or binding, so the facility created a default endpoint with BasicHttpBinding.  Again, I am lazy, but that is cool.

Show Me the Service

The image here shows what the service looks like in the WCFTestClient.exe application.

Easy peasy

Easy peasy

The default protocol is SOAP (does anyone use SOAP anymore?) but you could easily create a REST endpoint.  Maybe I’ll show that in my next post about this stuff.   This post only covers the very basics, so if you have more complicated stuff, I can take a stab at it or maybe ask Craig.

Just to be complete, here are most of the  big time new features:

  • Scoped Behaviors
  • The ability to create service hosts and control the host life cycle (see IServiceHostAware)
  • Fluent API

Speaking of which, lemme know if there is anything specific you want to see concerning the WCF Facility.  As I mentioned, the unit tests in the castle trunk are pretty comprehensive and the forthcoming documentation will have tons of configuration examples.  Try it out, it makes managing dependencies with WCF a breeze.


ArcGIS Server: “Cannot authenticate supplied identity” Error

So, I get this error from time to time in a dev environment or, occasionally, on a client’s server. It used to be very, very frustrating to solve, so I created a checklist of things that worked. I am writing it here to help others, hopefully, and make sure I never forget these steps.  I always try them in the following order.

  1. Make sure the ArcGISWebServices account password has not expired.  If you are comfortable with it, set this password to never expire.  This, more often than not, is the root cause of the issue.
  2. We have seen an issue where having the I_USR account in the “Guests” group on the box caused this error.  Not sure why, but the SA couldn’t tell me why that user was in that group either.Add ArcGISWebServices user to the Power Users group and restart the WWW service.  Again, this may be out of your security comfort zone.  If it is the problem, then you’ll need to mess with the privileges of that account until you find the issue.  Also, I’ve seen this not work until I deleted the user, reran the Post Install and THEN added them to the group.
  3. If it’s still an issue, time to get nasty.  Delete your ArcGISWebServices user and rerun the Web Applications Post Install.  This will force you to create a new ArcGISWebServices user.
  4. Last resort.  Do all the stuff in this thread, as well as the thread that is linked therein.  This is the “throw all the crap against the wall and hope something sticks” step.

Hopefully, that’ll get it done.  If not, and you find something else that works, please let me know.  I don’t run into this issue very often any more (but I did today and step 1 fixed it) but when I do, I am glad I have a checklist.