Infosys Microsoft Alliance and Solutions blog

« January 2007 | Main | March 2007 »

February 26, 2007

Roaming Profiles Implementation for SoftGrid-II

In the  "Roaming Profiles Implementation for SoftGrid-I" we discussed the concept of Softgrid roaming profiles for applications and it's contribution in achieving a true roaming user profile.

I will continue the discussion further and discuss a simple procedure to implement Softgrid Roaming profile in a test lab.

 

 

Assumption:

 

Windows roaming profile is implelemented already in the system 

 

Basic set up:

 

The Lab infrastructure is set up as:

 

1.       A Windows 2003 Server Machine with Active Directory installed with:

 

2.       A Domain

 

3.       Required User Groups with appropriate permissions and settings

 

4.       A Windows 2003 Server Machine with SoftGrid Virtual Application Server installed

 

5.       A Windows 2000 or higher machine with SoftGrid Client installed

 

 

All the computers are to be added to the domain on the Active Directory.

 

Procedure:

 

In order to implement the true Roaming User Profile with SoftGrid the following procedure is followed:

 

 

1.       On the computer that’s going to be the central share for all the profiles, create a folder share it on the network and give write permission to everyone on the domain, lets say the path is \\SERVER\Profiles

 

2.       In the Active Directory, add the new user which is meant to be a Roaming User, say TestUser

 

3.       Go to the properties for TestUser on the Active Directory, under the profile tab give the path to the user’s folder one level below the shared folder, e.g. \\SERVER\Profiles\TestUSer

 

4.       On the Client machine, go to the properties for the SoftGrid Management Client, for User Data Directory, give the path as %APPDATA% or the environment variable for any other folder that is a part of the profile, for example My Documents or Desktop

 

5.       Log off and back on

 

6.       From now on all the settings and files specific to the user are carried to any computer on the domain that he logs on to

 

 

Addressing User Security Elevation on a locked down PC using SoftGrid Virtualized Applications

Normally in a locked down environemnt in order to enable End Users (EU) to customize application specific settings and other customizations application packagers identify and elevate security permissions to secure NTFS and Registry areas. This is often considered as a security risk and also contributes to effort in troublershooting and identifying such areas on the local system.

SoftGrid eliminates the requirement of elevating security permissions to the restricted area. As Softgrid virtualized applications execute within a virtual environement the user has complete access to the virtual environemnt while executing the application. The advantage is that the users have access to the desired functionality withoutaffecting the state of the host system.

In order to test this feature of Softgrid, I chose Winzip trial version installation.

Test 1: A native install of WinZip was carried out on the local host system by logging in as administrator. After the installation, the WinZip application was accessed using a standard User Account (lock down). The result was as anticipated, no rights to change settings as shown below:

 

Figure 1: Application locally installed by the administrator and accessed by the users in locked down state
Test 2:

The WinZip application was then Sequenced using Softgrid and then ported to the SoftGrid Client on the same machine. This time when the virtualized shortcut was launched by the sane standard User the results were completely different. Standard User had full access to application specific options available as shown in the Figure below:

winzip after sequencing

Figure 2: Application sequenced and deployed on to the SoftGrid Client machine. Accessed by the users on the SoftGrid Client machine

 

Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  
Conclusion:Applications if virtualized using Softgrid can be tested using an administrative account during native installation testing.  

Microsoft Vista: Best Practices recommendation for Zero Touch Deployment using SMS and BDD-II

USMT Customization:

 

User State Migration Tool (USMT) is a small command line utility provided to capture and restore the user’s data and settings from a machine

 

Security Best Practices
·         Virus Scan à Run a complete virus scan before capturing the data.
·         USMT does not migrate passwords. It is advisable to instruct the users manually about this change.
·         Access Control Lists (ACLs) are not supported. The ACLs have to be restored manually after migration.
·         While migrating local accounts it is advisable to migrate with the “/lac” option to create the IDs locally. However, the IDs will remain disabled after migration.
·         Migrate only domain users and try to avoid backing up usual system service accounts in your backup script..
Scanstate options
·         Use the “/o” option to over-write files that have already been backed up.
·         Use the “/localonly” option to search only the local folders for data to be backed up. Ideally files stored on the network will be accessible post deployment also and hence there is no need to back these up.
·         Use compression to ensure that there is adequate space for all the machines.
·         Use verbosity level 5 for monitoring all logs.
General Guidelines
·         For Refresh scenario, try to store the USMT backup files on the local machine itself to reduce loads on the network and the time required for the migration.
·         For the replace computer scenario, identify storage areas which can be used to store the data from the users.
·         While dealing with critical data, it is a good practice to store data on a network share during the state capture phase.
·         While using an extremely critical machine, it is a good practice to capture a complete image before attempting to rollout to the machines.

 

February 22, 2007

Configuring Custom Http Handler in IIS 7

Recently when working on an application, I was playing around with writing custom http handlers. There is nothing new in that lots has been written about how to build and configure custom http handlers. So when after writing this I decided to configure it in IIS 7 on my Vista machine, I was pleasently surprised to see that I was lost !

IIS 7 UI has changed noticeably from its earlier versions and when I wanted to configure the file extension I wanted my custom handler to work with, I didn't know what to do.

Fortunately, I found that when viewing my particular website in IIS 7, in the two groups that IIS displayed the information (Grouped by Area by default and has ASP.NET and IIS groups), there was an option called Handler Mappings in IIS group. This seemed like the most relevent location where to look further.

Opening the Handler Mappings, the listing for all file extensions registered with various handlers was displayed, so this was the right place. However as I looked for an "Add new file mapping" option, I instead found three options

1. Add Managed Handler...
2. Add Script Map...
3. Add Module Mapping...

Nothing what each of these meant, I clickly opened each and figured out that managed handler had something to do with handlers witten in managed code, script map was to help configuring an executable and module mapping to work with http Modules. For more details do refer to the IIS 7.0 help file installed with IIS or online on technet.

I soon realized that Add Managed Handler is what I need to work with. There is a catch about Application pool setting being Integrated or ISAPI, which again you can get more details about in the IIS 7 help files. I did notice that on my Vista RTM, the two modes were Integrated and Classic. Since I didn't work with pre RTM versions, I can only guess that Classic was earlier called as ISAPI.

OK, so back to adding managed handlers. See the figure below to see how this looks like.

ManagedHandler.jpg

The interesting point is that after adding a custom http handler (you can follow MSDN documentation's How To article for this) and building the site, IIS already seemed to understand that I had a custom Http handler in my application and it displayed it in the Type drop down (HelloWorldHandler in the figure above). I selected it, provided *.sample as the extension I wanted to work with and also gave it a friendly name.
 
Completing the above steps also automatically edited the web.config file to add the necessary information about the custom handler. However unlike earlier where we added <httpHandlers> within <configuration><system.web> node, I got a new entry in the web.config inside of <configuration> node as below

<system.webServer>
    <handlers>
        <add name="Custom Handler" path="*.sample" verb="*" type="HelloWorldHandler"
                     resourceType="Unspecified" />
    </handlers>
</system.webServer>

Finally to test that it all worked, I accessed the handler as http://localhost/MySampleSite/Test.sample and I did see the message displayed on the page as written on the httpHandler code.

Hence when working with IIS 7, one needs to write the implementation class for the http handler and build the project. Configuring in IIS and also making the necessary entries in Web.config is easily taken care via the Add Managed Handler option in IIS 7.

February 20, 2007

SQL Server 2005 SP 2 Released

SQL Server 2005 SP2 is now available. Download it from here - http://www.microsoft.com/downloads/details.aspx?FamilyId=d07219b2-1e23-49c8-8f0c-63fa18f26d3a&DisplayLang=en

For other SQL Server 2005 related files, check - http://www.microsoft.com/technet/prodtechnol/sql/2005/downloads/servicepacks/sp2.mspx

February 12, 2007

MSMQ - Sending messages to remote private queues

Recently a friend was facing issues in sending messages to a private msmq queue on a remote machine. I had worked on this about 4-5 years back and I recalled that I was able to work with remote private queues. So I decided to give it a try again.

Following are things that I found.

1. When working with remote queues, the queue name in the format machinename\private$\queuename doesn't work. This results in an "invalid queue path" error.

2. The queue name has to be mentioned as "FormatName:Direct=OS:machinename\\private$\\queuename". This is necessary since the queue access is internally done using the format name syntax only. The other friendly representation is converted to the FormatName and then used. When working with remote queues, unless there is an AD to resolve the queue name, the friendly name won't work. Check out documentation for details.

For Eg.

    MessageQueue rmQ = new MessageQueue
                                    ("FormatName:Direct=OS:machinename\\private$\\queue");
    rmQ.Send("sent to regular queue - Atul");

3. Further to previous point, note that FormatName is case sensitive. If you mention the earlier string as "FORMATNAME:Direct=OS:machinename\\private$\\queuename", it won't work. Surprisingly, there is no error thrown in this case. "FormatName" part of the string seems to be the only case sensitive part. Others can appear in different case. For eg. You can write "DIRECT". 

4. In case you want to use the machine's IP address the syntax will be "FormatName:Direct=TCP:ipaddress\\private$\\queuename".

For Eg.

    MessageQueue rmQ = new MessageQueue
                                     ("FormatName:Direct=TCP:121.0.0.1\\private$\\queue");
    rmQ.Send("sent to regular queue - Atul");

5. The transactional properties of the queue instance you create in code should match with that of the queue you are trying to send the message to. So in the earlier examples, I was sending message to a non-transactional queue. To send to a transactional queue, the code would be

    MessageQueue rmTxnQ = new MessageQueue
                                            ("FormatName:Direct=OS:machinename\\private$\\queue");
    rmTxnQ.Send("sent to Txn queue - Atul", MessageQueueTransactionType.Single);

If the transactional properties don't match, the message will not be delivered. The surprising part is again, I didn't get any error, and the message just disappeared

6. Finally, when you send messages to remote queue, a temporary outgoing queue is created on your own machine. This is used in case the remote queue is unavailable. If you go to the computer Management console (compmgmt.msc), and expand the Services and Applications / Message Queuing / Outgoing Queues, you would see these queues. The right side of the console should show the details including the state (connected or not) and the IP address(es) for the next hop(s).

1

What's in a class/method name?

.NET framework 2.0 introduced a new feature called Anonymous methods via the delegate keyword. Some more details on this can be found in this MSDN Magazine article.

Working with anonymous methods can be a bit confusing and also make the code a bit difficult to read. Recently a colleague of mine, Vinay, however showed another interesting class and method naming convention.

public class _
{
    public _()
    {
    }

    public void __(string _, string __)
    {
    }
}

The above is perfectly legal C# code and will compile and build successfully. You can call the method in this class as

    _ _ = new _();
    _.__("_", "_");

So much for the class and method naming and cryptic code! Do note however that this code snippet will be a bit more readable with the VS2005 editor and its color coding.

February 9, 2007

Finding deserialization problems when using MSMQ with WCF

I was trying to build a sample that had a WCF service consuming an input message from MSMQ. Post writing the necessary code when I ran the service and waited for it to pick up the message from MSMQ, nothing happened.

The message was not getting picked up and I wasn't get any exception or an error logged in event log. I didn't know what to do. Since I had used a transactional queue, and the message continued to sit there, I knew this meant that something went wrong while reading the message, but what?

It turns out that there is a way we can get some insight into what went wrong. If there is any other way than what I descibe below, I would love to hear about it.

If one is working with inline with the samples, the typical code for console based service host will look as below

using (ServiceHost serviceHost = new ServiceHost(typeof(MyMSMQService)))
{
    serviceHost.Open();

    // The service can now be accessed.
    Console.WriteLine("The service is ready.");
    Console.WriteLine("Press <ENTER> to terminate service.");
    Console.ReadLine();
}

A little change to this code is what is required to get insight into what's going wrong. The modified code will look like below

using (ServiceHost serviceHost = new ServiceHost(typeof(MyMSMQService)))
{
    serviceHost.Faulted += new EventHandler(serviceHost_Faulted);

    serviceHost.Open();

    // The service can now be accessed.
    Console.WriteLine("The service is ready.");
    Console.WriteLine("Press <ENTER> to terminate service.");
    Console.ReadLine()
}

static void serviceHost_Faulted(object sender, EventArgs e)
{
    Console.WriteLine("Service has faulted");
}

Essentially I have added an event handler to handle the Service Faulted event. This by itself however doesn't give any additional information. However you can put a break point in the event handler and when a fault occurs, the Locals debug window in Visual Studio shows an additional local variable - $exception, and this shows the required details of what went wrong.

In my case, this clearly showed that the problem had happened while trying the deserialize the message from MSMQ. It couldn't be deserialied since the type wasn't known. This meant that the type I had declared didn't match with what was there in the input message and I was then able to resolve the problem.

A few tips when working with msmqIntegrationBinding and WCF

  1. Ensure the type you have defined as the input parameter is marked Serializable
  2. Decorate the Service as - [ServiceKnownType(typeof(<yourType>))]
  3. Good practice to decorate the Interface method as - [OperationContract(IsOneWay = true, Action = "*")]
  4. Tab the service method implementation as - [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
  5. Set receiveErrorHandling ="Fault" in the binding details for msmqIntegrationBinding if you want to get fault information. Setting this to "Drop" will just drop the message and you won't know what really happened. Refer to msmqIntegrationBinding documentation for more details

Transaction requirements will ofcourse depend on your application needs, but note that if the queue isn't set as transactional, in case of errors, there will be no way to recover the message since it will be deleted from the queue while reading.

User Settings Persistence in .Net 2.0

The other day a friend, having read my earlier blog on Settings in .net fwk 2.0 asked me a question about user settings. He had built an application where he was dealing with user settings and though he was getting the required behavior of user settings getting updated everytime he changed some value and using the new value on next load of the application also, but the mystery was he wasn't able to locate where the values were being saved?

I decided to look into this and created a small windows application which had a text box to input new values, a button to assign them and another button whose text would get updated based on the value in the text box. This value, I also storted as user settings by calling Setting.Default.Save(). I ran the application, set a new text and hit the update button and closed the application. When I ran the application again, I did see the button displaying the new value i had assigned in my previous run.

So far so good. I then opened the application configuration file and to my surprise found the initial default entry in the config file for the user setting. However little thought and this made perfect sense. If the user settings were to be stored in the application configuration file, it would require
1. Updating the configuration file to introduce user specific information like use ID to identify different  user sections
2. The previous point would then also require different reading logic for user settings, since then it would have to account for the user ID
3. With multiple users accessing the application and each saving their personalized values in the file, its size would grow and would then cause performance issues due to a larger file to load and parse.

On a side note, when working with VS, you will also notice a few additional files with .vshost extension. These file aren't necessary for actual execution of the application and more for VS's own use. Check out detailed information here.

So if it wasn't application configuration file, where would the values would. The logical explanation was the Documents and Settings directory that is usually used for storing user specific information. But where in this? I know Visual Studio (VS) itself uses directories like C:\Documents and Settings\atulg\Local Settings\Application Data\Microsoft\VisualStudio\8.0 and C:\Documents and Settings\atulg\Application Data\Microsoft\VisualStudio\8.0 to store information, so this is place I looked initially.

However this didn't show up anything convincing. I did get some pointers to isolated storage, but when I searched that directory, I didn't find anything remotely related to the application I was working with. As I continued to search, I decided to search using the application name in the Documents and Settings directory.

Sometimes these simpler methods return results much faster. I immediately found a directory with the name of my application (MyApp) at - C:\Documents and Settings\atulg\Local Settings\Application Data\MyApp. Note that Local Settings and Application Data are usually hidden directories and trying to browse for them in explorer will not show anything unless these are unhidden. For those of you, who are working with Vista, the directory would be C:\Users\atulg\AppData\Local\MyApp.

Opening the above directory, I found additional sub-directory within it and its name was a bit wierd. It was named as <application exe name>_Url_<some crazy combination of alphabets and numbers>. Maybe the crazy combination is base 64 encoding of some information, but that topic is for another day. This directory itself contained another sub directory as the version number of the application. In my case, it was 1.0.0.0. So the entire structure (inside of C:\Documents and Settings\atulg\Local Settings\Application Data\) looked like

MyApp
    MyApp.exe_Url_obmvyvpuihsr0asonav0kebmr40ckxfe
        1.0.0.0

The final directory with version number seemed interesting and this meant that information stored within would be version safe. The surprising part is when I tried by updating the version number and re-running the application, it created another directory below MyApp itself. So the directory now looked like

MyApp
    MyApp.exe_Url_obmvyvpuihsr0asonav0kebmr40ckxfe
        1.0.0.0
    MyApp.exe_Url_tl0clumf2wjtcgmdusso0mmd1cijtrkr
        2.0.0.0

If the newer version was housed within a different subdirectory alltogether, then what purpose the version numbered sub-directory served? I don't have an answer to this right now. However another point I noticed is that when you debug the application via VS, it creates a different directory structure as

MyApp
    MyApp.vshost.exe_Url_obmvyvpuihsr0asonav0kebmr40ckxfe
        1.0.0.0
        2.0.0.0

In this case, the version numbered directories made sense since they are both within the same parent directory.

Getting back to our earlier quest, there exists a file within the version numbered directory called as user.config and needless to say, it is in this file that the information i was seeking existed.

The mystery over the location of persistence of user settings is hence solved. I checked that when modifying the version number of the application, and making a copy of my executable of earlier version, I could see the different values getting stored in appropriate directories. Running a particular version would load the values saved only for that version.

To wrap up, in case you don't want to use the latest stored values and want to revert to the default values, you can call Settings.Default.Reset(). This clears the user setting values from the user.config file also and hence the default values are loaded the next time the application starts up (unless ofcourse you saved a new set of values).

February 2, 2007

Writing my first WCF service on Vista

Earlier today I decided to try my hands at building a Windows Communication Foundation (WCF) service on my Vista machine. Since Vista (RTM) comes pre-installed with .net framework 3.0, the necessary components for WCF are already on the machine.

However to work with VS2005 and be able to create WCF based projects, you still need to install the WCF extensions. You can get the necessary files from here - http://www.netfx3.com/blogs/technology_news_and_announcements/archive/2006/12/06/net-framework-3-0-has-been-released.aspx

I started VS 2005 and selected option for creating a new Website (since I wanted to work with WCF hosted on IIS). In the new Website wizard, I selected the option for WCF Service, named it appropriately and selected OK. The wizard created the base boiler plate code which had the service contract, the service implementation and also a DataContract to handle custom types.

WCFProject.jpg

With this done, it looked that i was ready to go. I built the project and it built successfully. I then went to my browser and tried to access the .Svc file via the url - http://localhost/TestService/Service.svc. To my surprise I ended up getting a Http 404.3 error stating that .svc MIME map wasn't enabled.

Since .net framework 3.0 comes by default on Vista, this was surprising that WCF service wasn't working already. However I realized that IIS 7.0 doesn't comes installed by default Vista and hence it meant that the necessary settings were not configured properly post my installation of IIS 7.0. Check my earlier blog on installing IIS.

I expected something similar on lines of aspnet_regiis.exe -i to work in this case and inspecting the Windows directory reveled that a similar EXE does exsist for WCF configuration. It is ServiceModelReg.exe and can be found in "C:\Windows\Microsoft.NET\Framework\v3.0\Windows Communication Foundation" directory. I then ran this from command prompt as ServiceModelReg.exe -i and it installed the necessary scriptmaps at the IIS metabase root and folders within.

I than accessed the service URL again, but still didn't succeed. The error this time read "Metadata publishing for this service is currently disabled." and it provided detailed steps on how to solve it. It required me to specific service behavior and I did this by editing the default created Web.config file and modifying the default <behaviors>/<serviceBehaviors> section. The changes done to this section is as highlighted in bold face below

    <behaviors>
      <serviceBehaviors>
        <behavior name="returnFaults" >
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

With this when I accessed the service URL again, I got the expected page displayed in IIS as shown below.

WCFWorking.jpg

So far, so good. It was then time to test the working of the service. So I created a new Windows Application and then added a service reference to it. While doing this I again had an error while generating proxy code and it complained about proxy authentication. Making changes to my IE Connection settings, I got past this error.

Finally after adding a button to the form and writing the necessary code behind which looked something like below, I could get the service executing the returning the results.

        private void button1_Click(object sender, EventArgs e)
        {
            localhost.MyServiceClient p = new ServiceClient.localhost.MyServiceClient();
            string result = p.MyOperation1("Atul Gupta");
            MessageBox.Show(result);
        }

Note that the names of service contract, its methods, service class etc are all left as default and hence you would see things like MyServiceClient and MyOperation1. Needless to say that when you build your WCF services, you should change these appropriately.

In general it will also help to follow the instructions for running the WCF samples from SDK. Check the details at - http://msdn2.microsoft.com/en-us/library/ms751527.aspx.

There is one last point about intellisense in the web.config file. It wasn't working for me and I found that it was due to the xml namespace in the Web.config - xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0". The problem is described here - http://weblogs.asp.net/scottgu/archive/2005/12/02/432077.aspx

1

Integrating WPF/E content in ASP.NET AJAX 1.0 based application,,,

In one of my previous blogs -  http://infosysblogs.com/microsoft/2006/10/aspnet_20_ajax_extensions_atla.html, I had provided details about invoking web services, without worrying about the hassles of creating proxy and all, from the client side of the ASP.NET applications, using ASP.NET 2.0 AJAX extensions. And I had also mentioned that client-side invocation of the remote (cross-domain) web services is forbidden because of security implications surrounding that. In order to invoke a remote web service, we have to use a local web service, that acts as a bridge service, which in turn invokes the remote web service. I had used such a bridge web service to invoke a remote web service http://www.xmlme.com/WSShakespeare.asmx  – Shakespeare quotes web service – that provides us back with the timeless quotes from not so forgettable protagonists of Shakespeare’s works.

And it has been long time since I blogged on that topic any further and when I get sufficient enthusiasm and time to come back to that, lot of changes have already happened – the major one is the version 1.0 release of ASP.NET AJAX extensions (and in fact ASP.NET AJAX  Feature Set  January CTP is also available in addition to that), apart from plethora of technology previews, evaluation versions of longhorn server, Orcas development tools, Expression Suite products, WPF/E, etc. Immediately after installing the relevant pieces (Well. To be honest, not so before fully overcoming the mental numbness that happens mainly due to the presence of sheer number of upcoming technologies at such a rapid pace Smile ) I wanted to port the sample piece of code that I wrote based on previous CTP of ASP.NET 2.0 AJAX Extension, to this new version. Not to anybody’s surprise (if anybody gets surprised, he/she might be visiting our planet from far away galaxy Smile ), it invariably breaks.

In the process of fixing up the code, I have picked up certain new exciting features. The previous sample code worked fine after a bit of tinkering, as exactly as it was, but I felt that the approach to invoke the web service got to be simpler and elegant in this new version. But that sample that worked as exactly as it had been, was still a matter of concern Smile, because the UI of the sample is nothing but a set of text boxes and a button and so the sample is as dull as that application was trivial. To jazz the UI up with less effort and without dealing with javascript and DHTML object model, I have pulled in the pieces of WPF/E CTP. Basic exposure to XAML and ever-expanding intellisense of VS 2005 really makes the wonders to let the developer to add the gizmos to the UI, though he is not professionally trained for that. But I really don’t know whether the designers who don’t deal with products such Expression suite, will feel the same way if they need to deal with XAML Smile. 

The installation of ASP.NET AJAX Extensions 1.0 is simple and straight forward but  the installation of WPF/E CTP SDK would require you to install something more like Visual Studio 2005 Updates to support Web Application projects. See the links http://www.microsoft.com/downloads/details.aspx?FamilyID=ca9d90fa-e8c9-42e3-aa19-08e2c027f5d6&displaylang=en. And http://msdn2.microsoft.com/en-us/asp.net/bb187452.aspx  and release notes for WPF/E for details.

Once all of them got installed, it is time to plunge into some action. As there are a lot of breaking changes between previous versions and the current version, it is better to keep this link http://ajax.asp.net/files/AspNet_AJAX_CTP_to_RC_Whitepaper.aspx handy. Though going through such documents at least cursorily, is a good time-saving process, before we plunge into new version of any technology, unfortunately I  am not the one who normally takes that approach, but I wish I had done that.

The first striking change is that even local web services can not be invoked in the client-side just like that and the web services need to specifically be attributed with “ScriptService” in order to be invoked from the client side scripts. It is a security measure to prevent accessing and exploiting  all web services through not-so-robust (or vulnerable, depends on what side you are) client technologies. Web methods would also require an extra attribute “ScriptMethod” for specific needs, though it is not mandatory one like “ScriptService” attribute. Secondly, there are attribute level changes with respect to the usage of  “ServiceReference” and to invoke web services. But overall, invocation of web services has become simpler and flexible. For example, now, generic client-side error handlers can be connected to more than one web services. But the real killer feature is the introduction of page level methods that can be exposed as web methods in this version of ASP.NET AJAX. It means that there is no need for a separate web service file such as an asmx file. If you wonder, what is the need indeed to embed the web service methods at page level as it would be contrary to the convention that they (Web Services) should be exposed from a centralized place to multiple clients, remember the fact that cross-domain invocation is still not allowable from the client-side technologies and we need to build our own bridge web service for such cross domain access. In such cases, in stead of building the wrapper local web services (.asmx files) unnecessarily, we can use the page level methods that would act as bridge methods to access remote web methods.

So, I took my old sample code and ported into new version of ASP.NET AJAX. I did a few changes in the code (highlighted below) when I took the customary approach of creating a local bridge web service to invoke the remote web service. In this approach, the solution will have a web reference to the remote web service (in this case, it is http://www.xmlme.com/WSShakespeare.asmx  and my local web service will invoke the remote web service by utilizing the proxy created by the VS 2005. The proxy for the local web service would be generated behind the screens, when the web service file is referenced through “asp:ServiceReference” at the server side. This technology would generate all relevant script code that enables the invocation seamlessly from the client side. The generated proxy would be used to invoke web methods. As these web methods are always invoked asynchronously, it is our responsibility to provide callback methods for eventual situations where the operation may succeed or fail.

Before we proceed further, let us take a look at the snapshot of the solution explorer. Ignore the folder “js” that appears in the solution explorer for a moment as we require this only when we integrate WPF/E pieces herewith.

Solution Explorer

You can see some folder inside App_WebReferences. These folders were created by VS 2005, as I used the Web Reference property of the solution in order to let the VS create a proxy for the remote web service. See the following snapshot of adding a web reference.

Adding Web Reference in VS

Now we have to create a local web service that would consume the remote web service.

The web service code (MyWebService.cs file) is given below. The point to be noted here is, there are additional attributes “ScriptService”  and “ScriptMethod” (of System.Web.Script.Services namespace) that are applied to the web service and its method. This attribute ensures that this web service can be accessed from the client side scripts. This web service in turn consumes the remote web service through the proxy, generated by the VS 2005.

using System;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using System.Net;
using System.Text;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService()]
public class MyWebService : System.Web.Services.WebService {
    com.xmlme.www.Shakespeare proxy;
   
    public MyWebService () {
       proxy = new com.xmlme.www.Shakespeare();      
    
    }
    [WebMethod][ScriptMethod()]
    public string GetInfo(string strName) {
        string strContent = proxy.GetSpeech(strName);       
       
        string strContent1 = strContent.Replace("<SPEECH>", "");
        string strContent2 = strContent1.Replace("</SPEECH>","");
        int playIndex = strContent2.IndexOf("<PLAY>");
        int playEndIndex = strContent2.IndexOf("</PLAY>");
        string playContent = strContent2.Substring(playIndex + 6, playEndIndex  - ( playIndex + 6 ) );
        string contentWithoutPlay = strContent2.Remove(playIndex, playEndIndex + 7);
       
        int speakerIndex = contentWithoutPlay.IndexOf("<SPEAKER>");
        int speakerEndIndex = contentWithoutPlay.IndexOf("</SPEAKER>");
        string speakerContent = contentWithoutPlay.Substring(speakerIndex + 9, speakerEndIndex - ( speakerIndex + 9 ) );
        string speechContent = contentWithoutPlay.Remove(speakerIndex, speakerEndIndex + 10);
        return playContent + "::" + speakerContent + "::" + speechContent;
       
    }
   
}

Now, we have to rely on the ASP.NET AJAX 1.0 to create a proxy for this local web service, to be eventually used in the client side. The following piece of code exactly does that. The element “asp:ServiceReference” creates the proxy for the local web service – MyWebService that is defined in the asmx file. The proxy creation is done at the server side and it is rendered as JavascriptObjectNotation (JSON) object to the client side scripts (Note the “runat” attribute of “asp:ScriptManager” element).

<asp:ScriptManager ID="ScriptManager1" runat="server" >
<Services>
<asp:ServiceReference Path="~/MyWebService.asmx" InlineScript="true"  />              
</Services>                     
</asp:ScriptManager>

As I had already mentioned, the web service’s method will always be executed in asynchronous manner. So, to get to know the result, we have to attach the callback methods to certain eventualities such as success or failure of the execution of web methods.

The following code provides details on how we can write client side functions that would invoke the web methods through the proxy generated through ASP.NET AJAX elements such as asp:ScriptManager and  asp:ServiceReference. “MyWebService” is the name of of class of type – web service – that is referenced in the “path” attribute of the element “asp:ServiceReference”. Inside our client side function – InvokeBiographyWebService – a web method named “GetInfo” of local web service – MyWebService” is invoked. This web method takes two parameters. First parameter is basically an input to the web method and the second parameter is the client-side callback method name that would be invoked when there is no error during the execution of the web method. As you might already have already realized by now, that there are more than these two parameters, one may need to pass depends on the complexity of the application. And we have also implemented the callback function – OnSuccess() for the web method “GetInfo” and it updates the resultant contents into html based UI elements.

<script language="javascript" type="text/javascript" >
    function InvokeBiographyWebService()
    {
        var strInput = document.getElementById('textBoxName').value;  
        MyWebService.GetInfo(strInput, OnSuccess);
    
    }
   
    function OnSuccess(result)
    {
      var strContent = result.split("::");
     
      document.getElementById('textBoxPlay').value = strContent[0];
      document.getElementById('textBoxSpeaker').value = strContent[1];
      document.getElementById('textAreaBio').value = strContent[2];
    }
    </script>

For reference, complete code is given below. If you wonder about those mysterious looking scripts that deal with “agHost” in the html, they are nothing but the script to create ActiveX controls for WPF/E content in web pages.More details on it, are provided at the end of this blog.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>ThusSpakeShakespeare!!!</title>   
    <script type="text/javascript" src="js/aghost.js"></script>
    <script type="text/javascript" src="js/eventhandlers.js"></script>
    <script language="javascript" type="text/javascript" >
    function InvokeBiographyWebService()
    {
        var strInput = document.getElementById('textBoxName').value;  
        MyWebService.GetInfo(strInput, OnSuccess);
    
    }
   
    function OnSuccess(result)
    {
      var strContent = result.split("::");
     
      document.getElementById('textBoxPlay').value = strContent[0];
      document.getElementById('textBoxSpeaker').value = strContent[1];
      document.getElementById('textAreaBio').value = strContent[2];
    }
    </script>
</head>
<body>
    <form id="form2" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" >
            <Services>
                <asp:ServiceReference Path="~/MyWebService.asmx" InlineScript="true"  />              
            </Services>                     
        </asp:ScriptManager>
       
        <table width="500" style="background-color: Teal" >
            <tr>
            <td>
            <div id="wpfeControl1Host" >
            <script type="text/javascript">
                  new agHost("wpfeControl1Host", // hostElementID (HTML element to put WPF/E control into)
                             "wpfeControl1",     // ID of the WPF/E ActiveX control we create
                             "500",              // Width
                             "100",              // Height
                             "black",            // Background color
                             null,               // SourceElement (name of script tag containing xaml)
                             "plugin.xaml",      // Source file
                             "true",            // IsWindowless
                             "50",               // MaxFrameRate
                             null                // OnError handler   
                            );
            </script>
            </div>
            </td>
            </tr>
            <tr>
             <td align="center" style="background-color:Lime" >
             <div id="contentPart" >
                <input id="textBoxName" type="text" runat="server" style="width: 207px" value="Type content to be searched!!" />
                <input id="btnForTextBoxName" type="button" value="Click!!!" onclick="InvokeBiographyWebService();" />
                <br/>
                <input id="textBoxPlay" type="text" style="width: 207px" value="Play" />
                <br />
                <input id="textBoxSpeaker" type="text" style="width: 207px" value="Speaker" />
                <br />
                <textarea id="textAreaBio"  visible="false" style="width: 208px; height: 80px" ></textarea>
            </div> 
            </td>
            </tr>
            <tr>
            <td>
            <div id="wpfeControl2Host" >
            <script type="text/javascript">
                  new agHost("wpfeControl2Host", // hostElementID (HTML element to put WPF/E control into)
                             "wpfeControl2",     // ID of the WPF/E ActiveX control we create
                             "500",              // Width
                             "100",              // Height
                             "black",            // Background color
                             null,               // SourceElement (name of script tag containing xaml)
                             "plugin.xaml",      // Source file
                             "true",            // IsWindowless
                             "50",               // MaxFrameRate
                             null                // OnError handler   
                            );
            </script>
        </div>
            </td>
            </tr>
        </table>
        <div>
        </div>         
    </form>
</body>

</html>

 

Having understood the ease with which we can create a proxy for the local web service and use that at the client side, we will now try the second approach, in which, we can create page level script method that can be exposed as a web method.

In the code snippet given below, I have simply cut and pasted the content from the separate web service – MyWebService - that we saw earlier. Only obvious change to be noted here is both the method and the proxy variable are defined to be “static” and it is a mandatory condition for page level web methods to stick with. Since the method is attributed with “WebMethod”, this page needs to reference “System.Web.Services” namespace. For that, we need to put the following preprocessor directive at the starting of the page.

<%@ Import Namespace="System.Web.Services" %> 

 
<script runat="server" language="C#" >
private static com.xmlme.www.Shakespeare proxy = new  com.xmlme.www.Shakespeare();  
           
[WebMethod]
public static string GetInfo(string strName)
        {
            string strContent = proxy.GetSpeech(strName);
            string strContent1 = strContent.Replace("<SPEECH>", "");
            string strContent2 = strContent1.Replace("</SPEECH>", "");
            int playIndex = strContent2.IndexOf("<PLAY>");
            int playEndIndex = strContent2.IndexOf("</PLAY>");
            string playContent = strContent2.Substring(playIndex + 6, playEndIndex - (playIndex + 6));
            string contentWithoutPlay = strContent2.Remove(playIndex, playEndIndex + 7);
            int speakerIndex = contentWithoutPlay.IndexOf("<SPEAKER>");
            int speakerEndIndex = contentWithoutPlay.IndexOf("</SPEAKER>");
            string speakerContent = contentWithoutPlay.Substring(speakerIndex + 9, speakerEndIndex - (speakerIndex + 9));
            string speechContent = contentWithoutPlay.Remove(speakerIndex, speakerEndIndex + 10);
            return playContent + "::" + speakerContent + "::" + speechContent;
}
</script>
 
Having defined the page level web method, let us see how we can consume it from inside the client-side script.
<script language="javascript" type="text/javascript" >
    function InvokeBiographyWebService()
    {
        var strInput = document.getElementById('textBoxName').value;  
        PageMethods.GetInfo(strInput, OnSuccess);   
    
    }
   
    function OnSuccess(result)
    {
      var strContent = result.split("::");
     
      document.getElementById('textBoxPlay').value = strContent[0];
      document.getElementById('textBoxSpeaker').value = strContent[1];
      document.getElementById('textAreaBio').value = strContent[2];
    }
 </script>

If you see the underlined content of the client side script that is only change that we have done on the code of the previous approach. However, it is bit tricky as it would create compilation error until you switched on the attribute “EnablePageMethods” of “asp:ScriptManager” element, as given in the following code snippet.

<asp:ScriptManager ID="ScriptManager1" EnablePageMethods="true" runat="server" >                   
</asp:ScriptManager>

The complete code is given below for the reference

<%@ Import Namespace="System.Web.Services" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>ThusSpakeShakespeare!!!</title>
    <script runat="server" language="C#" >
        private static com.xmlme.www.Shakespeare proxy = new com.xmlme.www.Shakespeare();  
          
        [WebMethod]
        public static string GetInfo(string strName)
        {
            string strContent = proxy.GetSpeech(strName);
            string strContent1 = strContent.Replace("<SPEECH>", "");
            string strContent2 = strContent1.Replace("</SPEECH>", "");
            int playIndex = strContent2.IndexOf("<PLAY>");
            int playEndIndex = strContent2.IndexOf("</PLAY>");
            string playContent = strContent2.Substring(playIndex + 6, playEndIndex - (playIndex + 6));
            string contentWithoutPlay = strContent2.Remove(playIndex, playEndIndex + 7);
            int speakerIndex = contentWithoutPlay.IndexOf("<SPEAKER>");
            int speakerEndIndex = contentWithoutPlay.IndexOf("</SPEAKER>");
            string speakerContent = contentWithoutPlay.Substring(speakerIndex + 9, speakerEndIndex - (speakerIndex + 9));
            string speechContent = contentWithoutPlay.Remove(speakerIndex, speakerEndIndex + 10);
            return playContent + "::" + speakerContent + "::" + speechContent;
        }
    </script>
    <script type="text/javascript" src="js/aghost.js"></script>
    <script type="text/javascript" src="js/eventhandlers.js"></script>
    <script language="javascript" type="text/javascript" >
    function InvokeBiographyWebService()
    {
        var strInput = document.getElementById('textBoxName').value;  
        PageMethods.GetInfo(strInput, OnSuccess);   
    
    }
   
    function OnSuccess(result)
    {
      var strContent = result.split("::");
     
      document.getElementById('textBoxPlay').value = strContent[0];
      document.getElementById('textBoxSpeaker').value = strContent[1];
      document.getElementById('textAreaBio').value = strContent[2];
    }
    </script>
</head>
<body>
    <form id="form2" runat="server">
        <asp:ScriptManager ID="ScriptManager1" EnablePageMethods="true" runat="server" >                   
        </asp:ScriptManager>
       
        <table width="500" style="background-color: Teal" >
            <tr>
            <td>
            <div id="wpfeControl1Host" >
            <script type="text/javascript">
                  new agHost("wpfeControl1Host", // hostElementID (HTML element to put WPF/E control into)
                             "wpfeControl1",     // ID of the WPF/E ActiveX control we create
                             "500",              // Width
                             "100",              // Height
                             "black",            // Background color
                             null,               // SourceElement (name of script tag containing xaml)
                             "plugin.xaml",      // Source file
                             "true",            // IsWindowless
                             "50",               // MaxFrameRate
                             null                // OnError handler   
                            );
            </script>
            </div>
            </td>
            </tr>
            <tr>
             <td align="center" style="background-color:Lime" >
             <div id="contentPart" >
                <input id="textBoxName" type="text" runat="server" style="width: 207px" value="Type content to be searched!!" />
                <input id="btnForTextBoxName" type="button" value="Click!!!" onclick="InvokeBiographyWebService();" />
                <br/>
                <input id="textBoxPlay" type="text" style="width: 207px" value="Play" />
                <br />
                <input id="textBoxSpeaker" type="text" style="width: 207px" value="Speaker" />
                <br />
                <textarea id="textAreaBio"  visible="false" style="width: 208px; height: 80px" ></textarea>
            </div> 
            </td>
            </tr>
            <tr>
            <td>
            <div id="wpfeControl2Host" >
            <script type="text/javascript">
                  new agHost("wpfeControl2Host", // hostElementID (HTML element to put WPF/E control into)
                             "wpfeControl2",     // ID of the WPF/E ActiveX control we create
                             "500",              // Width
                             "100",              // Height
                             "black",            // Background color
                             null,               // SourceElement (name of script tag containing xaml)
                             "plugin.xaml",      // Source file
                             "true",            // IsWindowless
                             "50",               // MaxFrameRate
                             null                // OnError handler   
                            );
            </script>
        </div>
            </td>
            </tr>
        </table>
        <div>
        </div>         
    </form>
</body>

</html>

And finally our client side method “InvokeBiographyWebService” is tied up with the “Click” event of the button.

<div id="contentPart" >
                <input id="textBoxName" type="text" runat="server" style="width: 207px" value="Type content to be searched!!" />
                <input id="btnForTextBoxName" type="button" value="Click!!!" onclick="InvokeBiographyWebService();" />
                <br/>
                <input id="textBoxPlay" type="text" style="width: 207px" value="Play" />
                <br />
                <input id="textBoxSpeaker" type="text" style="width: 207px" value="Speaker" />
                <br />
                <textarea id="textAreaBio"  visible="false" style="width: 208px; height: 80px"   ></textarea>
</div> 

Let us run the application and to see what we will get as the output.

Thus Spake Shakespeare

Apart from the content pulled off through client side invocation of remote web service, I hope that you might see the jazziness of the UI, though the animation and transformation techniques could not be depicted at all in that frozen snapshot Smile. The simple bells and whistles are provided through the piece of XAML and you can try out the following XAML file that creates those colorful frames, above and below to those normal html controls, to see the cool effects of basic and flexible animation. If the UI is not so cool, please remember that I am a not guy who is blessed with much aesthetic sense Smile.

<Canvas xmlns="http://schemas.microsoft.com/client/2007"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Loaded="javascript:root_Loaded" >
  <Canvas x:Name="button" Height="140" Width="550">   
    <Canvas.Background>     
      <SolidColorBrush x:Name="SolidColor" Color="white" Opacity ="20" >
      </SolidColorBrush>     
    </Canvas.Background>
    <Canvas.Triggers>
      <EventTrigger RoutedEvent="Canvas.Loaded" >
        <BeginStoryboard >
          <Storyboard>
            <ColorAnimation Storyboard.TargetName="SolidColor" Storyboard.TargetProperty="Color" From="white" To="lime" Duration="0:1:0" AutoReverse="True" RepeatBehavior="Forever"/>
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Canvas.Triggers>
    <Rectangle x:Name="ShakespeareShade" Opacity="100" Canvas.Top="0" Canvas.Left="0" Stroke="#FF8E8E8E" StrokeThickness="2" Height="100" Width="100" >
      <Rectangle.Fill> 
        <ImageBrush ImageSource="Shakespeare.jpg" Opacity="100" >
        </ImageBrush>
      </Rectangle.Fill>
      <Rectangle.RenderTransform>
        <TransformGroup>
            <RotateTransform x:Name="rotateShakespeare" Angle="45" CenterX="50" CenterY="50"/>          
        </TransformGroup>
      </Rectangle.RenderTransform>
      <Rectangle.Triggers>
        <EventTrigger RoutedEvent="Rectangle.Loaded" >
          <BeginStoryboard>
            <Storyboard>
              <DoubleAnimation Storyboard.TargetName="rotateShakespeare" Storyboard.TargetProperty="Angle" From="0" To="360" Duration="0:0:15" RepeatBehavior="Forever" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Rectangle.Triggers>
    </Rectangle>
      <Rectangle x:Name="Shakespeare" Opacity="100" Canvas.Top="0" Canvas.Left="10" Stroke="#FF8E8E8E" StrokeThickness="2" Height="100" Width="100" RadiusX="100" RadiusY="100"  MouseLeftButtonDown="javascript:MouseLeftButtonDownInShakespeare" >
        <Rectangle.Fill>
          <ImageBrush ImageSource="Hamlet01.jpg" Opacity="100" >
          </ImageBrush>
        </Rectangle.Fill>
        <Rectangle.RenderTransform>
          <TransformGroup>          
              <TranslateTransform x:Name="translateShakespeare" X="0" Y="0"/>          
          </TransformGroup>
        </Rectangle.RenderTransform>
        <Rectangle.Triggers>
          <EventTrigger RoutedEvent="Rectangle.Loaded" >
            <BeginStoryboard>
              <Storyboard>              
                <DoubleAnimation Storyboard.TargetName="translateShakespeare" Storyboard.TargetProperty="X" From="0" To="400" Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" />
              </Storyboard>
            </BeginStoryboard>           
          </EventTrigger>
        </Rectangle.Triggers>
      </Rectangle>
    <Rectangle x:Name="HamletShade" Opacity="100" Canvas.Top="0" Canvas.Left="400" Stroke="#FF8E8E8E" StrokeThickness="2" Height="100" Width="100" >
      <Rectangle.Fill>
        <ImageBrush ImageSource="Shakespeare01.jpg" Opacity="100" >
        </ImageBrush>
      </Rectangle.Fill>
      <Rectangle.RenderTransform>
        <TransformGroup>
          <RotateTransform x:Name="rotateShakespeare1" Angle="45" CenterX="50" CenterY="50"/>
        </TransformGroup>
      </Rectangle.RenderTransform>
      <Rectangle.Triggers>
        <EventTrigger RoutedEvent="Rectangle.Loaded" >
          <BeginStoryboard>
            <Storyboard>
              <DoubleAnimation Storyboard.TargetName="rotateShakespeare1" Storyboard.TargetProperty="Angle" From="360" To="0" Duration="0:0:15" RepeatBehavior="Forever" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Rectangle.Triggers>
    </Rectangle>
    <Rectangle x:Name="Hamlet" Opacity="100"  Canvas.Top="0" Canvas.Left="410" Stroke="#FF8E8E8E" StrokeThickness="2" Height="100" Width="100" RadiusX="100" RadiusY="100" MouseLeftButtonDown="javascript:MouseLeftButtonDownInHamlet" >
      <Rectangle.Fill>
          <ImageBrush ImageSource="Macbeth.jpg" Opacity="100" >
          </ImageBrush>       
      </Rectangle.Fill>
      <Rectangle.RenderTransform>
        <TransformGroup>         
        <TranslateTransform x:Name="translateHamlet" X ="0" Y="0"/>        
        </TransformGroup>
      </Rectangle.RenderTransform>
      <Rectangle.Triggers>
        <EventTrigger RoutedEvent="Rectangle.Loaded">
          <BeginStoryboard>
            <Storyboard>             
              <DoubleAnimation Storyboard.TargetName="translateHamlet" Storyboard.TargetProperty="X" From="0" To="-400"  Duration="0:0:5" AutoReverse="True" RepeatBehavior="Forever" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Rectangle.Triggers>
    </Rectangle>
  </Canvas>
</Canvas>

It will be really interesting to see how the above XAML file is linked to an html control so that they can be rendered in a browser. The following script actually embeds an AactiveX control that renders the WPF/E contents in the browsers and our xaml file – plugin.xaml is linked to the control.

<script type="text/javascript">
new agHost("wpfeControl1Host", "wpfeControl1", "500", "100", "black", null, "plugin.xaml", "true", "50",  null );
</script>

This script actually creates the ActiveX control object tags. This function “agHost” has been defined in a file named agHost.js that exists in the folder “js” in the solution. As ASP.NET Ajax application solution will not contain this folder, we have to create that folder and make sure that it contains aghost.js file and another javascript file eventhandlers.js that contains custom written event handlers. The aghost.js file will basically contain the following code, which is specific to IE browser.  

if((navigator.appVersion.indexOf('MSIE') != -1)) {  
   
    try {
      
        var WPFE = new ActiveXObject("AgControl.AgControl.0.8");
       
        innerHTML = '<object id="'+id+'" width="'+width+'" height="'+height+'" classid="CLSID:32C73088-76AE-40F7-AC40-81F62CB2C1DA">';
       
      if (sourceelement != null) {
          innerHTML += ' <param name="SourceElement" value="'+sourceelement+'" />';
      }
      if (source != null) {
          innerHTML += ' <param name="Source" value="'+source+'" />';
      }
      if (framerate != null) {
          innerHTML += ' <param name="MaxFrameRate" value="'+framerate+'" />';
      }
      if (errorhandler != null) {
          innerHTML += ' <param name="OnError" value="'+errorhandler+'" />';
      }
      if (backgroundcolor != null) {
          innerHTML += ' <param name="BackgroundColor" value="'+backgroundcolor+'" />';
      }
      if (windowlessmode != null) {
          innerHTML += ' <param name="WindowlessMode" value="'+windowlessmode+'" />';
      }
      innerHTML += '<\/object>';
   
      }

It means that we can embed the ActiveX control for WPF/E in the web page, using the following code snippet too.

<object id="wpfeControl1" width="500" height="100" classid="CLSID:32C73088-76AE-40F7-AC40-81F62CB2C1DA">
         <param name="SourceElement" value="null" />
         <param name="Source" value="plugin.xaml" />
         <param name="MaxFrameRate" value="50" />
         <param name="OnError" value="null" />
         <param name="BackgroundColor" value="black" />
         <param name="WindowlessMode" value="true" />

</object>           

Another file – eventhandlers.js may contain the event handling methods that will be wired with the events of html controls of the web pages, as depicted below.

function root_Loaded(sender, args) {
    var button = sender.findName("button");
    button.mouseEnter = "javascript:handleMouseEnter";
    button.mouseLeave = "javascript:handleMouseLeave";
    button.mouseLeftButtonUp = "javascript:handleMouseUp";
    button.mouseLeftButtonDown = "javascript:handleMouseDown";
}

function MouseLeftButtonDownInShakespeare(sender, eventArgs) {   
   
    var shakespeare = sender.findName("Shakespeare");
    shakespeare.radiusX = "50";
    shakespeare.radiusY = "50";
    shakespeare.width = "50";
    shakespeare.height = "50";   
   
   
}


function MouseLeftButtonDownInHamlet(sender, eventArgs) {
   
    var hamlet = sender.findName("Hamlet");
    hamlet.radiusX = "50";
    hamlet.radiusY = "50";
    hamlet.width = "50";
    hamlet.height = "50";
   
        
}

When we integrate WPF/W along with ASP.NET Ajax supporting pages, we have to make sure that the solution also references the dll – Microsoft.Web.Preview.dll.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter