Contact Me

Contact Us
For an informal discussion

Everyone's business is different. Our business is software - ask where we can help.
eMail: john at appsolo.com

Archive for the ‘blog’ Category

Running a web site hosting SL4 OOB on local host using II express

Saturday, February 4th, 2012

Free Tools available:

IIS Express free download

Visual Studio 2010 express free download (there might be a licensing issue here)

or Visual Web developer express 2010

SQL Server 2008  Express R2 free download (2010 version is available in RC0 version)

Making sure that IIS Express is installed you can develop against it

So you create your web site and client in Visual Studio and your Silverlight client also. You need to specify that the SL client will run out of the browser. In the solution property settings for your Web project you set the Web site to be hosted on the IIS express server.

image thumb Running a web site hosting SL4 OOB on local host using II express

In your Silverlight Project properties you specify that your application can run out of the browser. NOTE also the Port number is a 56528. This is because we are accessing the local file system in this application (hence OOB SL).

This gives the Silverlight client ability to request permission to install itself out of the browser it elevated thrust and allows it to access the personal folders (only!!) of the host machine. Our application needed access to these in order to pass them to an FTP hosted WCF service.

So you Compile and execute. It loads in the browser. Right click

image thumb1 Running a web site hosting SL4 OOB on local host using II express

This allows you to install on the desktop.

Once have installed the application OOB you need to set up a batch file to start up the IIS express service followed by the web site that hosts the Silverlight client. You’ll need to examine the shortcut created by the OOB action and paste result in the second line of the batch file

Locus Desktop thumb Running a web site hosting SL4 OOB on local host using II express

start /d "C:\Program Files (x86)\IIS Express" iisexpress.exe /site:LocusCRM.Web
start /wait /d "C:\Program Files (x86)\Microsoft Silverlight\sllauncher.exe" 3991240927.localhost

Now to run the OOB Silverlight you just need to click on the batch file icon.

Configuring Deployment of Silverlight

Wednesday, August 17th, 2011

It is sometimes necessary to configure Silverlight differently especially when using WCF services. I read this useful post by Mohamed Ibrahim Mostafa on how to configure the ServiceReferences.ClientConfig XML file to cater for different deployment environments. I found this most useful. As the post suggests the the XAP contains references to the ServiceReferences.ClientConfig XML file. But be warned when you are deploying to a host site the Client Bin folder (from which the XAP file is downloaded) must also have a copy  of ServiceReferences.ClientConfig in it with the same references. Don’t really know why as it should be bundled in the XAP and don’t really know how I would be expected to guess that but I did. You must also remember to rebuild with the changes to the ServiceReferences.ClientConfig file prior to deployment to the Host site.

For local development

 

<configuration>
  <appSettings>
    <add key="Host" value="http://localhost:1420/" />
    <add key="FileServiceEndPoint" value="http://localhost:1420/" />
    <add key="ServerContentFolder" value="LocusPackages" />
    <!--<add key="Host" value="http://Host.com/FTPFileServer/" />
    <add key="FileServiceEndPoint" value="http://Host.com/FTPFileServer/" />
    <add key="ServerContentFolder" value="LocusPackages" />-->

  </appSettings>
</configuration>

For Host deployment 1) rebuild and deploy 2) keep this copy of ServiceReferences.ClientConfig  on the Host Client Bin folder

<configuration>

  <
appSettings>

    <!–
<add key="Host" value="http://localhost:1420/" />

    <add key="FileServiceEndPoint" value="http://localhost:1420/" />

    <add key="ServerContentFolder" value="LocusPackages" />–>       
    <
addkey="Host" value=http://Host.com/FTPFileServer/ />

    <
addkey="FileServiceEndPoint" value=http://Host.com/FTPFileServer/ />

    <
addkey="ServerContentFolder" value="LocusPackages" />

   
  </
appSettings>

</
configuration>

WCF V ASP services for FTP access from Silverlight 4 Part 2

Wednesday, August 17th, 2011

Apologies as this Post is not as detailed as I would like it to be but time pressure dictates. Last time I discussed using a HTTP generic handler to handle the uploading of local files to a FTP server. In this Post I am going to discuss the WCF service to report back on the progress of the uploads to the eventual destination. I use ASP HTTP generic Handlers for file uploads as they do not time out as readily as WCF web services. This is an observational point of view. I don’t see much discussion on the matter of suitability so any comments would be welcome. Below is the Architecture of Our latest app ( I’ve left out the RIA services <-> Database part for simplicity).

image thumb WCF V ASP services for FTP access from Silverlight 4 Part 2

To my mind and in what I know of WCF web services they are suited for short message exchange between web clients and the services. Opinions?

So we use them here to monitor the Asynchronous delivery of local files from a Silverlight client to the FTP server VIA the HTTP generic handler.

I used Duplex Polling for this purpose. The method is based on the WCF fire starter video series on channel 9 and this a video by Tomasz Janczuk on Duplex communication Duplex Communication with WCF in Silverlight 4.

The second video explains the process well. The hardest part of WCF services is in getting the message timing right. The WCF service queries the FTP server and sends message back to the Silverlight 4 client that has subscribed to the the service. Time is a bit tight. So if anyone wants me to post details then lets hear from you and I will. A bit busy at the moment. Later.

P.

WCF v ASP services for FTP access from Silverlight 4 Part 1

Thursday, August 11th, 2011

We  at Appsolo have been charged with writing a SL 4 app that requires access to an FTP server  for a CRM system that we are writing. The system will maintain licences for Flash based software products  our business client produces at the moment. This package of software artefacts are uploaded by our SL client to an FTP server. A HTTP link to the  licenced package is then produced to be emailed to the customers of our business client. This presents a number of  problems that had to be solved.

Firstly you cannot access FTP directly from a SL client as the ports used are not available to the browser.

Secondly the SL client runs in a UI thread, ASP service(s) runs in another thread(s). I have two HTTP based ASP services. A first to create an FTP directory to place the files in. Upload speed needs to be addressed. A second ASP HTTP service to handle the streamed upload of the file. The timing of Threading of all these needs to handled.

Thirdly is the problem of reporting back to the SL client that the File has been delivered to the FTP server having first stopped off at the ASP service on the way. A multi streamed upload HTTP service as is used here is quick as it threads to handle requests. But you cannot access the HTTP response from the SL client as it is write and not a read. Also you do not want to tie up the client waiting for the slower FTP service to complete as called from the ASP service. I integrated a WCF Duplex Polling Service handle the polling of the FTP directory to report on the Files delivered and to decide when the upload is finished.

I’ll discuss things here that I found out in the course of bring this all together.

So part of the solution is to use an ASP based HTTP file streaming (WCF is not really built for this  type of service – although it can be done) service to upload the files to a staging area Directory on the host website. We also needed to create a directory to store the files in on the FTP Server. This is where it starts to get tricky. You want to make sure that the directory is created before you start sending Files to it (or check if it exists already). So on the SL client your have to have nested delegates

NOTE: there a lot of different ideas mixed in here that I got from various people out there, some of which I cannot remember. I’ll list all the references at the end of the posts.

   1:  // Host value is set in ServiceReferences.ClientConfig
   2:                  string hostbase = getAppSetting("Host");
   3:                  UriBuilder packagehandlerUrl = new UriBuilder(hostbase + "FTPSetupPackageFolder.ashx");
   4:                  string FTPDirName = "FTPpath=" + PackageTextBox.Text;
   5:                  packagehandlerUrl.Query = FTPDirName;
   6:                  WebClient FTPCreateDir = new WebClient();
   7:                  // Call asp handler
   8:                  FTPCreateDir.OpenReadAsync(packagehandlerUrl.Uri);
   9:                  // Read the response sent back from the packagehandlerUrl call
  10:                  FTPCreateDir.OpenReadCompleted += (read, ev) =>
  11:                  {
  12:                      int len = (int)ev.Result.Length;
  13:                      byte[] b = new byte[len];
  14:                      ev.Result.Read(b, 0, len);
  15:                      // Check the return message to see if directory already exists and report
  16:                      // Could put choice here
  17:                      if(len == 0)
  18:                          MessageBox.Show("Directory already exists ");
  19:                      else MessageBox.Show("Directory Created " + System.Text.UTF8Encoding.UTF8.GetString(b,0,len) );
  20:                      txtMessage.Text = "FTP Uploading........";
  21:                      // Kick off FTP service Listing operation See end of this file
  22:                      FtpListing();
  23:                      // Do Upload
  24:                      #region HTTP Handled Streamed Upload
  25:                      foreach (FileInfo file in filesToUpload)
  26:                      {
  27:                          // This section uses Asynchronous streamed uploading to upload the selected Files
  28:                          UriBuilder FilehandlerUrl = new UriBuilder(hostbase + "UploadFileHandler.ashx");
  29:                          string InputFile = "InputFile=" + file.Name;
  30:                          FilehandlerUrl.Query = InputFile + "&" + FTPDirName;
  31:   
  32:                          string fileName = file.Name;
  33:                          FileStream FsInputFile = file.OpenRead();
  34:                          WebClient webClient = new WebClient();
  35:                          //Now make an async class for writing the file to the server
  36:                          //Here I am using Lambda Expression
  37:                          webClient.Encoding = System.Text.UTF8Encoding.UTF8;
  38:                          webClient.OpenWriteCompleted += (sent, evt) =>
  39:                          {
  40:                              try
  41:                              {
  42:                                  // Do the actual streamed Upload which goes off on it's merry way
  43:                                  // and we continue
  44:                                  UploadFileData(FsInputFile, evt.Result);
  45:                                  evt.Result.Close();
  46:                                  FsInputFile.Close();
  47:                              }
  48:                              catch (Exception ex)
  49:                              { MessageBox.Show(ex.Message); }
  50:   
  51:                          };
  52:                          webClient.OpenWriteAsync(FilehandlerUrl.Uri);
  53:   
  54:                      }
  55:                      #endregion
  56:                  };

 

Lines 2 and 3 retrieve information from the ServiceReferences.ClientConfig file that is a handy place for storing Host based information which can be changed at deployment time after development and testing.

The contents if the ServiceReferences.ClientConfig XML file are something like this

   1:  <configuration>
   2:    <appSettings>
   3:      <add key="Host" value="http://[YourHost]/FTPFileServer/" />
   4:      <add key="FileServiceEndPoint" value="http://[YourHost or DifferentHost]/FTPFileServer/" />
   5:    </appSettings>
   6:  </configuration>

The associated Function to read this configuration file

   1:  private string getAppSetting(string strKey)
   2:          {
   3:              string strValue = string.Empty;
   4:              XmlReaderSettings settings = new XmlReaderSettings();
   5:              settings.XmlResolver = new XmlXapResolver();
   6:              XmlReader reader = XmlReader.Create("ServiceReferences.ClientConfig");
   7:              reader.MoveToContent();
   8:              while (reader.Read())
   9:              {
  10:                  if (reader.NodeType == XmlNodeType.Element && reader.Name == "add")
  11:                  {
  12:                      if (reader.HasAttributes)
  13:                      {
  14:                          strValue = reader.GetAttribute("key");
  15:                          if (!string.IsNullOrEmpty(strValue) && strValue == strKey)
  16:                          {
  17:                              strValue = reader.GetAttribute("value");
  18:                              return strValue;
  19:                          }
  20:                      }
  21:                  }
  22:              }
  23:              return strValue;
  24:          }
  25:      }

Lines 5 to 20 call and read the response from FTPSetupPackageFolder.ashx to find out if the Directory existed or not.

The ASHX Service to handle this call on the server is….

public void ProcessRequest(HttpContext context)
        {
            string FtpRootDir = context.Request.QueryString["FTPpath"].ToString();
            string FtpBaseAddress = null;
            string FtpUserName = null;
            string FtpPassword = null;
            if (System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"] != null)
                FtpBaseAddress = System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"].ToString();
            //Get FTP Credential settings
            if (System.Configuration.ConfigurationManager.AppSettings["FtpUserName"] != null)
                FtpUserName = System.Configuration.ConfigurationManager.AppSettings["FtpUserName"].ToString();
            if (System.Configuration.ConfigurationManager.AppSettings["FtpPassword"] != null)
                FtpPassword = System.Configuration.ConfigurationManager.AppSettings["FtpPassword"].ToString();

            NetworkCredential credentials = new NetworkCredential(FtpUserName, FtpPassword);
            string DirName = FtpBaseAddress;
            string WorkingDirectory = DirName + FtpRootDir + "/";
            if (!FtpDirectoryExists(credentials, WorkingDirectory))
            {
                FtpWebRequest setupDir = (FtpWebRequest)WebRequest.Create(WorkingDirectory);
                setupDir.Method = WebRequestMethods.Ftp.MakeDirectory;
                setupDir.Credentials = credentials;
                setupDir.Timeout = -1; // Don't timeout for Debugging and slow connection
                FtpWebResponse dirResponse = (FtpWebResponse)setupDir.GetResponse();
                context.Response.Write(dirResponse.StatusDescription.ToString());
                dirResponse.Close();
            }

        }

Again the web.config XML file on the server side holds the relevant values for flexibility of deployment.

Line 22 calls the WCF service to start reporting on delivery of Files to the created directory See next post.

Line 25 to 52 then uploads whatever files are in the FilesToUpload collection in separate threads. It calls the function to open read and write the appropriate streams to the HTTP service

private void UploadFileData(Stream inputFile,Stream resultFile)
        {
            try
            {
                byte[] fileData = new byte[4096];
                int fileDataToRead;
                while ((fileDataToRead = inputFile.Read(fileData, 0, fileData.Length)) != 0)
                {
                    resultFile.Write(fileData, 0, fileDataToRead);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Exception Thrown --> " + ex.Message);
            }
        }

The ASHX HTTP service to handle the uploading stream is as follows

public void ProcessRequest(HttpContext context)
        {

                ctx = context;
                //Query Parameters
                string filename = context.Request.QueryString["InputFile"].ToString();
                string FtpRootDir = context.Request.QueryString["FTPpath"].ToString();
                // Local HTTP Storage folder for staging to FTP upload
                string filePath = context.Server.MapPath("~/FilesServer/");
                // Get the Base address of the FTP server folder from the web.Config
                string  FtpBaseAddress = null;
                string FtpUserName = null;
                string FtpPassword = null;

                // Upload via HTTP stream initiatied by client
                using (FileStream fileStream = File.Create(context.Server.MapPath("~/FilesServer/" + filename)))
                {

                    byte[] bufferData = new byte[4096];
                    int bytesToBeRead;
                    while ((bytesToBeRead = context.Request.InputStream.Read(bufferData, 0, bufferData.Length)) != 0)
                    {
                        fileStream.Write(bufferData, 0, bytesToBeRead);
                    }
                    fileStream.Close();
                }
                try
                {
                    if (System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"] != null)
                        FtpBaseAddress = System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"].ToString();
                    //Get FTP Credential settings
                    if (System.Configuration.ConfigurationManager.AppSettings["FtpUserName"] != null)
                        FtpUserName = System.Configuration.ConfigurationManager.AppSettings["FtpUserName"].ToString();
                    if (System.Configuration.ConfigurationManager.AppSettings["FtpPassword"] != null)
                        FtpPassword = System.Configuration.ConfigurationManager.AppSettings["FtpPassword"].ToString();

                    // Create logon Credential
                    NetworkCredential credentials = new NetworkCredential(FtpUserName, FtpPassword);
                    string DirName = FtpBaseAddress;
                    string WorkingDirectory = DirName + FtpRootDir + "/";
                    // Upload File
                    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(WorkingDirectory + filename);
                    request.Method = WebRequestMethods.Ftp.UploadFile;
                    request.Credentials = credentials;
                    request.Timeout = -1; // never timeout incase of slow connection
                    // Copy the contents of the file to the request stream.
                    byte[] bufferData = new byte[7168];
                    int length = bufferData.Length;
                    using (FileStream fileStream =
                        new FileStream(context.Server.MapPath("~/FilesServer/" + filename)
                            , FileMode.Open, FileAccess.Read, FileShare.Read, length, false)) // Last argument should block thread
                    {
                        Stream requestStream = request.GetRequestStream();
                        int bytesToBeRead;
                        while ((bytesToBeRead = fileStream.Read(bufferData, 0, bufferData.Length)) != 0)
                        {
                            requestStream.Write(bufferData, 0, bytesToBeRead);
                        }
                        requestStream.Flush(); // Flush any outstanding buffer
                        requestStream.Close();
                        fileStream.Close();
                        File.Delete(context.Server.MapPath("~/FilesServer/" + filename)); //

                    }
                    //context.Response.Write(filename + "Uploaded");     no can do it's a writable stream processor
                }
        

In the Next Post I’ll Cover the Duplex Polling WCF service to poll the FTP folder to check if the files have arrived at the FTP site.

That’s all the Time I have for now. Cheers. P.

Silverlight app is out of date version

Wednesday, August 10th, 2011

An interesting and frustrating problem during Silverlight development that we regularly run into is that of the app you’re running during debug not reflecting the changes made to the code before the build. In other words, the version you’re witnessing running in the browser isn’t the one that just got compiled.

This can lead to serious loss of productivity as you make further changes to your code believing that the previous changes didn’t ‘work’ when in reality they may have but you just weren’t witnessing their effect.

Though I haven’t fully understood this phenomenon there are a few things that seem to help:

  1. Kill IIS development server before you run, forcing another session to be launched
  2. If using Chrome, click Wrench…Options…Beneath the Bonnet and Clear Browsing History. Now I know you may not want this since it affects other stuff but just delete the history for the last hour will suffice. I’ve found this to work most times so well worth trying.
  3. Sometimes, in VS2010 the running process gets attached to the wrong Silverlight runtime process. Clearing this and forcing a new Silverlight process to be instantiated can help. Do so by choose (in VS2010) Debug…Attach to Process and choose the process with the type ‘Silverlight’. This task is made easier if you don’t have many tabs open.

None of these are guaranteed but they all have worked on occasion. The key thing is to be aware when testing your app, that it may not reflect your work to date. Otherwise, you enter a spiral of fixes that weren’t needed. Or you could just toss Silverlight and make the app from sticks and duck-tape.

Entity Framework Metadata not generated

Monday, August 8th, 2011

For those of you suffering with the half-baked n-tier solution that is Entity Framework and RIA Services, here’s a snippet of use. As you’ll know the metadata file is very necessary (so why is it optional?) but there’s no way you’re going to get the database design right first (or fiftieth) time so you’ll be regenerating a domain service lots.

This is a pain generally speaking but more of a pain given that you’ll have made modifications to both the DomainService class as well as the associated metadata classes. Now, those in the know will have created a buddy partial class for the DomainService so your custom queries will live there and be undisturbed by the creation of a new DomainService. However, I haven’t figured out a way to do the same for the metadata classes and these have a fair bit of custom attributes for things like validation. So when the metadata classes are regenerated there’s a cut-paste body of work to do.

And then there’s the interesting issue of your metadata classes not being re-generated with the DomainService even though you ticked the ‘Metadata’ box. You might have thought that excluding the old metadata file from the project would do the trick and well, it should. However, Visual Studio in its wisdom still ’sees’ these metadata classes – heck, you could probably delete them and it’ll still think they’re there. So the trick is to build the web project to flush out the offending old metadata classes and it works.

Of course, the burning question is why this kind of fudge is necessary? Surely, someone is supposed to test these things properly. It’s not like RIA Services and Entity Framework is fresh from the factory. Microsoft – if you’re listening – this is causing you to bleed developers. There’s, what, 80,000 working there? You can’t all be supporting IE6?

Quick Note on Debugging problem in VS 2010

Monday, July 4th, 2011

 

 

 

 

 

 

 

I’m currently working on a project that involves an .ashx page with code behind and I was trying to debug it when I came across this error. A lot of confused people out there talking about this error in different situations.

2011 07 04 2321 Symbols not loaded thumb Quick Note on Debugging problem in VS 2010

The solution to this in this in my case which may help others is in two steps

1. in the mark-up for the .ashx file make sure you specify the debug=true attribute

<%@ WebHandler Language="C#" CodeBehind="FileUpload.ashx.cs" Class="SilverlightFileUploadWeb.FileUpload" debug="true"%>

2. Then make sure that under the properties for the website part of you application has native Code debugging set to true. VS will then load the debug symbols for the .ashx ASP page and allow breakpoints in the server code.

 

2011 07 05 0000 Project settings thumb Quick Note on Debugging problem in VS 2010

I have to say I had to stumble around for a long time to figure this out and the solution is 90% intuition and the 90% picking through other peoples posts and yes that is 180% of my time!! The MS information on the debugging process is rank and I’ve seen so much disparity in ideas on this error that I points to (as usual) it raising it’s ugly head in different circumstances. But this is my story and now I can move on to finding out where the server side errors are. If you want to be further boggled take a look at MS’s description of what they think native code is about. It’s counter intuitive. What’s it all about MS!!

Win 7 Phone setup Part 1

Wednesday, May 25th, 2011

I recently got a Win 7 Mobile. An LG E900. Well actually I’m on my second one. The first one had a faulty speaker. Some chatter on the Net said that it might be a software update problem but in true programmer fashion I reckon it’s a Hardware problem. This is supported by the fact that a colleague has had one for months now and no problems. So first things first to hook up the WIFI and transfer over all my contacts from my windows 6 mobile. Well second really as of course there where some updates to come down. I installed these through a Zune PC client with the new LG USB connected, but once the WIFI is set up I think you can download the updates, in fact the phone is regularly checking for them. So back to first or second if you get my drift. Transferring contacts was surprisingly easy as I had already synched my old Windows 6 IPAQ 510 with the Outlook exchange server in work. Well done the clever lads in IT services. You know who you are cause you’re that clever. So a quick re-synch up to the exchange server for recent contacts just in case and then I just had to set up the Outlook client on the Win 7 phone. This just requires my email address and my validation details on the Outlook exchange server which is running in the cloud now (again Kudos IT services). Then the Outlook client on the Phone downloads all my contacts with mobile numbers. And here is the cool thing. All thousands of users registered on Outlook exchange are now available on my mobile device for contact search and Email as there is an option search Outlook directory on the connected Outlook exchange. It can be a little hard to find though. You have to add a recipient using the plus (+) on the To: line, then click on search Icon. Type in the first few letters of the person you are looking for and then if they are not in your local contacts, the option to search Outlook directory appears (in fact one will do it, but then you have to scroll down to the end to see the option). Then it pulls all the matches from the Outlook Exchange Active Directory lookup (I presume). Pretty Cool, but could be better presented up front of the search. It’s cumbersome to scroll down through the list of recipients presented and you cannot further refine the search in the Outlook Directory list presented. Ok said. Off to bed.

RIA Services, DataForm, & PageCollectionView

Monday, April 18th, 2011

master detail dataform thumb RIA Services, DataForm, & PageCollectionViewA common UI pattern is master-detail record viewing and editing (see opposite). In the example opposite, the user can filter the Customers in the (empty) text box and selecting a Customer allows him to edit the details opposite (in the DataForm, from the Silverlight toolkit). It’s a very typical scenario but very poorly supported by Silverlight, though on the surface, it doesn’t appear so. Here begineth the lesson!

I won’t go into extolling the virtues of using DataForm in detail so here’s the highlights; not using it wouldn’t solve the problems we encounter later:

  • built-in data validation handling
  • multiple templates for viewing, editing, adding new records
  • tooltips for fields (pulled from annotations in the DomainService)

In this case, I have the Listbox and DataForm bound to the DataContext of the page grid so they stay neatly in sync. This is binding 101. Normally, folk will use a DomainDataSource created as a Resource in the XAML, load it with a query call to the DomainContext that it’s assigned and from there populate the listbox and DataForm. I don’t particularly like DomainDataSource elements and I’m not alone in this. It’s just not good practice to mix data access elements in the presentation layer. It’s a popular approach since the xaml to make the DomainDataSource filter, sort and group is a breeze. It is a compromise though and many are working to improve it (using what is being christened a DomainCollectionView, under development) – I look forward to its release.

Another issue with DomainDataSource

DomainDataSource elements make the querying process very easy. You can pass parameters or bind UI elements back to it. In the case shown above, I wanted the user to type (partial) Customer names into the text box and have the listbox beneath filter the results. However, with a DomainDataSource, this forces a requery back on the server. Now, this app is running OOB (out of browser) locally but the latency would still kill this feature. On top of this, I don’t see the reason. I’ve already queried the Customer table to populate the listbox – okay, I needn’t pull down all the data and related records in other tables, but I now I’ll need this data soon enough and there isn’t a huge volume, so I download all the records (and it’s quick). To avoid this server-side re-querying I embark on a journey…

Alternatives means of Binding

Colin Blair gives a good account of our options here. Most folk will bind LoadOperation.Entities to the DataContext but this is a read-only collection and so is only suited to browsing. One down!
Next contender is the cached EntitySet<> exposed by the DomainContext. This is ready to hand and supports INPC (INotifyPropertyChanged) so will reflect changes made without additional plumbing. However, it doesn’t support filtering (I need this) and the DataForm (or DataGrid) doesn’t permit add/delete operations on EntitySet<> objects. Two down!
Next up is to use our old friend ObservableCollection<>. While this implements INPC and is not read-only, I would now be responsible to manage propagate every change to the ObservableCollection<> back to the EntitySet<> for persisting to the DomainContext. A bridge too far. Three down!

Enter PagedCollectionView

Initially, I was happy with PagedCollectionView. I wrapped the LoadOperation.Entities query result in a PagedCollectionView and happily browsed and edited data. In the above example, the DataContext of the main grid is bound to a PagedCollectionView wrapper. However, a colleague pointed out the disabled nature of the ‘+’ button on the navigation bar of the DataForm – not a good sign. It turns out that (according to Colin Blair):

The PCV’s implementations of IEditableCollectionView and IPagedCollectionView are not compatible with the EntitySet. However, the incompatibility is reported by the PCV so the built in add/remove abilities of the DataForm and DataGrid are properly disabled.

Now I won’t pretend to fully ‘get’ this but the upshot is that the DataForm (and the DataGrid suffers from the same problem) can’t add/delete from a collection wrapped in a PCV (PagedCollectionView). I was running out of options at this point so dug about and found that if I convert the cached EntitySet<> (returned by the Load() on the DomainContext) to a List(), and then wrap it in a PCV, the Add/Delete buttons on the DataForm are magically enabled. Yippee!

You saw this coming, didn’t you? Well, what happens is that changes (e.g. additions) to the PCV don’t propagate back to the EntitySet<>. So while the UI experience worked fine, there was no persisting of data back to the database. Though by this point my enthusiasm for DataForms was waning, I didn’t fancy the alternative so ploughing on I began to look at the copious event hooks that DataForm exposes. There are lots and (typical MS this), they change the line-up regularly.

One that caught my eye was AddingNewItem. However, this fires just when the user clicks the ‘+’ (Add) button on the navigation header. By this point, the user hasn’t had an opportunity to enter any new data. We need an event that is fired later. The way I worked around this was to edit the NewItemTemplate of the DataForm. A DataForm can have different a DataTemplate for readonly, edit and for creating a new item. I made a DataTemplate for NewItemTemplate which includes an Add button. The user (after entering the new item details) clicks the button and puts in play the following:

private void btnAdd_Click(object sender, RoutedEventArgs e)
        {
            LocusDomainContext _lds = ((App)Application.Current).lds;
            if (_IsAddingCustomer != null)
            {
                _IsAddingCustomer = dfCustomerDetails.CurrentItem as Customer;
                _lds.Customers.Add(_IsAddingCustomer);
                (_IsAddingCustomer as IEditableObject).EndEdit();
                _lds.SubmitChanges();
            }
        }

This works in conjunction with my handling of the AddignNewItem event as follows:

private void dfCustomerDetails_AddingNewItem(object sender, DataFormAddingNewItemEventArgs e)
        {
            LocusDomainContext _lds = ((App)Application.Current).lds;
            _IsAddingCustomer = new Customer { EntityName = "Company Name" };
        }

You’ll see that I begin the creation of the new Customer in the AddingNewItem handler. Then when the user clicks the Add button later, I test for non-null in the _IsAddingCustomer variable. If this is non-null, there’s a new Customer to be created. I cast the object currently being editing in the DataForm (the new Customer) and add it to the cached EntitySet<Customer> exposed by the DomainContext.

The next line is needed to tell the DataForm to forget about the edit in play – if the DataForm tried to save its changes, it would fail and letting it proceed conflicts with my own next line, which submits the changes through to the DomainContext. This, in turn, is reflected in the DataForm (and Listbox) which are bound (indirectly) to the DomainContext.

I also have another button which handles the user changing his mind and not committing the new Customer.

Deleting a Record

To delete a record I adopt a similar approach though I can directly handle (if I want) the Delete ‘X’ button in the navigation header. I will instead probably dispense with the navigation header and put a button in the readonly and edit templates for the DataForm marked ‘Delete’. Here’s the code behind the DeletingItem event exposed by the DataForm:

private void btnDelete_Click(object sender, RoutedEventArgs e)
        {
            LocusDomainContext _lds = ((App)Application.Current).lds;
            _lds.Customers.Remove(dfCustomerDetails.CurrentItem as Customer);
            _lds.SubmitChanges();
        }

Conclusion

This to me is what is wrong with Silverlight (and WPF). There is basic functionality that is necessary for any lob application. This UI pattern is one such example and the level of understanding and coding needed to implement a solution is onerous and in parts ugly. Maybe there’s a better way – but I haven’t found it. I’ve seen examples of custom EntitySet<> collection in which you manage the CRUD operations yourself but I wasn’t going there. This is lob 101 and it isn’t well supported. And it should be. RIA Services is now fully part of Silverlight, we’re well past beta and I’m now reading of SL5 – when will MS look to these issues and provide better support?

Breakpoint will not currently be hit…

Monday, April 4th, 2011

This error arises spuriously. In doing so it prevents breakpoints in Silverlight code being reached. To solve, delete the obj and bin folders of the associated project. Additionally, you may need to disassociate the Silverlight app from the host web project. Do so by selecting Properties panel for the web project. Then select ‘Silverlight Applications’ and delete the associated Silverlight app. Click ‘Add’ to reinstate the Silverlight Project (already in your solution).

If none of this works, try clearing the cache in your browser. Chrome makes this particularly easy, allowing you to just clear the cache et al for the last hour, rather than losing all your cached material going back months.

4 pages

latest news

Running a web site hosting SL4 OOB on local host using II express

Posted on Saturday, 4th February, 2012

Free Tools available:
IIS Express free download
Visual Studio 2010 express free download (there might be a licensing issue here)
or Visual Web developer express 2010
SQL Server 2008  Express R2 free download (2010 version is available in RC0 version)
Making sure that IIS Express is installed you can develop against it
So you create your web site and client [...]

Testimonials

Excellent design skills

Posted on Sunday, 2nd May, 2010

We at Taxonomy.ie are happy to be associated with Appsolo and look forward to further work together.

follow me

twitter facebook delicious

AppsoloLtd. VAT No. IE97548691 - Copyright © 2010.