Sandrino Di Mattia

Content-type: Microsoft

Recent posts

Tags

Categories

Archive

Blogroll

Making WCF RIA Services work in a DMZ/Multitier architecture using Application Request Routing

Introduction

In large companies / governments / ... most of the time the application architecture needs to follow a set of rules (focused on maintainability and security).
These could be rules like the following:

  • The applications need to be developed following a Multitier architecture
  • Each tier should be physically separated for security.
  • The business logic tier is the only one that can connect to the data tier.
  • The presentation tier only connects to the business logic tier.
  • The presentation tier may not directly connect to the data tier.
  • The presentation tier is located in a DMZ, other tiers are heavily secured.
  • ...

Each project / company will have its own rules but the concept stays the same.
Here is an example of how this could be achieved in ASP.NET:

As you can see, for the data tier to be compromised one must first compromise the presentation and the business logic tier.
You can also assume that in most cases each physical tier is also protected by a firewall.

Default: Physical tiers in Silverlight + WCF RIA Services

When you're building an application in Silverlight with WCF RIA Services you'll get the following setup:

The setup is still a 3-tier achitecture, but the presentation tier runs on the client.
You could argue if the middle tier does or does not count as a presentation tier (since everything runs on the client) but let's say it does.

If you look at this from a security point of view it's less safer.
Once the webserver in the middle is compromised one has direct access to the data tier.

We could just add an extra tier containing some WCF Services that would be consumed by our WCF RIA Services but that would cause code duplication.
This would be an undesired side effect and thus we won't see this as a good solution to our problem.

Separate: An extra Web Application for WCF RIA Services

In my last post (Things you can do with WCF RIA Services and a regular .svc file) I described how you could separate the web application (hosting Silverlight) and the services. Resulting in the following setup:

 

As you can see here we're a step closer. Our services can live on one server and our web page (hosting the Silverlight application) on an other server. This does separate our tiers physically. But the problem is that Silverlight still requires a connection to WCF RIA Services directly.

In this setup our Business Logic Tier should be exposed to the internet / be in a DMZ / ... for our Silverlight client to access it.
And again, if this server is compromised one has direct access to the Data Tier!

Advanced: Using IIS Application Request Routing

For more information about ARR please visit: http://www.iis.net/download/ApplicationRequestRouting

We'll be using ARR (in combination with URL Rewrites) because it allows us to use IIS as a reverse proxy.
Using this reverse proxy we'll be able to achieve the following setup:

Follow these steps to install and configure IIS Application Request Routing.

A. Preparing the server(s)

  1. Make sure you have IIS 7 or IIS 7.5
  2. Download the Application Request Routing extension: x86 / x64
  3. Install the extension (it might install other extensions first)
     
  4. For testing purposes we'll simulate 2 servers on one machine.
    To do this, open c:\windows\system32\drivers\etc\hosts with notepad.

    127.0.0.1 presentationtier
    127.0.0.1 logictier


    We'll link these hostnames using host headers in IIS.

    If you have 2 servers you can use for testing, please do (and you can skip this step).
    But don't forget to install the WCF RIA Services Toolkit!
     
  5. Finally we'll configure the Application Request Routing.
    In IIS Manager, click your server and go to Application Request Routing Cache:



    Important note: If you work with multiple servers (and that's what you'll do in a real environment), you need to do this on your presentation tier (the ASP.NET website containing the Silverlight application).

  6. On the right select Server Proxy Settings and check the box Enable proxy.

B. Creating the site for the business logic tier

  1. In IIS, create a website called LogicSite
     

     
    Note that the 'Host name' points to logictier under Binding.
    This way we can simulate that this website is configured on the server logictier.
     
  2. Now go to Application Pools and open the LogicSite application pool.
    Change the .NET Framework version to v4.0.

C. Provisioning the business logic tier site

Splitting up a Silverlight+RIA Services application requires some actions and you can read all about it in my last article.
We'll be using the solution from that article to get started right away. Note that this solution does not connect to a data tier, it just simulates this using a static list.

  1. Download the complete solution and extract it: Sandworks.Silverlight.nTier.zip (464.99 kb)
  2. Open the solution using Visual Studio
  3. Right click the project and select publish.
     

     
  4. Point the publish to the correct directory.
     

      
  5. Press Publish.
  6. Now visit http://logictier/Tasks.svc and your WCF RIA Service should be working correctly.

  7. Now, create a file called clientaccesspolicy.xml in the root of this site containing the following XML (required for cross site access in Silverlight):

    <?xml version="1.0" encoding="utf-8"?>
    <access-policy>
      <cross-domain-access>
        <policy>
          <allow-from http-request-headers="*">
            <domain uri="http://*" />
            <domain uri="https://*" />
          </allow-from>
          <grant-to>
            <resource path="/" include-subpaths="true"/>
          </grant-to>
        </policy>
      </cross-domain-access>
    </access-policy>

That does it. Our business logic tier (on a 'separate server') has been configured and is working.

D. Creating the site for the presentation tier (and reverse proxy)

  1. In IIS, create a site called PresentationSite

    Note that the 'Host name' points to presentationtier under Binding.
    This way we can simulate that this website is configured on the server presentationtier.
     
  2. Now go to Application Pools and open the LogicSite application pool.
    Change the .NET Framework version to v4.0.
     
  3. For this application pool go to Advanced Settings and change the Idle-Time out to 0 minutes.
    And finally go to Recycle... and clear the Regular time intervals (in minutes) checkbox.

E. Provisioning the presentation tier site

  1. Go back to Visual Studio, to the Sandworks.Silverlight.nTier.Client project.
  2. Open MainPage.xaml.cs
  3. Change both links you see there to http://logictier/Tasks.svc
  4. Rebuild the complete solution.
  5. Publish the Sandworks.Silverlight.nTier.Web site to our PresentationSite.
     

     
  6. Now, open the following link: http://presentationtier/Sandworks.Silverlight.nTier.ClientTestPage.aspx
  7. If everything goes well you should see a Silverlight application, and when you press the button Get all tasks you'll see this:
     

Now you've got a fictive server running the services and an other server running the actual web application hosting the Silverlight application.
The Silverlight application runs locally but still connects to the business logic tier. Now we created a setup as described in Separate (WCF RIA Services split in 2 servers).

Let's continue.

F. Configuring IIS Application Request Routing and IIS Rewrite

  1. Open the web.config of the PresentationSite (running on the presentation tier).
     
  2. Now add the following to the configuration file:

     <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"
          multipleSiteBindingsEnabled="true" />
      </system.serviceModel> 
      <system.webServer>
        <validation validateIntegratedModeConfiguration="false" />
        <rewrite>
          <rules>
            <rule name="Reverse Proxy to Business Logic Tier" stopProcessing="true">
              <match url="^riaservices/(.*)" />
              <action type="Rewrite" url="
    http://logictier/{R:1}" />
            </rule>
          </rules>
        </rewrite>
      </system.webServer>


    This will make sure all requests to the path riaservices are forwarded to our logictier server (containing the business logic tier).
     
  3. Now, visit the page: http://presentationtier/riaservices/Tasks.svc

And there you have it. Even tough we're visiting a page on the server presentationtier it's showing us content from the logictier server.
This means our Silverlight application no longer needs to talk to the logictier server. And thus, we don't need to expose our logictier server to the internet or put it in a DMZ.

Note 1: The clientaccesspolicy.xml file we placed in the LogicSite is no longer required.
Note 2: The system.serviceModel part in the config is very important. If it's missing you'll get the following error:

In your browser:

Server Error in '/' Application.
The resource cannot be found.

In EventViewer:

WebHost failed to process a request.
 Sender Information: System.ServiceModel.Activation.HostedHttpRequestAsyncResult/27111447
 Exception: System.Web.HttpException (0x80004005): The service '/riaservices/Tasks.svc' does not exist. ---> System.ServiceModel.EndpointNotFoundException: The service '/riaservices/Tasks.svc' does not exist.

G. The final result in our Silverlight application

  1. Go back to Visual Studio.
  2. Open MainPage.xaml.cs
  3. Change both urls to http://presentationtier/riaservices/Tasks.svc
  4. Rebuild the complete solution.
  5. Publish the Silverlight application like we did in E-5
  6. Check the web.config if it still contains the URL Rewrite configuration.
  7. Visit http://presentationtier/Sandworks.Silverlight.nTier.ClientTestPage.aspx 

And we're done...

If you want you can start Fiddler and you'll see that our Silverlight application is only accessing our presentationtier server:

After a very long article this is what we've accomplished:

 

Our solution is ready for the enterprise!

Downloads:

Enjoy..

Posted: Sep 02 2010, 00:13 by Sandrino | Comments (3) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: WCF | Silverlight | Architecture | .NET
Things you can do with WCF RIA Services and a regular .svc file

Introduction

Let's say you create a new DomainService called TasksDomainService and host this service in an ASP.NET website.

The HttpModule DomainServiceModule will make sure you can access this service as if a physical .svc file was present on the webserver. It will take the namespace and the class name (in my case Sandworks.Silverlight.nTier.Services and TasksDomainService), combine them and append ".svc".

In my case I could access the service via: http://localhost:9346/Sandworks-Silverlight-nTier-Services-TasksDomainService.svc

Custom .svc file

But what to do when you want a cleaner url (http://localhost/Tasks.svc for example)?
This is possible, just follow these steps:

  1. In the web project containing your service, create a new file: Tasks.svc
  2. Open this file
  3. Enter the following:
     
    <%@ ServiceHost Service="Sandworks.Silverlight.nTier.Services.TasksDomainService" Factory="System.ServiceModel.DomainServices.Hosting.DomainServiceHostFactory, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>

The markup you see in step 3 tells the server what service to load (Service, this needs to include the namespace) and how to load it (Factory, the DomainServiceHostFactory will correctly host your DomainService class).
If you don't use the fully qualified assembly name this will result in the following error (thanks Luc!):

Parser Error Message: The CLR Type 'System.ServiceModel.DomainServices.Hosting.DomainServiceHostFactory' could not be loaded during service compilation. Verify that this type is either defined in a source file located in the application's \\App_Code directory, contained in a compiled assembly located in the application's \\bin directory, or present in an assembly installed in the Global Assembly Cache. Note that the type name is case-sensitive and that the directories such as \\App_Code and \\bin must be located in the application's root directory and cannot be nested in subdirectories.

Do read Rick's post if you also want to remove the .svc extension to go RESTfull: http://www.west-wind.com/weblog/posts/570695.aspx

Clean project structure

When creating a new solution I love to organize a clean project structure.
But when you're using Silverlight with WCF RIA Services this can be challenging though.

This is what we'll try to accomplish:

A little word on the projects:

  • Sandworks.Silverlight.nTier.Client: The Silverlight application.
  • Sandworks.Silverlight.nTier.Client.Model: A regular Silverlight Class Library with a WCF RIA Services link.
  • Sandworks.Silverlight.nTier.Services: A WCF Service Library.
  • Sandworks.Silverlight.nTier.Services.Host: A WCF Service Application.
  • Sandworks.Silverlight.nTier.Web: This hosts the Silverlight application.

Note, we won't be using a WCF RIA Services Class Library because the namespaces and assembly names are not so clean (.Web is appended to your server side class library):

 

Here are the steps you need to follow to create your own clean project structure:

  1. Open Visual Studio, select File, New Project
  2. Select Silverlight Application
  3. Enter the following:
     
    Name: My.Application.Client
    Solution: My.Application
     
  4. Visual Studio will ask you to host the application in a new ASP.NET Web Site.
    Make sure you change My.Application.Client.Web to My.Application.Web and press OK (do not check Enable WCF RIA Services).
  5. Add a new Silverlight Class Library called My.Application.Client.Model and remove Class1.cs
  6. In your project My.Application.Client add a project reference to My.Application.Client.Model
  7. Add a new WCF Service Library called My.Application.Services and remove all files.
  8. Add a new WCF Service Application called My.Application.Services.Host and remove the fake Service1.
  9. In your project My.Application.Services.Host add the following references:
     
    My.Application.Services
    (project reference)
    System.ComponentModel.DataAnnotations
    System.ServiceModel.DomainServices.Hosting

    System.ServiceModel.DomainServices.Server
     
  10. Rebuild your solution.
  11. In your project My.Application.Client.Model go to Properties and add a WCF RIA Services link to My.Application.Services
    This will make sure the code generation is in place.
  12. Create 3 solution folders: Client, Services, Web and reorganize your projects.

And you're done:

The only things left to do are:

  1. Create a new Domain Service Class in My.Application.Services
  2. Create a .svc file in My.Application.Services.Host (referencing that class, see start of this post)
  3. Rebuild your solution (code generation will take place in My.Application.Client.Model)
  4. Use your service context in My.Application.Client
  5. Configure the web applications on a fixed port (easier for when you're debugging).

Since your service isn't in the same project/Virtual Directory as the Web project you'll have to explicitly declare the url:

TasksDomainContext ctx = new TasksDomainContext(new Uri("http://localhost:10001/Tasks.svc"));

In a following article I'll explain how to configure these urls.
But for now here are the downloads:

Enjoy...

Posted: Sep 01 2010, 11:59 by Sandrino | Comments (7) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: WCF | Silverlight | Architecture
Charles Petzold’s free ebook: Programming Windows Phone 7

Charles Petzold just released the second draft of his ebook on Windows Phone 7.
It contains the following chapters:

Part I - The Basics
Chapter 1   Hello, Windows Phone 7
Chapter 2   Getting Oriented
Chapter 3   An Introduction to Touch
Chapter 4   Bitmaps, Also Known as Textures
Chapter 5   Sensors and Services
Chapter 6   Issues in Application Architecture

Part II - Silverlight
Chapter 7   XAML Power and Limitations
Chapter 8   Elements and Properties

Part III - XNA
Chapter 20   Principles of Movement
Chapter 21   Textures and Sprites
Chapter 22   Touch and Play

Here are the links:

Enjoy

Posted: Aug 04 2010, 09:03 by Sandrino | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development
Silverlight Business Application: Unable to connect to SQL Server database.

I'm following the WCF RIA Services Documentation to get up to speed with RIA Services. But in the walkthrough "Using the Silverlight Business Application Template" I got the following error when registering as new user:

Unable to connect to SQL Server database.

The reason here is simple. The example uses an ASP.NET SQL Membership Provider and this provider wants to connect to localhost\SQLEXPRESS (and I installed using default instance, so that's what causing the error).
To solve this issue you need to:

  • Open the machine.config located in: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config
  • Edit the entry LocalSqlServer in connectionStrings and change the data source (in my case I changed from .\SQLEXPRESS to .)

Hope this helps.

Posted: Jul 19 2010, 09:27 by Sandrino | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development
Consuming Google (Reader) with .NET: Part 2 - Google Reader using .NET

Introduction

This article starts where the previous one (Consuming Google (Reader) with .NET: Part 1 - Authentication) stopped.
An implementation of Google Reader in .NET

Before getting started it's important to know that there is a site with plenty of information on the Google Reader API: http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI. But what we'll also be doing is use Fiddler to reverse engineer the Google Reader API.

Reverse Engineering

When you visit Google Reader via your browser you're basically always sending GET and POST requests. Using Fiddler you can then view the content of those requests.

Let's say we want to subscribe to http://sandrinodimattia.net/Blog. When you're logged in to Google Reader you can press the Add Subscription button, enter the URL and press Add. If you do this while Fiddler is open, you'll see the following:
 

 
After pressing the Add button you can see that the url /reader/api/0/subscription/quickadd was visited and 2 fields were posted (quickadd and T). And for each available action in Google Reader you can use Fiddler to view the url and the post fields that are hidden underneath.

If you take a closer look at the screenshot you see a field called T, this is a token. It identifies your session but expires quickly. That's why you'll see that our code requests a new token for each new request.

Adding POST to GoogleSession

In the last article we created the GoogleSession class. This class helps us getting data from Google. But now that we have to make POST requests we'll also need to send data to Google. That's why we'll add the following method to our GoogleSession class:

 

        /// <summary>

        /// Send a post request to Google.

        /// </summary>

        /// <param name="url"></param>

        /// <param name="postFields"></param>

        /// <returns></returns>

        public void PostRequest(string url, params GoogleParameter[] postFields)

        {

            // Format the parameters.

            string formattedParameters = string.Empty;

            foreach (var par in postFields.Where(o => o != null))

                formattedParameters += string.Format("{0}={1}&", par.Name, par.Value);

            formattedParameters = formattedParameters.TrimEnd('&');

 

            // Append a token.

            formattedParameters += String.Format("&{0}={1}", "T", GetToken());

 

            // Get the current post data and encode.

            ASCIIEncoding ascii = new ASCIIEncoding();

            byte[] encodedPostData = ascii.GetBytes(

                String.Format(formattedParameters));

 

            // Prepare request.

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);

            request.Method = "POST";

            request.ContentType = "application/x-www-form-urlencoded";

            request.ContentLength = encodedPostData.Length;

 

            // Add the authentication header.

            request.Headers.Add("Authorization", "GoogleLogin auth=" + auth);

 

            // Write parameters to the request.

            using (Stream newStream = request.GetRequestStream())

                newStream.Write(encodedPostData, 0, encodedPostData.Length);

  

            // Get the response and validate.

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            if (response.StatusCode != HttpStatusCode.OK)

                throw new LoginFailedException(

                    response.StatusCode, response.StatusDescription);

        }

 

This function uses a URL and GoogleParameters to build a POST request. And the token we talked about is also automatically included in the post fields. Thanks to this function we'll be able to send POST requests to Google Reader with ease.

The GoogleService base class

Before really getting started we'll just go and create a base class we might want to re-use when implementing other Google services. It encapsulates the authentication mechanism and the GoogleSession class.

 

    public abstract class GoogleService : IDisposable

    {

        /// <summary>

        /// Current google session.

        /// </summary>

        protected GoogleSession session;

 

        /// <summary>

        /// Creating this class will automatically try to log in and create a session.

        /// That way for each service we create we don't need to worry about the implementation of authentication and session.

        /// </summary>

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

        /// <param name="username"></param>

        /// <param name="password"></param>

        /// <param name="source"></param>

        protected GoogleService(string service, string username,

           string password, string source)

        {

            // Get the Auth token.

            string auth = ClientLogin.GetAuthToken(service, username, password, source);

 

            // Create a new session using this token.

            this.session = new GoogleSession(auth);

        }

 

        /// <summary>

        /// Clean up the session.

        /// </summary>

        public void Dispose()

        {

            if (session != null)

                session.Dispose();

        }

    }

 

Item Types

When reading from Google Reader you'll be working with 2 different types of data. Pure XML and syndication items. Syndication items (SyndicationItem in .NET) are actually items that come from a feed. For example, if you visit this site you'll get a list of your read items in the shape of an atom feed: http://www.google.com/reader/atom/user/-/state/com.google/read. Each item in this feed is a SyndicationItem.

For both types we'll be using some basic base classes:

 

    public abstract class GoogleSyndicationItem

    {

        /// <summary>

        /// Initialize the item.

        /// </summary>

        /// <param name="item"></param>

        internal GoogleSyndicationItem(SyndicationItem item)

        {

            if (item != null)

            {

                LoadItem(item);

            }

        }

  

        /// <summary>

        /// Load the item (to be implemented by inheriting classes).

        /// </summary>

        /// <param name="item"></param>

        protected abstract void LoadItem(SyndicationItem item);

  

        /// <summary>

        /// Get the text from a TextSyndicationContent.

        /// </summary>

        /// <param name="content"></param>

        /// <returns></returns>

        public string GetTextSyndicationContent(SyndicationContent content)

        {

            TextSyndicationContent txt = content as TextSyndicationContent;

            if (txt != null)

                return txt.Text;

            else

                return "";

        }

    }

 

    public abstract class GoogleXmlItem : SyndicationItem

    {

        /// <summary>

        /// Initialize the item.

        /// </summary>

        /// <param name="item"></param>

        internal GoogleXmlItem(XElement item)

        {

            if (item != null)

            {

                LoadItem(item);

            }

        }

 

        /// <summary>

        /// Load the item (to be implemented by inheriting classes).

        /// </summary>

        /// <param name="item"></param>

        protected abstract void LoadItem(XElement item);

 

        /// <summary>

        /// Get a list of descendants.

        /// </summary>

        /// <param name="item"></param>

        /// <param name="descendant"></param>

        /// <param name="attribute"></param>

        /// <param name="attributeValue"></param>

        /// <returns></returns>

        protected IEnumerable<XElement> GetDescendants(XElement item, string descendant,

           string attribute, string attributeValue)

        {

            return item.Descendants(descendant).Where(o => o.Attribute(attribute) != null

               && o.Attribute(attribute).Value == attributeValue);

        }

 

        /// <summary>

        /// Get a descendant.

        /// </summary>

        /// <param name="item"></param>

        /// <param name="descendant"></param>

        /// <param name="attribute"></param>

        /// <param name="attributeValue"></param>

        /// <returns></returns>

        protected XElement GetDescendant(XElement item, string descendant,

           string attribute, string attributeValue)

        {

            return GetDescendants(item, descendant, attribute, attributeValue).First();

        }

 

        /// <summary>

        /// Get the value of a descendant.

        /// </summary>

        /// <param name="item"></param>

        /// <param name="descendant"></param>

        /// <param name="attribute"></param>

        /// <param name="attributeValue"></param>

        /// <returns></returns>

        protected string GetDescendantValue(XElement item, string descendant,

           string attribute, string attributeValue)

        {

            var desc = GetDescendant(item, descendant, attribute, attributeValue);

            if (desc != null)

                return desc.Value;

            else

                return "";

        }

    }

  

And for almost every type of data in Google Reader I've also created a class:

  • Feed (the url to a feed with the title of that feed)
  • ReaderItem (you could say this is an article, a blog post, ...)
  • State (this is the state of an item in Google Reader, like read, starred, ...)
  • Subscription (a subscription in google reader is a feed you subscribed to)

Here is the implementation for subscription:

 

    public class Subscription : GoogleXmlItem

    {

        /// <summary>

        /// Id of the subscription.

        /// </summary>

        public string Id { get; set; }

 

        /// <summary>

        /// Title of the feed.

        /// </summary>

        public string Title { get; set; }

 

        /// <summary>

        /// URL to the subscription.

        /// </summary>

        public string Url { get; set; }

 

        /// <summary>

        /// List of categories.

        /// </summary>

        public List<string> Categories { get; set; }

 

        /// <summary>

        /// Initialize the subscription.

        /// </summary>

        /// <param name="item"></param>

        internal Subscription(XElement item)

            : base(item)

        {

 

        }

 

        /// <summary>

        /// Load the subscription item.

        /// </summary>

        /// <param name="item"></param>

        protected override void LoadItem(XElement item)

        {

            // Initialize categories list.

            Categories = new List<string>();

 

            // Regular fields.

            Id = GetDescendantValue(item, "string", "name", "id");

            Title = GetDescendantValue(item, "string", "name", "title");

 

            // Parse the URL.

            if (Id.Contains('/'))

                Url = Id.Substring(Id.IndexOf('/') + 1,

                   Id.Length - Id.IndexOf('/') - 1);

 

            // Get the categories.

            var catList = GetDescendant(item, "list", "name", "categories");

            if (catList != null && catList.HasElements)

            {

                var categories = GetDescendants(item, "string", "name", "label");

                Categories.AddRange(categories.Select(o => o.Value));

            }

        }

    }

 

URLs everywhere

Like mentioned  before, the Google Reader API is based on URLs and GET/POST requests. To organise this we've also got a few classes regarding URLs:

  • ReaderUrl: A single class containing all the required URLs and paths
  • ReaderCommand: Enum representing common tasks (like adding a subscription)
  • ReaderCommandFormatter: Class containing extension methods for ReaderCommand to convert these enum values to actual Google Reader URLs

 

    public static class ReaderUrl

    {

        /// <summary>

        /// Base url for Atom services.

        /// </summary>

        public const string AtomUrl = "https://www.google.com/reader/atom/";

   

        /// <summary>

        /// Base url for API actions.

        /// </summary>

        public const string ApiUrl = "https://www.google.com/reader/api/0/";

  

        /// <summary>

        /// Feed url to be combined with the desired feed.

        /// </summary>

        public const string FeedUrl = AtomUrl + "feed/";

  

        /// <summary>

        /// State path.

        /// </summary>

        public const string StatePath = "user/-/state/com.google/";

  

        /// <summary>

        /// State url to be combined with desired state. For example: starred

        /// </summary>

        public const string StateUrl = AtomUrl + StatePath;

  

        /// <summary>

        /// Label path.

        /// </summary>

        public const string LabelPath = "user/-/label/";

  

        /// <summary>

        /// Label url to be combined with the desired label.

        /// </summary>

        public const string LabelUrl = AtomUrl + LabelPath;

    }

 

    public enum ReaderCommand

    {

        SubscriptionAdd,

        SubscriptionEdit,

        SubscriptionList,

        TagAdd,

        TagEdit,

        TagList,

        TagRename,

        TagDelete

    }

 

    public static class ReaderCommandFormatter

    {

        /// <summary>

        /// Get the full url for a command.

        /// </summary>

        /// <param name="comm"></param>

        /// <returns></returns>

        public static string GetFullUrl(this ReaderCommand comm)

        {

            switch (comm)

            {

                case ReaderCommand.SubscriptionAdd:

                    return GetFullApiUrl("subscription/quickadd");

                case ReaderCommand.SubscriptionEdit:

                    return GetFullApiUrl("subscription/edit");

                case ReaderCommand.SubscriptionList:

                    return GetFullApiUrl("subscription/list");

                case ReaderCommand.TagAdd:

                    return GetFullApiUrl("edit-tag");

                case ReaderCommand.TagEdit:

                    return GetFullApiUrl("edit-tag");

                case ReaderCommand.TagList:

                    return GetFullApiUrl("tag/list");

                case ReaderCommand.TagRename:

                    return GetFullApiUrl("rename-tag");

                case ReaderCommand.TagDelete:

                    return GetFullApiUrl("disable-tag");

                default:

                    return "";

            }

        }

 

        /// <summary>

        /// Get the full api url.

        /// </summary>

        /// <param name="append"></param>

        /// <returns></returns>

        private static string GetFullApiUrl(string append)

        {

            return String.Format("{0}{1}", ReaderUrl.ApiUrl, append);

        }

    }

 

And finally... ReaderService

Finally there's the implementation of the most common tasks in Google Reader:

 

    public class ReaderService : GoogleService

    {

        /// <summary>

        /// Current username.

        /// </summary>

        private string username;

 

        /// <summary>

        /// Initialize the Google reader.

        /// </summary>

        /// <param name="username"></param>

        /// <param name="password"></param>

        /// <param name="source"></param>

        public ReaderService(string username, string password, string source)

            : base("reader", username, password, source)

        {

            this.username = username;           

        }

   

        #region Feed

        /// <summary>

        /// Get the contents of a feed.

        /// </summary>

        /// <param name="feedUrl">

        /// Must be exact URL of the feed, ex: http://sandrinodimattia.net/blog/syndication.axd

        /// </param>

        /// <param name="limit"></param>

        /// <returns></returns>

        public IEnumerable<ReaderItem> GetFeedContent(string feedUrl, int limit)

        {

            try

            {

                return GetItemsFromFeed(String.Format("{0}{1}", ReaderUrl.FeedUrl,

                   System.Uri.EscapeDataString(feedUrl)), limit);

            }

            catch (WebException wex)

            {

                HttpWebResponse rsp = wex.Response as HttpWebResponse;

                if (rsp != null && rsp.StatusCode == HttpStatusCode.NotFound)

                    throw new FeedNotFoundException(feedUrl);

                else

                    throw;

            }

        }

        #endregion

        #region Subscription

        /// <summary>

        /// Subscribe to a feed.

        /// </summary>

        /// <param name="feed"></param>

        public void AddSubscription(string feed)

        {

            PostRequest(ReaderCommand.SubscriptionAdd,

               new GoogleParameter("quickadd"feed));

        }

  

        /// <summary>

        /// Tag a subscription (remove it).

        /// </summary>

        /// <param name="feed"></param>

        /// <param name="folder"></param>

        public void TagSubscription(string feed, string folder)

        {

            PostRequest(ReaderCommand.SubscriptionEdit,

                new GoogleParameter("a", ReaderUrl.LabelPath + folder),

                new GoogleParameter("s", "feed/" + feed),

                new GoogleParameter("ac", "edit"));

        }

  

        /// <summary>

        /// Get a list of subscriptions.

        /// </summary>

        /// <returns></returns>

        public List<Subscription> GetSubscriptions()

        {

            // Get the XML for subscriptions.

            string xml = session.GetSource(ReaderCommand.SubscriptionList.GetFullUrl());

 

            // Get a list of subscriptions.

            return XElement.Parse(xml).Elements("list").Elements("object")

                 .Select(o => new Subscription(o)).ToList();

        }

        #endregion

        #region Tags

        /// <summary>

        /// Add tags to an item.

        /// </summary>

        /// <param name="feed"></param>

        /// <param name="folder"></param>

        public void AddTags(ReaderItem item, params string[] tags)

        {

            // Calculate the amount of parameters required.

            int arraySize = tags.Length + item.Tags.Count + 2;

  

            // Set all parameters.

            GoogleParameter[] parameters = new GoogleParameter[arraySize];

            parameters[0] = new GoogleParameter("s", "feed/" + item.Feed.Url);

            parameters[1] = new GoogleParameter("i", item.Id);

  

            int nextParam = 2;

  

            // Add parameters.

            for (int i = 0; i < item.Tags.Count; i++)

                parameters[nextParam++] = new GoogleParameter("a", ReaderUrl.LabelPath + item.Tags[i]);

            for (int i = 0; i < tags.Length; i++)

                parameters[nextParam++] = new GoogleParameter("a", ReaderUrl.LabelPath + tags[i]);

  

            // Send request.

            PostRequest(ReaderCommand.TagAdd, parameters);

        }

  

        /// <summary>

        /// Rename a tag.

        /// </summary>

        /// <param name="tag"></param>

        /// <param name="newName"></param>

        public void RenameTag(string tag, string newName)

        {

            PostRequest(ReaderCommand.TagRename,

                new GoogleParameter("s", ReaderUrl.LabelPath + tag),

                new GoogleParameter("t", tag),

                new GoogleParameter("dest", ReaderUrl.LabelPath + newName));

        }

  

        /// <summary>

        /// Remove tag (for all items).

        /// </summary>

        /// <param name="tag"></param>

        public void RemoveTag(string tag)

        {

            PostRequest(ReaderCommand.TagDelete,

                new GoogleParameter("s", ReaderUrl.LabelPath + tag),

                new GoogleParameter("t", tag));

        }

  

        /// <summary>

        /// Remove tag from a single item.

        /// </summary>

        /// <param name="item"></param>

        /// <param name="tag"></param>

        public void RemoveTag(ReaderItem item, string tag)

        {

            PostRequest(ReaderCommand.TagEdit,

                new GoogleParameter("r", ReaderUrl.LabelPath + tag),

                new GoogleParameter("s", "feed/" + item.Feed.Url),

                new GoogleParameter("i", item.Id));

        }

  

        /// <summary>

        /// Get a list of tags.

        /// </summary>

        /// <returns></returns>

        public List<string> GetTags()

        {           

            string xml = session.GetSource(ReaderCommand.TagList.GetFullUrl());

  

            // Get the list of tags.

            var tagElements = from t in XElement.Parse(xml).Elements("list").Descendants("string")

                              where t.Attribute("name").Value == "id"

                              where t.Value.Contains("/label/")

                              select t;

  

            // Create a list.

            List<string> tags = new List<string>();

            foreach (XElement element in tagElements)

            {

                string tag = element.Value.Substring(element.Value.LastIndexOf('/') + 1,

                    element.Value.Length - element.Value.LastIndexOf('/') - 1);

                tags.Add(tag);

            }

  

            // Done.

            return tags;

        }

  

        /// <summary>

        /// Get all items for a tag.

        /// </summary>

        /// <param name="tag"></param>

        /// <param name="limit"></param>

        /// <returns></returns>

        public IEnumerable<ReaderItem> GetTagItems(string tag, int limit)

        {

            return GetItemsFromFeed(String.Format("{0}{1}", ReaderUrl.LabelPath,

                 System.Uri.EscapeDataString(tag)), limit);

        }

        #endregion

        #region State

        /// <summary>

        /// Add state for an item.

        /// </summary>

        /// <param name="item"></param>

        /// <param name="state"></param>

        public void AddState(ReaderItem item, State state)

        {

            PostRequest(ReaderCommand.TagEdit,

                new GoogleParameter("a", ReaderUrl.StatePath + StateFormatter.ToString(state)),

                new GoogleParameter("i", item.Id),

                new GoogleParameter("s", "feed/" + item.Feed.Url));

        }

  

        /// <summary>

        /// Remove a state from an item.

        /// </summary>

        /// <param name="item"></param>

        /// <param name="state"></param>

        public void RemoveState(ReaderItem item, State state)

        {

            PostRequest(ReaderCommand.TagEdit,

                new GoogleParameter("r", ReaderUrl.StatePath + StateFormatter.ToString(state)),

                new GoogleParameter("i", item.Id),

                new GoogleParameter("s", "feed/" + item.Feed.Url));

        }

  

        /// <summary>

        /// Get the content of a state.

        /// For example: Get all starred items.

        /// </summary>

        /// <param name="state"></param>

        /// <param name="limit"></param>

        /// <returns></returns>

        public IEnumerable<ReaderItem> GetStateItems(State state, int limit)

        {

            return GetItemsFromFeed(String.Format("{0}{1}", ReaderUrl.StateUrl,

              StateFormatter.ToString(state)), limit);

        }

        #endregion

   

        /// <summary>

        /// Post a request using a reader command.

        /// </summary>

        /// <param name="command"></param>

        /// <param name="postFields"></param>

        private void PostRequest(ReaderCommand command, params GoogleParameter[] postFields)

        {

            session.PostRequest(ReaderCommandFormatter.GetFullUrl(command), postFields);

        }

   

        /// <summary>

        /// Get items from a feed and convert them to a GoogleReaderItem.

        /// </summary>

        /// <param name="url"></param>

        /// <param name="limit"></param>

        /// <returns></returns>

        private IEnumerable<ReaderItem> GetItemsFromFeed(string url, int limit)

        {

            SyndicationFeed feed = session.GetFeed(url, new GoogleParameter("n", limit.ToString()));

            return feed.Items.Select<SyndicationItem, ReaderItem>(o => new ReaderItem(o));

        }

    }

 

And as you can see the ReaderService does a few things:

  • Subscriptions (list, add)
  • Tags (add, rename, delete, ...)
  • States (add, remove, list)
  • Feed (list contents)

And it actually re-uses a few of the things we talked about:

  • GoogleSession to send post requests, get feeds, ...
  • ReaderCommand, ReaderCommandFormatter, ReaderUrl to do all the URL related stuff
  • GoogleParameter to set POST fields (fields we can find using Fiddler)

Putting it all together

The console application was also updated with our ReaderService:

  

    class Program

    {

        static void Main(string[] args)

        {

            // Empty line.

            Console.WriteLine("");

 

            // Get username.

            Console.Write(" Enter your Google username: ");

            string username = Console.ReadLine();

 

            // Get password.

            Console.Write(" Enter your password: ");

            string password = Console.ReadLine();

 

            // Query.

            using (ReaderService rdr = new ReaderService(username, password, "Sandworks.Google.App"))

            {

                // Display.

                Console.WriteLine("");

                Console.WriteLine(" Last 5 articles from Sandrino's Blog: ");

 

                foreach (ReaderItem item in rdr.GetFeedContent("http://sandrinodimattia.net/blog/syndication.axd?format=rss", 5))

                {

                    Console.WriteLine("  - " + item.Author + ": " + item.Title);

                }

            }

 

            // Pause.

            Console.ReadLine();

        }

    }

 

There you go, now you've got everything you need to get started with Google Reader in .NET. The following article will be about creating a basic WPF application to have a simple desktop version of Google Reader.

Sandworks.Google Part 2.zip (121.41 kb)

Enjoy...

Posted: Jul 12 2010, 11:05 by Sandrino | Comments (2) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development
Fixing common issues when hosting a .NET 4.0 WCF service in IIS 7

Until today I never had to host a WCF service in IIS... I always prefered using a ServiceHost in a Windows Service. Before getting my service up and running I had to face some issues.

Could not load file or assembly 'service' or one of its dependencies. This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.

The problem here is that I'm running a .NET 4.0 service in a .NET 2.0 IIS and AppPool. I had to make the following changes:

  • Change the IIS .NET Framework version to v4.0.30319 (click on the machine name in IIS Manager and in the right pane choose Change .NET Framework Version)
  • Change the .NET Framework version of the AppPool too v4.0 (via Application Pools) or create a new Application Pool.

The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map. Detailed Error InformationModule StaticFileModule.

The reason for this issue: the ServiceModel for .NET 4.0 was not installed. Run ServiceModelReg.exe -ia to solve this.
Depending on your machine and the .NET version you are running the file could be in one of the following locations:

  • C:\Windows\Microsoft.NET\Framework\v4.0.30319\
  • C:\Windows\Microsoft.NET\Framework64\v4.0.30319\

Handler "svc-Integrated" has a bad module "ManagedPipelineHandler" in its module list

  1. This means that ASP.NET 4.0 was not installed (correctly). Run aspnet_regiis.exe -i using Visual Studio Command Prompt (2010) or via "C:\Windows\Microsoft.NET\Framework\v4.0.30319".
  2. I also had to install the WCF Activation feature under .NET Framework 3.5.1 Features

Enjoy...

Posted: Jul 08 2010, 10:34 by Sandrino | Comments (1) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development
Consuming Google (Reader) with .NET: Part 1 - Authentication

Introduction

Since a few weeks I've become more and more a fan of Google Reader. This great online RSS reader supports starring items, extended search, ... anything you expect from a good RSS reader. What I would like to do in the following articles is connect to and consume Google services.

Before starting I do need to point out that there is a .NET ready API written by Google, called the Google Data Protocol. This library supports many of the Google services and is very well documented.

You can find it here: http://code.google.com/apis/gdata/

Downsides for me personally:

  • No support for the .NET Client Profile
  • Reference to external assembly required
  • No support/documentation for Google Reader?

Also important to know is that there are other .NET libraries available to consume Google Reader:

The problem with these libraries is that authentication does not work (anymore). Google changed the way authentication is done. More info: http://groups.google.com/group/fougrapi/browse_thread/thread/e331f37f7f126c00

This solution:

  • Works with ClientLogin authentication
  • Using the .NET 4.0 Client Profile
  • GoogleSession class answering all your needs

Authentication with ClientLogin

If you want to authenticate a few things need to happen. A post made by xandy on StackOverFlow explains the process (points 1 to 3):

  1. Post to https://www.google.com/accounts/ClientLogin with login credentials.
  2. In return, three tokens will be passed if correct login: a. SID b. LSID c. Auth
  3. Save the Auth somewhere in application. Forget about SID and LSID (I guess they might remove them later on)
  4. In every request, add following in the header: headername:
    Authorization value: GoogleLogin auth={Auth string} e.g. (in java)

The following class does all this and returns the Auth token after a successful login:

 

    public static class ClientLogin

    {

        /// <summary>

        /// Client login url where we'll post login data to.

        /// </summary>

        private static string clientLoginUrl =

            @"https://www.google.com/accounts/ClientLogin";

 

        /// <summary>

        /// Data to be sent with the post request.

        /// </summary>

        private static string postData =

            @"service={0}&continue=http://www.google.com/&Email={1}&Passwd={2}&source={3}";

 

        /// <summary>

        /// Get the Auth token you get after a successful login.

        /// You'll need to reuse this token in the header of each new request you make.

        /// </summary>

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

        /// <param name="username"></param>

        /// <param name="password"></param>

        /// <param name="source"></param>

        /// <returns></returns>

        public static string GetAuthToken(

            string service, string username, string password, string source)

        {

            // Get the response that needs to be parsed.

            string response = PostRequest(service, username, password, source);

 

            // Get auth token.

            string auth = ParseAuthToken(response);

            return auth;

        }

 

        /// <summary>

        /// Parse the Auth token from the response.

        /// </summary>

        /// <param name="response"></param>

        /// <returns></returns>

        private static string ParseAuthToken(string response)

        {           

            // Get the auth token.

            string auth = "";

            try

            {

                auth = new Regex(@"Auth=(?<auth>\S+)").Match(response).Result("${auth}");

            }

            catch (Exception ex)

            {

                throw new AuthTokenException(ex.Message);

            }

 

            // Validate token.

            if (string.IsNullOrEmpty(auth))

            {

                throw new AuthTokenException("Missing or invalid 'Auth' token.");

            }

 

            // Use this token in the header of each new request.

            return auth;

        }

 

        /// <summary>

        /// Create a post request with all the login data. This will return something like:

        ///

        /// SID=AQAAAH1234

        /// LSID=AQAAAH8AA5678

        /// Auth=AQAAAIAAAAB910

        ///

        /// And we need the Auth token for each subsequent request.

        /// </summary>

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

        /// <param name="email"></param>

        /// <param name="password"></param>

        /// <param name="source"></param>

        /// <returns></returns>

        private static string PostRequest(

            string service, string email, string password, string source)

        {

            // Get the current post data and encode.

            ASCIIEncoding ascii = new ASCIIEncoding();

            byte[] encodedPostData = ascii.GetBytes(

                String.Format(postData, service, email, password, source));

 

            // Prepare request.

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(clientLoginUrl);

            request.Method = "POST";

            request.ContentType = "application/x-www-form-urlencoded";

            request.ContentLength = encodedPostData.Length;

 

            // Write login info to the request.

            using (Stream newStream = request.GetRequestStream())

                newStream.Write(encodedPostData, 0, encodedPostData.Length);

 

            // Get the response that will contain the Auth token.

            HttpWebResponse response = null;

            try

            {

                response = (HttpWebResponse)request.GetResponse();

            }

            catch (WebException ex)

            {

                HttpWebResponse faultResponse = ex.Response as HttpWebResponse;

                if (faultResponse != null && faultResponse.StatusCode == HttpStatusCode.Forbidden)

                    throw new IncorrectUsernameOrPasswordException(

                        faultResponse.StatusCode, faultResponse.StatusDescription);

                else

                    throw;

            }

 

            // Check for login failed.

            if (response.StatusCode != HttpStatusCode.OK)

                throw new LoginFailedException(

                    response.StatusCode, response.StatusDescription);

 

            // Read.

            using (StreamReader reader = new StreamReader(response.GetResponseStream()))

                return reader.ReadToEnd();

        }

    }

 

That's it. If you invoke ClientLogin.GetAuthToken you'll get the auth token that can be used for each subsequent request.
You'll also get correctly typed Exceptions depending on the error you get. If the exception is not know it's just re-thrown.

The GoogleSession class

Before we can use the Auth token to make the request we need to take care of a few things:

  • Reusing the Auth token
  • Adding the Auth token to the request header (point 4 in the StackOverFlow post)
  • Support for parameters (like top 10 items etc...)
  • Process the request in a reusable manner (Response as stream, string, feed, ...)

That's why we'll use the following class that encapsulates all these features:

 

    public class GoogleSession : IDisposable

    {

        /// <summary>

        /// Auth token.

        /// </summary>

        private string auth;

 

        /// <summary>

        /// Initialize request.

        /// </summary>

        /// <param name="auth"></param>

        public GoogleSession(string auth)

        {

            this.auth = auth;

        }

 

        /// <summary>

        /// Create a google request and get the response.

        /// </summary>

        /// <param name="url"></param>

        /// <param name="parameters"></param>

        /// <returns></returns>

        public WebResponse GetResponse(string url, params GoogleParameter[] parameters)

        {

            // Format the parameters.

            string formattedParameters = string.Empty;

            foreach (var par in parameters)

                formattedParameters += string.Format("{0}={1}&", par.Name, par.Value);

            formattedParameters = formattedParameters.TrimEnd('&');

 

            // Create a request with or without parameters.

            HttpWebRequest request = null;

            if (formattedParameters.Length > 0)

                request = (HttpWebRequest)WebRequest.Create(string.Format("{0}?{1}",

                    url, formattedParameters));

            else

                request = (HttpWebRequest)WebRequest.Create(url);

 

            // Add the authentication header.

            request.Headers.Add("Authorization", "GoogleLogin auth=" + auth);

 

            // Get the response, validate and return.

            HttpWebResponse response = request.GetResponse() as HttpWebResponse;

            if (response == null)

                throw new GoogleResponseNullException();

            else if (response.StatusCode != HttpStatusCode.OK)

                throw new GoogleResponseException(response.StatusCode,

                    response.StatusDescription);

            return response;

        }

 

        /// <summary>

        /// Create a google request and get the response stream.

        /// </summary>

        /// <param name="url"></param>

        /// <param name="parameters"></param>

        /// <returns></returns>

        public Stream GetResponseStream(string url, params GoogleParameter[] parameters)

        {

            return GetResponse(url, parameters).GetResponseStream();

        }

 

        /// <summary>

        /// Create a google request and get the page source.

        /// </summary>

        /// <param name="url"></param>

        /// <param name="parameters"></param>

        /// <returns></returns>

        public string GetSource(string url, params GoogleParameter[] parameters)

        {

            using (StreamReader reader = new StreamReader(

                GetResponseStream(url, parameters)))

            {

                return reader.ReadToEnd();

            }

        }

 

        /// <summary>

        /// Create a google request and get the feed.

        /// </summary>

        /// <param name="url"></param>

        /// <param name="parameters"></param>

        /// <returns></returns>

        public SyndicationFeed GetFeed(string url, params GoogleParameter[] parameters)

        {

            // Load the stream into the reader.

            using (StreamReader reader = new StreamReader(

                GetResponseStream(url, parameters)))

            {

                // Create an xml reader out of the stream reader.

                using (XmlReader xmlReader = XmlReader.Create(reader,

                    new XmlReaderSettings()))

                {

                    // Get a syndication feed out of the xml.

                    return SyndicationFeed.Load(xmlReader);   

                }               

            }

        }

 

        /// <summary>

        /// Clean up the authentication token.

        /// </summary>

        public void Dispose()

        {

            auth = null;

        }

    }

 

What you can do with it:

  • Create an authenticated response supporting parameters
  • Get this response as a stream
  • Get this response as a feed
  • Get this response as a string (raw source)

Putting it all together

Now that we can easily authenticate and make requests let's make use of it. Here is an example of a command line application showing the last 5 articles from Google Reader.

 

    class Program

    {

        static void Main(string[] args)

        {

            // Empty line.

            Console.WriteLine("");

 

            // Get username.

            Console.Write(" Enter your Google username: ");

            string username = Console.ReadLine();

 

            // Get password.

            Console.Write(" Enter your password: ");

            string password = Console.ReadLine();

 

            // Authenticate.

            string auth = ClientLogin.GetAuthToken("reader", username, password, "Sandworks.Google.App");

 

            // Query.

            using (GoogleSession session = new GoogleSession(auth))

            {

                var feed = session.GetFeed("http://www.google.com/reader/atom/user/-/state/com.google/reading-list",

                    new GoogleParameter("n", "5"));

 

                // Display.

                Console.WriteLine("");

                Console.WriteLine(" Last 5 articles in Google Reader: ");

                foreach (var item in feed.Items)

                {

                    Console.WriteLine("  - " + item.Title.Text);

                }

            }

 

            // Pause.

            Console.ReadLine();

        }

    }

 

What it does:

  • Ask for your Google account
  • Ask for your password
  • Authenticate using ClientLogin and get the Auth token.
  • Create a new GoogleSession using the Auth token.
  • Connect to Google Reader and get the last 5 articles (using a GoogleParameter).
  • Display those articles.

And the result:

Don't worry about the Google Reader url for now. In the next article I'll talk about creating a class to talk to Google reader and how the URLs work. 

Download: Sandworks.Google.zip (60.77 kb)

Enjoy..

Posted: Jul 06 2010, 12:01 by Sandrino | Comments (1) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development
Visual Studio 2010 on your desktop: Themes and wallpapers

Yesterday I found these 3 great sites on Visual Studio 2010.

  • Visual Studio 2010 Community Wallpapers: http://vs2010wallpapers.com/
     


    This site has a tremendous amount of wallpapers on Visual Studio 2010.
    Only downside: They all have a pretty low resolution.
     
  • Visual Studio 2010 Themes for Windows 7: http://closeup.jp.msn.com/visualstudio2010/
     


    This site offers a few themes for Windows 7 and each theme contains multiple wallpapers containing a subtle Visual Studio 2010 logo and was created by Microsoft Japan. I'm already running the Highway theme!
     
  • Studio Styles: http://studiostyles.info/ (UPDATE 11:15)
     
     
     
    I almost forgot about this one. This site has nothing to do with your desktop but it was worth mentioning. StudioStyles allows you to rate, download and share Visual Studio color shemes.

Enjoy...

 

Posted: Jul 02 2010, 10:58 by Sandrino | Comments (0) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development
New SharePoint 2010 Exams Available in July

Exams

In the last MCP flash Microsoft announced the availability of SharePoint 2010 Exams:

These exams will be available July 12th.

Resources

To study for the exams you'll need books.
But the only decent book (already available) I found was Beginning SharePoint 2010 Administration: Windows SharePoint Foundation 2010 and Microsoft SharePoint Server 2010.

Aside from that book you'll want to get  a Safari Books Online subscription.
They offer "Rough Cuts" of books still in development and also online video courses.

There are also the free online videos:

And finally there's the community. Blogs, community sites, CodePlex, ...
This is also a great resource to get acquainted to SharePoint.

Enough material to start with your preparations!

Posted: Jun 18 2010, 09:35 by Sandrino | Comments (1) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: SharePoint
Complete Expression Blend and Silverlight 5 day OnRamp training course: All videos, all assets, download scripts in one place

Since my last post with a few assets regarding the Expression Blend and Silverlight 5 day OnRamp training course I received a lot of mails from people asking a download link for the training.
After a long long search I finally found the training course on the official Expression site.

This article lists all the videos and all the assets available on the Expression site and Microsoft.com downloads.
At the end of the article you'll also find a short guide and scripts on how to download everything in batch. 

Day 1

Part 1: An introduction to Silverlight and Expression Blend (part 1)

Part 2: Learn how to keep precise control over your application UI using dynamic layout
Part 3: Use your existing Photoshop and Illustrator skills with Silverlight and Expression Blend

Day 2

Part 1: Learn how to use Sketchflow to rapidly prototype, present and receive feedback on your idea
Part 2: Dive deeper into the platform and tools: An introduction to Silverlight and Blend (part 2)
Part 4: Transferring your skills from Flash to Silverlight (Part 2)
Part 3: Using animation and interactivity to bring your application to life
Part 4: Project 1: Let's use the skills you've learnt in a real project

Day 4

Part 1: Working with Data when designing your UI
Part 2: Creating Editing and Incorporating Video into Silverlight project with Expression Encoder
Part 3: HTML Integration and Publishing
Part 4: Project 2: Create a WordPress site using HTML, the Microsoft Web Platform and the skills you’re now familiar with in Expression Blend and SketchFlow
Part 5: Top gotcha's when working with Silverlight and Expression Blend

Assets

The following new downloads are available:

Older downloads that were posted before:

Download Everything

To download all the assets you need the following:

To download all the videos you need the following:

Why not just use cURL you say? The videos cannot be downloaded since they are streamed with MMS. To be able to download them you need a tool (like SDP) to process the streaming and save it to file.
SDP is interesting because it allows to import .asx files. This is like an xml playlist. I created one containing all the videos.

To download all the videos:

  1. Open SDP
  2. Press the Open button
  3. Open the .asx file
  4. Press Batch
  5. All files will be downloaded

I noticed that sometimes the download breaks and the file does not completely download. Changing the protocol in SDP from TCP to HTTP seemed to improve the download stability.

After downloading all the files you'll see a dirty file list:

And then after running the rename script you'll see the clean file list:

 

Notes

  • Part 3 and 4 of Day 2 are not videos but online labs (they cannot be downloaded).
  • The .asx file has 6 missing files. If you run the rename script it will create *.missing files so you know what files are missing.
  • Day 5 is not online yet. This post will be updated when the videos are published.
  • I'll see if and how I can upload all videos somewhere so that a cURL script can be used to download them all

Enjoy and happy learning!

Posted: Jun 07 2010, 11:36 by Sandrino | Comments (5) RSS comment feed |
  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Filed under: Development