Tuesday, March 8, 2011

Email Alerts in Team Foundation Server

If you ever wonder how to send an E-Mail notification to a user participating in the event in TFS, the solution would be a little bit tricky for a flexible, extensible mechanism for subscription and notification. One of the easiest ways to achieve this is using the Event Service to listen to the event, and then send the notification.

These are some of the situations you may want to have this feature out of box:

  • Notify the developer checked in when the build is failed;
  • Notify the reporters when a bug work item is changed by others.

Obviously, programing a web service or event handler for each of these cases requires a lot of work. It would be nice to utilize the subscription service in TFS, so that we don't have to worry about filtering and formatting events. Here are the steps:

  1. Listen the target event;
  2. Find out the E-Mail address of the user;
  3. Subscribe the user to the same event with proper filter.

In the following example, I am going to handle the WorkItemChangedEvent to notify the reporters when a bug work item is changed by others, with the helps of TeamFoundationIdentityService and TeamFoundationNotificationService. (Sadly, the documentation on MSDN doesn't actually tell you anything more than Object Browser regarding the TFS server API.)

Before performing any task, we need to get notify when the a work item has been updated. This is done by implementing the ISubscriber interface.

public sealed class EmailAlertSubscriber2 : ISubscriber
{

    public string Name
    {
        get { return "EmailAlertSubscriber" }
    }

    public SubscriberPriority Priority
    {
        get { return SubscriberPriority.Normal }
    }

    public EventNotificationStatus ProcessEvent(
        TeamFoundationRequestContext requestContext, 
        NotificationType notificationType, 
        object notificationEventArgs, 
        out int statusCode, 
        out string statusMessage,
        out ExceptionPropertyCollection properties) 
    {
        statusCode = 0;
        statusMessage = null;
        properties = null;

        if (notificationType == NotificationType.Notification)
        {
            var eventArgs = notificationEventArgs as WorkItemChangedEvent;
            if (eventArgs != null)
            {
                // Process event...
            }
        }

        return EventNotificationStatus.ActionPermitted;
    }

    public Type[] SubscribedTypes()
    {
        return new Type[] { typeof(WorkItemChangedEvent) };
    }
}

Next, we can retrieve the value of person field form notificationEventArgs. As per our example, I need the user who created the bug work item.

var createdByDisplayName = 
    eventArgs
    .CoreFields
    .StringFields
    .Single(
        f => f.ReferenceName.Equals(
            "System.CreatedBy", StringComparison.OrdinalIgnoreCase))
    .NewValue;

The field value may be a display name, domain account or Sid (Security Identifier), but the base steps to retrieve E-Mail address are the same - read the TeamFoundationIdentity; and then load the E-Mail address from Active Directory. Just make sure you reading the E-Mail address from the correct domain.

var identityService = requestContext.GetService<TeamFoundationIdentityService>();

// Read TFS identity by display name.
var tfsId = identityService.ReadIdentity(
    requestContext, 
    IdentitySearchFactor.DisplayName, 
    displayName, 
    MembershipQuery.None, ReadIdentityOptions.None);

// Read TFS identity by account name.
var tfsId = identityService.ReadIdentity(
    requestContext, 
    IdentitySearchFactor.AccountName, 
    accountName, 
    MembershipQuery.None, ReadIdentityOptions.None);

// Read TFS identity by Sid.
var idDescriptor = IdentityHelper.CreateDescriptorFromSid(sid);
var tfsId = identityService.ReadIdentity(
    requestContext, 
    idDescriptor, 
    MembershipQuery.None, ReadIdentityOptions.None);

var domainName = IdentityHelper.GetDomainName(identity);

using (var context = new PrincipalContext(ContextType.Domain, domainName))
{
    var userPrincipal = UserPrincipal.FindByIdentity(
        context, IdentityType.Sid, identity.Descriptor.Identifier);

    // Now we got the E-Mail address as userPrincipal.EmailAddress;
}

Now, we have all the information we need to subscribe the user to event. In the following code snippet, the classification is used to identify the subscription. It likely contains the event type and user identifier, so that the handler won't subscribe the user every time the event fired. On the other hand, we may supply a filter expression limiting the E-Mail going out.

var classification = // Identifer for the subscription.

var notificationService = requestContext.GetService<TeamFoundationNotificationService>();
var subscriptions = notificationService.GetEventSubscriptions(
    requestContext, 
    userIdentity.Descriptor,
    classification);

// Check for whether the subscription already exists.
if (subscriptions.Count == 0)
{
    var filterExpression = // Filter used to limit the E-Mail notifications.

    var perference = new DeliveryPreference()
    {
        Address = userPrincipal.EmailAddress,
        Schedule = DeliverySchedule.Immediate,
        Type = DeliveryType.EmailHtml
    };

    notificationService.SubscribeEvent(
        requestContext,
        userIdentity.Descriptor, 
        "WorkItemChangedEvent", 
        filterExpression, 
        perference, 
        classification);
}

After all these, the handler is ready to be deployed. To use it, simply compile the code to an assembly, and copy it to %Program Files%\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins of the TFS server. The E-Mail will start filling up your mail box now.

You may want to unsubscribe the user, such as when the work item is closed. This can be done by:

if (subscriptions.Count > 0)
{
    foreach (var subscription in subscriptions)
    {
        requestContext.GetService<TeamFoundationNotificationService>()
            .UnsubscribeEvent(requestContext, subscription.ID);
    }
}

The problem here is that the E-Mail is send by the delayed job. By the time the job being executed, the subscription is removed, and the user won't get notify when the work item is closed. Interestingly, this delay is exactly what ensuring the user get notify the first time when we subscribing the user.

If you don't mind the event subscriptions table get fill up, or you willing to clean the table once a while. This approach will work quite well.

So, in order to find a better solution, I will show you how to filter events using VSEFL , and then wire up everything in other posts.

Monday, May 10, 2010

Comsuming WCF Services With Android

It seems processing XML is too heavy for mobile devices. Android did not provide any tool to help consuming SOAP web service. But as Android bundled with org.apache.http and org.json packages, it is relative simple to consume RESTful WCF services.

The following sections describe the steps to create RESTfule WCF services and the Android client to consume the services.

First, I created a service contract with two GET and one POST operations. Since the Android client will transfer data in JSON objects, I specify JSON as request and response format. In order to support more than one parameter, I set BodyStyle to WrappedRequest.

namespace HttpWcfWeb
{
    [ServiceContract(Namespace = "http://services.example.com")]
    public interface IVehicleService
    {
        [OperationContract]
        [WebGet(
            UriTemplate = "GetPlates",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        IList<string> GetPlates();

        [OperationContract]
        [WebGet(UriTemplate = "GetVehicle/{plate}",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        Vehicle GetVehicle(string plate);

        [OperationContract]
        [WebInvoke(
            Method = "POST",
            UriTemplate = "SaveVehicle",
            BodyStyle = WebMessageBodyStyle.WrappedRequest,
            ResponseFormat = WebMessageFormat.Json,
            RequestFormat = WebMessageFormat.Json)]
        void SaveVehicle(Vehicle vehicle);
    }
}

Next, I defined the composite object will be transferred between server and Android client. It is simple but enough to prove we will be able to transfer complex objects.

namespace HttpWcfWeb
{
    [DataContract]
    public class Vehicle
    {
        [DataMember(Name = "year")]
        public int Year
        {
            get;
            set;
        }

        [DataMember(Name = "plate")]
        public string Plate
        {
            get;
            set;
        }

        [DataMember(Name = "make")]
        public string Make
        {
            get;
            set;
        }

        [DataMember(Name = "model")]
        public string Model
        {
            get;
            set;
        }
    }
}

Now, expose the WCF service via webHttp behavior in web.config.

<system.serviceModel>
  <behaviors>
    <endpointBehaviors>
      <behavior name="httpBehavior">
        <webHttp />
      </behavior>
    </endpointBehaviors>
    <serviceBehaviors>
      <behavior name="">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="false" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    <services>
      <service name="HttpWcfWeb.VehicleService">
        <endpoint address=""
                     behaviorConfiguration="httpBehavior"
                     binding="webHttpBinding"
                     contract="HttpWcfWeb.IVehicleService" />
    </service>
  </services>
</system.serviceModel>

It you are using Visual Studio's Development Server to test the WCF service, you may need to deploy the service to IIS. This is due to the Development Server only serve request from local machine, and the Android client won't be able to access the service hosted on it.

Further, if you are using host name (e.g. computer name) in the URL of the service, you may have to setup the DNS in you device or emulator, so that it can resolve the host name. Simply go to Settings -> Wireless Control -> Mobile Networks -> Access Point Names, click on the one that is in use, fill in Proxy and Port with your DNS server.
Setup Proxy and Port

Now, I have my WCF service ready, and I am going to build the Android client to consume the WCF service.
Android UI

During initialization, the Activity will invoke IVehicleService.GetPlates method to populate the Spinner. When the Load Vehicle button is clicked, the vehicle will be loaded from the IVehicleService.GetVehicle method and the EditText views will be populated. On the other hand, Save button will wrap the data entered and post to IVehicleService.SaveVehicle method.

The code the initialize the UI I created.

public class MainActivity extends Activity {

    private final static String SERVICE_URI = "http://lt0.studio.entail.ca:8080/VehicleService.svc";

    private Spinner plateSpinner;
    private EditText makeEdit;
    private EditText plateEdit;
    private EditText yearEdit;
    private EditText modelEdit;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        plateSpinner = (Spinner)findViewById(R.id.plate_spinner);
        makeEdit = (EditText)findViewById(R.id.make_edit);
        plateEdit = (EditText)findViewById(R.id.plate_edit);
        yearEdit = (EditText)findViewById(R.id.year_edit);
        modelEdit = (EditText)findViewById(R.id.model_edit);
    }
    
    @Override
    public void onResume() {
        super.onResume();

        // Invoke IVehicleService.GetPlates and populate plateSpinner
        refreshVehicles();
    }
}

The refreshVehicles method will be invoked when the activity is resumed or a new vehicle is saved. It send a GET request to the WCF service and retrieves a list of plates in JSON string, and the response string is parsed by JSONArray.

    private void refreshVehicles() {
        try {

            // Send GET request to <service>/GetPlates
            HttpGet request = new HttpGet(SERVICE_URI + "/GetPlates");
            request.setHeader("Accept", "application/json");
            request.setHeader("Content-type", "application/json");

            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpResponse response = httpClient.execute(request);

            HttpEntity responseEntity = response.getEntity();
            
            // Read response data into buffer
            char[] buffer = new char[(int)responseEntity.getContentLength()];
            InputStream stream = responseEntity.getContent();
            InputStreamReader reader = new InputStreamReader(stream);
            reader.read(buffer);
            stream.close();

            JSONArray plates = new JSONArray(new String(buffer));

            // Reset plate spinner
            ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item);
            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            for (int i = 0; i < plates.length(); ++i) {
                adapter.add(plates.getString(i));
            }
            plateSpinner.setAdapter(adapter);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

The onLoadVehicleClick method is the event handler for Load Vehicle button. Just like refreshVehicles method, It send a GET request to the WCF service and retrieve the vehicle information by plate number. But instead of JSONArray, it used JSONObject to parse the response data, since the WCF service is returning an vehicle object.

    public void onLoadVehicleClick(View button) {
        try {
            // Send GET request to <service>/GetVehicle/<plate>
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpGet request = new HttpGet(SERVICE_URI + "/GetVehicle/" + plateSpinner.getSelectedItem());

            request.setHeader("Accept", "application/json");
            request.setHeader("Content-type", "application/json");

            HttpResponse response = httpClient.execute(request);

            HttpEntity responseEntity = response.getEntity();

            // Read response data into buffer
            char[] buffer = new char[(int)responseEntity.getContentLength()];
            InputStream stream = responseEntity.getContent();
            InputStreamReader reader = new InputStreamReader(stream);
            reader.read(buffer);
            stream.close();

            JSONObject vehicle = new JSONObject(new String(buffer));

            // Populate text fields
            makeEdit.setText(vehicle.getString("make"));
            plateEdit.setText(vehicle.getString("plate"));
            modelEdit.setText(vehicle.getString("model"));
            yearEdit.setText(vehicle.getString("year"));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

When Save button is clicked, onSaveVehicleClick method will be invoked. It simply gather all text fields into a JSONObject and post it to the WCF service. Noticed that the all the data was wrapped into an object named vehicle, WCF will pass this object as parameter vehicle.

    public void onSaveVehicleClick(View button) {

        try {

            Editable make = makeEdit.getText();
            Editable plate = plateEdit.getText();
            Editable model = modelEdit.getText();
            Editable year = yearEdit.getText();

            boolean isValid = true;

            // Data validation goes here

            if (isValid) {

                // POST request to <service>/SaveVehicle
                HttpPost request = new HttpPost(SERVICE_URI + "/SaveVehicle");
                request.setHeader("Accept", "application/json");
                request.setHeader("Content-type", "application/json");

                // Build JSON string
                JSONStringer vehicle = new JSONStringer()
                    .object()
                        .key("vehicle")
                            .object()
                                .key("plate").value(plate)
                                .key("make").value(make)
                                .key("model").value(model)
                                .key("year").value(Integer.parseInt(year.toString()))
                            .endObject()
                        .endObject();
                StringEntity entity = new StringEntity(vehicle.toString());

                request.setEntity(entity);

                // Send request to WCF service
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpResponse response = httpClient.execute(request);

                Log.d("WebInvoke", "Saving : " + response.getStatusLine().getStatusCode());
                
                // Reload plate numbers
                refreshVehicles();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

Finally, add the internet permission into AndroidManifest.xml, to allow the sample application access WCF service.

    <uses-permission android:name="android.permission.INTERNET" />
    

And the demo is ready to go.
Android Application

Tuesday, January 12, 2010

A Silverlight Text Diff Control

Text diff tool is really useful for checking the updates of versioning files. On the web, it is widely used in wiki systems to see the changes of articles. It may also useful for tracing forum posts, page updates of CMS, etc.

This article will describes how to create a simple text diff control in Silverlight. Before going into the details, this is screen shot of our text diff control.
Text Diff Control Screen Shot

This control contains two major parts, the core algorithm to do the calculation job, and the user interface to display the result.

Diff Algoirithms

The purpose of diff algorithm is to compute the Shortest Edit Script of two list of equalable items. Hence, all of our algorithm will implement the following IDiffEngine interface.

/// <summary>
/// Supports aligning two sequences.
/// </summary>
public interface IDiffEngine
{
    IList<DiffResult> Align<T>(IList<T> from, IList<T> to, IEqualityComparer<T> comparer);
}

/// <summary>
/// Represents a particular elements alignment result.
/// </summary>
public enum DiffResult
{
    /// <summary>
    /// Two elements are considered as the same.
    /// </summary>
    Matched,

    /// <summary>
    /// The element in target sequence is inserted to the result alignment.
    /// </summary>
    Inserted,

    /// <summary>
    /// The element in original sequence is inserted to the result alignment.
    /// </summary>
    Deleted
}
    

Other than this, we need an interface accepting string list, in other word, lines of a text. It is because we may have extensions specified to text, i.e. spacing handling, case sensitivity, etc.

/// <summary>
/// Supports aligning two list of strings.
/// </summary>
public interface ITextDiffEngine
{
    IList<DiffResult> Algin(IList<string> from, IList<string> to);
}
    

Considering implementation, a widely used diff algorithm is the one described by Eugene W. Myers, in his paper, "An O(ND) Difference Algorithm and Its Variations". A simple implementation is included in the sample, which has O((N + M)D) time and space complexity. This article won't explain the details of this algorithm, but a good tutorial is available on The Code Project by Nicholas Butler.

Another algorithm in our control is Needleman-Wunsch algorithm which is commonly use for sequence alignment. This algorithm has complexity O(N + M) in both time and space. Now, we can verify both algorithms by comparing the outputs.

User Interface

As the screen shot shown previously, the control UI is similar with some text diff applications. It is using two text blocks to display the texts comparing, two scroll bars used for scrolling text blocks, and a bar on the left to summarize the diff result. Further, the text blocks is wrapping in side scroll viewer so that the scroll bar can hook on them.

Let's define a Silverlight templated control DiffViewer, with the sub controls addressed above. These controls are retrieved in OnApplyTemplate method.

/// <summary>
/// Represents the text diff control.
/// </summary>
[TemplatePart(Name = RightTextBlockElement, Type = typeof(TextBlock))]
[TemplatePart(Name = LeftTextBlockElement, Type = typeof(TextBlock))]
[TemplatePart(Name = DiffBrushElement, Type = typeof(LinearGradientBrush))]
[TemplatePart(Name = HorizontalScrollBarElement, Type = typeof(ScrollBar))]
[TemplatePart(Name = VerticalScrollBarElement, Type = typeof(ScrollBar))]
[TemplatePart(Name = RightTextScrollViewerElement, Type = typeof(ScrollViewer))]
[TemplatePart(Name = LeftTextScrollViewerElement, Type = typeof(ScrollViewer))]
public class DiffViewer : Control
{
    /// <summary> ...
    private const string RightTextBlockElement = "RightTextBlockElement";

    /// <summary> ...
    private const string LeftTextBlockElement = "LeftTextBlockElement";

    /// <summary> ...
    private const string DiffBrushElement = "DiffBrushElement";

    /// <summary> ...
    private const string HorizontalScrollBarElement = "HorizontalScrollBarElement";

    /// <summary> ...
    private const string VerticalScrollBarElement = "VerticalScrollBarElement";

    /// <summary> ...
    private const string RightTextScrollViewerElement = "RightTextScrollViewerElement";

    /// <summary> ...
    private const string LeftTextScrollViewerElement = "LeftTextScrollViewerElement";
    
    /// <summary> ...
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        // Lookup template parts
        rightTextBlock = GetTemplateChild(RightTextBlockElement) as TextBlock;
        leftTextBlock = GetTemplateChild(LeftTextBlockElement) as TextBlock;
        horizontalScrollBar = GetTemplateChild(HorizontalScrollBarElement) as ScrollBar;
        verticalScrollBar = GetTemplateChild(VerticalScrollBarElement) as ScrollBar;
        leftTextScrollViewer = GetTemplateChild(LeftTextScrollViewerElement) as ScrollViewer;
        rightTextScrollViewer = GetTemplateChild(RightTextScrollViewerElement) as ScrollViewer;
        diffBrush = GetTemplateChild(DiffBrushElement) as LinearGradientBrush;
        
        leftTextBlock.SizeChanged += TextBlockSizeChanged;
        rightTextBlock.SizeChanged += TextBlockSizeChanged;

        verticalScrollBar.Scroll += VerticalScrollBarScroll;
        horizontalScrollBar.Scroll += HorizontalScrollBarScroll;

        templatedApplied = true;

        UpdateComparison();
    }
        
    /// ...
}
    

In OnApplyTemplate method, I also added event handlers to SizeChanged and Scroll event of text blocks and scroll bars. Essentially, the scroll bars will be adjusted according to the size of text blocks, and the viewport of text blocks will be moved according to the position of scroll bars.

public class DiffViewer : Control
{
    /// <summary> ...
    private void HorizontalScrollBarScroll(object sender, ScrollEventArgs e)
    {
        // Scroll text blocks horiontally
        leftTextScrollViewer.ScrollToHorizontalOffset(horizontalScrollBar.Value);
        rightTextScrollViewer.ScrollToHorizontalOffset(horizontalScrollBar.Value);
    }

    /// <summary> ...
    private void VerticalScrollBarScroll(object sender, ScrollEventArgs e)
    {
        // Scroll text blocks vertically
        leftTextScrollViewer.ScrollToVerticalOffset(verticalScrollBar.Value);
        rightTextScrollViewer.ScrollToVerticalOffset(verticalScrollBar.Value);
    }

    /// <summary> ...
    private void TextBlockSizeChanged(object sender, SizeChangedEventArgs e)
    {
        UpdateScrollBars();
    }

    /// <summary> ...
    private void UpdateScrollBars()
    {
        // Horizontal scroll bar
        var viewportWidth = Math.Max(leftTextScrollViewer.ViewportWidth, rightTextScrollViewer.ViewportWidth);
        var extentWidth = Math.Max(leftTextScrollViewer.ExtentWidth, rightTextScrollViewer.ExtentWidth);
        horizontalScrollBar.ViewportSize = viewportWidth;
        horizontalScrollBar.Maximum = extentWidth - viewportWidth;

        // Vertical scroll bar
        var viewportHeight = Math.Max(leftTextScrollViewer.ViewportHeight, rightTextScrollViewer.ViewportHeight);
        var extentHeight = Math.Max(leftTextScrollViewer.ExtentHeight, rightTextScrollViewer.ExtentHeight);
        verticalScrollBar.ViewportSize = viewportHeight;
        verticalScrollBar.Maximum = extentHeight - viewportHeight;
    }
        
    /// ...
}

Then, we need the dependency properties for two text inputs and the ITextDiffEngine instance to be used. A static method UpdateComparison is tracing these properties and delegates to namesake method of the DiffView instance.

public class DiffViewer : Control
{
    /// <summary> ...
    public static readonly DependencyProperty LeftTextProperty =
        DependencyProperty.Register("LeftText", typeof(string), typeof(DiffViewer), new PropertyMetadata(UpdateComparison));

    /// <summary> ...
    public static readonly DependencyProperty RightTextProperty =
        DependencyProperty.Register("RightText", typeof(string), typeof(DiffViewer), new PropertyMetadata(UpdateComparison));

    /// <summary> ...
    public static readonly DependencyProperty DiffEngineProperty =
        DependencyProperty.Register("DiffEngine", typeof(ITextDiffEngine), typeof(DiffViewer), new PropertyMetadata(UpdateComparison));

    /// <summary> ...
    public string LeftText
    {
        get { return (string)GetValue(LeftTextProperty); }
        set { SetValue(LeftTextProperty, value); }
    }

    /// <summary> ...
    public string RightText
    {
        get { return (string)GetValue(RightTextProperty); }
        set { SetValue(RightTextProperty, value); }
    }

    /// <summary> ...
    public ITextDiffEngine DiffEngine
    {
        get { return (ITextDiffEngine)GetValue(DiffEngineProperty); }
        set { SetValue(DiffEngineProperty, value); }
    }
    
    private static void UpdateComparison(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
    {
        var diffView = dependencyObject as DiffViewer;
        if (diffView != null)
        {
            if (diffView.templatedApplied && diffView.AutoUpdate)
            {
                diffView.UpdateComparison();
            }
        }
    }
        
    /// ...
}
    

Finally, UpdateComparison method will invokes the diff engine and displaying result. Inside the method, each line in both texts will be aligned and added to text blocks as Inline objects. The color brush will be draw in the same time as well.

public class DiffViewer : Control
{
    /// <summary> ...
    public void UpdateComparison()
    {
        // ...

        // Compute alignment
        var diffSolution = engine.Algin(leftTextLines, rightTextLines);

        // ...

        for (int i = 0; i < solutionLength; ++i)
        {
            var lineResult = diffSolution[i];

            // Append inlines according to result
            switch (lineResult)
            {
                case DiffResult.Matched:
                    // ...
                    break;
                case DiffResult.Deleted:
                    // ...
                    break;
                case DiffResult.Inserted:
                    // ...
                    break;
            }


            // ...

            if (lastResult != lineResult)
            {
                // End previous color
                gradientStops.Add(CreateGradientStop(lastResult, i, solutionLength));

                // Start new color
                gradientStops.Add(CreateGradientStop(lineResult, i, solutionLength));

                lastResult = lineResult;
            }
        }
        
        // ...
    }
        
    /// ...
}
    

If you have Silverlight 3 installed, you should be able to see the sample application running underneath. Feel free to have a try!

Get Microsoft Silverlight

Monday, January 4, 2010

Converting XPS to PDF Programmatically

I am working on a simple utility tool which pulls data from database and then generates reports as E-Mail attachments. An additional requirement is to exam the reports before sending them out. Hence, I want a document format that meets these criteria.

  • Easy to generate;
  • Can be review in Win Form program;
  • Widely support by client computers.

Using WPF, XPS is the best choice for first two requirements. Precisely, generating a FlowDocument is as simple as writing HTML, and XSLT can translate any XML data into XPS flow document. On the other side, FixedDocument provides the same feel as a print out.

However, XPS requires a viewer installed. Some of our recipients may not have the privilege to install the viewer. So I would like to send out a PDF instead of XPS.

Converting FlowDocument to FixedDocument

In order to review the exact same documents as the ones sending out. We need to first convert the flow document to fixed document. A easy way is simply save the flow document by setting it"s page size.

var source = (IDocumentPaginatorSource)flowDoc;
var paginator = source.DocumentPaginator;

// Letter size
paginator.PageSize = new Size(8.5 * 96, 11 * 96);

using (XpsDocument doc = new XpsDocument(tempXpsPath, FileAccess.Write))
{
    var writer = XpsDocument.CreateXpsDocumentWriter(doc);
    writer.Write(paginator);
}

Now, use a DocumentViewer to display it in WPF. Here is the result:
XPS Viewer

If you would like to do it in memory, Here is an article from Tamir Khason. But I will need the temprary XPS files for next step anyway.

Converting XPS to PDF

This part is much more challenging. I would like to do a trick to get my PDFs, rather than read through the XPS/PDF specifications. I am using GhostPCL to convert XPS to PDF. Download and build GhostXPS following the instruction, then copy gxps.exe (under xps/obj folder) to our project.

Now, we may call gxps to convert the temporary XPS to PDF.

// Convert XPS to PDF using gxps
ProcessStartInfo gxpsArguments = new ProcessStartInfo("gxps.exe", String.Format("-sDEVICE=pdfwrite -sOutputFile={0} -dNOPAUSE {1}", path, tempXpsPath));
gxpsArguments.WindowStyle = ProcessWindowStyle.Hidden;
using (var gxps = Process.Start(gxpsArguments))
{
    gxps.WaitForExit();
}

The sample program can be download here. Hope this help!

Friday, January 1, 2010

Sharing ADSL using RRAS

Lately, I found that my router keep dropping packages because the session table running out pretty fast, makes the internet almost unusable. So that I decided to configure a machine to do the job. Plus, this "super" router will provide me the functionalities that only available on high-end routers.

The goad is simple: sharing the ADSL connection with LAN devices. These are related devices I used:

  • ADSL modem of course;
  • A machine with two NICs and Routing and Remote Access (RRAS) enabled, also act as DHCP and DNS server;
  • Switch connecting LAN devices.

The IP address of ADSL modem is set to 192.168.1.1, so NIC 1 on the server, which connecting to the modem, is configured as:
IP address: An IP in the same subnet with modem, in my case 192.168.1.18
Subnet mask: 255.255.255.0
DNS server: Any DNS server IP, blank as I am using the same machine
NIC configuration

NIC 2 is connected to the Switch and here is how it configured:
IP address: An IP in LAN subnet, in my case 192.168.0.18
Subnet mask: 255.255.255.0
DNS server: Any DNS server IP, blank as I am using the same machine

Make sure DNS service is enabled and proper configured. Currently, I am simply forwarding any DNS requests to Google Public DNS.
DNS Forwarding

Next, is to setup DHCP. The progress is straightforward. Just make sure Router and DNS Servers are proper configured in Scope Options. Another thing I would like to mention is the IP reservation function. It is pretty cool if some of your devices will be used in different LANs (most likely mobile devices). It allows your devices configured to get IP from DHCP but still having a static IP in your own LAN. Unfortunately, this function is not widely available in budgeted routers.
IP Reservation

Now, enable RRAS and follow the wizard.
RRAS Wizard

Select NAT.
Configurate RRAS as NAT

Here, make sure the second option "Create a new demand-dial interface to the Internet" is selected.
Create a new demand-dial interface to the Internet

Select the NIC that connected to switch.
LAN NIC

Choose PPPoE for the ADSL connection.
RRAS Wizard

Fill in account information.
RRAS Wizard

After the wizard ended. Go to Newwork Interfaces, verify the properties of the connection. You may want to set it as persistent connection and adjust the dialing policy.
RRAS Wizard

If you encounter error "Access was denied because the username and/or password was invalid on the domain.", try different security setting. For my ISP, I will have to set it as "Allow unsecured password".

Now, the setup has been done. But make sure the "Router" has proper security mechanisms, since it is exposed to Internet.

Have fun!