Silverlight - the magic of Silverlight RIA Toolkit

 

I checked in some code.  Moments later, my colleague asked me:

"did you check in the service reference for this ServiceReference.DispatchKing.Web.Services.RunboardDuplexService"

I pondered, and answered:

"no need - it was generated for me"

In deed - my Silverlight project started to do something magical for me.  It was creating GeneratedWcfClientCode\ServiceReference.cs

Because this was generated like RIA services, it was not included in the project, and thus - if you didn't have the right tools installed, it appears that your colleague has forgotten to check in files!

 

image

 

Memory Lane

In the old days before RIA services, we rolled out WCF Service and added our own Service Reference via the Silverlight project.  This generated all sorts of service reference proxy classes.

The biggest downside, is that each time we updated the service, we had to regenerate the service reference, otherwise they'd become out of sync and Silverlight will crash and burn.  (or in the case where it didn't crash… we got really scared).

 

In Silverlight 3 we got RIA and the DomainService, having Silverlight project linked to a Web Project meant that Visual Studio automatically started generating all the RIA/DomainService code, as well as the data objects (or entity) required, and making sure everything's available and synchronized between the Web (service) and Silverlight (client).  There was much rejoicing.

What's this magic?

What made this case really special then, is that I'm not using only Domain Services.

image

 

Hmm it doesn't work for my colleague.  We quickly compared out installed Programs and Features, and found out why:

image

 

You can grab this via Web platform installer, or directly from:

http://www.microsoft.com/downloads/en/details.aspx?FamilyID=7b43bab5-a8ff-40ed-9c84-11abb9cda559&displaylang=en

The RIA Services Toolkit enables the following 5 features:

 

1. LinqToSql DomainService
2. Soap endpoint - This enables you to expose a soap endpoint for your DomainService
3. JSON endpoint - This enables you to expose a JSON endpoint for your DomainService
4. ASP.net DomainDataSource - This control will enable you to create an ASP.net application that can talk to your DomainService
5. WCF client proxy auto generation/updating for WCF Core Service – This enables you to get up-to-date WCF proxy and configuration each time you build your solution, when you add Silverlight-enable WCF service in your Silverlight application.

 

The magic, is number 5.

Silverlight - RadScheduler and required field data validation

In my current project I'm having the pleasure of working with the Silverlight RadScheduler control.  While you may read and feel it is a very bloated control, I argue otherwise, it is highly extensible for such a complex control.  I really think the Telerik guys did a very good job.

My particular scenario is interesting.  I need to:

  1. Extend the appointment dialog to support addresses
  2. The addresses aren't always required (so I can't just use RequiredAttribute)
  3. When fields are required, I'd like the validation to kick off and prevent me from saving that appointment

Here we go:

Updating the RadScheduler template:

This is pretty simple - find the EditAppointmentTemplate

  <ControlTemplate x:Key="EditAppointmentTemplate" TargetType="telerik:AppointmentDialogWindow">
  1. Customize the control template, we added additional fields
  2. NotifyOnValidationError and ValidatesOnException are important.
..snip

          <input:ValidationSummary x:Name="ValidationSummary" />

          <TextBlock Text="Suburb" Style="{StaticResource FormElementTextBlockStyle}"/>
          <TextBox Text="{Binding Suburb, Mode=TwoWay, NotifyOnValidationError=True, 
ValidatesOnExceptions=True}" />

Extend the Appointment class:

  1. Create a ValidationEnabled property - sometimes you need to "not validate"
  2. Create a ValidateProperty method - this takes a property name and triggers all validate attributes on that property - Validator.ValidateProperty is the method that will trigger all kinds of fun validation exceptions for us.
  3. Set the appropriate validation attributes on the property e.g. Suburb - I'm using a custom validation property that I've created based on the RequiredAttribute
  4. In the setter, call ValidateProperty.
    public class JobAppointment : Appointment
    {
        private string _suburb;

        public ObservableCollection<ValidationError> Errors { get; set; }

        public bool ValidationEnabled
        {
            get;
            set;
        }

        private void ValidateProperty(string propertyName, object value)
        {
            if (ValidationEnabled)
            {
                var context = new ValidationContext(this, null, null);
                context.MemberName = propertyName;

                Validator.ValidateProperty(value, context);
            }
        }

        [ValidationRequired]
        public string Suburb
        {
            get
            {
                return _suburb;
            }
            set
            {
                ValidateProperty("Suburb", value);

                if (_suburb != value)
                {
                    _suburb = value;
                    OnPropertyChanged("Suburb");
                }
            }
        }

Add our own Validation attributes

  1. This Required attribute checks against our settings to see if "Suburb" happens to be a required field.  If not, then skip the validation and just return success.
 public class ValidationRequiredAttribute : RequiredAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            if (SettingsLifeTimeService.Current != null )
            {
                if (SettingsLifeTimeService.Current.RequiredFields.Contains(validationContext.MemberName))
                {
                    // RequiredAttribute validation
                    return base.IsValid(value, validationContext);
                }
            }
            return ValidationResult.Success;
        }
    }

 

Stop validation on Scheduler's Appointment_Saving event

  1. In the AppointmentSaving event, grab all the children framework elements from the appointment dialog window
  2. Find their binding expression (e.g., for TextBox I want the binding expression for Textbox.Text)
  3. If exists, I want it to push the values back into the datasource (our customized Appointment class) - this triggers the property setter (which triggers our ValidateProperty, which triggers the custom Required attribute)
  4. Finally, check the validation summary to see if we have any binding errors, if we do, cancel the save.
void _scheduler_AppointmentSaving(object sender, AppointmentSavingEventArgs e)
        {
            JobAppointment jobApp = e.Appointment as JobAppointment;
            AppointmentDialogWindow window = e.OriginalSource as AppointmentDialogWindow;
            var children = window.ChildrenOfType<FrameworkElement>();

            if (children != null)
            {
                foreach (var element in children)
                {
                    BindingExpression binding = null;
                    if (element is TextBox)
                    {
                        binding = element.GetBindingExpression(TextBox.TextProperty);
                    }

                    if (binding != null)
                    {
                        // force control to update databound VM.  This triggers the validation.
                        binding.UpdateSource();
                    }
                }
            }

            ValidationSummary summary = window.FindChildByType<ValidationSummary>();

            if (summary != null)
            {
                if (summary.HasErrors)
                {
                    e.Cancel = true;
                    return;
                }
            }
        }

Finished!

 

image

SharePoint enable iFilter for TIFF OCR

In some companies, paper documents are scanned into TIFF formats and stored electronically.  To search for them, you'll need to enable the TIFF OCR iFilter to allow SharePoint to index TIFF documents.

1. Install Windows Server feature Windows TIFF IFilter:

clip_image002

 

2. Enable OCR filter

clip_image002[7]

 

clip_image002[10]

 

3. You may need to restart the machine

4. Force SharePoint to perform a full crawl from Search Administration

5. Search for your file - here, I'm searching for "Therefore"

tiff-ifilter2

SharePoint 2010 GlobalNavigationNodes Moved

This is a very short blog, but it appears that the GlobalNavigationNodes member on the PublishingWeb class has moved in SharePoint 2010.

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.publishing.publishingweb.globalnavigationnodes(office.12).aspx

In SharePoint 2007, this was under Microsoft.SharePoint.Publishing.PublishingWeb.GlobalNavigationNodes

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.publishing.navigation.portalnavigation.globalnavigationnodes.aspx

In SP2010, this has moved under Microsoft.SharePoint.Publishing.PublishingWeb.Navigation.PortalNavigation.GlobalNavigationNodes

 

This particular property is used to read a publishing site's navigation settings - I've used this to export site navigation as XML to accompany a site export.  Since site exports doesn't seem to include any customized site navigation information.  (It still doesn't in SP2010).

 

SharePoint 2010 with IIS URL Rewrite 2.0

Or, how do you remove /Pages/ from SharePoint URL.

Almost all the hard work is done by Waldek Mastykarz (@waldekm)
http://blog.mastykarz.nl/friendly-urls-sharepoint-site-4-steps-iis7-url-rewrite-module/

These are just my extra notes for SharePoint 2010, I'm going to assume you are reading Waldek's article with this blog as a supplement.

How to install IIS URL Rewrite

The easiest way is via the MS Web Platform Installer

clip_image002

On a good connection you are good to go within 30 seconds - faster if you already have MS Web Platform Installer on the server.

Installed, IIS URL Rewrite 2.0 lives here

clip_image004

IIS URL Rewrite 2.0

I seriously think there's a bug with IIS Url Rewrite 2.0's Regex

Waldek's regex pattern for step 3 is perfect:

^(.*/)?Pages/([^/]+)\.aspx$

and so is step 4:

^(.*/)?Pages/default.aspx$

BUG: IIS URL Rewrite matches badly - see screenshot:

clip_image006

Regex will try to maximize to match as many characters as it can. But this simply does not explain why group 1 match includes Pages/ - Why was Pages/ included in the match? This doesn't make any sense.

You can test the pattern with any other regex library, including .NET (via PowerShell), and you still won't get the stupid buggy match that is IIS URL Rewrite's regex…

(Sorry I'm annoyed that this caused a lot of problem for something that shouldn't have existed...)

$pattern = [regex] "^(.*/)?Pages/default.aspx$"

$result = $pattern.match("Publishing/Pages/default.aspx")

$result.Groups[1]

Success : True

Captures : {Publishing/}

Index : 0

Length : 11

Value : Publishing/

Anyway, to fix this we need to tweak the pattern

^(.*?/)?Pages/([^/]+)\.aspx$

^(.*?/)?Pages/default.aspx$

This will finally force IIS URL Rewrite to work properly.

clip_image008

the extra ? tells regex to try to match as little as it can, while still making a match.

clip_image010

Final result – the order is important – see Waldek’s article

 

TRICKS: Debugging URL Rewrite by enabling trace

When things don't work - this is your only hope. Read trace logs.

clip_image012

clip_image014

clip_image016

clip_image018

clip_image020

Fortunately it's not very hard to read, I mean we read SharePoint logs and survived. It's just tedious to read logs in general.

Open the log in Internet Explorer - which picks up the XSL and gives you a nicer looking UI. Head over to the compact view tab, and look for URL_REWRITE_START

clip_image022

 

HORRIBLE PITFALL: URL Rewrite Cache

I've noticed that when you reshuffle a rule (or add a condition to a rule), it doesn't force the cache to bugger off. So you thought you tweaked the rule but it doesn't seem to have any effect. The trace log will tell you that it actually is ignoring your rule reshuffle because it is listening to the "Url Rewrite Cache".

clip_image024

This is OK, until you have a bad URL cached, then suddenly it's annoying. IIS Reset doesn't cut it.  My tip that I ended up with is to toggle a rule's disable/enable state to trigger the URL Rewrite cache to refresh.

clip_image026

 

SharePoint Authenticated

WARNING: I've had SharePoint on various unknown occasions suddenly raise a login dialogue. I can't reproduce this on demand, but it happens frequent enough that I think it's no accident. It also sometimes goes away when I hit F5 refresh and Windows Authentication just works and I don't see any login prompt at all.

The experience reminded me highly of the days in 2007 where sometimes SharePoint will mysteriously ask you to sign in when you are supposedly browsing the site anonymous.

Please test thoroughly.

 

SharePoint Anonymous

For a public anonymous website, this works absolutely great - no authentication to worry about either.

clip_image028

I didn't do much testing with the postbacks and the ribbon. They do work, but I think I'll need a lot more testing to work out if everything is still OK.

My gut feeling is that SharePoint 2010 relies a lot more on AJAX-based client side calls without a full page postback. While the URL rewrite would have affected the postback somewhat, AJAX calls would be largely immune to this problem.

Summary

Definitely try this out on your SharePoint site and I think you'll be surprised how well it worked (once you get it to work). Thanks Waldek for sharing!