bobby's blog

randomly specific...


News

My Stats

  • Posts - 15
  • Comments - 48
  • Trackbacks - 0

Twitter












Recent Comments


Recent Posts


Archives


Post Categories


Image Galleries


My Links


User Groups



Have you ever needed to detect what part of the application is currently being viewed?  This might be a bigger issue if you write a lot of shared/partial views or custom display or editor templates.  Another scenario, which is the one I encountered when I first started down this path, is when you have some type of menu and you’d like to be able to determine which item represents the current page so you can highlight it in some way.  A simple example is the menu that is created as part of the default ASP.NET MVC 2 Application template.

 

<div id="menucontainer">

 

    <ul id="menu">

        <li><%= Html.ActionLink("Home", "Index", "Home") %></li>

        <li><%= Html.ActionLink("About", "About", "Home") %></li>

    </ul>

 

</div>

 

The part that got me at first, however, was the following entry in the default style sheet (Site.css):

 

ul#menu li.selected a

{

    background-color: #fff;

    color: #000;

}

 

I assumed that the .selected class would automatically get applied to the active menu item.  After trying a few different things, including the MvcContrib MenuBuilder, I decided to write my own extension methods so I would have more control over the output.  First, I needed a way to determine what view the user has navigated to based on the requested URL and route configuration.  Now, I am sure there are many ways to do this, but this is what I came up with:

 

public static class RequestExtensions

{

    public static bool IsCurrentRoute(this RequestContext context, String areaName,

        String controllerName, params String[] actionNames)

    {

        var routeData = context.RouteData;

        var routeArea = routeData.DataTokens["area"] as String;

        var current = false;

 

        if ( ((String.IsNullOrEmpty(routeArea) && String.IsNullOrEmpty(areaName)) ||

              (routeArea == areaName)) &&

             ((String.IsNullOrEmpty(controllerName)) ||

              (routeData.GetRequiredString("controller") == controllerName)) &&

             ((actionNames == null) ||

               actionNames.Contains(routeData.GetRequiredString("action"))) )

        {

            current = true;

        }

 

        return current;

    }

 

    // additional overloads omitted...

}

 

With that in place, I was able to write several UrlHelper methods that check if the supplied values map to the current view.

 

public static class UrlExtensions

{

    public static bool IsCurrent(this UrlHelper urlHelper, String areaName,

        String controllerName, params String[] actionNames)

    {

        return urlHelper.RequestContext.IsCurrentRoute(areaName, controllerName, actionNames);

    }

 

    public static string Selected(this UrlHelper urlHelper, String areaName,

        String controllerName, params String[] actionNames)

    {

        return urlHelper.IsCurrent(areaName, controllerName, actionNames)

            ? "selected" : String.Empty;

    }

 

    // additional overloads omitted...

}

 

Now I can re-work the original menu to utilize these new methods.  Note: be sure to import the proper namespace so the extension methods become available inside your views!

 

<div id="menucontainer">

 

    <ul id="menu">

        <li class="<%= Url.Selected(null, "Home", "Index") %>">

            <%= Html.ActionLink("Home", "Index", "Home")%></li>

 

        <li class="<%= Url.Selected(null, "Home", "About") %>">

            <%= Html.ActionLink("About", "About", "Home")%></li>

    </ul>

 

</div>

 

If we take it one step further, we can clean up the markup even more.  Check out the Html.ActionMenuItem() extension method and the refined menu:

 

public static class HtmlExtensions

{

    public static MvcHtmlString ActionMenuItem(this HtmlHelper htmlHelper, String linkText,

        String actionName, String controllerName)

    {

        var html = new StringBuilder("<li");

 

        if ( htmlHelper.ViewContext.RequestContext

                .IsCurrentRoute(null, controllerName, actionName) )

        {

            html.Append(" class=\"selected\"");

        }

 

        html.Append(">")

            .Append(htmlHelper.ActionLink(linkText, actionName, controllerName))

            .Append("</li>");

 

        return MvcHtmlString.Create(html.ToString());

    }

 

    // additional overloads omitted...

}

 

UPDATE: Thanks to Ryan for reminding me to use the TagBuilder class instead of the StringBuilder for generating the HTML for the menu item!  Here is the refactored method:

 

public static class HtmlExtensions

{

    public static MvcHtmlString ActionMenuItem(this HtmlHelper htmlHelper, String linkText,

        String actionName, String controllerName)

    {

        var tag = new TagBuilder("li");

 

        if ( htmlHelper.ViewContext.RequestContext

            .IsCurrentRoute(null, controllerName, actionName) )

        {

            tag.AddCssClass("selected");

        }

 

        tag.InnerHtml = htmlHelper.ActionLink(linkText, actionName, controllerName).ToString();

 

        return MvcHtmlString.Create(tag.ToString());

    }

}

 

<div id="menucontainer">

 

    <ul id="menu">

        <%= Html.ActionMenuItem("Home", "Index", "Home") %>

        <%= Html.ActionMenuItem("About", "About", "Home") %>

    </ul>

 

</div>

 

Which generates the following HTML:

 

<div id="menucontainer">

 

    <ul id="menu">

        <li class="selected"><a href="/">Home</a></li>

        <li><a href="/Home/About">About</a></li>

    </ul>

 

</div>

 

I have created a codepaste of these extension methods if you are interested in using them in your own projects.  Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


Recently, I started working on a new ASP.NET MVC 2 project and I wanted to reuse the data access (LINQ to SQL) and business logic methods (WCF RIA Services) that had been developed for a previous project that used Silverlight for the front-end.  I figured that I would be able to instantiate the various DomainService classes from within my controller’s action methods, because after all, the code for those services didn’t look very complicated.  WRONG!  I didn’t realize at first that some of the functionality is handled automatically by the framework when the domain services are hosted as WCF services.  After some initial searching, I came across an invaluable post by Joe McBride, which described how to get RIA Service .svc files to work in an MVC 2 Web Application, and another by Brad Abrams.  Unfortunately, Brad’s solution was for an earlier preview release of RIA Services and no longer works with the version that I am running (PDC Preview).

I have not tried the RC version of WCF RIA Services, so I am not sure if any of the issues I am having have been resolved, but I wanted to come up with a way to reuse the shared libraries so I wouldn’t have to write a non-RIA version that basically did the same thing.  The classes I came up with work with the scenarios I have encountered so far, but I wanted to go ahead and post the code in case someone else is having the same trouble I had.  Hopefully this will save you a few headaches!

1. Querying


When I first tried to use a DomainService class to perform a query inside one of my controller’s action methods, I got an error stating that “This DomainService has not been initialized.”  To solve this issue, I created an extension method for all DomainServices that creates the required DomainServiceContext and passes it to the service’s Initialize() method.  Here is the code for the extension method; notice that I am creating a sort of mock HttpContext for those cases when the service is running outside of IIS, such as during unit testing!

    public static class ServiceExtensions

    {

        /// <summary>

        /// Initializes the domain service by creating a new <see cref="DomainServiceContext"/>

        /// and calling the base DomainService.Initialize(DomainServiceContext) method.

        /// </summary>

        /// <typeparam name="TService">The type of the service.</typeparam>

        /// <param name="service">The service.</param>

        /// <returns></returns>

        public static TService Initialize<TService>(this TService service)

            where TService : DomainService

        {

            var context = CreateDomainServiceContext();

            service.Initialize(context);

            return service;

        }

 

        private static DomainServiceContext CreateDomainServiceContext()

        {

            var provider = new ServiceProvider(new HttpContextWrapper(GetHttpContext()));

            return new DomainServiceContext(provider, DomainOperationType.Query);

        }

 

        private static HttpContext GetHttpContext()

        {

            var context = HttpContext.Current;

 

#if DEBUG

            // create a mock HttpContext to use during unit testing...

            if ( context == null )

            {

                var writer = new StringWriter();

                var request = new SimpleWorkerRequest("/", "/",

                    String.Empty, String.Empty, writer);

 

                context = new HttpContext(request)

                {

                    User = new GenericPrincipal(new GenericIdentity("debug"), null)

                };

            }

#endif

 

            return context;

        }

    }

 

With that in place, I can use it almost as normally as my first attempt, except with a call to Initialize():

    public ActionResult Index()

    {

        var service = new NorthwindService().Initialize();

        var customers = service.GetCustomers();

 

        return View(customers);

    }


2. Insert / Update / Delete


Once I got the records showing up, I was trying to insert new records or update existing data when I ran into the next issue.  I say issue because I wasn’t getting any kind of error, which made it a little difficult to track down.  But once I realized that that the DataContext.SubmitChanges() method gets called automatically at the end of each domain service submit operation, I could start working on a way to mimic the behavior of a hosted domain service.  What I came up with, was a base class called LinqToSqlRepository<T> that basically sits between your implementation and the default LinqToSqlDomainService<T> class.

    [EnableClientAccess()]

    public class NorthwindService : LinqToSqlRepository<NorthwindDataContext>

    {

        public IQueryable<Customer> GetCustomers()

        {

            return this.DataContext.Customers;

        }

 

        public void InsertCustomer(Customer customer)

        {

            this.DataContext.Customers.InsertOnSubmit(customer);

        }

 

        public void UpdateCustomer(Customer currentCustomer)

        {

            this.DataContext.Customers.TryAttach(currentCustomer,

                this.ChangeSet.GetOriginal(currentCustomer));

        }

 

        public void DeleteCustomer(Customer customer)

        {

            this.DataContext.Customers.TryAttach(customer);

            this.DataContext.Customers.DeleteOnSubmit(customer);

        }

    }


Notice the new base class name (just change LinqToSqlDomainService to LinqToSqlRepository).  I also added a couple of DataContext (for Table<T>) extension methods called TryAttach that will check to see if the supplied entity is already attached before attempting to attach it, which would cause an error!

3. LinqToSqlRepository<T>


Below is the code for the LinqToSqlRepository class.  The comments are pretty self explanatory, but be aware of the [IgnoreOperation] attributes on the generic repository methods, which ensures that they will be ignored by the code generator and not available in the Silverlight client application.

    /// <summary>

    /// Provides generic repository methods on top of the standard

    /// <see cref="LinqToSqlDomainService&lt;TContext&gt;"/> functionality.

    /// </summary>

    /// <typeparam name="TContext">The type of the context.</typeparam>

    public abstract class LinqToSqlRepository<TContext> : LinqToSqlDomainService<TContext>

        where TContext : System.Data.Linq.DataContext, new()

    {

        /// <summary>

        /// Retrieves an instance of an entity using it's unique identifier.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="keyValues">The key values.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual TEntity GetById<TEntity>(params object[] keyValues) where TEntity : class

        {

            var table = this.DataContext.GetTable<TEntity>();

            var mapping = this.DataContext.Mapping.GetTable(typeof(TEntity));

 

            var keys = mapping.RowType.IdentityMembers

                .Select((m, i) => m.Name + " = @" + i)

                .ToArray();

 

            return table.Where(String.Join(" && ", keys), keyValues).FirstOrDefault();

        }

 

        /// <summary>

        /// Creates a new query that can be executed to retrieve a collection

        /// of entities from the <see cref="DataContext"/>.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual IQueryable<TEntity> GetEntityQuery<TEntity>() where TEntity : class

        {

            return this.DataContext.GetTable<TEntity>();

        }

 

        /// <summary>

        /// Inserts the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Insert<TEntity>(TEntity entity) where TEntity : class

        {

            //var table = this.DataContext.GetTable<TEntity>();

            //table.InsertOnSubmit(entity);

 

            return this.Submit(entity, null, DomainOperation.Insert);

        }

 

        /// <summary>

        /// Updates the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Update<TEntity>(TEntity entity) where TEntity : class

        {

            return this.Update(entity, null);

        }

 

        /// <summary>

        /// Updates the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <param name="original">The original.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Update<TEntity>(TEntity entity, TEntity original)

            where TEntity : class

        {

            if ( original == null )

            {

                original = GetOriginal(entity);

            }

 

            var table = this.DataContext.GetTable<TEntity>();

            table.TryAttach(entity, original);

 

            return this.Submit(entity, original, DomainOperation.Update);

        }

 

        /// <summary>

        /// Deletes the specified entity.

        /// </summary>

        /// <typeparam name="TEntity">The type of the entity.</typeparam>

        /// <param name="entity">The entity.</param>

        /// <returns></returns>

        [IgnoreOperation]

        public virtual bool Delete<TEntity>(TEntity entity) where TEntity : class

        {

            //var table = this.DataContext.GetTable<TEntity>();

            //table.TryAttach(entity);

            //table.DeleteOnSubmit(entity);

 

            return this.Submit(entity, null, DomainOperation.Delete);

        }

 

        protected virtual bool Submit(Object entity, Object original, DomainOperation operation)

        {

            var entry = new ChangeSetEntry(0, entity, original, operation);

            var changes = new ChangeSet(new ChangeSetEntry[] { entry });

            return base.Submit(changes);

        }

 

        private TEntity GetOriginal<TEntity>(TEntity entity) where TEntity : class

        {

            var context = CreateDataContext();

            var table = context.GetTable<TEntity>();

            return table.FirstOrDefault(e => e == entity);

        }

    }


4. Conclusion


So there you have it, a fully functional Repository implementation for your RIA Domain Services that can be consumed by your ASP.NET and MVC applications.  I have uploaded the source code along with unit tests and a sample web application that queries the Customers table from inside a Controller, as well as a Silverlight usage example.

As always, I welcome any comments or suggestions on the approach I have taken.  If there is enough interest, I plan on contacting Colin Blair or maybe even the man himself, Brad Abrams, to see if this is something worthy of inclusion in the WCF RIA Services Contrib project.  What do you think?

Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


A popular topic that comes up when talking about MVVM is the use of a ViewModelLocator and the many different ways one can be implemented.  Rather than getting into the pros and cons on when or why you should use it, I decided I would just post my version of a simple ViewModelLocator and let those who like it use it, and those who don’t, well you know…  :)

First, a disclaimer.  I have not used this code in a production application, it is just something I was tossing around while reading others’ posts on the subject.

1. MainView.xaml

image

 

2. MainViewModel.cs

image


3. ViewModelLocator.cs

image

 

I have a codepaste of the ViewModelLocator.cs file if you are interested but don’t feel like re-typing the 50 lines of code!

Enjoy!

Additional Resources

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


Well, day 2 of the MIX10 conference did not disappoint.  The keynote speakers introduced the preview release of IE9, which looks really cool and quick, and Visual Studio 2010 RC that is scheduled to RTM on April 12th.  It seemed to have a lot of improvements aimed at making developers more productive.  Here are the current links to these two offerings:


While both of these were interesting, the demos that really blew me away today centered around the work being done with The Open Data Protocol, or OData for short!  OData is a recommended standard being pushed by Microsoft that uses a REST based interface to interact with various types of data in a uniform manner.  Data producers then provide the data to consumer in either ATOM or JSON formats as requested by the client application.


The OData SDK contains client and server libraries for many of the popular languages in use today, including .NET, Java, PHP, Objective C and JavaScript, so you consume or even produce your own OData services.  More information can be found using the following links:


Netflix has made available one of the first live OData services by exposing their entire movie catalog.  You can browse and query using URLs similar to the following:


So now I just need to find an excuse reason to start using OData in a real project!


Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


There were a lot of announcements made during the keynote at MIX10 today, most notable were the releases of Silverlight 4 RC, Silverlight 4 Tools for Visual Studio 2010, Expression Blend 4 Beta and the Windows Phone 7 Developer Tools.  I was glad to see that developers will be able to use Silverlight to create awesome applications for Windows Phone 7 so we can reuse our WPF and Silverlight skills to target mobile devices!

With so much information coming out of this conference, I wanted to be sure to save a list of links that I can quickly reference as I learn about these exciting new technologies:

Silverlight 4

Windows Phone 7


Whew, and that’s just from day 1!  Can’t wait to see what else comes out tomorrow.  Hopefully these links will give you a good starting point for Silverlight 4 and Windows Phone 7 information.


Enjoy!
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


Just a quick update to let everyone know that I have updated the Whiteboard demo application.  I added a few options to make it more interesting to use!  I showed it to the kids and they loved it (even though they kept asking ME to draw pictures for them)!
 

Here is a list of available options:

  • Color Picker for line color
  • Slider for line thickness
  • Save to valid XAML file
  • Open saved drawing
  • Clear whiteboard
  • Hold down ESC key to erase


And here is a screenshot of my beautiful artwork! :)


So what are you waiting for?  Go play with the Live Demo (and be sure to share with the kids) or download the source code.


Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


I have come across several instances of people having trouble using the new Reactive Extensions (v1.0.2.5) in projects that reference the Silverlight Toolkit (Nov09) due to the fact that the original release of the Rx Framework (v1.0.0.0) was bundled with the Toolkit.  The trouble really becomes evident if you are using the Managed Extensibility Framework (MEF) to discover and compose portions of your application.
 

Update:  After getting some feedback in the comments, I was able to find a solution for the type load exception that works along with the DragDrop class of the Silverlight Toolkit!  The key is to load both versions of the System.Reactive.dll into the application.  You can't do it via the standard Add Reference, but the following code illustrates how it can be done:

    public MainPage()

    {

        // force previous version to be loaded

        var part = new AssemblyPart() { Source = "References/System.Reactive.v1.0.0.0.dll" };

        Deployment.Current.Parts.Add(part);

        LoadReferences();

 

        InitializeComponent();

 

 

        // this works now!

        CompositionInitializer.SatisfyImports(this);

        References.ItemsSource = references;

    }

The updated source code includes the previous dll in the Silverlight project as Content.  End Update.


If you are using the CompositionInitializer, or any other mechanism that probes all of the loaded assemblies for valid exports, you will likely receive the following error:


Inspecting the LoaderExceptions property yields the following:
 

System.IO.FileNotFoundException: Could not load file or assembly 'System.Reactive, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1b331ac6720247d9' or one of its dependencies. The system cannot find the file specified.  File name: 'System.Reactive, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1b331ac6720247d9'


This is due to some of the Toolkit assemblies referencing the older System.Reactive.dll.  I was able to work around the issue by bypassing the automatic probing of loaded assemblies and instead specified which assemblies my exports could be found.

    public MainPage()

    {

        InitializeComponent();

 

        // the following line causes a ReflectionTypeLoadException

        //CompositionInitializer.SatisfyImports(this);

 

        // skip the toolkit assemblies by specifying assemblies

        var catalog = new AssemblyCatalog(GetType().Assembly);

        var container = new CompositionContainer(catalog);

        container.ComposeParts(this);

 

        ShowReferences();

    }


With some simple xaml, I was able to print out exactly which libraries are currently loaded in the application.


You can download the sample project to run it for yourself!


Hope that helps!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


Update (14-Mar-2010): Updated the Whiteboard Demo
 

I must have tried reading through the various explanations and introductions to the new Reactive Extensions for .NET before the concepts finally started sinking in.  The article that gave me the ah-ha moment was over on SilverlightShow.net and titled Using Reactive Extensions in Silverlight.  The author did a good job comparing the "normal" way of handling events vs. the new "reactive" methods.


Admittedly, I still have more to learn about the Rx Framework, but I wanted to put together a sample project so I could start playing with the new Observable and IObservable<T> constructs.  I decided to throw together a whiteboard application in Silverlight based on the Drawing with Rx example on the aforementioned article.  At the very least, I figured I would learn a thing or two about a new technology, but my real goal is to create a fun application that I can share with the kids since they love drawing and coloring so much!


Here is the code sample that I borrowed from the article:

var mouseMoveEvent = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");

var mouseLeftButtonDown = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");

var mouseLeftButtonUp = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");

 

    var draggingEvents = from pos in mouseMoveEvent

                             .SkipUntil(mouseLeftButtonDown)

                             .TakeUntil(mouseLeftButtonUp)

                             .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>

                                 new

                                 {

                                     X2 = cur.EventArgs.GetPosition(this).X,

                                     X1 = prev.EventArgs.GetPosition(this).X,

                                     Y2 = cur.EventArgs.GetPosition(this).Y,

                                     Y1 = prev.EventArgs.GetPosition(this).Y

                                 })).Repeat()

                         select pos;

 

    draggingEvents.Subscribe(p =>

    {

        Line line = new Line();

        line.Stroke = new SolidColorBrush(Colors.Black);

        line.StrokeEndLineCap = PenLineCap.Round;

        line.StrokeLineJoin = PenLineJoin.Round;

        line.StrokeThickness = 5;

        line.X1 = p.X1;

        line.Y1 = p.Y1;

        line.X2 = p.X2;

        line.Y2 = p.Y2;

        this.LayoutRoot.Children.Add(line);

    });


One thing that was nagging at the back of my mind was having to deal with the event names as strings, as well as the verbose syntax for the Observable.FromEvent<TEventArgs>() method.  I came up with a couple of static/helper classes to resolve both issues and also created a T4 template to auto-generate these helpers for any .NET type.  Take the following code from the above example:

var mouseMoveEvent = Observable.FromEvent<MouseEventArgs>(this, "MouseMove");

var mouseLeftButtonDown = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonDown");

var mouseLeftButtonUp = Observable.FromEvent<MouseButtonEventArgs>(this, "MouseLeftButtonUp");


Turns into this with the new static Events class:

var mouseMoveEvent = Events.Mouse.Move.On(this);

var mouseLeftButtonDown = Events.Mouse.LeftButtonDown.On(this);

var mouseLeftButtonUp = Events.Mouse.LeftButtonUp.On(this);


Or better yet, just remove the variable declarations altogether:

    var draggingEvents = from pos in Events.Mouse.Move.On(this)

                             .SkipUntil(Events.Mouse.LeftButtonDown.On(this))

                             .TakeUntil(Events.Mouse.LeftButtonUp.On(this))

                             .Let(mm => mm.Zip(mm.Skip(1), (prev, cur) =>

                                 new

                                 {

                                     X2 = cur.EventArgs.GetPosition(this).X,

                                     X1 = prev.EventArgs.GetPosition(this).X,

                                     Y2 = cur.EventArgs.GetPosition(this).Y,

                                     Y1 = prev.EventArgs.GetPosition(this).Y

                                 })).Repeat()

                         select pos;


The Move, LeftButtonDown and LeftButtonUp members of the Events.Mouse class are readonly instances of the ObservableEvent<TTarget, TEventArgs> class that provide type-safe access to the events via the On() method.  Here is the code for the class:

using System;

using System.Collections.Generic;

using System.Linq;

 

namespace System.Linq

{

    /// <summary>

    /// Represents an event that can be managed via the <see cref="Observable"/> API.

    /// </summary>

    /// <typeparam name="TTarget">The type of the target.</typeparam>

    /// <typeparam name="TEventArgs">The type of the event args.</typeparam>

    public class ObservableEvent<TTarget, TEventArgs> where TEventArgs : EventArgs

    {

        /// <summary>

        /// Initializes a new instance of the <see cref="ObservableEvent"/> class.

        /// </summary>

        /// <param name="eventName">Name of the event.</param>

        protected ObservableEvent(String eventName)

        {

            EventName = eventName;

        }

 

        /// <summary>

        /// Registers the specified event name.

        /// </summary>

        /// <param name="eventName">Name of the event.</param>

        /// <returns></returns>

        public static ObservableEvent<TTarget, TEventArgs> Register(String eventName)

        {

            return new ObservableEvent<TTarget, TEventArgs>(eventName);

        }

 

        /// <summary>

        /// Creates an enumerable sequence of event values for the specified target.

        /// </summary>

        /// <param name="target">The target.</param>

        /// <returns></returns>

        public IObservable<IEvent<TEventArgs>> On(TTarget target)

        {

            return Observable.FromEvent<TEventArgs>(target, EventName);

        }

 

        /// <summary>

        /// Gets or sets the name of the event.

        /// </summary>

        /// <value>The name of the event.</value>

        public string EventName { get; private set; }

    }

}


And this is how it's used:

    /// <summary>

    /// Categorizes <see cref="ObservableEvents"/> by class and/or functionality.

    /// </summary>

    public static partial class Events

    {

        /// <summary>

        /// Implements a set of predefined <see cref="ObservableEvent"/>s

        /// for the <see cref="System.Windows.System.Windows.UIElement"/> class

        /// that represent mouse related events.

        /// </summary>

        public static partial class Mouse

        {

            /// <summary>Represents the MouseMove event.</summary>

            public static readonly ObservableEvent<UIElement, MouseEventArgs> Move =

                ObservableEvent<UIElement, MouseEventArgs>.Register("MouseMove");

 

            // additional members omitted...

        }

    }


The source code contains a static Events class with prefedined members for various categories (Key, Mouse, etc.).  There is also an Events.tt template that you can customize to generate additional event categories for any .NET type.  All you should have to do is add the name of your class to the types collection near the top of the template:

    types = new Dictionary<String, Type>()

    {

        //{ "Microsoft.Maps.MapControl.Map, Microsoft.Maps.MapControl", null }

        { "System.Windows.FrameworkElement, System.Windows", null },

        { "Whiteboard.MainPage, Whiteboard", null }

    };


The template is also a bit rough at this point, but at least it generates code that *should* compile.  Please let me know if you run into any issues with it.  Some people have reported errors when trying to use T4 templates within a Silverlight project, but I was able to get it to work with a little black magic... 


You can download the source code for this project or play around with the live demo.  Just be warned that it is at a very early stage so don't expect to find much today.  I plan on adding alot more options like pen colors and sizes, saving, printing, etc. as time permits.  HINT: hold down the ESC key to erase!


Enjoy!


Additional Resources

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


Quick Links


Since volcanos are often associated with earthquakes, and vice versa, I decided to show recent volcanic activity on the Earthquake Locator map.  I am pulling the data from a website created for a joint project between the Smithsonian's Global Volcanism Program and the US Geological Survey's Volcano Hazards Program, found here.  They provide a Weekly Volcanic Activity Report as an RSS feed.
 

I started implementing this new functionality by creating a new Volcano entity in the domain model and adding the following to the EarthquakeService class (I also factored out the common reading/parsing helper methods to a separate FeedReader class that can be used by multiple domain service classes):
 

        private static readonly string VolcanoFeedUrl =

            ConfigurationManager.AppSettings["VolcanoFeedUrl"];

 

        /// <summary>

        /// Gets the volcano data for the previous week.

        /// </summary>

        /// <returns>A queryable collection of <see cref="Volcano"/> objects.</returns>

        public IQueryable<Volcano> GetVolcanos()

        {

            var feed = FeedReader.Load(VolcanoFeedUrl);

            var list = new List<Volcano>();

 

            if ( feed != null )

            {

                foreach ( var item in feed.Items )

                {

                    var quake = CreateVolcano(item);

                    if ( quake != null )

                    {

                        list.Add(quake);

                    }

                }

            }

 

            return list.AsQueryable();

        }

 

        /// <summary>

        /// Creates a <see cref="Volcano"/> object for each item in the RSS feed.

        /// </summary>

        /// <param name="item">The RSS item.</param>

        /// <returns></returns>

        private Volcano CreateVolcano(SyndicationItem item)

        {

            Volcano volcano = null;

            string title = item.Title.Text;

            string desc = item.Summary.Text;

            double? latitude = null;

            double? longitude = null;

 

            FeedReader.GetGeoRssPoint(item, out latitude, out longitude);

 

            if ( !String.IsNullOrEmpty(title) )

            {

                title = title.Substring(0, title.IndexOf('-'));

            }

            if ( !String.IsNullOrEmpty(desc) )

            {

                desc = String.Join("\n\n", desc

                        .Replace("<p>", "")

                        .Split(

                            new string[] { "</p>" },

                            StringSplitOptions.RemoveEmptyEntries)

                        .Select(s => s.Trim())

                        .ToArray())

                        .Trim();

            }

 

            if ( latitude != null && longitude != null )

            {

                volcano = new Volcano()

                {

                    Id = item.Id,

                    Title = title,

                    Description = desc,

                    Url = item.Links.Select(l => l.Uri.OriginalString).FirstOrDefault(),

                    Latitude = latitude.GetValueOrDefault(),

                    Longitude = longitude.GetValueOrDefault()

                };

            }

 

            return volcano;

        }


I then added the corresponding LoadVolcanos() method and Volcanos collection to the EarthquakeViewModel class in much the same way I did with the Earthquakes in my previous article in this series.

Now that I am starting to add more information to the map, I wanted to give the user some options as to what is displayed and allowing them to choose what gets turned off.  I have updated the MainPage.xaml to look like this:
 

<UserControl x:Class="EarthquakeLocator.MainPage"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    xmlns:basic="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"

    xmlns:bing="clr-namespace:Microsoft.Maps.MapControl;assembly=Microsoft.Maps.MapControl"

    xmlns:vm="clr-namespace:EarthquakeLocator.ViewModel"

    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480"

>

    <UserControl.Resources>

        <DataTemplate x:Key="EarthquakeTemplate">

            <Ellipse Fill="Red" Stroke="Black" StrokeThickness="1"

                     Width="{Binding Size}" Height="{Binding Size}"

                     bing:MapLayer.Position="{Binding Location}"

                     bing:MapLayer.PositionOrigin="Center">

                <ToolTipService.ToolTip>

                    <StackPanel>

                        <TextBlock Text="{Binding Title}" FontSize="14" FontWeight="Bold" />

                        <TextBlock Text="{Binding UtcTime}" />

                        <TextBlock Text="{Binding LocalTime}" />

                        <TextBlock Text="{Binding DepthDesc}" />

                    </StackPanel>

                </ToolTipService.ToolTip>

            </Ellipse>

        </DataTemplate>

 

        <DataTemplate x:Key="VolcanoTemplate">

            <Polygon Fill="Gold" Stroke="Black" StrokeThickness="1" Points="0,10 5,0 10,10"

                     bing:MapLayer.Position="{Binding Location}"

                     bing:MapLayer.PositionOrigin="Center"

                     MouseLeftButtonUp="Volcano_MouseLeftButtonUp">

                <ToolTipService.ToolTip>

                    <StackPanel>

                        <TextBlock Text="{Binding Title}" FontSize="14" FontWeight="Bold" />

                        <TextBlock Text="Click icon for more information..." />

                    </StackPanel>

                </ToolTipService.ToolTip>

            </Polygon>

        </DataTemplate>

    </UserControl.Resources>

 

    <UserControl.DataContext>

        <vm:EarthquakeViewModel AutoLoadData="True" />

    </UserControl.DataContext>

 

    <Grid x:Name="LayoutRoot">

 

        <bing:Map x:Name="map" CredentialsProvider="--Your-Bing-Maps-Key--"

                  Center="{Binding MapCenter, Mode=TwoWay}"

                  ZoomLevel="{Binding ZoomLevel, Mode=TwoWay}">

 

            <bing:MapItemsControl ItemsSource="{Binding Earthquakes}"

                                  ItemTemplate="{StaticResource EarthquakeTemplate}" />

 

            <bing:MapItemsControl ItemsSource="{Binding Volcanos}"

                                  ItemTemplate="{StaticResource VolcanoTemplate}" />

        </bing:Map>

 

        <basic:TabControl x:Name="tabs" VerticalAlignment="Bottom" MaxHeight="25" Opacity="0.7">

            <basic:TabItem Margin="90,0,-90,0" MouseLeftButtonUp="TabItem_MouseLeftButtonUp">

                <basic:TabItem.Header>

                    <TextBlock x:Name="txtHeader" Text="Options"

                               FontSize="13" FontWeight="Bold" />

                </basic:TabItem.Header>

 

                <StackPanel Orientation="Horizontal">

                    <TextBlock Text="Earthquakes:" FontWeight="Bold" Margin="3" />

                    <StackPanel Margin="3">

                        <CheckBox Content=" &lt; 4.0"

                                  IsChecked="{Binding ShowLt4, Mode=TwoWay}" />

                        <CheckBox Content="4.0 - 4.9"

                                  IsChecked="{Binding Show4s, Mode=TwoWay}" />

                        <CheckBox Content="5.0 - 5.9"

                                  IsChecked="{Binding Show5s, Mode=TwoWay}" />

                    </StackPanel>

 

                    <StackPanel Margin="10,3,3,3">

                        <CheckBox Content="6.0 - 6.9"

                                  IsChecked="{Binding Show6s, Mode=TwoWay}" />

                        <CheckBox Content="7.0 - 7.9"

                                  IsChecked="{Binding Show7s, Mode=TwoWay}" />

                        <CheckBox Content="8.0 +"

                                  IsChecked="{Binding ShowGe8, Mode=TwoWay}" />

                    </StackPanel>

 

                    <TextBlock Text="Other:" FontWeight="Bold" Margin="50,3,3,3" />

                    <StackPanel Margin="3">

                        <CheckBox Content="Volcanos"

                                  IsChecked="{Binding ShowVolcanos, Mode=TwoWay}" />

                    </StackPanel>

                </StackPanel>

 

            </basic:TabItem>

        </basic:TabControl>

 

    </Grid>

</UserControl>


Notice that I added a VolcanoTemplate that uses a triangle-shaped Polygon to represent the Volcano locations, and I also added a second <bing:MapItemsControl /> tag to the map to bind to the Volcanos collection.  The TabControl found below the map houses the options panel that will present the user with several checkboxes so they can filter the different points based on type and other properties (i.e. Magnitude).  Initially, the TabItem is collapsed to reduce it's footprint, but the screen shot below shows the options panel expanded to reveal the available settings:
 

 

I have updated the Source Code and Live Demo to include these new features.
 

Happy Mapping!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati


Quick Links


I finally got a live demo up and running!  I signed up for a shared hosting account over at discountasp.net so I could post a working version of the Earthquake Locator application, but ran into a few minor issues related to RIA Services.  Thankfully, Tim Heuer had already encountered and explained all of the problems I had along with solutions to these and other common pitfalls.  You can find his blog post here.  The ones that got me were the default authentication tag being set to Windows instead of Forms, needed to add the <baseAddressPrefixFilters> tag since I was running on a shared server using host headers, and finally the Multiple Authentication Schemes settings in the IIS7 Manager.
 

To get the demo application ready, I pulled down local copies of the earthquake data feeds that the application can use instead of pulling from the USGS web site.  I basically added the feed URL as an app setting in the web.config:
 

    <appSettings>

        <!-- USGS Data Feeds: http://earthquake.usgs.gov/earthquakes/catalogs/ -->

        <!--<add key="FeedUrl"

            value="http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml" />-->

        <!--<add key="FeedUrl"

            value="http://earthquake.usgs.gov/earthquakes/catalogs/7day-M2.5.xml" />-->

        <!--<add key="FeedUrl"

            value="~/Demo/1day-M2.5.xml" />-->

        <add key="FeedUrl"

             value="~/Demo/7day-M2.5.xml" />

    </appSettings>


You will need to do the same if you want to run from local copies of the feed data.  I also made the following minor changes to the EarthquakeService class so that it gets the FeedUrl from the web.config:
 

    private static readonly string FeedUrl = ConfigurationManager.AppSettings["FeedUrl"];

 

    /// <summary>

    /// Gets the feed at the specified URL.

    /// </summary>

    /// <param name="url">The URL.</param>

    /// <returns>A <see cref="SyndicationFeed"/> object.</returns>

    public static SyndicationFeed GetFeed(String url)

    {

        SyndicationFeed feed = null;

 

        if ( !String.IsNullOrEmpty(url) && url.StartsWith("~") )

        {

            // resolve virtual path to physical file system

            url = System.Web.HttpContext.Current.Server.MapPath(url);

        }

 

        try

        {

            log.Debug("Loading RSS feed: " + url);

 

            using ( var reader = XmlReader.Create(url) )

            {

                feed = SyndicationFeed.Load(reader);

            }

        }

        catch ( Exception ex )

        {

            log.Error("Error occurred while loading RSS feed: " + url, ex);

        }

 

        return feed;

    }


You can now view the live demo or download the source code here, but be sure you have WCF RIA Services installed before running the application locally and make sure the FeedUrl is pointing to a valid location.  Please let me know if you have any comments or if you run into any issues with the code.
 

Enjoy!

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati