Tag Archives: Silverlight

Integrating a Legacy DAL with WCF RIA services

You’ve gotta admit that RIA services is pretty awesome, it’s the perfect companion to Silverlight. However it’s relatively cumbersome and messy to get all the bells and whistles working with a legacy DAL (paging, sorting, etc) and in many cases it’s just not feasible to throw out your investment in your existing infrastructure. So what should you do? Well, I have started a project on Codeplex to address this issue (http://riatodal.codeplex.com/). Rather than go through the code now, I’ll post on what the framework accomplishes and give some code samples that use it. In subsequent posts I’ll delve into bits and pieces of it’s underpinnings.

First, imagine this scenario. You have a DAL and DAL providers. Specifically, let’s take a look at a user manager/provider like the ones shown below:

    public class UserManager
    {
        List<User> _users = new List<User>();

        public List<User> GetUsersByNameAndSearch(string name, int userStatusId, string whereClause, string orderBy, int pageIndex, int pageSize)
        {
            // Paginate our fake data source
            return _users.Skip(pageIndex * pageSize).Take(pageSize).ToList();
        }
    }

    public class DalProvider
    {
        private DalProvider()
        {
        }

        private static DalProvider _instance = null;
        public static DalProvider ProviderInstance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new DalProvider();
                }

                return _instance;
            }
        }

        private UserManager _umInstance = null;
        public UserManager UserManagerInstance
        {
            get
            {
                if (_umInstance == null)
                {
                    _umInstance = new UserManager();
                }

                return _umInstance;
            }
        }
    }

Pretty straight forward. There’s a provider following the singleton pattern, so to get a list of users, we would write something like:

List<User> users 
= LegacyDal.DalProvider.ProviderInstance.UserManagerInstance.GetUsersByNameAndSearch(...);

and from there we can bind that list of users to a grid. But, that’s not very good for RIA services. Paging would become a nightmare (how do we get the skip/take parameters to our legacy DAL for paging?). And custom where clauses? eww! How do we link these two together and have them play nicely? The answer is to provide a framework similar to the one described below, which is composed of two primary things:

1) A LINQ provider which we can use to query our DAL
2) “Hints” on our RIA entities and domain service methods that will tell the LINQ provider how to resolve the appropriate DAL methods.

The best part is the solution will result in you do not having to modify anything in your DAL, it will just link up and work.

So how does it work? First, decorate your RIA entities that are exposed through your domain service with some custom attributes:

    [EntityMap(typeof(LegacyDal.Entities.User))]
    public class RIAUser
    {
        [Key]
        [FieldMap("UserName")]
        public string RiaUserName { get; set; }

        [FieldMap("FirstName")]
        public string RiaFirstName { get; set; }

        [FieldMap("UserStatus")]
        public string RiaUserStatus { get; set; }
    }

Pretty straightforward. Above we are mapping our RIA user to a user entity in our legacy DAL by mapping the entity type and it’s corresponding fields.

Then, we add some configuration:

  <riaToDalLinqSettings
     resolverType="Configuration"
     dalProviderInstance="LegacyDal.DalProvider.ProviderInstance, LegacyDal"
     entityManagerNameFormatString="{0}ManagerInstance"
    />

The above says we will resolve our DAL information using the current configuration by setting resolverType=”Configuration” (we can write our own and export it using MEF if we want, in which case we would set the resolverType=”Custom”). The dalProviderInstance string let’s the framework know the provider instance to use in the DAL. And for the entityManagerNameFormatString it’s just a format string which would evaluate to[EntityName]ManagerInstance, or “UserManagerInstance” if we were looking for a UserManager.

Then, our domain service looks like:

    [EnableClientAccess]
    public class GetDataService : LegacyDalDomainServiceBase
    {
        // Create the query context
        RiaToDalQueryContext<RIAUser> RIAUserQueryContext;

        public GetDataService()
        {
            // Instantiate the context with this domain service
            RIAUserQueryContext = new RiaToDalQueryContext<RIAUser>(this);
        }

        /// <summary>
        /// MethodMap will map our queryable GetUser() ria method 
        /// to the GetUsersByNameAndSearch() method  in our DAL.
        /// It will look for an overload which takes name and userStatusId parameter.
        /// The full method signature in the legacy DAL looks like:
        ///     GetUsersByNameAndSearch(string name, int userStatusId, 
        ///           string whereClause, string orderBy, int pageIndex, int pageSize)
        /// but by convention any parameter name of:
        ///       whereClause, orderBy, pageIndex, pageSize 
        /// will be excluded from the match and
        /// will be passed data based on the LINQ query that is composed.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        [MethodMap("GetUsersByNameAndSearch")]
        public IQueryable<RIAUser> GetUser(
            string name,
            int statusId
            )
        {
            // Use some LINQ against our RIA entities. 
            // The Entities property should already filtered by the "name" 
            // and statusId parameters of the current query method,
            // but we can do some additional filtering, and it will be propagated 
            // through to the whereClause in the legacy DAL method.
            var query = from u in RIAUserQueryContext.Entities
                        where (u.RiaFirstName != "Jim" && u.RiaUserStatus == "Active") || u.RiaFirstName == "Ronnie"
                        orderby u.RiaFirstName descending
                        select u;

            // Return the query, let RIA services compose 
            // any additional LINQ sent from the client
            // (such as any paging information)
            return query;
        }
    }

Woah! What’s this doing? I’ll let you figure that out from the comments and the code, but that’s all that needs to be done to integrate RIA with our legacy DAL, the framework will handle the rest. Here’s a screenshot of what the Legacy DAL method receives after this LINQ is executed:

and here’s what the client that executed the query method looked like:

Notice how the where clause from our LINQ expression got passed to the where clause parameter in our DAL method? (it even resolved the correct names by translating RIAFirstName to FirstName). The paging parameters came from the DataPager in the Silverlight client, as did the order by clause. The userName and userStatusId were received from the parameters in the IQueryable GetUser() domain service. All of this was done without any additional code, we just needed to give the framework some “hints” on how to resolve our DAL.

So what’s this running on? Just MEF, Silverlight 4 and WCF RIA Services. This is a very preliminary design, but I’m planning on adding:

  • Update support. Call this.Update(entity) in your domain service and based on the mappings it will persist the entity back to the DAL
  • Transaction support via TransactionScope
  • Deep loading with the [Associate] attribute
  • [Ignore] attributes for the query parameters so parameters can be selectively excluded from matching when trying to resolve the appropriate DAL method
  • Re-factoring the code and making it more plugable
  • Anything I can think of or that’s suggested to me!

Be sure to take a look on Codeplex for the source (and a working example), and if you have any suggestions please let me know in the comments section!

The .NET Facebook API and Silverlight

Awesome! There is now a library for .NET developers to interface with the Facebook services, opening up a whole new world of integration between your app and all of your user’s information in Facebook. In this post I have included code samples for a simple Facebook friend viewer. With the viewer, you can pull all tagged photos of yourself, your friends, and all their information. While the app functions, there is so much more that can be done. Comments can be posted to Facebook remotely, pictures uploaded and tagged remotely, videos uploaded, etc. Virtually all of the Facebook functionality is exposed through the API. Checkout the bottom of the post for a link to the project, as well as a working demo. Let’s dive in!

First, downoad the required assemblies. Note that there are assemblies for WinForms, WPF, ASP.NET MVC (not just Silverlight) included!

Then, you will need to setup your account at http://www.facebook.com/developers/. Click on “Setup New Application” and enter a new application name as shown in the screenshot below:

CreateApp

Then fill out the fields shown below on the “Basic Info” tab. Note the developers area. Add any devs who will be working on the app there:

FillOutInfo

From there fill out the connect information. In development this will be the local http://localhost:[port] that that you are developing on. In production it will be yourdomain.com:

connect

Then click on the advanced tab and enable Sandbox mode, as we will be using this account for development purposes:

advancedSandbox

Cool! Now that we have our account setup we need to create a “Silverlight Navigation Application” in Visual Studio. To make everything work, we need to add a javascript file to the same directory as the HTML page that is hosting our Silverlight app. This will allow us to facilitate the cross domain communication between our app and Facebook (note the var silverLightPluginId must match the DOM id of the Silverlight plugin on the page!!!):

fblogin.js

//must match the Silverlight plugin's DOM ID
var silverlightPluginId = 'Silverlight1';

function facebook_init(appid) {
    FB.init(appid, "/xd_receiver.htm");
}

function isUserConnected() {
    FB.ensureInit(function () {
        FB.Connect.get_status().waitUntilReady(function (status) {
            var plugin = document.getElementById(silverlightPluginId);
        });
    });
}

function facebook_login() {
    FB.ensureInit(function () {
        FB.Connect.requireSession(facebook_getSession, true);
    });
}

function facebook_logout() {
    FB.Connect.logout(facebook_onlogout);
}

function facebook_getSession() {

    FB.Facebook.get_sessionState().waitUntilReady(function () {
        var session = FB.Facebook.apiClient.get_session();
        var plugin = document.getElementById(silverlightPluginId);
        plugin.Content.FacebookLoginControl.LoggedIn(session.session_key, session.secret, session.expires, session.uid);
    });
}

function facebook_onlogout() {
    var plugin = document.getElementById(silverlightPluginId);
    plugin.Content.FacebookLoginControl.LoggedOut();
}

function facebook_onpermission(accepted) {
    var plugin = document.getElementById(silverlightPluginId);
    plugin.Content.FacebookLoginControl.PermissionCallback(accepted);
}

function facebook_prompt_permission(permission) {
    FB.ensureInit(function () {
        FB.Connect.showPermissionDialog(permission, facebook_onpermission);
    });
}

Include the script in the page hosting the Silverlight app:

<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>
<script type="text/javascript" src="fblogin.js"></script>

Now, add the file xd_receiver.htm to the same directory as the javascript file. This will receive the response from Facebook and route it appropriately:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <body>
    <script src=http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.js
                    type="text/javascript">
    </script>
  </body>
</html>

Whew! Now that all that’s done we can tackle the Silverlight part. Add a reference the assembly Facebook.Silverlight.dll to your Silverlight project. Note that if you are using Visual Studio 2010 you might get a build error similar to the following after you add the reference:

—> System.NotSupportedException: An attempt was made to load an assembly from a network location which would have caused the assembly to be sandboxed in previous versions of the .NET Framework. This release of the .NET Framework does not enable CAS policy by default, so this load may be dangerous. If this load is not intended to sandbox the assembly, please enable the loadFromRemoteSources switch. See http://go.microsoft.com/fwlink/?LinkId=155569 for more information.

This is because the CAS policies have changed in VS2010. No worries though, just locate the assembly in the file system and Unblock it by clicking on the button in the screenshot below:

UnBlock

In your Silverlight application you then just need to initialize the Facebook API object and invoke the Login() method:

        private Api _facebookAPI;
        private readonly BrowserSession _browserSession;
        private const string ApplicationKey = "[Put your application key here, yes I know it's not the best place to initialize it!]";

        public Home()
        {
            InitializeComponent();

            _browserSession = new BrowserSession(ApplicationKey);
            _browserSession.LoginCompleted += browserSession_LoginCompleted;
            _browserSession.GetStatusCompleted += new EventHandler<GetStatusCompletedEventArgs>(_browserSession_GetStatusCompleted);
            _browserSession.Login();
        }

This will generate the browser popup to have the user login to their Facebook account. Once the user is logged in the _browserSession_GetStatusCompleted method gets invoked:

        private void browserSession_LoginCompleted(object sender, EventArgs e)
        {
            _facebookAPI = new Api(_browserSession);

            //set the photo and friends list item sources
            Photos.ItemsSource = myImages;
            FriendsList.ItemsSource = myFriends;

            //get my friends
            _facebookAPI.Friends.GetUserObjectsAsync(
                (friends, state, ex) =>
                {
                    if (ex == null)
                    {
                        Dispatcher.BeginInvoke(
                            () =>
                            {
                                AddFriends(friends);
                            });
                    }
                }, null);

            Facebook.Session.BrowserSession session = sender as Facebook.Session.BrowserSession;

            _facebookAPI.Photos.GetAsync(session.UserId.ToString(), null, null,
                new Photos.GetCallback(
                    (photos, obj, ex) =>
                    {
                        Dispatcher.BeginInvoke(
                            () =>
                            {
                                AddPhotos(photos);
                            });
                    }), null);
        }

In here we’re doing a couple things. First we’re invoking the FB-API and getting the user’s friends and a listing of all photos they have been tagged in. Once we have those we add them to the myImages and myFriends observable collections, which are bound to the UI. The AddFriends() and AddPhotos() implementations are listed below:

        //add friends to the observable collection
        private void AddFriends(IList<Facebook.Schema.user> friends)
        {
            foreach (Facebook.Schema.user friend in friends)
            {
                myFriends.Add(new Friend()
                {
                    Name = friend.first_name,
                    Photo = new BitmapImage(new Uri(friend.pic_small)),
                    UserId = friend.uid.GetValueOrDefault(),
                    Activities = friend.activities,
                    Affiliations = friend.affiliations != null && friend.affiliations.affiliation != null && friend.affiliations.affiliation.Count > 0 ? friend.affiliations.affiliation.Select(a => a.name).Aggregate((i, j) => string.Concat(i, ", ", j)) : string.Empty,
                    Books = friend.books,
                    EducationHistory = friend.education_history != null && friend.education_history.education_info != null ? GetEducation(friend.education_history) : string.Empty,
                    Interests = friend.interests,
                    Movies = friend.movies,
                    Music = friend.music,
                    Quotes = friend.quotes,
                    Sex = friend.sex,
                    TV = friend.tv,
                    WorkHistory = friend.work_history != null && friend.work_history.work_info != null && friend.work_history.work_info.Count > 0 ? friend.work_history.work_info.Select(e => e.company_name).Aggregate((i, j) => string.Concat(i, ", ", j)) : string.Empty
                });
            }
        }

        private void AddPhotos(IList<Facebook.Schema.photo> photos)
        {
            foreach (Facebook.Schema.photo photo in photos)
            {
                myImages.Add(
                        new Photo() { 
                            Small = new BitmapImage(new Uri(photo.src_small)),
                            Normal = new BitmapImage(new Uri(photo.src)),
                            Large = new BitmapImage(new Uri(photo.src_big))
                        }
                    );
            }
        }


        /// <summary>
        /// gets a string representing all education history
        /// </summary>
        /// <param name="educationHistory"></param>
        /// <returns></returns>
        private string GetEducation(Facebook.Schema.userEducation_history educationHistory)
        {
            return educationHistory.education_info.Select(
                    (e) =>
                    {
                        return string.Format(" {0} {1} {2} {3}", 
                            //degree info
                           !string.IsNullOrEmpty(e.degree) ? "Degree: " + e.degree : string.Empty, 

                            //concentration info
                            e.concentrations != null && e.concentrations.concentration != null && e.concentrations.concentration.Count > 0
                            ? 
                                "Concentrations: " + e.concentrations.concentration.Aggregate(
                                (i, j) => {
                                    return string.Concat(i, ", ", j);
                                }) : string.Empty, 
                            
                            //college name
                            !string.IsNullOrEmpty(e.name) ? "College Name: " + e.name : string.Empty, 
                            
                            //college year
                            e.year > 0 ? "Year Graduated: " + e.year : string.Empty);
                    }).Aggregate((i, j) =>
                    {
                        return string.Concat(i, j);
                    });
        }

That’s the core code! Now our UI just needs to bind to the collections and we have all the data we need. Check it out!


Working Demo (If your firewall blocks requests to/from Facebook this will just hang and will not load!!)

Download the project to get the fully working solution (in VS2010 and Silverlight 4)

Silverlight 4 Webcam and Microphone Support

The beta of Silverlight 4 has lots of neat stuff packaged with it. One of these things included is native Webcam and Microphone support. In this post I will show how to create a simple webcam viewer complete with A/V and picture capture. However, one thing not currently in the beta is streaming the video and audio to a remote destination such as a file, another client, or a media server. I have not tried to implement this in this functionality in this post because it will most likely be implemented by the time the final release comes out, so rather than “pre-invent” the wheel, I have decided to wait until Microsoft releases a version of their own. I have, however, included implementations of the AudioSink and VideoSink which can be used to pull the raw audio and video and either stream it or save it. Since the the data passed to the OnSamples () method in VideoSink is nothing more than 32bpp ARGB raw bitmap frames it is feasible to create a custom implementation that can save the video to a stream suitable for saving to a file or media server. Be sure to check out the bottom of the post for links to a screenshot of the final result and complete source code. There are also links to shader effects and an attempt at saving the streams to AVI files.

First, to house the video we create a rectangle in XAML. Note the render transform. This will flip the image about it’s Y-Axis so it appears like a mirror, which is more intuitive when viewing the video on screen.

        <Rectangle Height="254" HorizontalAlignment="Left" Margin="31,47,0,0" Name="VideoPane" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Width="511" RenderTransformOrigin="0.5,0.5">
            <Rectangle.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="-1"/>
                    <SkewTransform AngleX="0" AngleY="0"/>
                    <RotateTransform Angle="0"/>
                    <TranslateTransform/>
                </TransformGroup>
            </Rectangle.RenderTransform>
        </Rectangle>

Next we create two listboxes to show a list of devices suitable for audio and video capture. The user needs to select an audio and video device before starting capture.

        <dataInput:Label Height="18" HorizontalAlignment="Left" Margin="109,426,0,0" Content="Video Devices" Name="label1" VerticalAlignment="Top" Width="83" />
        <ListBox Height="48" Margin="31,443,0,0" Name="VideoCaptureDevices" VerticalAlignment="Top" HorizontalAlignment="Left" Width="256">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding FriendlyName}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <dataInput:Label Height="18" HorizontalAlignment="Left" Margin="367,426,0,0" Content="Audio Devices" Name="label2" VerticalAlignment="Top" Width="83" />
        <ListBox Height="48" HorizontalAlignment="Left" Margin="293,443,0,0" Name="AudioCaptureDevices" VerticalAlignment="Top" Width="249">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding FriendlyName}" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox> 

The following will display a list of images captured when the user takes a CamShot:

        <ScrollViewer Margin="34,313,33,102" Width="505" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Auto">
            <ItemsControl x:Name="PhotoShots" Height="106">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding}" Margin="5" Stretch="UniformToFill" Height="70" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
            </ItemsControl>
        </ScrollViewer>

In our code behind we create a capture source and an ObservableCollection to hold our images captured from the webcam. We then get the list of devices and set the item source properties when the page is loaded.

        CaptureSource captureSource = new CaptureSource();
        ObservableCollection<WriteableBitmap> photoShots = new ObservableCollection<WriteableBitmap>();

        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            VideoCaptureDevices.ItemsSource = CaptureDeviceConfiguration.GetAvailableVideoCaptureDevices();
            AudioCaptureDevices.ItemsSource = CaptureDeviceConfiguration.GetAvailableAudioCaptureDevices();
            PhotoShots.ItemsSource = photoShots;
        }

When we are told to start the capture, make sure the user selected the audio and video capture devices, request access to the device, and if the user allowed us access then set the sink capture sources, create the brush, start capture, and give the rectangle a drop shadow effect.

        private void StartWebCam_Click(object sender, RoutedEventArgs e)
        {
            if (VideoCaptureDevices.SelectedItem != null && AudioCaptureDevices.SelectedItem != null)
            {
                //stop the source if it's started
                if (captureSource.State == CaptureState.Started)
                {
                    captureSource.Stop();
                }

                //VideoCaptureDevice device = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
                VideoCaptureDevice videoDevice = VideoCaptureDevices.SelectedItem as VideoCaptureDevice;

                //AudioCaptureDevice device = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();
                AudioCaptureDevice audioDevice = AudioCaptureDevices.SelectedItem as AudioCaptureDevice;

                //check to see if we have already been allowed access or request if we have not
                if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
                {
                    captureSource.VideoCaptureDevice = videoDevice;
                    captureSource.AudioCaptureDevice = audioDevice;

                    //set the video sink's capture source
                    videoSink.CaptureSource = captureSource;
                    audioSink.CaptureSource = captureSource;

                    //create a brush and set the source to the capture source
                    VideoBrush brush = new VideoBrush();
                    brush.SetSource(captureSource);
                    brush.Stretch = Stretch.UniformToFill;

                    //start the capture
                    captureSource.Start();

                    //paint the rectangle with the video
                    VideoPane.Fill = brush;

                    //set the effect
                    VideoPane.Effect = new DropShadowEffect() { ShadowDepth = 7 };
                }
            }
            else
            {
                MessageBox.Show("Please select an audio and a video capture device to start the session");
            }
        }

The following will take a screenshot and add it to the observable collection of WriteableBitmaps:

        private void TakeCamShot_Click(object sender, RoutedEventArgs e)
        {
            if (captureSource != null)
            {
                captureSource.AsyncCaptureImage(
                    (img) =>
                    {
                        photoShots.Add(img);
                    });
            }
        }

Below are the AudioSink and VideoSink implementations. They can be extended to save the streams to whatever destination is needed:

    /// <summary>
    /// 
    /// </summary>
    public class MSVideoSink : VideoSink
    {
        /// <summary>
        /// Video
        /// </summary>
        public MemoryStream VideoStream
        {
            get;
            private set;
        }


        /// <summary>
        /// Format
        /// </summary>
        public VideoFormat Format
        {
            get;
            set;
        }

        /// <summary>
        /// Capture started
        /// </summary>
        protected override void OnCaptureStarted()
        {
            VideoStream = new MemoryStream();
        }


        /// <summary>
        /// Capture stops
        /// </summary>
        protected override void OnCaptureStopped()
        {
            //post-process video data
        }


        /// <summary>
        /// Format changing
        /// </summary>
        /// <param name="videoFormat"></param>
        protected override void OnFormatChange(VideoFormat videoFormat)
        {
            //do stuff on format change, i.e. close off stream, etc
            Format = videoFormat;
        }

        /// <summary>
        /// Write the sample data to the stream
        /// </summary>
        /// <param name="sampleTime"></param>
        /// <param name="frameDuration"></param>
        /// <param name="sampleData"></param>
        protected override void OnSample(long sampleTime, long frameDuration, byte[] sampleData)
        {
            //do something here...i.e. stream it to a file, etc
            //VideoStream.Write(sampleData, 0, sampleData.Length);
        }
    }


    /// <summary>
    /// 
    /// </summary>
    public class MSAudioSink : AudioSink
    {
        /// <summary>
        /// Capture starts
        /// </summary>
        protected override void OnCaptureStarted()
        {
            WaveformDataStream = new MemoryStream();
        }

        /// <summary>
        /// Post-process audio data
        /// </summary>
        protected override void OnCaptureStopped()
        {
        }

        /// <summary>
        /// Audio format
        /// </summary>
        public AudioFormat Format
        {
            get;
            set;
        }

        /// <summary>
        /// Memory stream
        /// </summary>
        public MemoryStream WaveformDataStream
        {
            get;
            set;
        }

        /// <summary>
        /// Format is changing
        /// </summary>
        /// <param name="format"></param>
        protected override void OnFormatChange(AudioFormat format)
        {
            //do stuff on format change, i.e. close off stream, etc
            Format = format;
        }

        /// <summary>
        /// Save each sample
        /// </summary>
        /// <param name="sampleTime"></param>
        /// <param name="sampleDuration"></param>
        /// <param name="sampleData"></param>
        protected override void OnSamples(long sampleTime, long sampleDuration, byte[] sample)
        {
            //do something here...i.e. stream it to a file, etc
           // WaveformDataStream.Write(sample, 0, sample.Length);
        }
    }  

Below is a screenshot of the result:
WebcamAppScreenshot

Download the source code (VS2010):
SilverlightWebcamApp.zip

Edge detection (some cool effects):
http://kodierer.blogspot.com/2009/11/edgecam-silverlight-4-webcam-edge.html
http://kodierer.blogspot.com/2009/07/livin-on-edge-silverlight-parametric_4324.html

Saving streams to AVI files:
http://mtaulty.com/CommunityServer/blogs/mike_taultys_blog/archive/2009/12/10/silverlight-4-more-on-capturing-video-from-webcams.aspx