Converting a Flash Video File for Windows Phone 7

I wanted to be able to view a downloaded video in Flash video format (.flv) on my Windows Phone 7. This proved to be somewhat of a challenge. After installing the Windows 7 Codec Pack, I could open the file in Windows Live Movie Maker, but it froze when I tried to save it to Windows Media Video format (.wmv). So I searched around and found out that VLC Player could do the conversion.

My first attempt was to select Media->Convert/Save, select my input file, press the Convert/Save button, enter an output file with .mp4 extension and select profile Video – H.264 + AAC (MP4). The video was converted all right, but there was no sound. After running with messages turned on (Tools->Messages, Verbosity Level 2) I saw the following error messages: “main error: Failed to create audio filter” and “stream_out_transcode error: Failed to find conversion filter for resampling”. It turned out that the source sample rate was 48 kHz, but the target was 44.1 kHz, and VLC was unable to resample. (Tip: Use MediaInfo to see what your file really contains.)

So I had to change the profile to use 48 kHz sample rate, but in this case, since both the Flash video container and the MP4 container use the same audio format (AAC) I could just check Keep original audio track.

image

Save output to a Windows video library folder (e.g. “My Videos”) so that it shows up in the Zune videos collection. Now you should be able to sync it to the phone.

Sorting View Model Items Using CollectionViewSource

If you create a Windows Phone 7 data bound application using the template, the item list is not sorted. I found that the simplest way to do that is to use System.Windows.Data.CollectionViewSource. The example on MSDN is perhaps not so helpful, since a static resources are used. You would typically want to bind to dynamic data. I found the solution in a blog post called Use CollectionViewSource effectively in MVVM applications, but decided to implement it a little different. Here is what I did:

In MainViewModel.cs, I kept the Items property of type ObservableCollection<T> and created a new property called SortedItems of type CollectionViewSource:

public CollectionViewSource SortedItems { get; private set; }

The getter of Items returns the CollectionViewSource Source, and the setter of Items updates the CollectionViewSource Source:

       using AccountCollection = ObservableCollection<Account>;

       public AccountCollection Items
        {
            get { return (AccountCollection)SortedItems.Source; }
            private set
            {
                var items = (AccountCollection)SortedItems.Source;
                items.Clear();
                foreach (var item in value)
                {
                    items.Add(item);
                }
             }
         }

In the MainViewModel constructor, I initialize the CollectionViewSource and tell it to sort ascending on a property called Name:

        public MainViewModel()
        {
            this.SortedItems = new CollectionViewSource();
            this.SortedItems.Source = new AccountCollection();
            this.SortedItems.SortDescriptions.Add(new System.ComponentModel.SortDescription("Name", System.ComponentModel.ListSortDirection.Ascending));
        }

Then I changed the binding in MainPage.xaml like this:

<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding SortedItems.View}">

#yam

Retrieving Entity Data Using JScript

This is a step-by-step recipe for retrieving CRM entity data using JScript and the OData (REST) endpoint in an asynchronous manner.

1. Install JScript Editor Extensions  in Visual Studio.

2. Install Visual Studio project template by double-clicking SDK\Templates\XrmPageScriptDevelopmentProjectCS.vsix.

3. Import SDK\Templates\Xrm.PageScriptProjectTemplate\XrmPageScriptDevelopmentFormSnapshot_1_0_0_0_managed.zip in CRM.

4. Create a new project and select the Xrm.Page JScript Library Project template.

5. Open an instance of the form, in my case the Quote Product form, and click Xrm.Page on the Customize tab.

6. Click Get Data and then Copy to Clipboard.

7. In your Visual Studio project, replace the contents of PageData.js with the contents of your clipboard.

8. Add a new JScript file called e.g. Product.js with following contents:

/// <reference path=”XrmPageTemplate.js” />
/// <reference path=”SDK.JScriptRESTDataOperations.js” />
function retrieveProduct() {
// This assumes there is a lookup field called productid that contains the selected product.
var lookup = Xrm.Page.getAttribute(“productid”).getValue();
var id = lookup[0].id;
SDK.JScriptRESTDataOperations.Retrieve(
id,
“Product”,
function (product) {
alert(“Retrieved the product named \”” + product.Name + “\”. This product was created on : \”” + product.CreatedOn + “\”.”);
},
errorHandler
);
}

function errorHandler(error)  {
alert(error.message);
}

9. In your CRM solution, add the following web resources:

10. Add these web resources as libraries on the form.

11. Add a call to retrieveProduct e.g. on the productid OnChange event.

Hello, Claims!

This a step-by-step guide for converting a ASP.NET web application to claims-based authentication using Windows Identity Foundation and ADFS 2.0. It assumes you have a development environment consisting of Visual Studio 2010 with ASP.NET MVC 3 and Windows Identity Foundation SDK. You also need ADFS 2.0  – in my case I installed it on a virtual machine (called testad1) which is also domain controller (domain test1.se).

(Depending on your network setup, you may have to enter the FQN of the virtual machine, testad1.test1.se in my case, in the host C:\Windows\System32\Drivers\etc\hosts file.)

Create a Web Application with Forms Authentication

Create an ASP.NET MVC 3 Web Application (HelloClaimsWeb) and choose the Internet Application template and the Razor view engine.

Make sure IIS Default Web Site has application pool ASP.NET 4.0.

Publish the application to IIS.

image

Test the web application (https://hostname/HelloClaimsWeb/). Notice that the user name is displayed in the upper right corner.

Enable the World Wide Web Services rules in Windows Firewall.

Test the web application from the test domain (virtual machine).

Convert to Claims Based Authentication

Run the Federation Utility:

Then, publish the application to IIS again.

Changes Made by the Federation Utility

The utility is not some kind of magic – it makes some changes to web.config. From top to bottom:

It adds a new section in configSections:

  <configSections>
      <section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
  </configSections>

(More on this later.)

It adds a new application setting which points to the STS federation metadata document location:

    <add key="FederationMetadataLocation" value="https://testad1.test1.se/FederationMetadata/2007-06/FederationMetadata.xml" />

Then there is an important change:

    <authentication mode="Forms" />

is changed to

    <authentication mode="None" />

This is because authentication is moved to a different part of the http pipeline. The following http modules are added in <system.web>:

    <httpModules>
         <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
         <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </httpModules>

The WSFederationAuthenticationModule redirects the user to the identity provider’s logon page. It also parses and validates the security token that is posted back. This module writes an encrypted cookie to avoid repeating the logon process. The SessionAuthenticationModule detects the logon cookie, decrypts it, and constructs the ClaimsPrincipal object.

These modules are also added to <system.webserver>:

    <modules runAllManagedModulesForAllRequests="true">
         <add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
         <add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
    </modules>

Then, there is the large microsoft.identitymodel section:

  <microsoft.identityModel>
     <service>
       <audienceUris>
         <add value="https://<hostname>/HelloClaimsWeb/" />
       </audienceUris>
       <federatedAuthentication>
         <wsFederation passiveRedirectEnabled="true" issuer="https://testad1.test1.se/adfs/ls/" realm="https://stc11118m1.softronic.se/HelloClaimsWeb/" requireHttps="true" />
         <cookieHandler requireSsl="true" />
       </federatedAuthentication>
       <applicationService>
         <claimTypeRequired>
           <!--Following are the claims offered by STS 'http://TestAD1.test1.se/adfs/services/trust'. Add or uncomment claims that you require by your application and then update the federation metadata of this application.—>
           <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
           <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/CommonName" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/EmailAddress" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/Group" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/claims/UPN" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod" optional="true" />—>
           <!--<claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid" optional="true" />—>
           <!--<claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname" optional="true" />—>
         </claimTypeRequired>
       </applicationService>
       <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
         <trustedIssuers>
           <add thumbprint="BA264CE398B297029466B7C547D4A2FB8B285991" name="http://TestAD1.test1.se/adfs/services/trust" />
         </trustedIssuers>
       </issuerNameRegistry>
       <certificateValidation certificateValidationMode="None" />
     </service>
   </microsoft.identityModel>

Configure ADFS 2.0

If you try to browse the web application now, you will get an error.

In the AD FS 2.0 Management console, add a new relying party trust.

  • Choose Import data about the relying party from a file and specify the file with path <web project folder>\FederationMetadata\2007-06\FederationMetadata.xml.
  • Type a display name, e.g. HelloClaimsWeb.

Add a new transform rule:

  • Select claim rule template Send LDAP Attributes as Claims.
  • As claim rule name, type LDAP attributes.
  • Select Active Directoryas attribute store.
  • Map from Display-Name to Name and from Token-Groups – Unqualified Names to Role.

Code Changes

Modify the home page to display the collection of claims as follows:

Add a reference to Microsoft.IdentityModel.

Change HomeController.Index with the following code:

            var principal = Thread.CurrentPrincipal;
            var identity = principal.Identity as IClaimsIdentity;
            var claims = identity.Claims;
            return View(claims);

Change the index view (Index.cshtml) as follows:

<p>    
    @{
        var grid = new WebGrid(Model);
        @grid.GetHtml(columns: grid.Columns(grid.Column("ClaimType"), grid.Column("Value")));
    }
</p>

Now if you run the web application from the virtual machine, you will get the following error:

A potentially dangerous Request.Form value was detected from the client (wresult=”<t:RequestSecurityTo…”).

This is described in detail in Request Validation – Preventing Script Attacks and the solution in this article: http://social.technet.microsoft.com/wiki/contents/articles/windows-identity-foundation-wif-a-potentially-dangerous-request-form-value-was-detected-from-the-client-wresult-quot-lt-t-requestsecurityto-quot.aspx

Moving the Web Application to the Cloud

Create Windows Azure Project

Add a new project of type Windows Azure and call it HelloClaimsCloud. Right-click on Roles and add Web Role Project in Solution.

Right-click on the newly added role and select Properties. Change VM size to Extra small.

Follow the following guide to add MVC dependencies: http://msdn.microsoft.com/en-us/library/hh369932.aspx#PublishMVC

image

You must now delete the four WebMatrix files in _bin_DeployableAssemblies; otherwise the web application tries to redirect to ~/Account/Login.

Then, set reference Microsoft.IdentityModel property Copy Local to True.

Create and Use SSL Certificate

Start a Visual Studio command prompt and create a certificate using makecert:
makecert -r -pe -n "CN=helloclaims.cloudapp.net" -b 01/01/2010 -e 01/01/2036 -eku 1.3.6.1.5.5.7.3.1 -ss my -sr localMachine -sky exchange -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12
Right-click the HelloClaimsWeb role and select properties. Select the Certificates tab. Add the newly created certificate. Then switch to the Endpoints tab and add a new endpoint with protocol https, port 443 and the certificate just added.

Then export the certificate, including private key, and upload it as a service certificate using the Windows Azure Platform Management Portal.

Reconfigure the Application

In web.config, there are a couple of entries that specifies the application URL. These must be changed. For that purpose, create a new project and solution configuration and call it e.g. Cloud. Right-click on web.config and select Add Config Transforms. Then, edit Web.Cloud.config and add the following before </configuration>:

  <microsoft.identityModel>
    <service>
      <audienceUris>
        <add value="https://helloclaims.cloudapp.net/" xdt:Transform="Replace"/>
      </audienceUris>
      <federatedAuthentication>
        <wsFederation realm="https://helloclaims.cloudapp.net/" xdt:Transform="SetAttributes(realm)"/>
      </federatedAuthentication>
    </service>
  </microsoft.identityModel>

Build and deploy this configuration to Windows Azure.

Configure ADFS 2.0

In the AD FS 2.0 Management console, add a new relying party trust.

  • Take a copy of the existing application federation metadata file (<web project folder>\FederationMetadata\2007-06\FederationMetadata.xml).
  • Edit the copy and change all three instances of the old URL (https://hostname/HelloClaimsWeb/) to the cloud URL (https://helloclaims.cloudapp.net/ in my case).
  • Choose Import data about the relying party from a file and specify the new modified file.
  • Type a display name, e.g. HelloClaimsCloud.

Add the same transform rule as before:

  • Select claim rule template Send LDAP Attributes as Claims.
  • As claim rule name, type LDAP attributes.
  • Select Active Directory as attribute store.
  • Map from Display-Name to Name and from Token-Groups – Unqualified Names to Role.

References

Claims-Based Identity for Windows

AD FS 2.0 Federation with a WIF Application Step-by-Step Guide

Single Sign-On from Active Directory to a Windows Azure Application